具有多个客户端的 TCP 服务器将消息发送回所有连接的客户端

问题描述 投票:0回答:3

我有一个tcp聊天程序:

server.c
client.c

服务器处于

while(1)
循环中,并使用
select
来检测想要连接到其套接字的客户端。然后为接受的客户端创建一个新线程,并将其套接字描述符作为线程的参数给出:
pthread_create (&thread,NULL, do_something, (void *) &socket_descriptor);

当服务器收到来自客户端的消息时,应将该消息发送给所有连接的客户端。 (尚未实施)。

现在我想知道如何做到这一点。我绝对需要每个接受的连接都在一个线程中。

我也在考虑在

select
内使用
do_something
select
会检测套接字描述符上是否有数据传入吗?或者你会用其他方法吗?

编辑:添加代码 我的代码:

#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <string.h>
#include "tcp_comm.h"
#include <sys/time.h>
#include <sys/types.h>

#define BUFSIZE 1024
#define PORT 1234

void *do_something(void *a);

int main (void){
    Socket server = tcp_passive_open( PORT );
    MySocket *s = (MySocket *)server;
    printf("Server socked_id (main): %i", s->sd);

    pthread_t thread;

    fd_set active_socketDescriptors,read_socketDescriptors;

    FD_ZERO(&active_socketDescriptors);         
    FD_SET(s->sd,&active_socketDescriptors);    

    while (1){
        read_socketDescriptors = active_socketDescriptors;
        if (select (FD_SETSIZE, &read_socketDescriptors, NULL, NULL, NULL) < 0){
            perror ("select"); 
            exit (EXIT_FAILURE);
        }

        int i;
        for (i = 0; i < FD_SETSIZE; ++i){
            if (FD_ISSET (i, &read_socketDescriptors)){
                if (i == s->sd){
                    Socket client = tcp_wait_for_connection( server ); 
                    pthread_create (&thread,NULL, do_something, (void *)client); 
                    FD_SET (s->sd, &active_socketDescriptors);          
                } else {
                }
            }
        }
    }

    tcp_close( server );
    return 0;

}
void *do_something(void *client){
    unsigned char input[BUFFER_SIZE]; 
    pthread_detach(pthread_self());

    MySocket *s = (MySocket *)client;
    printf("Client socked_id (thread): %i", s->sd);
    int j;
    while (1){
        int nbytes = tcp_receive(client, input, BUFSIZE );
        if (nbytes <= 0){
                if (nbytes ==0){
                    /* connection closed by client*/    
                    printf("Client closed connection");         
                } else {
                    /* other error*/
                    perror("tcp_receive");              
                }
                tcp_close(&client);
                /*remove the socket descriptor from set in the main BRAINSTORM ABOUT THIS */
        } else {
            /*data incoming */
            printf("\nMessage from client: %s",input);
        }
    }
    return 0;
}

编辑2:重新表述问题 我必须使用线程(这不是因为系统;linux),而是因为在分配中强制每个客户端都有一个线程。

我遇到的具体问题是,只有主线程可以将从每个客户端接收到的数据发送到所有客户端,因为只有主线程可以访问包含套接字描述符的集合。

edit3:我需要在每个线程中添加什么,但我不能,因为 s.thread 和 s.main 位于不同的地方,并且线程不知道主线程的集合。

for (j=0; j<=FD_SETSIZE;j++){
    if(FD_ISSET(j,&active_socketDescriptors)){
        if (j != s.thead && j!=s.main){
            tcp_send(j, (void*)input,nbytes);
        }       
    }   
}

编辑4:我是这样解决的: 我有一个动态数组列表,其中放置了带有套接字描述符的已连接客户端列表。在服务器的线程内(做某事)我有接收阻塞,直到它获得输入,然后使用它循环的列表中的套接字描述符将该输入发送到所有连接的客户端。客户端内部有一个线程监听和一个线程发送。

c tcp pthreads recv posix-select
3个回答
3
投票

如果客户端连接套接字是非阻塞的,则使用例如

select
等待套接字接收数据是一种可能的方法。但是,由于您已经在线程中拥有连接的套接字,因此您可以让它们保持阻塞,然后只需对它们进行
read
调用即可。对
read
的调用将阻塞,直到您收到数据,然后数据可以传播到其他线程。

编辑

更好地了解您的要求后,您可能应该让套接字成为非阻塞的,并使用带有

select
的循环和较短的超时时间。当
select
超时(即返回
0
)时,您将检查是否有数据要发送。如果有,则发送数据,然后返回
select
调用。


1
投票

根据您的描述,可能值得重新考虑您的应用程序的架构。 (除非这是由您的系统限制决定的)。让我再解释一下......

根据您的描述,如果我理解正确的话,在客户端连接到服务器后,它(客户端)发送的任何消息都将(由服务器)中继到所有其他客户端。因此,与其创建一个新线程,不如简单地将新连接的套接字添加到 select 的 FDSET 中。然后,当收到消息时,您可以简单地转发给其他人。

如果您期望单个服务器有大量客户端,您应该查看

poll
系统调用在您的系统上是否可用(它就像 select 但支持监视更多客户端)。一个好的民意调查/选择版本应该胜过您的线程版本。

如果您确实想继续使用线程版本,这是完成您想要做的事情的一种方法。当您为每个接受的客户端创建线程时,您还创建一个返回服务器线程的管道(并将其添加到服务器选择/轮询集)并将其传递给客户端线程。因此,您的服务器线程现在不仅接收新连接,还中继消息。

尽管您说您绝对必须在单独的线程中处理每个客户端,但除非您使用实时操作系统,否则您可能会发现您需要执行的线程上下文切换/同步很快就会超过多路复用开销我建议的第一个解决方案。 (但因为我猜你没有提到操作系统!)


0
投票

这和你的设计有关。

如果您只需要为每个连接的客户端执行一两个功能,那么建议您仅使用一个线程来实现您的服务器。

如果您必须为每个连接的客户端执行大量功能,那么多线程设计是可以的。 但是,您问的问题应该是我如何将数据从接收线程传递给所有其他线程。我建议的答案是以太:

a) 使用消息队列传递线程间数据:每个线程都有一个消息队列,每个线程都会监听自己的套接字和该消息队列。当从socket接收数据时,线程将数据发送到所有其他消息队列

b) 使用单个全局缓冲区:如果有任何来自套接字的传入数据,则将此数据放入此全局缓冲区中,并向此数据添加一个标记,指示此数据来自何处。

我的2美分。

© www.soinside.com 2019 - 2024. All rights reserved.