我有一个多线程服务器(线程池),它使用 20 个线程处理大量请求(一个节点最多 500/秒)。有一个侦听器线程接受传入连接并将它们排队以供处理程序线程处理。一旦响应准备好,线程就会写出到客户端并关闭套接字。一切似乎都很好,直到最近,一个测试客户端程序在读取响应后开始随机挂起。经过大量挖掘,似乎来自服务器的 close() 实际上并没有断开套接字。我已经使用文件描述符编号向代码中添加了一些调试打印,我得到了这种类型的输出。
Processing request for 21
Writing to 21
Closing 21
close() 的返回值为 0,否则将打印另一个调试语句。在客户端挂起的此输出之后,lsof 显示已建立的连接。
服务器 8160 根 21u IPv4 32754237 TCP 本地主机:9980->本地主机:47530(已建立)
客户端 17747 根 12u IPv4 32754228 TCP 本地主机:47530->本地主机:9980(已建立)
就好像服务器从不向客户端发送关闭序列,并且此状态一直挂起,直到客户端被杀死,使服务器端处于关闭等待状态
服务器 8160 根 21u IPv4 32754237 TCP localhost:9980->localhost:47530 (CLOSE_WAIT)
此外,如果客户端指定了超时,它将超时而不是挂起。我也可以手动运行
call close(21)
从 gdb 在服务器中,然后客户端将断开连接。这可能在 50,000 个请求中发生一次,但可能不会在很长一段时间内发生。
Linux 版本:2.6.21.7-2.fc8xen Centos 版本:5.4(最终版)
套接字动作如下
服务器:
int client_socket;
struct sockaddr_in client_addr;
socklen_t client_len = sizeof(client_addr);
while(true) {
client_socket = accept(incoming_socket, (struct sockaddr *)&client_addr, &client_len);
if (client_socket == -1)
continue;
/* insert into queue here for threads to process */
}
然后线程拾取套接字并构建响应。
/* get client_socket from queue */
/* processing request here */
/* now set to blocking for write; was previously set to non-blocking for reading */
int flags = fcntl(client_socket, F_GETFL);
if (flags < 0)
abort();
if (fcntl(client_socket, F_SETFL, flags|O_NONBLOCK) < 0)
abort();
server_write(client_socket, response_buf, response_length);
server_close(client_socket);
server_write 和 server_close。
void server_write( int fd, char const *buf, ssize_t len ) {
printf("Writing to %d\n", fd);
while(len > 0) {
ssize_t n = write(fd, buf, len);
if(n <= 0)
return;// I don't really care what error happened, we'll just drop the connection
len -= n;
buf += n;
}
}
void server_close( int fd ) {
for(uint32_t i=0; i<10; i++) {
int n = close(fd);
if(!n) {//closed successfully
return;
}
usleep(100);
}
printf("Close failed for %d\n", fd);
}
客户:
客户端使用 libcurl v 7.27.0
CURL *curl = curl_easy_init();
CURLcode res;
curl_easy_setopt( curl, CURLOPT_URL, url);
curl_easy_setopt( curl, CURLOPT_WRITEFUNCTION, write_callback );
curl_easy_setopt( curl, CURLOPT_WRITEDATA, write_tag );
res = curl_easy_perform(curl);
没什么特别的,只是一个基本的 curl 连接。客户端在 tranfer.c(在 libcurl 中)中挂起,因为未将套接字视为已关闭。它正在等待来自服务器的更多数据。
到目前为止我尝试过的事情:
关闭前关闭
shutdown(fd, SHUT_WR);
char buf[64];
while(read(fd, buf, 64) > 0);
/* then close */
设置 SO_LINGER 1 秒后强制关闭
struct linger l;
l.l_onoff = 1;
l.l_linger = 1;
if (setsockopt(client_socket, SOL_SOCKET, SO_LINGER, &l, sizeof(l)) == -1)
abort();
这些没有任何区别。任何想法将不胜感激。
编辑——这最终成为队列库中的线程安全问题,导致套接字被多个线程不恰当地处理。