【问题标题】:How to implement a TCP server that receives and sends data in two processes?如何实现一个分两个进程接收和发送数据的TCP服务器?
【发布时间】:2018-03-17 15:18:39
【问题描述】:

我正在尝试在 Linux 中使用 C 实现 TCP 服务器。我希望这台服务器永远接受来自多个客户端的传入数据,同时每 3 秒将一些数据发送回每个连接的客户端。

我的问题是我不知道如何在与处理客户的过程不同的过程中正确执行send()

我正在做的是在程序的开头做一个fork()然后做

while (1) {
  sleep(3);
  // compute and `send()` data to each connected peers
}

在子进程中,做

sock = create_socket();
while (1) {
  client_sock = accept_connection(sock);
  if (fork() == 0) {
    close(sock);
    handle_client(client_sock);
    exit(0);
  }
  close(client_sock);
  // clean up zombies
}

在父进程中。 handle_client() 只是recv() 无限循环中的数据。因为send()recv() 是在不同的进程中执行的,所以我不能在父进程中使用send() 的套接字文件描述符。我需要在父进程中做什么才能执行send()

【问题讨论】:

  • 如果在父节点中完成了接受,然后分叉完成,则客户端和服务器共享客户端套接字并可以在其上发送/接收。它不适用于 SSL 套接字,因为 SSL 状态保存在用户空间中,即每个进程都有一个单独的状态。无论如何,我认为这从一开始就是一个糟糕的设计,您应该改用线程或基于事件的编程,从而将所有内容都放在一个进程中。
  • 事件驱动编程对我来说是新的,但我一定会看看它。谢谢!
  • @SteffenUllrich 基本的 Apache 工作正如你所说,通过分叉,并且处理 SSL 很好......所以它不一定是一个糟糕的设计。与其他任何一种方法一样,它也有其优点和缺点。
  • @PatrickMevzek:Apache 不像 OP 喜欢那样使用一个进程来接收和另一个进程在同一个套接字上发送数据。它使用相同的子进程进行发送和接收,这对 SSL 来说也很好,因为只有一个进程包含 SSL 状态。
  • 我认为 OP 只是没有正确指定它的问题,因为没有特定的限制,我认为没有理由在单独的进程中进行读写;所以可能只是因为不知道如何处理accept()

标签: networking process c sockets


【解决方案1】:

您有三个级别的进程,一个父进程、一个子进程和许多孙子进程。摆脱这些级别,不要分叉;而是在单个流程中使用事件驱动模型。

粗略的伪代码(翻译成您喜欢的语言):

监听_fd = create_socket(); EventQueueOfSomeKind q; // kqueue() 样式 q.add_or_update_event(listening_fd, EVFILT_READ, EV_ENABLE); q.add_or_update_event(3, EVFILT_TIMER, EV_ENABLE, NOTE_SECONDS); FDToContextMapOfSomeKind context_map; 事件向量 event_vector; // 类似 kevent 的东西的向量 而(1){ q.wait_for_events(&event_vector); // kevent() 风格 foreach e

我已经忽略了部分 send()s 和 recv()s、写入端关闭和所有错误处理,但这是大体思路。

进一步阅读

【讨论】:

  • 感谢您的详细解答!我不知道kqueue 是什么,但我很高兴知道那里有很多很棒的工具等着我学习。我正在检查文档,稍后会回来。谢谢
  • @iamlazynic - 请注意,kqueue 是 BSD 方法(在 macOS 上也可用,尽管略有损坏)。 Linux 使用epoll(应该是更新的)。恕我直言,kqueue 的设计更好(因此,the suggested library),但如果您只针对 Linux,则最好使用实际的系统调用。
  • @JdeBP。我采用了事件驱动的方法,但改用epoll,因为我认为它是一种更 Linux 的方式。 timerfd 非常适合我的要求,因为它允许指定间隔。我不会接受您的回答,因为我想使用 epolltimerfd 发布我当前的实现,但非常感谢您的提示。
【解决方案2】:

这是使用Linuxepolltimerfd的解决方案(省略错误处理):

int start_timer(unsigned int interval) {
  int tfd;
  struct itimerspec tspec;

  tspec.it_value.tv_sec = 1;
  tspec.it_value.tv_nsec = 0;
  tspec.it_interval.tv_sec = 3;
  tspec.it_interval.tv_nsec = 0;

  tfd = timerfd_create(CLOCK_MONOTONIC, 0);
  timerfd_settime(tfd, TFD_TIMER_ABSTIME, &tspec, NULL);
  return tfd;
}

void epset_add(int epfd, int fd, uint32_t events)
{
  struct epoll_event ev;
  ev.data.fd = fd;
  ev.events = events;
  epoll_ctl(epfd, EPOLL_CTL_ADD, fd, &ev);
}

int main()
{
  int epfd, tfd, sock, nfds, i;
  struct epoll_event events[MAX_EVENTS];

  /* create new epoll instance */
  epfd = epoll_create1(0);

  tfd = start_timer(TIMER_INTERVAL);
  /* socket(), bind() and listen() omitted in create_socket() */
  sock = create_socket(PORT_NUMBER);
  /* add sock and tfd to epoll set */
  epset_add(epfd, tfd, EPOLLIN);
  epset_add(epfd, sock, EPOLLIN | EPOLLET);

  for (;;) {
    for (i = 0; i < nfds; ++i) {
      if (events[i].data.fd == tfd) {
        /* handle timer notification, it's run
           periodically with interval TIMER_INTERVAL */
      } else if (events[i].data.fd == sock) {
        /* accept() incoming connections,
           set non-blocking,
           and add new connection sockets to epoll set */
      } else {
        /* recv() from connection sockets and handle */
      }
    }
  }
}

这个程序很有帮助https://github.com/eklitzke/epollet/blob/master/poll.c 我在 epoll 集合中添加了 timerfd,以便服务器继续监听和接收数据,同时可以定期向客户端发送数据 em>。

【讨论】:

    猜你喜欢
    • 2019-08-26
    • 1970-01-01
    • 1970-01-01
    • 2014-01-23
    • 1970-01-01
    • 1970-01-01
    • 2011-09-13
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多