5

这个问题类似于网络端口打开,但没有附加进程?netstat 显示一个没有 pid 但 lsof 没有的监听端口。但是他们的答案无法解决我的问题,因为它太奇怪了。

我有一个名为的服务器应用程序lps,它在端口 8588 上等待 tcp 连接。

[root@centos63 lcms]# netstat -lnp | grep 8588   
tcp        0      0 0.0.0.0:8588                0.0.0.0:*                   LISTEN          6971/lps

如您所见,监听套接字没有任何问题,但是当我将几千个测试客户端(由另一位同事编写)连接到服务器时,无论是 2000、3000 还是 4000。一直有 5 个客户端(它们也是random) 连接并向服务器发送登录请求,但收不到任何响应。以 3000 个客户为例。这是netstat命令给出的内容:

[root@centos63 lcms]# netstat -nap | grep 8588 | grep ES | wc -l
3000

这是lsof命令输出:

[root@centos63 lcms]# lsof -i:8588 | grep ES | wc -l
2995

这5个连接在这里:

[root@centos63 lcms]# netstat -nap | grep 8588 | grep -v 'lps'                   
tcp    92660      0 192.168.0.235:8588          192.168.0.241:52658         ESTABLISHED -                   
tcp    92660      0 192.168.0.235:8588          192.168.0.241:52692         ESTABLISHED -                   
tcp    92660      0 192.168.0.235:8588          192.168.0.241:52719         ESTABLISHED -                   
tcp    92660      0 192.168.0.235:8588          192.168.0.241:52721         ESTABLISHED -                   
tcp    92660      0 192.168.0.235:8588          192.168.0.241:52705         ESTABLISHED -                   

上面的 5 表明它们在 8588 端口上连接到服务器,但没有附加任何程序。随着客户端发送请求,第二列(即RECV-Q)不断增加。

上面的链接说明了 NFS 挂载和 RPC。至于 RPC,我使用了命令rcpinfo -p,结果与端口 8588 无关。和 NFS 挂载,nfssta输出说Error: No Client Stats (/proc/net/rpc/nfs: No such file or directory).

问题:这怎么会发生?总是 5 个,也不是来自同一个 5 个客户。我不认为这是端口冲突,因为其他客户端也连接到相同的服务器 IP 和端口,并且它们都由服务器正确处理。

注意:我使用 Linuxepoll来接受客户端请求。我还在我的程序中编写调试代码并记录accept返回但找不到 5 个连接的每个套接字(以及客户端的信息)。这是uname -a输出:

Linux centos63 2.6.32-279.el6.x86_64 #1 SMP Fri Jun 22 12:19:21 UTC 2012 x86_64 x86_64 x86_64 GNU/Linux

感谢您的帮助!我真的很困惑。


2013-06-08更新:将系统升级到 CentOS 6.4 后,出现同样的问题。最后我回到epoll,发现这个页面说 set listen fd 是非阻塞的,并且accept直到EAGAINEWOULDBLOCK错误返回。是的,它有效。没有更多的连接挂起。但这是为什么呢?Unix 网络编程第 1卷说

accept is called by a TCP server to return the next completed connection from the 
front of the completed connection queue. If the completed connection queue is empty,
the process is put to sleep (assuming the default of a blocking socket).

那么如果队列中还有一些已完成的连接,为什么进程要休眠呢?

2013-7-1更新:我在添加侦听套接字时使用,所以如果不一直接受直到遇到EPOLLET,我就不能接受所有EAGAIN我才意识到这个问题。我的错。记住:如果使用,总是readaccept直到EAGAIN出现EPOLLET,即使它正在侦听套接字。再次感谢 Matthew 用测试程序证明了我。

4

1 回答 1

1

我尝试使用以下参数复制您的问题:

  1. 服务器使用 epoll 来管理连接。
  2. 我建立了 3000 个连接。
  3. 连接被阻塞。
  4. 服务器基本上“简化”为仅处理连接并执行非常少的复杂工作。

我无法复制这个问题。这是我的服务器源代码。

#include <stddef.h>
#include <stdint.h>
#include <stdbool.h>
#include <stdlib.h>
#include <stdio.h>

#include <errno.h>
#include <netdb.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/epoll.h>

#include <err.h>
#include <sysexits.h>
#include <string.h>
#include <unistd.h>

struct {
  int numfds;
  int numevents;
  struct epoll_event *events;
} connections = { 0, 0, NULL };

static int create_srv_socket(const char *port) {
  int fd = -1;
  int rc;
  struct addrinfo *ai = NULL, hints;

  memset(&hints, 0, sizeof(hints));
  hints.ai_flags = AI_PASSIVE;

  if ((rc = getaddrinfo(NULL, port, &hints, &ai)) != 0)
    errx(EX_UNAVAILABLE, "Cannot create socket: %s", gai_strerror(rc));

  if ((fd = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol)) < 0)
    err(EX_OSERR, "Cannot create socket");

  if (bind(fd, ai->ai_addr, ai->ai_addrlen) < 0)
    err(EX_OSERR, "Cannot bind to socket");

  rc = 1;
  if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &rc, sizeof(rc)) < 0)
    err(EX_OSERR, "Cannot setup socket options");

  if (listen(fd, 25) < 0)
    err(EX_OSERR, "Cannot setup listen length on socket");

  return fd;
}

static int create_epoll(void) {
  int fd;
  if ((fd = epoll_create1(0)) < 0)
    err(EX_OSERR, "Cannot create epoll");
  return fd;
}

static bool epoll_join(int epollfd, int fd, int events) { 
  struct epoll_event ev;
  ev.events = events;
  ev.data.fd = fd;

  if ((connections.numfds+1) >= connections.numevents) {
    connections.numevents+=1024;
    connections.events = realloc(connections.events, 
      sizeof(connections.events)*connections.numevents);
    if (!connections.events)
      err(EX_OSERR, "Cannot allocate memory for events list");
  }

  if (epoll_ctl(epollfd, EPOLL_CTL_ADD, fd, &ev) < 0) {
    warn("Cannot add socket to epoll set");
    return false;
  }

  connections.numfds++;
  return true;
}

static void epoll_leave(int epollfd, int fd) {
  if (epoll_ctl(epollfd, EPOLL_CTL_DEL, fd, NULL) < 0)
    err(EX_OSERR, "Could not remove entry from epoll set");

  connections.numfds--;
}


static void cleanup_old_events(void) {
  if ((connections.numevents - 1024) > connections.numfds) {
    connections.numevents -= 1024;
    connections.events = realloc(connections.events,
      sizeof(connections.events)*connections.numevents);
  }
}


static void disconnect(int fd) {
  shutdown(fd, SHUT_RDWR);
  close(fd);
  return;
}

static bool read_and_reply(int fd) {
  char buf[128];
  int rc;
  memset(buf, 0, sizeof(buf));

  if ((rc = recv(fd, buf, sizeof(buf), 0)) <= 0) {
    rc ? warn("Cannot read from socket") : 1;
    return false;
  }

  if (send(fd, buf, rc, MSG_NOSIGNAL) < 0) {
    warn("Cannot send to socket");
    return false;
  }

  return true;
}

int main()
{
  int srv = create_srv_socket("8558");
  int ep = create_epoll();
  int rc = -1;
  struct epoll_event *ev = NULL;

  if (!epoll_join(ep, srv, EPOLLIN)) 
    err(EX_OSERR, "Server cannot join epollfd");

  while (1) {
    int i, cli;

    rc = epoll_wait(ep, connections.events, connections.numfds, -1);
    if (rc < 0 && errno == EINTR)
      continue;
    else if (rc < 0)
      err(EX_OSERR, "Cannot properly perform epoll wait");

    for (i=0; i < rc; i++) {
      ev = &connections.events[i];

      if (ev->data.fd != srv) {

        if (ev->events & EPOLLIN) {
          if (!read_and_reply(ev->data.fd)) {
            epoll_leave(ep, ev->data.fd);
            disconnect(ev->data.fd);
          }
        } 

        if (ev->events & EPOLLERR || ev->events & EPOLLHUP) {
          if (ev->events & EPOLLERR)
            warn("Error in in fd: %d", ev->data.fd);
          else
            warn("Closing disconnected fd: %d", ev->data.fd);

          epoll_leave(ep, ev->data.fd);
          disconnect(ev->data.fd);
        }

      }
      else {

        if (ev->events & EPOLLIN) {
          if ((cli = accept(srv, NULL, 0)) < 0) {
            warn("Could not add socket");
            continue;
          }

          epoll_join(ep, cli, EPOLLIN);
        }

        if (ev->events & EPOLLERR || ev->events & EPOLLHUP)
          err(EX_OSERR, "Server FD has failed", ev->data.fd);

      }
    }

    cleanup_old_events();
  }

}

这是客户端:

from socket import *
import time
scks = list()

for i in range(0, 3000):
  s = socket(AF_INET, SOCK_STREAM)
  s.connect(("localhost", 8558))
  scks.append(s)

time.sleep(600)

在我的本地机器上运行它时,我使用端口 8558 获得 6001 个套接字(1 个侦听、3000 个客户端套接字和 3000 个服务器端套接字)。

$ ss -ant | grep 8558 | wc -l
6001

检查客户端上连接的 IP 连接数时,我得到 3000。

# lsof -p$(pgrep python) | grep IPv4 | wc -l
3000

我也成功地在远程机器上尝试了服务器的测试。

我建议你尝试做同样的事情。

此外,请尝试完全关闭 iptables,以防万一出现连接跟踪问题。有时 iptables 选项/proc也可以提供帮助。所以试试sysctl -w net.netfilter.nf_conntrack_tcp_be_liberal=1

编辑:我做了另一个测试,它产生了你在你身边看到的输出。您的问题是您先发制人地关闭了服务器端的连接。

我可以复制类似于您在执行以下操作时看到的结果:

  • 将一些数据读入我的服务器后,调用shutdown(fd, SHUT_RD).
  • send(fd, buf, sizeof(buf))在服务器上做。

执行此操作后,将看到以下行为。

  • 在客户端上,我通过 ESTABLISHED 在 netstat/ss 中打开了 3000 个连接。
  • 在 lsof 输出中,我建立了 2880 个(我关闭时的性质)连接。
  • 其余连接lsof -i:8558 | grep -v ES处于 CLOSE_WAIT 中。

这只发生在半关机连接上。

因此,我怀疑这是您的客户端或服务器程序中的错误。您正在向服务器反对的服务器发送某些内容,或者服务器由于某种原因无效地关闭了连接。

您需要确认“异常”连接处于什么状态(如 close_wait 或其他)。

在这个阶段,我还认为这是一个编程问题,而不是真正属于 serverfault 的问题。如果不查看客户端/服务器源的相关部分,任何人都无法追踪故障原因。尽管我非常有信心这与操作系统处理连接的方式无关。

于 2013-06-08T14:06:07.637 回答