一、Time_Wait状态。
我们先回顾一下之前学过的socket断开连接时的四次握手过程:第一次握手:主机A向主机B发送FIN,提出断开连接的请求。
第二次握手:主机B接收到请求后向主机A传递ACK
第三次握手:主机B端向主机A发送FIN.
第四次握手:主机A向主机B发送ACK
发起断开连接请求的主机A在完成第四次握手之后,会进入Time_Wait状态,此时其并没有完全的断开连接,而是在一段时间内保持Time_Wait状态。目的主要是防止第四次握手的数据包丢失,没有传递到主机B时,B会重新传递第三次握手的FIN,而此时保持在Time_Wait状态的主机A会重新发送第四次握手的数据包以保证主机B也能正常的断开连接。
socket在TImeWait状态时,由于已经占用了端口号,当前端口号无法在这段时间内被重复使用.这就是我们有时候用Ctrl+C强制停止和客户端连接过的服务器端时,发生在启动服务器会出现bind error的现象的原因。因为谁发起断开连接的请求谁就会在第四次握手结束后进入Time_Wait的状态。当我们使用Ctrl+C强制停止服务器端的连接时,服务器会向客户端发起断开连接的请求,当进入Time_Wait状态后,此时的端口仍被服务器的socket占据着,因此我们重新服务器时运来的断开会出现bind error的现象。
opelen=sizeof(option);
option=1;
setsockopt(sock,SOL_SOCKET,SO_REUSEADDR,(void*)&option,optlen);
option=1,设置Time_Wait状态下将可将端口号重新分配给新的套接字。
option=0,缺省值,设置Time_Wait状态下将不可将端口号重新分配给新的套接字。
注意:设置时一定要在bind之前设置才会生效!!!!
二、Nagle算法
Nagle算法应用于TCP层,是为了解决数据包过多而发生网络过载的问题。它的原理很简单,以传递一个字符串hello为例:
未使用Nagle算法时:
主机A向主机B传递“hello”时,不管主机B有没有回应,依次传递‘h’、‘e’、‘l’、‘l’、‘o’(这里是极端情况,实际上传送时不会一个字节一个字节的传送),这样算上主机B回给主机A的包共用了10个数据包;
使用Nagle算法时:
主机A向主机B传递“hello”时,先传一个‘h’,此时主机A会最大限度的进行缓冲,当收到主机B发回的‘h’的ACK消息时,将缓存的数据‘ello’一起发送给主机B,这样通过4个数据包就完成了发送任务。
在默认情况下,socket在通信时是使用Nagle算法的,我们可以使用setsockopt函数改变这一设置:
opelen=sizeof(option);
option=1;
setsockopt(sock,IPPROTO_TCP,TCP_NODELAY,(void*)&option,optlen);
option=1,缺省值,开启Nagle算法。
option=0,关闭Nagle算法。
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<arpa/inet.h>
#include<sys/socket.h>
#include<linux/tcp.h>
#define TRUE 1
#define FLASE 0
void error_handling(char* message);
int main(int argc,char* argv[])
{
int serv_sock,clnt_sock;
char message[30];
struct sockaddr_in serv_addr,clnt_addr;
int clntAdrLen,strLen,optlen,option;
if(argc!=2)
{
printf("Usage %s <port>\n",argv[0]);
exit(1);
}
//创建socket
serv_sock=socket(AF_INET,SOCK_STREAM,0);
if(serv_sock==-1)
error_handling("socket error");
//设置Time_Wait状态下套接字可重新分配
optlen=sizeof(option);
option=TRUE;
setsockopt(serv_sock,SOL_SOCKET,SO_REUSEADDR,(void*)&option,optlen);
//设置Nagle算法禁用
optlen=sizeof(option);
optlen=TRUE;
setsockopt(serv_sock,IPPROTO_TCP,TCP_NODELAY,(void*)&option,optlen);
//准备通信地址
memset(&serv_addr,0,sizeof(serv_addr));
serv_addr.sin_family=AF_INET;
serv_addr.sin_addr.s_addr=htonl(INADDR_ANY);
serv_addr.sin_port=htons(atoi(argv[1]));
//socket和通信地址的bind
if(bind(serv_sock,(struct sockaddr*)&serv_addr,sizeof(serv_addr))!=0)
error_handling("bind error");
//监听
if(listen(serv_sock,5)==-1)
error_handling("listen error");
//接收连接
clntAdrLen=sizeof(clnt_addr);
clnt_sock=accept(serv_sock,(struct sockaddr*)&clnt_addr,&clntAdrLen);
//读取数据
while((strLen=read(clnt_sock,message,sizeof(message)))!=0)
{
printf("sizeof(message) = %d\n",sizeof(message));
printf("接收到的数据是:%s 数据长度:%d\n",message,strLen);
write(clnt_sock,message,strLen);
//write(1,message,strLen);
fputs("回送成功。",stdout);
fputc('\n',stdout);
}
close(clnt_sock);
close(serv_sock);
return 0;
}
void error_handling(char* message)
{
fputs(message,stderr);
fputc('\n',stderr);
exit(1);
}
Github位置:
https://github.com/HymanLiuTS/NetDevelopment
克隆本项目:
git clone git@github.com:HymanLiuTS/NetDevelopment.git
获取本文源代码:
git checkout NL10