博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
套接字的多种可选项
阅读量:2177 次
发布时间:2019-05-01

本文共 7172 字,大约阅读时间需要 23 分钟。

套接字可选项和I/O缓冲大小

套接字多种可选项

之前的程序创建好套接字之后是直接使用的,此时通过默认的套接字特性进行数据通信。

示例有时需要特别操作套接字特性,就要更改一些可选项。



套接字可选项分层:

IPPROTO_IP层可选项是IP协议相关事项

IPPROTO_TCP层是TCP协议相关的事项

SOL_SOCKET层是套接字相关的通用可选项



函数getsockopt & setsockopt

getsockopt函数读取套接字可选项:


setsockopt更改可选项:



getsockopt()调用方法及其作用:

/* getsockopt函数的调用方法 * 用协议层SOL_SOCKET,名为SO_TYPE的可选项查看套接字类型(TCP或UDP) */#include
#include
#include
#include
void error_handling(char *message){ fputs(message,stderr); fputc('\n',stderr); exit(1);}int main(int argc, char *argv[]){ int tcp_sock, udp_sock; int sock_type; socklen_t optlen; int state; optlen = sizeof(sock_type); tcp_sock = socket(PF_INET,SOCK_STREAM,0); //生成TCP套接字 udp_sock = socket(PF_INET,SOCK_DGRAM,0); //生成UDP套接字 printf("SOCK_STREAM: %d \n",SOCK_STREAM); printf("SOCK_DGRAM : %d \n",SOCK_DGRAM); //输出创建套接字时传入的SOCK_STREAM,SOCK_DGRAM. /* getsockopt()获取套接字信息 * TCP套接字将获得SOCK_STREAM常数值1,UDP套接字将获得SOCK_DGRAM常数值2 */ state = getsockopt(tcp_sock,SOL_SOCKET,SO_TYPE,(void*)&sock_type,&optlen); //SO_TYPE表示查看套接字的类型 if (state) error_handling("getsockopt() error!"); printf("Socket type one : %d \n",sock_type); state = getsockopt(udp_sock,SOL_SOCKET,SO_TYPE,(void*)&sock_type,&optlen); if(state) error_handling("getsockopt() error!"); printf("Socket type two : %d \n",sock_type); return 0;}

运行结果:./sock_type

可选项SO_SNDBUF & SO_RCVBUF

创建套接字将同时生成I/O缓冲。SO_RCVBUF是输入缓冲大小可选项。SO_SNDBUF是输出缓冲大小可选项。

用这两个可选项既可以读取当前I/O缓冲大小,也可以进行更改。


通过以下示例读取创建套接字时默认I/O缓冲大小:

/* 读取I/O缓冲大小 */#include
#include
#include
#include
void error_handling(char *message){ fputs(message,stderr); fputc('\n',stderr); exit(1);}int main(int argc,char *argv[]){ int sock; int snd_buf,rcv_buf,state; socklen_t len; sock = socket(PF_INET,SOCK_STREAM,0); len = sizeof(snd_buf); state = getsockopt(sock,SOL_SOCKET,SO_SNDBUF,(void*)&snd_buf,&len); //SO_SNDBUF代表读取输出(发送)缓冲区 if (state) error_handling("getsockopt() error!"); len = sizeof(rcv_buf); state = getsockopt(sock,SOL_SOCKET,SO_RCVBUF,(void*)&rcv_buf,&len); //SO_RCVBUF代表读取输入(接收)缓冲区 if (state) error_handling("getsockopt() error!"); printf("Input buffer size: %d \n",rcv_buf); printf("Output buffer size: %d \n",snd_buf); return 0;}

运行结果: ./get_buf



下例setsockopt()函数改变I/O缓冲大小:

/* setsockopt()设置套接字一些特性 * 本例设置I/O缓冲大小 * */#include
#include
#include
#include
void error_handling(char *message){ fputs(message,stderr); fputc('\n',stderr); exit(1);}int main(int argc,char *argv[]){ int sock; int snd_buf = 1024*3, rcv_buf = 1024*3; int state; socklen_t len; sock = socket(PF_INET,SOCK_STREAM,0); state = setsockopt(sock,SOL_SOCKET,SO_RCVBUF,(void*)&rcv_buf,sizeof(rcv_buf)); //设置输入缓冲长度为rcv_buf:3 if (state) error_handling("setsockopt() error!"); state = setsockopt(sock,SOL_SOCKET,SO_SNDBUF,(void*)&snd_buf,sizeof(snd_buf)); //设置输出缓冲长度为snd_buf:3 if (state) error_handling("setsockopt() error!"); len = sizeof(snd_buf); state = getsockopt(sock,SOL_SOCKET,SO_SNDBUF,(void*)&snd_buf,&snd_buf); if (state) error_handling("getsockopt() error!"); len = sizeof(rcv_buf); state = getsockopt(sock,SOL_SOCKET,SO_RCVBUF,(void*)&rcv_buf,&rcv_buf); if (state) error_handling("getsockopt() error!"); printf("Input buffer size : %d \n",rcv_buf); printf("Output buffer size : %d \n",snd_buf); return 0;}

运行结果:

(并不完全按照我们的要求进行)



SO_REUSEADDR

可选项SO_REUSEADDR及其相关的Time_wait状态很重要!


发生地址分配错误(Binding Error)

/* 回声服务器端 */#include
#include
#include
#include
#include
#include
#define TRUE 1#define FALSE 0void error_handling(char *message){ fputs(message,stderr); fputc('\n',stderr); exit(1);}int main(int argc,char *argv[]){ int serv_sock, clnt_sock; char message[30]; int option , str_len; socklen_t optlen , clnt_adr_sz; struct sockaddr_in serv_adr, clnt_adr; if (argc != 2) { printf("Usage : %s
\n",argv[0]); exit(1); } serv_sock = socket(PF_INET,SOCK_STREAM,0); if (serv_sock == -1) error_handling("socket() error!"); /* * optlen = sizeof(option); * option = TRUE; * setsockopt(serv_sock,SOL_SOCKET,SO_REUSEADDR,(void*)&option,optlen);    //使套接字在Time-wait状态下的端口号可以重新分配给新的套接字 */ memset(&serv_adr,0,sizeof(serv_adr)); serv_adr.sin_family = AF_INET; serv_adr.sin_addr.s_addr = htonl(INADDR_ANY); serv_adr.sin_port = htons(atoi(argv[1])); if (bind(serv_sock,(struct sockaddr*)&serv_adr,sizeof(serv_adr)) == -1) error_handling("bind() error!"); if (listen(serv_sock,5) == -1) error_handling("listen() error!"); clnt_adr_sz = sizeof(clnt_adr); clnt_sock = accept(serv_sock,(struct sockaddr*)&clnt_adr,&clnt_adr_sz); while ((str_len = read(clnt_sock,message,sizeof(message))) != 0) { write(clnt_sock,message,str_len); write(1,message,str_len); //1是标准输出,将message中的消息输出 } close(clnt_sock); close(serv_sock); return 0;}

//在客户端控制台输入Q或者通过CTRL+C终止程序

(调用close函数,向服务器端发送FIN消息并经过四次握手过程)


通常都是由客户端先请求断开连接,这种情况重新运行服务器端不成问题。


若是在服务器端控制台输入CTRL+C,强制关闭服务器端(模拟服务器端向客户端发送FIN消息),服务器端重新运行会产生问题,用同一端口运行服务器端,将输出bind() error的消息。


上述两种情况唯一的区别是谁先传输了FIN消息,结果却迥然不同,原因何在呢?


Time-wait状态

重温四次握手过程:

                            



假设A是服务器端,主机A向B发送FIN信息相当于在服务器端控制台输入CTRL+C。

套接字经过四次握手过程后并非立即删除,而是要经过一段时间的Time-wait状态。套接字处在Time-wait状态时,相应端口是正在使用的状态。因此bind()调用过程中当然会发生错误。


先端开连接的(先发送FIN消息)的主机才经过Time-wait状态。


提示:

不管是服务器端还是客户端都会有Time-wait状态。先端开连接的套接字必然会经过Time-wait过程。因为客户端套接字的端口号是任意指定的,与服务器端不同,客户端每次运行程序时都会动态分配端口号,因此无需过多关注Time-wait过程 (所以上例中从客户端结束,再运行服务器端分配同样端口号不会bind() error!)



Time-wait状态的作用:

四次握手过程中, 假设A向B发送完最后一条ACK消息后立即消除套接字:

若主机A向主机B传输最后一条ACK消息(SEQ 7501,ACK 5001)在传递途中丢失,未能传给主机B。主机B没收到确认信号,会认为之前发送的FIN消息(SEQ 7501, ACK 5001)未能抵达主机A,继而重传。若此时A已是完全终止的状态,则主机B永远无法收到从主机A最后传来的ACK消息。相反,若主机A的套接字处于Time-wait状态,则会向主机B重传最后的ACK消息。

基于这些考虑,先传输FIN消息的主机应经过Time-wait过程。



地址再分配

Time-wait有时并不那么方便。若系统发生故障从而紧急停止的情况,需要尽快重启服务器端以提供服务,但因处于Time-wait状态而必须等几分钟。因此,TIme-wait状态也有缺点。


四次握手不得不延长Time-wait过程的情况


                        

若最后的数据丢失,B会重传FIN消息,收到FIN消息的主机A将重启Time-wait计时器。因此,若网络不理想,Time-wait状态将持续。

解决方案:

     在套接字的可选项中更改SO_REUSEADDR的状态。调整该参数,可将Time-wait状态下的套接字端口号重新分配给新的套接字。SO_REUSEADDR的默认值是0,意味着无法分配Time-wait状态下的套接字端口号。将此值该为1。(如上示例代码,改动在注释中)

optlen = sizeof(option);option = TRUE;setsockopt(serv_sock, SOL_SOCKET, SO_REUSEADDR, (void*)&option, optlen);

此时,处于服务器端变为可随时运行的状态。



TCP_NODELAY

TCP_NODELAY可选项属于IPPROTO_TCP协议层 (头文件是arpa/inet.h)

而要使用TCP_NODELAY可选项,必须包含头文件(netinet/tcp/h)

Nagle算法

Nagle算法防止因数据包过多而发生网络过载。应用与TCP层。是否使用的差异如下图:

                


结论:只有收到前一条数据的ACK消息时,Nagle算法才发送下一数据!


TCP套接字默认使用Nagle算法交换数据,最大限度地进行缓冲,直到收到ACK。


左侧:为了发送Nagle,将其传递到缓冲区。因为头字符N之前没有其他数据(没有需接收的ACK),因此立即传输。等待收到了N的ACK消息(等待过程中agle填入输出缓冲),收到ACK后,将输出缓冲中的agle装入一个数据包发送。    整个过程只用了4个数据包。


右侧:N到e字符依次传到输出缓冲,发送过程与ACK接收与否无关。数据到达缓冲后立即被发送出uq。整个过程用了10个数据包。    因此不使用Nagle算法将对网络流量产生负面影响。


因此,为了提高网络传输效率,必须使用Nagle算法。


提示:上图过程分析是极端情况,实际程序中将字符传给输出缓冲时并不是逐字传递的。



有些情况下Nagle算法也不适用:传输大文件数据时,将文件数据传入输出缓冲不会花太多时间,因此,即便不使用Nagle算法,也会装满输出缓冲时传输数据包。不使用Nagle算法反而可以无需等待ACK连续传输。


禁用Nagle算法

只需将套接字可选项TCP_NODELAY改为1

int opt_val = 1;setsockopt(sock, IPPROTO_TCP, TCP_NODELAY, (void*)&opt_val, sizeof(opt_val));

可以通过TCP_NODELAY的值查看Nagle算法的设置状态:

int opt_val;socklen_t opt_len;opt_len = sizeof(opt_val);getsockopt(sock, IPPROTO_TCP, TCP_NODELAY, (void*)&opt_val, &opt_len);


一般情况下,不使用Nagle算法可以提高传输速率。但如果无条件放弃使用Nagle算法,会增加过多的网络流量,反而影响传输。因此,是否使用Nagle算法应再三斟酌。

你可能感兴趣的文章
【LEETCODE】86- Partition List [Python]
查看>>
【LEETCODE】147- Insertion Sort List [Python]
查看>>
【算法】- 动态规划的编织艺术
查看>>
用 TensorFlow 让你的机器人唱首原创给你听
查看>>
对比学习用 Keras 搭建 CNN RNN 等常用神经网络
查看>>
深度学习的主要应用举例
查看>>
word2vec 模型思想和代码实现
查看>>
怎样做情感分析
查看>>
用深度神经网络处理NER命名实体识别问题
查看>>
用 RNN 训练语言模型生成文本
查看>>
RNN与机器翻译
查看>>
用 Recursive Neural Networks 得到分析树
查看>>
RNN的高级应用
查看>>
TensorFlow-7-TensorBoard Embedding可视化
查看>>
轻松看懂机器学习十大常用算法
查看>>
一个框架解决几乎所有机器学习问题
查看>>
特征工程怎么做
查看>>
机器学习算法应用中常用技巧-1
查看>>
机器学习算法应用中常用技巧-2
查看>>
通过一个kaggle实例学习解决机器学习问题
查看>>