TCP的异常终止连接及SO_LINGER选项:
终止一个连接的正常方式是一方发送FIN,进入FIN_WAIT1状态。当我们调用close()函数关闭连接之后,如果在发送缓冲区中还有数据,那么系统会发送这些数据,然后再发送FIN,但是也有可能发送一个复位报文段来释放一个连接,这种现象就称为异常终止连接。
异常终止一个连接有两个优点:(1.马上丢弃待发送数据并立即发送复位报文段(2.RST的接收方会区分另一端执行的是异常关闭还是正常关闭。
我们可以自己编写sock程序并使用tcpdump工具进行分析查看异常关闭的整个过程(sock代码放在最后)。
- 首先运行
tcpdump "port 6666" -i lo -S(指定监听的端口,-i参数代表选择监听的网卡,lo是用来进行环回测试的,-S命令是完整的显示序号) - 接下来运行我们编译完成的server端
./server - 再运行client端
./client - 这个时候就可以看到运行了
tcpdump那个终端显示出了三次握手的三个报文 - 再启动一个终端运行
netstat -ano | grep 6666,查看6666端口的使用情况 ctrl+c终止任意一端,这时候查看运行了tcpdump那个终端显示的数据,就会发现多了一行数据,即复位报文段。再次运行netstat -ano | grep 6666,会发现已经没有进程占用6666端口了。这点和以前不同,正常的终止连接主动关闭方会进入TIME_WAIT状态,但是此时已经没有进程占用6666端口了,说明主动关闭方跳过了TIME_WAIT状态直接关闭了。
贴出测试图:
tcpdump命令显示的结果(前三个是tcp三次握手,最后一个是RST复位报文段):
netstat命令显示的结果:
异常关闭的实现是通过setsockopt()函数中的SO_LINGER选项实现的。前面我们提到在正常情况下主动关闭连接会把发送缓冲区中的数据都发送出去,但是并不知道对端的TCP是否确认了数据。而使用SO_LINGER选项,并将l_linger的值设置为大于0,那么就会在调用close()之后,close()不立即返回,而是等待l_linger秒,直到对端发送了对数据和FIN的确认之后,close()再成功返回,还有一种close()返回的情况,那就是过了l_linger秒之后,对端还没有发送对数据和FIN的确认,这时close()会返回-1并设置errno为EWOULDBLOCK。这里有很重要的一点就是,即使close成功返回了,但是并不代表对端应用程序是否已读取数据,只能说明对端确认了数据和FIN。
如果l_linger的值设置为0,那么就会造成上面我们进行实验的情况,立即关闭连接,跳过了TIME_WAIT状态。
SO_LINGER选项总的来说有四个方面需要注意:
- 进程会阻塞,直到状态不为FIN_WAIT1、CLOSING、LAST_ACK或者等待超时
- 在等待的过程中如果接收到带数据的包还是会发送复位报文段
- 消耗更多的额外资源
- 设置等待时间为0时,不会发送FIN报文段而是发送RST报文段直接关闭连接,不管缓冲区中有无数据。
server端代码(多线程处理):
#include <stdio.h>
#include <unistd.h>
#include <sys/socket.h>
#include <stdlib.h>
#include <string.h>
#include <netinet/ip.h>
#include <arpa/inet.h>
#include <pthread.h>
#define SERV_PORT 6666
void *deal(void *arg)
{
int listenfd = (int )arg;
printf("fd:%d\n", listenfd);
while(1)
{
char msg[BUFSIZ];
int n = read(listenfd, msg, BUFSIZ);
int i = 0;
for(; i < n; i++)
{
msg[i] = toupper(msg[i]);
}
write(listenfd, msg, n);
}
close(listenfd);
}
int main()
{
struct sockaddr_in serv_addr, clie_addr;
socklen_t len;
int sockfd, listenfd;
sockfd = socket(AF_INET, SOCK_STREAM, 0);
char msg[BUFSIZ];
pthread_t pid;
bzero(&serv_addr, sizeof(serv_addr));
serv_addr.sin_family = AF_INET;
serv_addr.sin_port = htons(SERV_PORT);
inet_pton(AF_INET, "127.0.0.1", &serv_addr.sin_addr.s_addr);
bind(sockfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr));
struct linger flag;
flag.l_onoff = 1; //l_onoff置1代表开启该选项
flag.l_linger = 0; //指定等待时间
setsockopt(sockfd, SOL_SOCKET, SO_LINGER, &flag, sizeof(flag));
listen(sockfd, 5);
printf("wait accepting......\n");
int i = 0;
while(1)
{
len = sizeof(clie_addr);
char addr[33];
listenfd = accept(sockfd, (struct sockaddr *)&clie_addr, &len);
printf("%dth client connected.ip:%s,port:%d\n", i++,
inet_ntop(AF_INET, &serv_addr.sin_addr.s_addr, addr, sizeof(addr)),
ntohs(serv_addr.sin_port));
printf("fd.:%d\n", listenfd);
pthread_attr_t attr;
pthread_attr_init(&attr);
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
pthread_create(&pid, &attr, deal, (void *)listenfd);
pthread_attr_destroy(&attr);
}
close(listenfd);
close(sockfd);
return 0;
}
client端:
#include <stdio.h>
#include <unistd.h>
#include <sys/socket.h>
#include <stdlib.h>
#include <string.h>
#include <netinet/ip.h>
#include <arpa/inet.h>
#define SERV_PORT 6666
int main()
{
struct sockaddr_in serv_addr, clie_addr;
int sockfd, len;
sockfd = socket(AF_INET, SOCK_STREAM, 0);
char buf[BUFSIZ];
bzero(&serv_addr, sizeof(serv_addr));
serv_addr.sin_family = AF_INET;
serv_addr.sin_port = htons(SERV_PORT);
inet_pton(AF_INET, "127.0.0.1", &serv_addr.sin_addr.s_addr);
connect(sockfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr));
while(1)
{
fgets(buf, sizeof(buf), stdin);
write(sockfd, buf, strlen(buf));
len = read(sockfd, buf, sizeof(buf));
write(STDOUT_FILENO, buf, len);
}
close(sockfd);
return 0;
}
8392



被折叠的 条评论
为什么被折叠?



