客户端/服务器程序中的线程

5
为了学校项目,我正在开发一个本地即时聊天系统。我需要编写聊天服务器和客户端代码,使它们能够在同一台计算机的不同终端窗口之间以FIFOs和线程的形式发送消息。
FIFOs部分没有问题,线程部分让我有些头疼。服务器有一个线程用于从所有客户端使用的FIFO接收命令,并且每个连接的客户端都有另一个线程。
对于每个已连接的客户端,我需要了解某些信息。最初,我使用全局变量,但只适用于连接了一个客户端这样的情况,这与聊天的目的相背离。
因此,我希望每个客户端都有以下数据:
-昵称
-姓名
-电子邮件地址
-等等...
但我不知道如何实现这一点。我可以创建一个client_data [MAX_NUMBER_OF_THREADS]数组,其中client_data是具有所需访问权限的结构体,但这要求在服务器和客户端之间的每次通信中询问数组client_data中的客户端ID,这似乎不太实用。
我也可以在创建线程后立即实例化client_data,但它只在那个块中可用,这也不太实用。
正如您所看到的,我需要一些指导。任何评论、代码或任何相关信息的链接都将受到高度赞赏。
2个回答

3

我不知道你在使用什么语言,但是这里有一些基本的想法:

  • 在一个线程中启动您的服务器(可能是主线程)。
  • 服务器的while循环将阻塞接受套接字连接。
  • 当套接字连接被接受时,它应该生成一个新的线程来处理连接。
  • 在新线程中开始与客户端通信。

一个简单的socket服务器循环看起来像这样(在Java中):

while(true){
    ClientWorker w;
    try{
      //server.accept returns a client connection
      w = new ClientWorker(server.accept(), textArea);
      Thread t = new Thread(w);
      t.start();
    } catch (IOException e) {
      // log the exception or something...  
    }
  }

如果您想知道它的作用-ClientWorker可在这里获得。在C#中,如果您正在创建一个new Thread,请不要忘记将其IsBackground属性设置为true,这样当您的应用程序关闭时,线程将关闭,即没有挂起的线程。 记住:接受套接字连接或从套接字接收数据通常是一种阻塞调用,这意味着您的线程将阻塞,直到有人连接到套接字或数据通过套接字传输。
在C#中:
  1. 聊天客户端:http://www.geekpedia.com/tutorial239_Csharp-Chat-Part-1---Building-the-Chat-Client.html
  2. 聊天服务器:http://www.geekpedia.com/tutorial240_Csharp-Chat-Part-2---Building-the-Chat-Server.html
  3. 基本客户端/服务器:http://www.dreamincode.net/forums/topic/33396-basic-clientserver-chat-application-in-c%23/
在Java中:
  1. 聊天客户端/服务器:http://pirate.shu.edu/~wachsmut/Teaching/CSAS2214/Virtual/Lectures/chat-client-server.html
  2. Nakov聊天客户端/服务器:http://inetjava.sourceforge.net/lectures/part1_sockets/InetJava-1.9-Chat-Client-Server-Example.html
在C++中:
  1. 在Code Project上:http://www.codeproject.com/KB/cpp/chat_client_server.aspx
  2. 另一个Code Project TCP/IP聊天客户端/服务器:http://www.codeproject.com/KB/IP/ipchat.aspx

更新

不要使用全局变量,只需为客户端帐户定义一个struct,并为每个用户声明一个帐户变量...以下是如何定义帐户信息的示例:

struct account {
   char nickname[32];
   char first_name[32];
   char last_name[32];
   char e_mail[32];
   char password[32];
};

客户端发送消息时,应采用标准格式:FROM|TO|CONTENT
struct message{
   char nickname_from[32];
   char nickname_to[32]; // optional
   char msg_content[256];
};

将每个消息放入FIFO [队列]中,您将拥有所有所需的信息来识别发送者。

我感谢您的回复,但我必须在没有套接字的情况下实现它。只使用FIFO和线程。这是一个本地中继聊天。 - nunos
1
@nunos,这与阻塞 I/O 的概念相同:您将阻塞在从任何地方读取的位置上,而不是在套接字上阻塞。您仍然需要为每个“已连接”的客户端创建一个新线程,并与客户端建立专用的 I/O 通信通道。 - Kiril
1
线程对每个客户端显然不具有可扩展性,因此,如果您想要一个非常健壮的服务器,则应使用非阻塞IO,并使用诸如select()之类的东西来监视单个线程中的多个套接字。但是对于一些简单的东西,线程对每个客户端是可以的。 - Mike Weller

1

这里是一些几乎可以运行的伪代码。请注意,我没有释放我分配的内存,也没有检查错误,我只是试图演示如何将结构体传递给线程并使用简单的互斥锁。

有一件事需要小心注意,线程的函数指针指定了一个void *参数,这可以是任何类型。在线程中,我们假设将线程参数强制转换为我们定义的类型是安全的。如果您正在传递多个可能的类型,则必须小心:)

我不太确定您的程序结构或如何处理线程,但这是一个与方法无关的简短示例,说明如何向它们传递数据:

typedef struct client {
    char *firstname;
    char *lastname;
    char *email;
    char *nickname
} client_t;


pthread_mutex_t client_lock = PTHREAD_MUTEX_INITIALIZER;

void *client_thread(void *threadarg)
{
    client_t *client = (client_t *) threadarg;

    pthread_mutex_lock(&client_lock);
    /* read and write to client structure */
    pthread_mutex_unlock(&client_lock);

    /* do some other stuff */
    pthread_exit(NULL);
}

int main(void)
{
    client_t *client;
    pthread_t cthread;

    client = (client_t *)malloc(sizeof(struct client));
    if (client == NULL)
      return 1;

    client->firstname = strdup("Joe Public");
    /* set the rest of the data */

    pthread_create(&cthread, NULL, (void *)client_thread, (void *)client);
    /* join / detach / etc */

    /* Free all the structure members and the structure itself */

   return 0;
}

我很确定这就是你所询问的内容吧?


你和Lirik的回复让我对如何解决这个问题有了一些想法。现在我有一个开始实现线程的想法。谢谢。 - nunos

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接