【问题标题】:Why always 5 connections with no program attached?为什么总是 5 个连接而没有附加程序?
【发布时间】:2013-06-05 05:55:36
【问题描述】:

这个问题类似于Network port open, but no process attached?netstat shows a listening port with no pid but lsof does not。但是他们的答案无法解决我的问题,因为它太奇怪了。

我有一个名为 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 个客户端(这也是随机的)连接并向服务器发送登录请求,但收不到任何响应。以 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 和端口,并且它们都由服务器正确处理。

注意:我使用 Linux epoll 来接受客户端请求。我还在我的程序中编写调试代码并记录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,发现this page 说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 之前不继续接受,我将无法接受所有内容。我才意识到这个问题。我的错。请记住:如果使用EPOLLET,则始终使用readaccept 直到出现EAGAIN,即使它正在侦听套接字。再次感谢 Matthew 用测试程序证明了我。

【问题讨论】:

  • 您的环境中的IP 192.168.0.241有什么特别之处吗?
  • @Nils 又加了一个,我觉得不是 IP 192.168.0.241 的问题。我们有几个测试虚拟机,这 5 个可以来自不同的主机。
  • 等一下。这个服务器lps是你正在写的程序吗?
  • @MichaelHampton 是的。

标签: linux networking netstat lsof


【解决方案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,以防万一出现连接跟踪问题。 有时/proc 中的 iptables 选项也可以提供帮助。所以试试sysctl -w net.netfilter.nf_conntrack_tcp_be_liberal=1

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

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

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

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

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

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

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

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

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

【讨论】:

  • 感谢您抽出宝贵时间编写测试程序。我机器上的测试结果和你的一样。我将我的服务器程序修改回阻止listen fd,也可以接受3000个连接。但是回到原来的程序处理进来的数据,那些丢失的不能被接受的连接返回了。我还尝试按照您的建议关闭 iptables 并修改 sysctl 参数表。还是不行。
  • 在处理方面是加载 I/O、内存还是 CPU?
  • 我已对我的原始答案添加了更新。我还投票决定将其迁移到 stackoverflow,因为它可能不再属于这里。
  • 我写的是服务端程序,客户端是另一个同事,所以贴出来的信息都在服务端。问题是netstat 没有给出CLOSE_WAIT 状态。正如您在上面看到的,这 5 个也处于 ESTABLISHED 状态,但没有附加程序。我只在客户端关闭连接或几分钟后客户端没有传入数据时才关闭连接。 PS:感谢您的迁移,我也在考虑它与Serverfault关系不大。
  • 我最近遇到了epoll的一些其他问题,在阅读了几次手册页后,我注意到我们的程序之间存在很大差异。我在添加监听 fd 时使用EPOLLET,而您的只是使用EPOLLIN,默认为电平触发。这就是为什么我不能接受所有(但我仍然不明白为什么总是五个)。测试后触发电平。那些 5 也没有出现(这也解释了为什么在遇到 EAGAIN 之前接受可以工作)。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-01-10
相关资源
最近更新 更多