(一)socket阻塞与非阻塞问题
在创建socket函数中的第二个参数types用来指定服务类型,在Linux2.6.17版本起,除了之前的SOCK_STREAM(流式服务)、SOCK_UGRAM(数据报服务)以外,又增加了SOCK_NONBLOCK和SOCK_CLOEXEC,分别表示将创建的socket设置为非阻塞的和用fork创建的子进程在子进程中关闭该socket;
第一个问题:如何将socket设置为非阻塞的?
第一种,fcntl系统调用函数是用来控制文件描述符常用的属性和行为,通过此函数可以修改socket属性;
#include <fcntl.h>
int fcntl(int fd,int cmd,...)
fd是要被操作的文件描述符,cmd参数指定执行何种类型操作,常见的cmd有以下几种:
获取文件描述符标志 ----> F_GETFD 设置文件名描述符标志----->F_SETFD
获取文件描述符状态标志--->F_GETFL 设置文件名描述符状态标志----->F_SETFL
修改socket为非阻塞socket
int flags = fcntl(sockfd,F_GETFL,0);
fcntl(sockfd,F_SETFL,flags | SOCK_NONOBLOCK);
第二种:直接在创建socket时设置
int sockfd= socket(AF_INET,SOCK_STREAM | SOCK_NONOBLOCK,0);
注意accept返回的socket也是阻塞的,应用accept4函数将最后一个参数设置为SOCK_NONOBLOCK
int accept4(sockfd,(struct sockaddr*)&cli,&len,SOCK_NONBLOCK);
第二个问题:在完成三次握手之后,网络异常(掉线)或者客户端正常退出,对accept是否会有影响?
netstat -nt | grep hostport 查看此端口的TCP连接状态
掉线以及客户端退出都不会影响accept调用返回,由于accept只是从监听队列中取出连接,而不论连接状态处于何种状态,更不关心网络状态.不会因为监听队列中socket状态的变化而决定accept是否能调用成功。
第三个问题:关于socket选项的知识
#include <sys/socket.h>
int getsockopt(int sockfd,int level,int option_name,void *option_value,socklen_t *restrict option_len);
int setsockopt(int sockfd,int level,int option_name,const void *option_value,socklen_t option_len);
level指定要操作那个协议的选项,通用socket选项的level是SOL_SOCKET,option_name 是指定选项的名字
常用的通用socket选项有:SO_REUSEADDR 重用本地址 、SO_RCVBUF --->TCP接受缓冲区大小、SO_SNDBUF --->TCP发送缓冲区大小、SO_LINGER 若有数据待发送则延缓关闭、TCP_MAXSEG ---> TCP最大报文段大小 上述这几个必须在listen调用之前设置才有效。
socket选项一:SO_REUSEADDR
通过设置socket此选项可以强制使用处于time_wait状态的连接占用的socket地址,也可以通过修改内核参数/proc/sys/net/ipv4/tcp_tw_recycle来快速回收被关闭的连接socket,从而使得TCP连接根本不进入time_wait状态。进而允许本次程序重用socket地址
socket选项二:SO_RCVBUF SO_SNDBUF
SO_RCVBUF:TCP接受缓冲区的大小最小值为256字节 ,如果我们修改此大小小于256字节时,系统会忽略此次设置,最小值还是256字节
SO_SNDBUF:TCP发送缓冲区大小最小值是2048字节,是为了保证一个TCP连接有足够的空闲缓冲区处理拥塞,如果我们修改此大小,系统会将我们设置的值翻倍
也可以修改内核参数:/proc/sys/net/ipv4/tcp_rmem、/proc/sys/net/ipv4/tcp_wmem来强制TCP接受和发送缓冲区的大小没有最小值限制。
选项三:SO_LINGER
SO_LINGER用于控制close系统调用在关闭TCP连接时的行为,默认情况下,当我们使用close系统调用关闭socket时会立即返回,TCP模块将该socket对应的发送缓冲区的残留数据发送给对方
在设置此选项时,需要向函数传递Linger结构体:
struct linger
{
int l_onoff;//0未开启,1开启
int l_linger;//滞留时间
}
根据linger结构体中两个变量的值不同,close调用会有不同的方式
情况一:l_onoff==0;此时SO_LINGER不起作用,close使用默认行为关闭socket;
情况二:l_onoff != 0 && l_linger == 0;此时close调用立即返回,将该socket对应的发送缓冲区的数据发给对方,同时给对方发送一个复位报文段。这种情况给服务器提供了一个异常终止连接的方法;
情况三:l_onoff != 0 && l_linger >0;此时close的行为取决于两个条件:1.该socket对应的发送缓冲区是否还有残留数据;2.该socket是阻塞的还是非阻塞的;
对于阻塞的socket,close将等待一段长为l_linger的时间等TCP模块将发送缓冲区的数据发给对方并得到对方确认,返回-1并设置EWOULDBLOCK(不需要重新读写);
对于非阻塞的socket,close立即返回,此时需要根据返回值和error来判断残留数据是否发送完毕;如果tcp缓冲区还有数据需要发送,数据能够正确的发送到对端;在发送的最后一个包会加FIN标志;如果另一端也要关闭发FIN时,本端就会发RST,因为本端的SOCKET已经关了。