socket编程,通信
client端 socket() ----->connect() ------->recv() -----> close();
server端 socket() ----->bind() ------> listen() ---->accept() ------>send() ------->close();
1> socket(int family,int type,int protocol);
family ->协议类型
AF_INET --------------> IPV4
AF_INET6 ---------------> IPV6
AF_ROUTE ---------------> 路由套接口
type->是指套接字类型
SOCK_STREAM --------------------> 字节流套接字 TCP
SOCK_DGRAM --------------------> 数据报套接字 UDP
SOCK_RAW --------------------> 原始套接字
函数调用成功返回小的非负整数,文件描述符类型,否则返回-1表示调用失败
2.connect(int sockfd,struct sockaddr*addr,socklen_t addrlen)
sockfd 是从socket的返回值;
addr是指向服务器的套接字的地址结构的指针
addrlen是该套接字的地址结构的大小
1 struct sockaddr_in server; 2 bzero(&server,sizeof(server)); 3 server.sin_family = AF_INET; 4 server.sin_port = htonl(1234); 5 server.sin_addr.s_addr = inte_addr("127.0.0.1"); 6 if(connect(sockfd,(struct sockaddr *)&server,sizeof(server)) == -1) 7 { 8 //强制性地址转换,转为通用套接字结构 9 }
3>bind(int sockfd,struct sockaddr *server,socklen_len addrlen);
sockfd->socket的返回值
server->表示指向于特定协议的地址结构的指针
addrlen->表示该套接字的地址结构的长度
1 struct sockaddr_in server; 2 bzero(&server,sizeof(server)); 3 server.sin_family = AF_INET; 4 server.sin_port = htonl(1234); 5 server.sin_addr.s_addr = htonl(INADDR_ANY); 6 if(bind(sockfd,(struct sockaddr *)&server,sizeof(server)) == -1) 7 { 8 ///sockaddr 强制转换 9 }
4>listen(int sockfd,int backlog)
sockfd --->socket的返回值
backlog --->规定了请求返回队列的最大连接数,它对队列中等待服务请求的数目进行限制,如果一个服务请求到来时,输入队列已满,该套接字将拒绝连接请求。
listen(sockfd,5);
对于一个监听套接字,内核要维护两个队列:未完成连接队列和已完成连接队列
未完成连接队列:为每个请求建立连接的SYN分节设一个条目,服务器正等待完成三次握手,当前的套接字处在SYN_RECD状态
已完成连接队列:为每个完成TCP三次握手的客户端开设一个条目,当前的套接字状态时ESTABLISHED。
5>accept(int sockfd,struct sockaddr *client,socklen_t *addrlen)
sockfd 是socket()返回的套接字描述符,在调用listen的时候此套接字变成了监听套接字
client 是返回对方的套接字和地址结构
addrlen 是对应结构的长度
accetp()函数返回,已连接套接字描述符。
注:
一个服务器只能有一个监听套接字,而且会一直存在,直到服务器关闭,而已连接套接字描述符是内核为每个被接受的客户都创建一个,当服务器完成与客户的数据传输时,要关闭连接套接字,所以监听套接字接受客户的链接请求,已连接套接字描述符负责对应客户进行数据传送。
1 int listenfd,connfd; 2 struct sockaddr_in client; 3 socklen_t addrlen; 4 addrlen = sizeof(client); 5 connfd = accept(listenfd,(struct sockaddr *)&client,&addrlen); 6 if(connfd == -1) 7 { 8 9 }
原型如下:
各个参数含义如下:
- int maxfdp:最大描述符值 + 1
- fd_set *readfds:对可读感兴趣的描述符集
- fd_set *writefds:对可写感兴趣的描述符集
- fd_set *errorfds:对出错感兴趣的描述符集
- struct timeval *timeout:超时时间(注意:对于linux系统,此参数没有const限制,每次select调用完毕timeout的值都被修改为剩余时间,而unix系统则不会改变timeout值)
select函数会在发生以下情况时返回:
- readfds集合中有描述符可读
- writefds集合中有描述符可写
- errorfds集合中有描述符遇到错误条件
- 指定的超时时间timeout到了
当select返回时,描述符集合将被修改以指示哪些个描述符正处于可读、可写或有错误状态。可以用FD_ISSET宏对描述符进行测试以找到状态变化的描述符。如果select因为超时而返回的话,所有的描述符集合都将被清空。
select函数返回状态发生变化的描述符总数。返回0意味着超时。失败则返回-1并设置errno。可能出现的错误有:EBADF(无效描述符)、EINTR(因终端而返回)、EINVAL(nfds或timeout取值错误)。
设置描述符集合通常用如下几个宏定义:
1 FD_ZERO(fd_set *fdset); /* 将所有位设为0 */ 2 FD_SET(int fd, fd_set *fdset); /* 将fd位设为1 */ 3 FD_CLR(int fd, fd_set *fdset); /* 将fd位设为0 */ 4 int FD_ISSET(int fd, fd_set *fdset); /* 检测fd位是否为1 */
如:
1 fd_set set; 2 FD_ZERO(&rset); /* i初始化rset */ 3 FD_SET(1, &rset); /* 将fd = 1 的描述符设为1 */ 4 FD_SET(4, &rset); /* 将fd = 4 的描述符设为1 */ 5 FD_SET(5, &rset); /* 将fd = 5 的描述符设为1 */
当select返回的时候,rset位都将被置0,除了那些有变化的fd位。
当发生如下情况时认为是可读的:
- socket的receive buffer中的字节数大于socket的receive buffer的low-water mark属性值。(low-water mark值类似于分水岭,当receive buffer中的字节数小于low-water mark值的时候,认为socket还不可读,只有当receive buffer中的字节数达到一定量的时候才认为socket可读)
- 连接半关闭(读关闭,即收到对端发来的FIN包)
- 发生变化的描述符是被动套接字,而连接的三路握手完成的数量大于0,即有新的TCP连接建立
- 描述符发生错误,如果调用read系统调用读套接字的话会返回-1。
当发生如下情况时认为是可写的:
- socket的send buffer中的字节数大于socket的send buffer的low-water mark属性值以及socket已经连接或者不需要连接(如UDP)。
- 写半连接关闭,调用write函数将产生SIGPIPE
- 描述符发生错误,如果调用write系统调用写套接字的话会返回-1。
注意:
select默认能处理的描述符数量是有上限的,为FD_SETSIZE的大小。
对于timeout参数,如果置为NULL,则表示wait forever;若timeout->tv_sec = timeout->tv_usec = 0,则表示do not wait at all;否则指定等待时间。
如果使用select处理多个套接字,那么需要使用一个数组(也可以是其他结构)来记录各个描述符的状态。而使用poll则不需要,下面看poll函数。
(二)poll()函数
原型如下:
各参数含义如下:
- struct pollfd *fdarray:一个结构体,用来保存各个描述符的相关状态。
- unsigned long nfds:fdarray数组的大小,即里面包含有效成员的数量。
- int timeout:设定的超时时间。(以毫秒为单位)
poll函数返回值及含义如下:
- -1:有错误产生
- 0:超时时间到,而且没有描述符有状态变化
- >0:有状态变化的描述符个数
着重讲fdarray数组,因为这是它和select()函数主要的不同的地方:
pollfd的结构如下:
1 struct pollfd { 2 int fd; /* 测试描述符*/ 3 short events; /* 测试条件*/ 4 short revents; /* 测试结果 */ 5 };
其实poll()和select()函数要处理的问题是相同的,只不过是不同组织在几乎相同时刻同时推出的,因此才同时保留了下来。select()函数把可读描述符、可写描述符、错误描述符分在了三个集合里,这三个集合都是用bit位来标记一个描述符,一旦有若干个描述符状态发生变化,那么它将被置位,而其他没有发生变化的描述符的bit位将被clear,也就是说select()的readset、writeset、errorset是一个value-result类型,通过它们传值,而也通过它们返回结果。这样的一个坏处是每次重新select 的时候对集合必须重新赋值。而poll()函数则与select()采用的方式不同,它通过一个结构数组保存各个描述符的状态,每个结构体第一项fd代表描述符,第二项代表要监听的事件,也就是感兴趣的事件,而第三项代表poll()返回时描述符的返回状态。合法状态如下:
- POLLIN: 有普通数据或者优先数据可读
- POLLRDNORM: 有普通数据可读
- POLLRDBAND: 有优先数据可读
- POLLPRI: 有紧急数据可读
- POLLOUT: 有普通数据可写
- POLLWRNORM: 有普通数据可写
- POLLWRBAND: 有紧急数据可写
- POLLERR: 有错误发生
- POLLHUP: 有描述符挂起事件发生
- POLLNVAL: 描述符非法
对于POLLIN | POLLPRI等价与select()的可读事件;POLLOUT | POLLWRBAND等价与select()的可写事件;POLLIN 等价与POLLRDNORM | POLLRDBAND,而POLLOUT等价于POLLWRBAND。如果你对一个描述符的可读事件和可写事件以及错误等事件均感兴趣那么你应该都进行相应的设置。
对于timeout的设置如下:
- INFTIM: wait forever
- 0: return immediately, do not block
- >0: wait specified number of milliseconds