转换为多线程套接字应用程序

时间:2009-01-11 19:15:43

标签: c multithreading posix unix

由于我目前只在C中进行this project,所以直到这一点为止,我只使用我的网络服务器作为单线程应用程序。但是,我不想再这样了!所以我有以下代码来处理我的工作。

void BeginListen()
{
        CreateSocket();

        BindSocket();

        ListenOnSocket();

        while ( 1 )
        {
            ProcessConnections();
        }
}

现在我在fork();开始之前添加了ProcessConnection();,它允许多个连接!但是,当我添加用于守护this answer中找到的应用程序的代码时。我已经解决了一个小问题,使用fork()将创建我的整个正在运行的应用程序的副本,这是fork()的目的。所以,我想解决这个问题。

我的ProcessConnection()看起来像这样

void ProcessConnections()
{
        fork();

        addr_size = sizeof(connector);

        connecting_socket = accept(current_socket, (struct sockaddr *)&connector, &addr_size);

        if ( connecting_socket < 0 )
        {
                perror("Accepting sockets");
                exit(-1);
        }

        HandleCurrentConnection(connecting_socket);


        DisposeCurrentConnection();
}

如何在connecting=socket = accept之上或之后添加几行,以便在当时接受多个连接?我可以使用fork();但是当它归结为DisposeCurrentConnection();时,我想杀死该进程并且只运行父线程。

5 个答案:

答案 0 :(得分:4)

我不是百分之百地确定你要做的是什么,买下我的头脑,我宁愿在接受之后做分叉,只需退出()当你'重做。但请记住,当子进程退出时,您需要对SIGCHLD信号作出反应,否则您将有大量的僵尸进程,等待将其退出状态传递给父进程。 C-伪代码:

for (;;) {
  connecting_socket = accept(server_socket);
  if (connecting_socket < 0)
    {
      if (errno == EINTR)
        continue;
      else
        {
          // handle error
          break;
        }
    }

  if (! (child_pid = fork ()))
    {
       // child process, do work with connecting socket
       exit (0);
    }
  else if (child_pid > 0)
    {
      // parent process, keep track of child_pid if necessary.
    }
  else
    {
      // fork failed, unable to service request, send 503 or equivalent.
    }
}

需要child_pid(如上所述)来杀死子进程,但是如果你想使用waitpid来收集退出状态,也需要。

关于僵尸进程,如果你对进程发生的事情不感兴趣,你可以为SIGCHLD安装一个信号处理程序,然后用-1循环on waitpid,直到它没有更多的子进程,就像这样

while (-1 != waitpid (-1, NULL, WNOHANG))
  /* no loop body */ ;

waitpid函数将返回退出子节点的pid,因此如果您希望可以将此与其他有关连接的信息相关联(如果您确实跟踪了pid)。请记住,如果捕获了SIGCHLD,则接受可能会在errno设置为EINTR的情况下退出,而没有有效的连接,所以请记住在接受return时检查这一点。

修改:
不要忘记检查错误情况,即fork返回-1。

答案 1 :(得分:2)

在unix上讨论fork()和线程并不严格正确。 Fork创建了一个全新的进程,它与父进程没有共享地址空间。

我认为您正在尝试实现每个请求进程的模型,就像传统的unix Web服务器(如NCSA httpd或Apache 1.x),或者可能构建具有共享全局内存的多线程服务器:

按请求处理的服务器:

当您调用fork()时,系统会创建父进程的克隆,包括文件描述符。这意味着您可以接受套接字请求然后fork。子进程有套接字请求,它可以回复然后终止。

这在unix上相对有效,因为进程的内存没有物理复制 - 页面在进程之间共享。当子进程写入内存时,系统使用称为copy-on-write的机制逐页进行复制。因此,unix上的每请求进程服务器的开销并不是很大,许多系统都使用这种架构。

答案 2 :(得分:1)

最好使用select()函数,使你能够从不同的方式收听和连接 请求在一个程序中....它避免阻塞但分叉创建一个新的地址空间 因为该程序的副本导致内存效率低下....

select(Max_descr, read_set, write_set, exception_set, time_out);

即你可以

fd_set* time_out;
fd_set* read_set;
listen(1);
listen(2);
while(1)
{
  if(select(20, read_set, NULL,NULL, timeout) >0)
  {
    accept(1);
    accept(2); .....
    pthread_create(func);....
  }
  else
}

答案 3 :(得分:0)

检查fork()的返回值。如果它为零,那么您就是子进程,并且可以在完成工作后退出()。如果是正数,则它是新创建的进程的进程ID。如果子进程由于某种原因挂起太长时间,这可以让你杀死子进程。

答案 4 :(得分:0)

根据我的评论,这个服务器实际上并不是多线程的,而是多进程的。

如果您想要一种简单的方法让它接受多个连接(并且您不太关心性能),那么您可以使用inetd。这留下了生成进程并成为inetd守护进程的工作,您只需要编写一个处理和处理单个连接的程序。 编辑:或者如果这是一个编程练习,你可以获取inetd的来源并看看它是如何做到的

使用select。

,您也可以在没有线程或新进程的情况下执行您想要执行的操作。

这是一个article,它解释了如何使用select(与fork或线程相比开销相当低 - 这是以这种方式编写的轻量级Web服务器的example

此外,如果您不想在C中执行此操作,并且C ++没问题,您可以考虑移植代码以使用ACE。这也是寻找如何做到这一点的设计模式的好地方,因为我相信它几乎支持任何连接处理模型并且非常便携。