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

时间:2021-07-23 21:00:46

As I am currently doing this project in only C, I've up untill this point only used my webserver as a single threaded application. However, I dont want that anymore! So I have the following code that handles my Work.

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

void BeginListen()
{
        CreateSocket();

        BindSocket();

        ListenOnSocket();

        while ( 1 )
        {
            ProcessConnections();
        }
}

Now I've added fork(); before the start of ProcessConnection(); which helpes me allowing multiple connections! However, when I add code for daemoning the application found in this answer. I've encounted a little problem, using fork() will create a copy of my whole running app, which is the purpose of fork(). So, I'd like to solve this problem.

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

My ProcessConnection() looks like this

我的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();
}

How would I do to simply just add a couple of lines above or after connecting=socket = accept... in order to make it accept more than one connection at the time? Can i use fork(); but when it comes down to DisposeCurrentConnection(); I want to kill that process and just have the parent-thread running.

我怎么做只是简单地在连接= socket = accept ...之后或之后添加几行,以使其当时接受多个连接?我可以使用fork();但是当它归结为DisposeCurrentConnection();我想杀死那个进程,只是让父线程运行。

5 个解决方案

#1


I'm not a 100% sure what it is that you're trying to do, buy off the top of my head, I'd prefer to do the fork after the accept, and simply exit() when you're done. Keep in mind though, that you need to react to the SIGCHLD signal when the child process exits, otherwise you'll have a ton of zombie-processes hanging around, waiting to deliver their exit-status to the parent process. C-pseudo-code:

我不是100%肯定你要做的是什么,买下我的头脑,我宁愿在接受之后做分叉,只需在你完成后退出()。但请记住,当子进程退出时,您需要对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.
    }
}

The child_pid is needed to (as already mentioned) to kill the child-process, but also if you wish to use waitpid to collect the exit status.

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

Concerning the zombie-processes, if you're not interested in what happened to the process, you could install a signal hander for SIGCHLD and just loop on waitpid with -1 until it there are no more child-processes, like this

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

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

The waitpid function will return the pid of the child that exited, so if you wish you can correlate this to some other information about the connection (if you did keep track of the pid). Keep in mind that accept will probably exit with errno set to EINTR, without a valid connection if a SIGCHLD is caught, so remember to check for this on accepts return.

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

EDIT:
Don't forget to check for error conditions, i.e. fork returns -1.

编辑:不要忘记检查错误条件,即fork返回-1。

#2


Talking about fork() and threads on unix is not strictly correct. Fork creates a whole new process, which has no shared address space with the parent.

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

I think you are trying to achieve a process-per-request model, much like a traditional unix web server such as NCSA httpd or Apache 1.x, or possibly build a multi-threaded server with shared global memory:

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

Process-per-request servers:

When you call fork(), the system creates a clone of the parent process, including file descriptiors. This means that you can accept the socket request and then fork. The child process has the socket request, which it can reply to and then terminate.

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

This is relatively efficient on unix, as the memory of the process is not physically copied - the pages are shared between the process. The system uses a mechanism called copy-on-write to make copies on a page-by-page basis when the child process writes to memory. Thus, the overhead of a process-per-request server on unix is not that great, and many systems use this architecture.

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

#3


Better to use select() function which enables u to listen and connect from different requests in one program.... It avoids blocking but forking creates a new address space for the copy of the program which leads to memory inefficiency....

最好使用select()函数,它允许你在一个程序中监听和连接来自不同的请求....它避免了阻塞但是分叉为程序副本创建了一个新的地址空间,导致内存效率低下....

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

i.e u can

你可以

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
}

#4


Check the return value of fork(). If it is zero, you are the child process, and you can exit() after doing your work. If it is a positive number then it's the process ID of the newly created process. This can let you kill() the child processes if they are hanging around too long for some reason.

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

#5


As per my comment, this server is not really multi-threaded, it is multi-process.

根据我的评论,这个服务器并不是真正的多线程,它是多进程的。

If you want a simple way to make it accept multiple connections (and you don't care too much about performance) then you can make it work with inetd. This leaves the work of spawning the processes and being a daemon to inetd, and you just need to write a program that handles and processes a single connection. edit: or if this is a programming exercise for you, you could grab the source of inetd and see how it does it

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

You can also do what you want to do without either threads or new processes, using select.

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

Here's an article that explains how to use select (pretty low overhead compared to fork or threads - here's an example of a lightweight web server written this way)

这篇文章解释了如何使用select(与fork或threads相比开销相当低 - 这里是一个以这种方式编写的轻量级Web服务器的示例)

Also if you're not wedded to doing this in C, and C++ is OK, you might consider porting your code to use ACE. That is also a good place to look for design patterns of how to do this as I believe it supports pretty much any connection handling model and is very portable.

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

#1


I'm not a 100% sure what it is that you're trying to do, buy off the top of my head, I'd prefer to do the fork after the accept, and simply exit() when you're done. Keep in mind though, that you need to react to the SIGCHLD signal when the child process exits, otherwise you'll have a ton of zombie-processes hanging around, waiting to deliver their exit-status to the parent process. C-pseudo-code:

我不是100%肯定你要做的是什么,买下我的头脑,我宁愿在接受之后做分叉,只需在你完成后退出()。但请记住,当子进程退出时,您需要对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.
    }
}

The child_pid is needed to (as already mentioned) to kill the child-process, but also if you wish to use waitpid to collect the exit status.

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

Concerning the zombie-processes, if you're not interested in what happened to the process, you could install a signal hander for SIGCHLD and just loop on waitpid with -1 until it there are no more child-processes, like this

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

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

The waitpid function will return the pid of the child that exited, so if you wish you can correlate this to some other information about the connection (if you did keep track of the pid). Keep in mind that accept will probably exit with errno set to EINTR, without a valid connection if a SIGCHLD is caught, so remember to check for this on accepts return.

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

EDIT:
Don't forget to check for error conditions, i.e. fork returns -1.

编辑:不要忘记检查错误条件,即fork返回-1。

#2


Talking about fork() and threads on unix is not strictly correct. Fork creates a whole new process, which has no shared address space with the parent.

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

I think you are trying to achieve a process-per-request model, much like a traditional unix web server such as NCSA httpd or Apache 1.x, or possibly build a multi-threaded server with shared global memory:

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

Process-per-request servers:

When you call fork(), the system creates a clone of the parent process, including file descriptiors. This means that you can accept the socket request and then fork. The child process has the socket request, which it can reply to and then terminate.

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

This is relatively efficient on unix, as the memory of the process is not physically copied - the pages are shared between the process. The system uses a mechanism called copy-on-write to make copies on a page-by-page basis when the child process writes to memory. Thus, the overhead of a process-per-request server on unix is not that great, and many systems use this architecture.

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

#3


Better to use select() function which enables u to listen and connect from different requests in one program.... It avoids blocking but forking creates a new address space for the copy of the program which leads to memory inefficiency....

最好使用select()函数,它允许你在一个程序中监听和连接来自不同的请求....它避免了阻塞但是分叉为程序副本创建了一个新的地址空间,导致内存效率低下....

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

i.e u can

你可以

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
}

#4


Check the return value of fork(). If it is zero, you are the child process, and you can exit() after doing your work. If it is a positive number then it's the process ID of the newly created process. This can let you kill() the child processes if they are hanging around too long for some reason.

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

#5


As per my comment, this server is not really multi-threaded, it is multi-process.

根据我的评论,这个服务器并不是真正的多线程,它是多进程的。

If you want a simple way to make it accept multiple connections (and you don't care too much about performance) then you can make it work with inetd. This leaves the work of spawning the processes and being a daemon to inetd, and you just need to write a program that handles and processes a single connection. edit: or if this is a programming exercise for you, you could grab the source of inetd and see how it does it

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

You can also do what you want to do without either threads or new processes, using select.

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

Here's an article that explains how to use select (pretty low overhead compared to fork or threads - here's an example of a lightweight web server written this way)

这篇文章解释了如何使用select(与fork或threads相比开销相当低 - 这里是一个以这种方式编写的轻量级Web服务器的示例)

Also if you're not wedded to doing this in C, and C++ is OK, you might consider porting your code to use ACE. That is also a good place to look for design patterns of how to do this as I believe it supports pretty much any connection handling model and is very portable.

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