套接字编程

+++++++套接字编程+++++++

(位于6-4的中间)
【socket( ); 创建套接字,返回一个套接字描述符】
int socket(int family, int type, int protocol);
family:协议族
AF_INET: IPv4协议
AF_INET6: IPv6协议
AF_LOCAL: UNIX域协议
FA_ROUTE: 路由套接字(socket)
AF_KEY: 秘钥套接字(socket)
type:套接字类型
SOCK_STREAM: 字节流套接字(socket)
SOCK_DGRAM: 数据报套接字(socket)
  SOCK_RAW: 原始套接字(socket)
protoco:0
<<成功:非负套接字描述符   出错:-1>>

【bind( ); 为创建的套接字通信接口绑定IP和端口】
int bind(int sock_fd, struct socdaddr *my_addr,  int addrlen);
sockfd:套接字描述符
my_addr:本地地址
addrlen:地址长度
<<成功:0    出错:-1>>

【listen( ); 设置套接字为监听状态】
/*被动套接字,即套接字只能接受别的主机的请求,而不能主动连接别的主机*/
int listen(int sock_fd, int backlog);
sock_fd:套接字描述符
backlog:在同一时刻能接受的连接请求个数(5-10)
<<成功:0    出错:-1>>

【accept( ); 接受连接请求】 服务端用
/*  接受一个新的连接请求,并返回一个新的套接字,这个套接字代表一个新的连接  */
int accept(int sock_fd, struct sockaddr *addr, socklen_t *addrlen);
sock_fd:套接字描述符
addr:用来保存发起连接请求的那一端的信息(ip和端口)。
addrlen:保存结构体长度地址
<<成功:非负套接字描述符   出错:-1>>
【connect( ); 向服务器发起连接请求】
/*  客户端来调用该函数,用来向服务器发起连接请求  */
int connect(int sock_id, struct sockaddr *serv_sddr, int addrlen);
aock_id:通过哪个套接字来发送请求
serv_sttr:保存服务器的信息,也就是把连接请求发给ip和端口为指定的那个服务器
addrlen:地址长度
<<成功:0    出错:-1>>

【设置套接字的属性】
int setsockopt(int sockfd,int level,int optname,const void *optval,socklen_t *optlen)
level:指定控制套接字的层次.可以取三种值: 
1)SOL_SOCKET:通用套接字选项.
2)IPPROTO_IP:IP选项.
3)IPPROTO_TCP:TCP选项. 
optname:获得或者是设置套接字选项
optval:为套接字设置的参数的地址
optlen:为套接字设置的参数的地址长度

//======实例==================
【允许重复绑定IP和端口的函数,照写即可】
int i = 1;
setsockopt(sock_fd, SOL_SOCKET, SO_REUSEADDR, &i, sizeof(i));

【设定套接字定时阻塞】
    struct timeval  tv;
     tv.tv_sec = 5; //设置5秒时间
    tv.tv_usec = 0;
    setsockopt(sockfd, SOL_SOCKET, SO_RCVTIMEO,  &tv, sizeof(tv)); //  设置接收超时
accept();
==================================================================
struct fd_set rdfs;
    struct timeval  tv = {5 , 0};   // 设置5秒时间
    FD_ZERO(&rdfs);
    FD_SET(sockfd, &rdfs);
    if (select(sockfd+1, &rdfs, NULL, NULL, &tv) > 0)   // socket就绪
          recv() /  recvfrom()    //  从socket读取数据
==================================================================
  void  handler(int signo)  {   return;  }
signal(SIGALRM, handler);
    alarm(5);
    if(recv(,,,) < 0) ……

【心跳检测】
为了维持服务器和客户端的之间的连接,客户端每隔一个固定的时间(1-255秒)向服务器发送一个心跳包,
以表明客户端和软硬件运行正常,如果服务器在间隔时间内,没有收到心跳包,则认为客户端出现问题,可以
根据相应情况进行处理。


将二进制IP转换为字符串并返回字符串的地址
char *inet_ntoa(sock_client.sin_addr);
测试程序:建立一个服务器,另建立一个客户端登陆服务器,并向其发送数据,服务器端显示收到的数据
步骤:
服务器端:(server)
No.1: 创建套接字并接收套接字描述符 socket
No.2: 配置服务器结构体信息 set:struct sockaddr
No.3: 绑定IP地址和端口到套接字 bind
No.4: 设置套接字为监听状态 listen
No.5: 接收客户端连接(阻塞)accept
No.6: 读取数据并做相关的操作 read、recv
No.7: close(sock_fd);

客户端:(client)
No.1: 创建套接字并接受套接字描述符socket
No.2: 配置服务器结构体信息set:struct sockaddr
No.3: 向客户端发起连接请求 connect
No.4: 进行数据写入操作 wirte、send
No.5: close(sock_fd);




+++++++UDP+++++++
(位于6-4的中间)
【sendto( ); 】
int sendto(int sockfd, const void *msg, int len, unsigned int flags, const struct sockaddr *to, int tolen);
sockfd:套接字描述符
  msg:指向要发送数据的指针
len:数据长度
  flags:一般为0
  to:目的机的IP地址和端口号信息
  fromlen:地址长度
  <<成功:实际发送字节数     失败:-1>>

【recvfrom( ); 】
int recvfrom (int sockfd, void *buf, int len, unsigned int flags, const struct sockaddr *from, int fromlen);
sockfd:套接字描述符
  buf:存放接收数据的缓冲区
len:数据长度
  flags:一般为0
  from:源主机的IP地址和端口号信息
  fromlen:地址长度
  <<成功:实际收到的字节数     失败:-1>>
>>>>以上两个函数只用于UDP协议


Connect 和 accept函数间会发生三次握手(3个空包)
第一次握手:client向server发送一个包(SYN包)TCP头内的SYN=1, seq=一个随机序列号x 
客户端处于SYN_SENT状态
第二次握手:Server回复一个包给client(SYN+ACK包)TCP头内的SYN=1, ACK=1, seq= y, ack=x+1
服务端处于SYNf_RCVD状态 
第三次握手:ACK包(ACK=1, ack=y+1)
客户端和服务端都处于ESTABUSHED 状态,表示握手成功

为什么TCP是面向对象的,因为它要进行三次握手

四次握手
Client向server发送一个请求关闭的包
Server给client一个回复
Server告诉client已经没有数据可发送了
Client给server回复 




内核和驱动帮我们完成阻塞的

阻塞I/O
·
非阻塞I/O

+++++++I/O多路复用+++++++

【lelect() 函数,实现自动监控文件描述符所对应的文件是否可写、可读......s】
int select(int n, fd_set *read_fds, fd_set *write_fds, fd_set *except_fds, struct timeval *timeout);
n:表示最大的文件描述符的值加1
read_fds:要关注的可读(有数据可读、有新的连接请求)的文件描述符的集合
write_fds:要关注的可写的文件描述符的集合
except_fds:要关注的异常的文件描述符的集合
timeout:超时时间,如果在规定的时间内,没有任何文件描述符准备好,则select直接返回6
//select调用一次,只能进行一次监控,如果要连续监控,则需使用循环
//select返回之后,不满足条件的文件描述符对应的为都被清零,所以如果要重新检查,必须将fd重新加入集合
返回值:<0:出错     ==0:超时     >0: 有文件描述符准备好(某些IO端口满足可读、可写或异常的条件)
//select不会告诉用户具体是哪一个IO准备好,需要程员自己来判断

【timeout结构体】
 struct timeval {
    long    tv_sec;         /*秒数*/
    long    tv_usec;        /*微秒数*/
};

在设置文件描述符时我们需要用到几个宏
void FD_ZERO(fd_set *fdset);//把文件描述符集合中所有的位都清零
void FD_SET(int fd,fd_set *fdset);//将文件描述符fd加入到集合fdset中
void FD_CLR(int fd,fd_set *fdset);//将文件描述符从集合fdset中去除
int FD_ISSET(int fd,fd_set *fdset);//判断描述符fd是否在集合fdset中,检测集合中fd对应的位是否为1
 
select编程步骤:
1:获得文件描述符
int fd_serial = open("/dev/tty", O_RDWR);
int fd_socket = socket(AF_INET, SOCK_STREAM, 0);
bind(xxx);
listen(xxxx);
2:把所有关注的文件描述符加入到集合中
fd_set fdset;
FD_ZERO(&fdset);//对所有位进行清零
    FD_SET(fd_serial, &fdset);
FD_SET(fd_socket, &fdset);
3:调用select进行监控 
  struct timeval tm = {5,0};
max_fd = fd_serial>fd_socket?fd_serial:fd_socket;
  ret = select(max_fd +1 ,&fdset, NULL, NULL,  &tm);
4:根据返回情况进行判断
if(ret < 0)
{
perror("select");
exit(1)
}
if(ret == 0)
printf("说明在5秒之内,没有任何IO准备好,超时\n!");
if(ret> 0) //有IO满足可读条件
{
if(FD_ISSET(fd_serial,&fdset))
read(fd_serial, buf, 20);//串口有数据可读
if(FD_ISSET(fd_socket,&fdset))
conn_fd = accept(fd_socket, xxxxx); //有新的连接请求
}


其他相关函数:
struct hostent *gethostbyname(const char *name); //根据主机名或域名获得主机信息

           struct hostent {
               char  *h_name;         /*官方主机名*/
               char **h_aliases;        /*主机的别名列表*/--->char *h_aliases[ ]={“host1”, “host1”};
               int    h_addrtype;      /*主机的地址类型(IPv4   IPv6)*/
               int    h_length;        /*地址长度*/
               char **h_addr_list;      /*主机的IP地址列表*/--->char *h_addr_list[ ] ={ip1, 1p2};
本质:解析文件 /etc/hosts


----------------------------------------------------------------------------------------------------------------
测试程序:用select创建一个类似于server的模型
编写步骤:
Not1:创建一个链表并初始化;创建集合:fd_set read_fds; int max;
No.2:创建套接字并使套接字处于监听状态
No.3:先将max置为sock_fd的值,然后进入while(1)进行select循环监控
while(1)
{
FD_ZERO(&read_fds);  //对”集合“所有位清零
FD_SET(sock_fd, &read_fds); //将sock_fd加入”集合“
if(!(is_empty(head))
{
p = head->next;
遍历链表将将每个链表节点放入”集合“
  }
    ret=select(max_fd+1, &read_fds, NULL, NULL, NULL);//调用select开始监控
ret<0:--->perror
if(FD_ISSET(sock_fd, &read_fds))
Accept:进行客户端连接 并将client_fd放入链表
if(!is_empty(head))
p=head37_->next;   if(p!=NULL);
if(FD_ISSET(p->client, &read_fds)); //如果某client有数据过来了,则集合中相应的位为1
read(); 返回值为0说明客户端调用了close(sock_fd)关闭了套接字
从链表中关闭文件描述符并关闭该文件
        否则输出接收到的内内容
p=p->next;
}
No.4:close(sock_fd);


发布了49 篇原创文章 · 获赞 28 · 访问量 11万+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章