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

时间:2012-05-30 08:48:46

标签: c select tcp pthreads recv

我有一个tcp聊天程序:server.cclient.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位于不同的地方&amp;线程不知道主要的集合。

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:我这样解决了:  我有一个动态数组列表,其中我列出了连接客户端与套接字描述符。在服务器的线程内部(做某事)我收到阻塞,直到它得到输入然后这个输入发送到所有连接的客户端使用它循环的列表中的套接字描述符。在客户端内部有一个线程监听和一个线程发送。

3 个答案:

答案 0 :(得分:3)

如果客户端连接套接字是非阻塞的,那么使用例如select等待套接字接收数据是一种可行的方法。但是,由于您已经在线程中连接了套接字,因此可以阻止它们,并且只需对它们进行read调用即可。对read的调用将阻塞,直到您收到数据,然后可以将数据传播到其他线程。

修改

在更好地理解您的要求之后,您可能应该使套接字无阻塞,并使用带有select的循环,并具有短暂超时。当select超时(即返回0)时,您会检查是否有要发送的数据。如果有,则发送数据,然后返回select电话。

答案 1 :(得分:1)

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

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

如果您希望单个服务器有大量客户端,您应该看看系统上是否有poll系统调用(它就像选择但支持监控更多客户端)。一个好的民意调查/选择版本应该胜过您的线程版本。

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

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

答案 2 :(得分:0)

这与您的设计有关。

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

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

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

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

我的2美分。