如何使用select()函数处理服务器中的多个客户端?

时间:2022-01-22 19:50:21

Is it possible to receive data from one client and send data to some other client at the same time using the select() function? How do I implement the timeout? How do I send and receive data from more than one client at the same time? I can understand how it can be done using fork(), but select() is confusing me.

是否可以使用select()函数从一个客户端接收数据并同时将数据发送到其他客户端?如何实现超时?如何同时从多个客户端发送和接收数据?我可以理解如何使用fork()完成它,但是select()让我感到困惑。

P.S. Please forgive me if this question is not up to the level of Stack Overflow, but I did not find any good tutorial on select() and I am new to the socket programming world. Please help me out.

附:如果这个问题达不到Stack Overflow的水平,请原谅我,但是我没有找到关于select()的任何好教程,而且我是socket编程世界的新手。请帮帮我。

2 个解决方案

#1


1  

To literally perform two actions at the same time, you need two threads (possibly, but not necessarily, belonging to different processes) running on a machine that has at least two CPU cores. (In the separate process case you you don't need to explicitly manage threads; you can just rely on the processes' default threads.) That's not what select() is for. In fact, select() is mainly for avoiding that.

要在同一时间执行两个操作,您需要在至少具有两个CPU核心的计算机上运行两个线程(可能但不一定属于不同的进程)。 (在单独的进程中,您不需要显式管理线程;您可以只依赖进程的默认线程。)这不是select()的用途。实际上,select()主要是为了避免这种情况。

The select() function helps [a single thread of] a single process to efficiently service I/O over multiple channels by allowing it to recognize which channels are ready for reading and/or writing at any given time. The program using select() queues up pending I/O requests, or otherwise knows somehow what I/O is needed, and loops, performing each operation only when select() informs it that the target I/O device is ready. That allows it both to avoid blocking on I/O to devices that are unready while others are waiting for service, and to avoid wasting CPU by continuously polling devices to determine whether they are ready.

select()函数通过允许单个进程在任何给定时间识别哪些通道已准备好进行读取和/或写入,帮助单个进程的[单个线程]有效地为多个通道提供I / O服务。使用select()的程序将挂起的I / O请求排队,或以其他方式知道需要什么I / O,并循环,仅在select()通知目标I / O设备准备就绪时执行每个操作。这样既可以避免在其他人等待服务的情况下阻止I / O到未准备好的设备,也可以通过不断轮询设备来确定它们是否准备好,从而避免浪费CPU。

#2


1  

Is it possible to receive data from one client and send data to some other client at the same time using the select() function?

是否可以使用select()函数从一个客户端接收数据并同时将数据发送到其他客户端?

Yes (note that this is not the same as your program being blocked within a send() call and a recv() call at the same time -- that is not possible with a single thread, but fortunately it's not necessary either. See my comment to John Bollinger's answer, above)

是(请注意,这与您在send()调用和recv()调用中同时阻止的程序不同 - 这对于单个线程是不可能的,但幸运的是,它也没有必要。请参阅我的评论John Bollinger的回答,上面)

How do I implement the timeout?

如何实现超时?

The first question is, why do you want to implement a timeout? An efficient server does not need to depend on timeouts.

第一个问题是,为什么要实现超时?高效的服务器不需要依赖超时。

How do I send and receive data from more than one client at the same time?

如何同时从多个客户端发送和接收数据?

Set all of your sockets to non-blocking mode, so that neither send() nor recv() will ever block; rather each will always return immediately. That way there is no way that poor network connectivity to client A will cause your server to block for a long time (inside a recv() or send() call) and to stop responding to client B.

将所有套接字设置为非阻塞模式,这样send()和recv()都不会阻塞;而是每个人都会立即回归。这样,客户端A的网络连接不良将导致服务器长时间阻塞(在recv()或send()调用内)并停止响应客户端B.

Once you've done that, the only place your program should ever block/wait is inside the select() call. Set up your fd_set arguments so that select() returns whenever any of your sockets is ready-for-read, and also so that it returns whenever any of your sockets that you have data ready to send to is ready-for-write.

完成后,程序应该阻止/等待的唯一位置是select()调用。设置你的fd_set参数,以便当你的任何套接字准备好读取时select()返回,并且只要你有数据准备发送到的任何套接字都是可写的,它就会返回。

Once select() returns, use FD_ISSET() in a loop to find out which socket(s) have data ready for you to recv() and which are ready to accept some data from you (via send()).

select()返回后,在循环中使用FD_ISSET()来找出哪些套接字有数据可供您使用recv()以及哪些套接字已准备好接受来自您的一些数据(通过send())。

Call recv() on each of the ready-for-read sockets to get some bytes of data from them. Be aware that recv() may hand you any number of bytes less than or equal to the number you asked it to give you, and it may even return EWOULDBLOCK (if for some reason it didn't have any bytes to give you after all).

在每个准备好读取的套接字上调用recv()以从它们获取一些字节的数据。请注意,recv()可能会向您提供小于或等于您要求它的数字的任意数量的字节,甚至可能返回EWOULDBLOCK(如果由于某种原因它没有任何字节可以提供给您)。

Call send() on each of the ready-for-write sockets to send some more bytes to them. Again, be aware that send() may accept fewer bytes than you passed to it (its return value will tell you how many bytes it actually copied from your buffer into a kernel buffer), and it may also return EWOULDBLOCK (if for some reason it couldn't accept any bytes from you right now).

在每个准备好写入的套接字上调用send()以向它们发送更多字节。同样,请注意send()可能接受的字节数少于传递给它的字节数(其返回值将告诉您实际从缓冲区复制到内核缓冲区的字节数),并且它也可能返回EWOULDBLOCK(如果由于某种原因)它现在无法接受你的任何字节)。

Then just repeat all of the above in a loop, and that's your single-threaded/multi-client server's event loop. Note that this design requires you to keep send and receive buffers and explicit state variables for each connection to keep track of how many bytes are currently sent/received of the message you are currently sending/receiving, so it's a bit more work than the alternative (blocking) model where the blocking calls allow you use the thread's execution-location to help hold that state; but the upside is that you can have a single thread service many connections at once without the connections interfering with each other.

然后在循环中重复上述所有操作,这就是您的单线程/多客户端服务器的事件循环。请注意,此设计要求您为每个连接保留发送和接收缓冲区以及显式状态变量,以跟踪当前正在发送/接收的消息当前发送/接收的字节数,因此它比替代方案要多一些工作(阻塞)模型,阻塞调用允许您使用线程的执行位置来帮助保持该状态;但好处是你可以让一个线程同时为多个连接提供服务而不会相互干扰。

#1


1  

To literally perform two actions at the same time, you need two threads (possibly, but not necessarily, belonging to different processes) running on a machine that has at least two CPU cores. (In the separate process case you you don't need to explicitly manage threads; you can just rely on the processes' default threads.) That's not what select() is for. In fact, select() is mainly for avoiding that.

要在同一时间执行两个操作,您需要在至少具有两个CPU核心的计算机上运行两个线程(可能但不一定属于不同的进程)。 (在单独的进程中,您不需要显式管理线程;您可以只依赖进程的默认线程。)这不是select()的用途。实际上,select()主要是为了避免这种情况。

The select() function helps [a single thread of] a single process to efficiently service I/O over multiple channels by allowing it to recognize which channels are ready for reading and/or writing at any given time. The program using select() queues up pending I/O requests, or otherwise knows somehow what I/O is needed, and loops, performing each operation only when select() informs it that the target I/O device is ready. That allows it both to avoid blocking on I/O to devices that are unready while others are waiting for service, and to avoid wasting CPU by continuously polling devices to determine whether they are ready.

select()函数通过允许单个进程在任何给定时间识别哪些通道已准备好进行读取和/或写入,帮助单个进程的[单个线程]有效地为多个通道提供I / O服务。使用select()的程序将挂起的I / O请求排队,或以其他方式知道需要什么I / O,并循环,仅在select()通知目标I / O设备准备就绪时执行每个操作。这样既可以避免在其他人等待服务的情况下阻止I / O到未准备好的设备,也可以通过不断轮询设备来确定它们是否准备好,从而避免浪费CPU。

#2


1  

Is it possible to receive data from one client and send data to some other client at the same time using the select() function?

是否可以使用select()函数从一个客户端接收数据并同时将数据发送到其他客户端?

Yes (note that this is not the same as your program being blocked within a send() call and a recv() call at the same time -- that is not possible with a single thread, but fortunately it's not necessary either. See my comment to John Bollinger's answer, above)

是(请注意,这与您在send()调用和recv()调用中同时阻止的程序不同 - 这对于单个线程是不可能的,但幸运的是,它也没有必要。请参阅我的评论John Bollinger的回答,上面)

How do I implement the timeout?

如何实现超时?

The first question is, why do you want to implement a timeout? An efficient server does not need to depend on timeouts.

第一个问题是,为什么要实现超时?高效的服务器不需要依赖超时。

How do I send and receive data from more than one client at the same time?

如何同时从多个客户端发送和接收数据?

Set all of your sockets to non-blocking mode, so that neither send() nor recv() will ever block; rather each will always return immediately. That way there is no way that poor network connectivity to client A will cause your server to block for a long time (inside a recv() or send() call) and to stop responding to client B.

将所有套接字设置为非阻塞模式,这样send()和recv()都不会阻塞;而是每个人都会立即回归。这样,客户端A的网络连接不良将导致服务器长时间阻塞(在recv()或send()调用内)并停止响应客户端B.

Once you've done that, the only place your program should ever block/wait is inside the select() call. Set up your fd_set arguments so that select() returns whenever any of your sockets is ready-for-read, and also so that it returns whenever any of your sockets that you have data ready to send to is ready-for-write.

完成后,程序应该阻止/等待的唯一位置是select()调用。设置你的fd_set参数,以便当你的任何套接字准备好读取时select()返回,并且只要你有数据准备发送到的任何套接字都是可写的,它就会返回。

Once select() returns, use FD_ISSET() in a loop to find out which socket(s) have data ready for you to recv() and which are ready to accept some data from you (via send()).

select()返回后,在循环中使用FD_ISSET()来找出哪些套接字有数据可供您使用recv()以及哪些套接字已准备好接受来自您的一些数据(通过send())。

Call recv() on each of the ready-for-read sockets to get some bytes of data from them. Be aware that recv() may hand you any number of bytes less than or equal to the number you asked it to give you, and it may even return EWOULDBLOCK (if for some reason it didn't have any bytes to give you after all).

在每个准备好读取的套接字上调用recv()以从它们获取一些字节的数据。请注意,recv()可能会向您提供小于或等于您要求它的数字的任意数量的字节,甚至可能返回EWOULDBLOCK(如果由于某种原因它没有任何字节可以提供给您)。

Call send() on each of the ready-for-write sockets to send some more bytes to them. Again, be aware that send() may accept fewer bytes than you passed to it (its return value will tell you how many bytes it actually copied from your buffer into a kernel buffer), and it may also return EWOULDBLOCK (if for some reason it couldn't accept any bytes from you right now).

在每个准备好写入的套接字上调用send()以向它们发送更多字节。同样,请注意send()可能接受的字节数少于传递给它的字节数(其返回值将告诉您实际从缓冲区复制到内核缓冲区的字节数),并且它也可能返回EWOULDBLOCK(如果由于某种原因)它现在无法接受你的任何字节)。

Then just repeat all of the above in a loop, and that's your single-threaded/multi-client server's event loop. Note that this design requires you to keep send and receive buffers and explicit state variables for each connection to keep track of how many bytes are currently sent/received of the message you are currently sending/receiving, so it's a bit more work than the alternative (blocking) model where the blocking calls allow you use the thread's execution-location to help hold that state; but the upside is that you can have a single thread service many connections at once without the connections interfering with each other.

然后在循环中重复上述所有操作,这就是您的单线程/多客户端服务器的事件循环。请注意,此设计要求您为每个连接保留发送和接收缓冲区以及显式状态变量,以跟踪当前正在发送/接收的消息当前发送/接收的字节数,因此它比替代方案要多一些工作(阻塞)模型,阻塞调用允许您使用线程的执行位置来帮助保持该状态;但好处是你可以让一个线程同时为多个连接提供服务而不会相互干扰。