我必须从15个不同的客户端接收数据,每个客户端在5个不同的端口上发送 . 完全15 * 5插座 .
对于每个客户端端口,没有定义和修复 . 示例客户端1,端口3001至3005,客户端2,端口3051至3055等 . 它们有一个共同点,即第一个端口(3001,3051)用于发送命令 . 其他端口发送一些数据 .
收到数据后,我必须检查校验和 . 跟踪recvd数据包,如果丢失则重新请求数据包,还必须写入硬盘上的文件 .
Restriction 我无法更改上述设计,我无法从UDP更改为TCP . 阅读后我知道的两种方法是
-
使用select()进行异步多路复用 .
-
每个插槽的线程 .
我尝试了第一个,我能够接收数据 . 我有一些处理要这样做我想为每个套接字(或)启动一个线程,以便套接字处理(比如所有第一个端口,所有第二个,等等.ie3001,3051等)但是如果客户端发送任何数据,那么FD_ISSET变为真,所以如果我开始一个线程,那么它就成了每个消息的线程 . Question: 如何在这里添加线程代码,如果我在if(FD_ISSET ..)中包含pthread_create然后为我收到的每条消息创建一个线程 . 但我想要每个插槽一个线程 .
while(1)
{
int nready=0;
read_set = active_set;
if((nready = select(fdmax+1,&read_set,NULL,NULL,NULL)) == -1)
{
printf("Select Errpr\n");
perror("select");
exit(EXIT_FAILURE);
}
printf("number of ready desc=%d\n",nready);
for(index=1;index <= 15*5;index++)
{
if(FD_ISSET(sock_fd[index],&read_fd_set))
{
rc = recvfrom(sock_fd[index],clientmsgInfo,MSG_SIZE,0,
(struct sockaddr *)&client_sockaddr_in,
&sockaddr_in_length);
if(rc < 0)
printf("socket %d down\n",sock_fd[index]);
printf("Recieved packet from %s: %d\nData: %s\n\n", inet_ntoa(client_sockaddr_in.sin_addr), ntohs(client_sockaddr_in.sin_port), recv_client_message);
}
} //for
} //while
3 回答
在程序启动时创建线程并将它们分成处理数据,命令e.t.c.
怎么样?
更好的方法是拥有一个队列,该队列由主线程填充,并且可以由其他线程以元素方式访问 .
我假设每个客户端上下文独立于其他客户端,即 . 一个客户端套接字组可以单独管理,从套接字中提取的数据可以单独处理 .
您表达了处理问题的两种可能性:
异步多路复用:在此设置中,套接字全部由一个线程管理 . 这线程
select
s接下来必须读取哪个套接字,并从中拉出数据每个套接字的线程数:在这种情况下,你有多少个线程和套接字一样多,或者更多可能是套接字组,即 . 客户 - 这将是我将构建的解释 .
在这两种情况下,线程必须保持其各自资源的所有权,即插槽 . 如果你开始在线程之间移动套接字,你将使它变得更加困难 .
在需要完成的工作之外,您将需要处理线程管理:
线程如何开始?
他们如何以及何时停止?
什么是错误处理政策?
您的问题不包括这些问题,但它们可能会在您的最终设计中发挥重要作用 .
场景(2)似乎更简单:你有一个主"template"(我在这里使用一般意义上的单词)来处理一组使用
select
的套接字,并在同一个线程中接收和处理数据 . 实现起来非常简单,使用包含上下文特定数据的结构(套接字端口,用于数据包处理的函数的指针),以及在select和process上循环的单个函数,以及可能的其他一些错误检查和线程生命管理 .场景(1)需要不同的设置:一个I / O线程读取所有数据包并将它们传递给专用工作线程进行处理 . 如果发生处理错误,工作线程必须生成要发送到客户端的adhoc数据包,并将其传递给I / O线程进行发送 . 您将需要数据包队列以允许I / O和工作程序之间的通信,并让I / O线程以某种方式检查工作队列以获得重新发送请求 . 因此,这种解决方案在开发方面要贵一些,但将I / O争用降低到一个单点 . 它也更灵活,以防必须对来自多个客户端的数据进行某些处理,或者如果您想以某种方式链接处理 . 例如,您可以在每个客户端套接字中使用一个线程,然后在每个客户端套接字的另一个线程中进一步向下工作管道,管道的每个步骤通过数据包队列互连 .
当然可以实现两种解决方案的混合,每个客户端有一个IO线程,以及流水线工作线程 .
两个概述的解决方案的优点是固定数量的线程:无需按需生成和销毁线程(尽管您也可以设计一个线程池来处理它) .
对于涉及移动插座的解决方案在线程之间,问题是:
什么时候应该传递这些资源?工作线程读取数据包后会发生什么?它是应该将套接字返回到IO线程,还是冒着在套接字上阻塞读取下一个数据包的风险?如果它使用
select
轮询套接字以获取更多数据包,我们将陷入方案(2),其中每个客户端都有自己的I / O线程,当有来自所有客户端的网络流量时,在这种情况下,增益是多少执行select
的初始I / O线程?如果它再次通过套接字,IO线程是否应该等待所有工作者在启动另一个
select
之前回放其套接字?如果它等待,则需要使未服务的客户端等待已经在网络缓冲区中的数据包的风险,从而导致处理延迟 . 如果它没有等待,并返回到select
以避免延迟未服务的套接字,那么服务的服务器将不得不等待下一次唤醒,以便在select
池中看到它们的套接字 .如您所见,问题很难处理 . 这就是为什么我推荐使用线程的独占套接字所有权的原因,如scenarii(1)和(2)中所述 .
您的解决方案需要固定的,相对较少的连接数 .
创建一个帮助过程,创建线程过程,监听五个端口中的每个端口并阻塞
recvfrom()
,处理数据,然后再次阻止 . 然后,您可以调用帮助程序15次以创建线程 .这样可以避免所有轮询,并允许Linux在I / O完成时安排每个线程 . 等待时不使用CPU,这可以扩展到更大的解决方案 .
如果需要大规模扩展,为什么不使用一组端口,并从
client_sockaddr_in
结构中获取合作伙伴地址 . 如果处理花费了大量时间,您可以通过保持一个可用线程池来扩展它,并在每次收到消息时分配一个新线程,然后继续处理该消息,并在响应后将该线程添加回池中已发送 .