3

我有一个 tcp 聊天程序:server.cclient.c.

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

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

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

我也在考虑使用selectinside 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),而是因为在分配中必须为每个客户端都有一个线程。

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

编辑3:我需要在每个线程中添加什么,但我不能因为 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:我以这种方式解决了它:我有一个动态数组列表,我在其中放置了一个带有套接字描述符的已连接客户端列表。在服务器的线程内(做某事)我有接收阻塞,直到它得到输入然后这个输入被发送到所有连接的客户端,使用它循环通过的列表中的套接字描述符。在客户端内部有一个线程监听和一个线程发送。

4

3 回答 3

3

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

编辑

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

于 2012-05-30T08:54:20.687 回答
1

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

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

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

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

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

于 2012-05-30T09:42:27.017 回答
0

这与您的设计有关。

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

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

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

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

我的 2 美分。

于 2012-05-30T09:50:56.553 回答