在accept返回之前,连接终止
2013-08-20 16:34
169 查看
情况概述
有一种类似于中断系统调用的情况,会导致accept返回一个非致命错误,这种情况下,我们只需要再次调用accept就行。下图所示的数据包系列,在繁忙的服务器上很常见(特别是繁忙的Web服务器)。在这里,三次握手完成后,连接建立完成,然后Client TCP发送一个RST(reset)。在服务器方,连接被它的TCP放入等待连接队列,当RST到来时,连接正在等待服务器进程调用accept。一段时间后,服务器进程调用accept。
场景模拟方法
一种简单的模拟这种场景的方法是:启动server,让它调用socket, bind, listen, 然后在调用accept之前睡眠一端时间。当server进程在休眠时,启动客户端程序并且让它调用socket和connect。一旦connect返回,就设置SO_LINGER套接字选项来产生RST并且终止程序。不幸的是,如何处理这种已终止的连接是与实现相关的。继承自Berkeley的实现完全由内核来处理已终止连接,服务器进程永远看不到它。然而,大部分的SVR4实现,在accept返回时返回一个错误给进程,而返回的错误又是依赖于实现的。这些SVR4实现返回值为EPROTO的errno(“protocol error”),但是POSIX指定在这种情况必须返回ECONNABORTED(“Software caused connection abort”)。POSIX这样规定是考虑到,当流子系统中一些致命的协议相关的事件发生时,同样会返回EPROTO。为client已建立连接的非致命终止返回相同的错误,可以让server知道是否应该再次调用accept。在出现ECONABORTED错误的情况下,server可以忽略该错误而继续调用accept等待连接。
Berkeley-derived实现
继承自Berkeley的内核实现不会把该错误传递到用户进程。所涉及的步骤如下:处理RST,导致tcp_close被调用。这个函数先调用in_pcbdetach ,接着调用sofree,然后发现这个已被终止的socket仍然在监听套接字连接完成队列中(连接就绪,等待accept返回给进程),就把该socket删除并且释放相应的资源。当server抽出时间调用accept时,它重来不知道一个已经就绪的连接被从队列中删除了模拟场景代码
服务器:#include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <netdb.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <fcntl.h> #include <errno.h> int open_listenfd(int port) { struct sockaddr_in myAddr; int listenfd; /* Create a socket descriptor */ if ( (listenfd = socket(AF_INET, SOCK_STREAM, 0)) < 0) return -1; /* listenfd will be an end point for all requests to port on any IP address for this host */ bzero((char *)&myAddr, sizeof(myAddr)); myAddr.sin_family = AF_INET; myAddr.sin_port = htons(port); myAddr.sin_addr.s_addr = htonl(INADDR_ANY); if (bind(listenfd, (struct sockaddr *)&myAddr, sizeof(myAddr)) < 0) { close(listenfd); return -1; } if (listen(listenfd, 1024) < 0) { close(listenfd); return -1; } return listenfd; } void SetNonblocking(int fd) { int flags = fcntl(fd, F_GETFL, 0); fcntl(fd, F_SETFL, flags | O_NONBLOCK); } #define MAXLEN 512 int main(int argc, char **argv) { int port, listenfd, connfd, addrLen = sizeof(struct sockaddr_in); struct sockaddr_in clientAddr; port = atoi(argv[1]); if ( (listenfd = open_listenfd(port)) < 0) { perror("open_listenfd: "); return 0; } fd_set readfds, readyfds; FD_ZERO(&readfds); FD_SET(listenfd, &readfds); int fds; //SetNonblocking(listenfd); while (1) { readyfds = readfds; fds = select(listenfd+1, &readyfds, NULL, NULL, NULL); if (fds <= 0) { printf("select error e: %s\n", strerror(errno)); continue; } printf("listening socket readable\n"); sleep(10); connfd = accept(listenfd, (struct sockaddr *)&clientAddr, &addrLen); if (connfd <= 0) { printf("accept error: %d\n", errno); } printf("%d connection from %s:%d\n", connfd, inet_ntoa(clientAddr.sin_addr), ntohs(clientAddr.sin_port)); echo(connfd); close(connfd); } close(listenfd); } void echo(int fd) { char buff[MAXLEN]; int n; while( (n = recv(fd, buff, MAXLEN, 0)) > 0) { buff = 0; puts(buff); } printf("recv return: %d errno: %d \n", n, errno); }客户端:
#include <sys/types.h> #include <sys/socket.h> #include <string.h> #include <errno.h> #include <netinet/in.h> #include <stdio.h> #include <unistd.h> #include <netdb.h> #define SERV_PORT 1234 int main(int argc, char **argv) { int sockfd; struct linger ling; struct sockaddr_in servaddr; if (argc != 2) printf("usage: tcpcli <IPaddress>"); sockfd = socket(AF_INET, SOCK_STREAM, 0); bzero(&servaddr, sizeof(servaddr)); servaddr.sin_family = AF_INET; servaddr.sin_port = htons(SERV_PORT); inet_pton(AF_INET, argv[1], &servaddr.sin_addr); connect(sockfd, (struct sockaddr *) &servaddr, sizeof(servaddr)); ling.l_onoff = 1; /* cause RST to be sent on close() */ ling.l_linger = 0; setsockopt(sockfd, SOL_SOCKET, SO_LINGER, &ling, sizeof(ling)); close(sockfd); exit(0); }
测试结果
在ubuntu系统(Linux ubuntu 2.6.35-22-generic #33-Ubuntu SMP Sun Sep 19 20:34:50 UTC 2010 i686 GNU/Linux)上的测试结果如下:当注释掉client中的close(sockfd)时,server端的accept会返回成功,接下来的recv返回errno为ECONNRESET Connection reset by peer
如果不注释掉close(sockfd),server端的accept会返回成功,接下来的recv返回0,表示对方关闭连接。
server端的sockfd无论是否是设置非阻塞,accept都会立即返回(这好像《unix网络编程》上说的不一样?)
启动了多少个client,server端的accept就返回多少次,并且都成功。
带来的问题
这种情况带来的问题是:造成accept不必要的阻塞。不过我测试中没发现这个问题。解决方法
解决这个问题的方法是:1 当使用select来指明socket可以被连接时,始终把监听socket设为非阻塞
2 忽略accept返回的EWOULDBLOCK(Berkeley实现,client终止连接) ,ECONNABORTED(POSIX实现,client终止连接) ,EPROTO(SVR4实现,client终止连接) ,EINTR (如果捕捉到信号)
相关文章推荐
- accept返回的socket的端口号和连接socket一样的!!! socket绑定信息结构
- 34-异常处理(accept 返回前连接中止)
- TCP异常处理(accept返回前连接中止)与SO_LINGER选项
- accept返回的socket的端口号和连接socket一样的!!! socket绑定信息结构
- UNIX网络编程5.11accept返回前连接中止5.12服务器进程终止5.13SIGPIPE信号5.14服务器主机崩溃5.15服务器崩溃后重启
- 用左连接完成对一个表中的多个外键字段替换查询返回名称
- TCP/IP详解卷1:协议(十)【TCP:传输控制协议, TCP连接的建立与终止】
- .net连接SAP返回Table 整理
- 高性能网络编程(一)----accept建立连接
- ORA-00257: 归档程序错误。在释放之前仅限于内部连接
- 四、Linux网络编程-TCP/IP基础(四)传输层协议TCP、TCP报文格式、连接三次握手、终止四次挥手
- TCP/IP详解学习 -- TCP连接的建立与终止
- 在阻塞式的tcp连接中使用recv接收数据未达到指定长度返回问题
- TCP连接的建立和终止 详解
- 如何在登陆后返回之前浏览的页面
- 计算机网络协议第九章,TCP连接的建立与终止
- 构建可靠的网络服务器之连接的建立和终止
- IOS 在当前日期时间加上 某个时间段(传负数即返回当前时间之前x月x日的时间)
- 返回起始日期到终止日期的日期数据
- C# ODBC连接MySQL Varchar字段返回byte[]问题