C语言中的多线程服务器/客户端实现

8
我刚开始学习基本的网络概念。我想在C语言中实现一个多线程的服务器-客户端程序。问题是,与其为每个客户端运行多个窗口/终端/实例,我应该使用fork()来创建客户端的子进程。这样一来,将创建多个客户端。现在,每个子客户端都将在一个线程上与服务器通信。
之前我创建了类似的程序,但对于多个客户端,您必须为每个客户端打开多个窗口并同时运行所有客户端。
我不知道在哪里修改我的代码(无论是在服务器还是客户端中)。我认为服务器端没问题。但我不知道在客户端程序中应该在哪里使用fork(),以及需要进行哪些更改。
实际上,我不想打开多个窗口来运行多个客户端,这就是我使用fork()来创建多个副本的原因。是否有其他方法可以通过线程创建多个客户端,并将它们连接到我的服务器程序中。
服务器:
// socket server example, handles multiple clients using threads

#include<stdio.h>
#include<string.h>    //strlen
#include<stdlib.h>    //strlen
#include<sys/socket.h>
#include<arpa/inet.h> //inet_addr
#include<unistd.h>    //write
#include<pthread.h> //for threading , link with lpthread

//the thread function
void *connection_handler(void *);

int main(int argc , char *argv[])
{
    int socket_desc , client_sock , c , *new_sock;
    struct sockaddr_in server , client;

    //Create socket
    socket_desc = socket(AF_INET , SOCK_STREAM , 0);
    if (socket_desc == -1)
    {
        printf("Could not create socket");
    }
    puts("Socket created");

    //Prepare the sockaddr_in structure
    server.sin_family = AF_INET;
    server.sin_addr.s_addr = INADDR_ANY;
    server.sin_port = htons( 3000 );

    //Bind
    if( bind(socket_desc,(struct sockaddr *)&server , sizeof(server)) < 0)
    {
        //print the error message
        perror("bind failed. Error");
        return 1;
    }
    puts("bind done");

    //Listen
    listen(socket_desc , 3);

    //Accept and incoming connection
    puts("Waiting for incoming connections...");
    c = sizeof(struct sockaddr_in);

        c=sizeof(struct sockaddr_in);
       while(client_sock=accept(socket_desc,(struct sockaddr*)&client,(socklen_t*)&c))
       {
        puts("Connection accepted");

        pthread_t sniffer_thread;
        new_sock = malloc(1);
        *new_sock = client_sock;

        if( pthread_create( &sniffer_thread , NULL ,  connection_handler , (void*) new_sock) < 0)
        {
            perror("could not create thread");
            return 1;
        }

        puts("Handler assigned");
    }

    if (client_sock < 0)
    {
        perror("accept failed");
        return 1;
    }
    return 0;
}
/*
  This will handle connection for each client
  */
void *connection_handler(void *socket_desc)
{
    //Get the socket descriptor
    int sock = *(int*)socket_desc;
    int n;

        char    sendBuff[100], client_message[2000];

      while((n=recv(sock,client_message,2000,0))>0)
      {

        send(sock,client_message,n,0);
      }
      close(sock);

      if(n==0)
      {
        puts("Client Disconnected");
      }
      else
      {
        perror("recv failed");
      }
    return 0;
}

客户端:

#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <unistd.h>

#define MAX_SIZE 50

int main()
{
    int sock_desc;
    struct sockaddr_in serv_addr;
    char sbuff[MAX_SIZE],rbuff[MAX_SIZE];

    if((sock_desc = socket(AF_INET, SOCK_STREAM, 0)) < 0)
        printf("Failed creating socket\n");

    bzero((char *) &serv_addr, sizeof (serv_addr));

    serv_addr.sin_family = AF_INET;
    serv_addr.sin_addr.s_addr = inet_addr("127.0.0.1");
    serv_addr.sin_port = htons(3000);

    if (connect(sock_desc, (struct sockaddr *) &serv_addr, sizeof (serv_addr)) < 0) {
        printf("Failed to connect to server\n");
        return -1;
    }

    printf("Connected successfully - Please enter string\n");
    while(fgets(sbuff, MAX_SIZE , stdin)!=NULL)
    {
      send(sock_desc,sbuff,strlen(sbuff),0);

          if(recv(sock_desc,rbuff,MAX_SIZE,0)==0)
           printf("Error");
          else
           fputs(rbuff,stdout);

       bzero(rbuff,MAX_SIZE);//to clean buffer-->IMP otherwise previous word characters also came
    }
        close(sock_desc);
    return 0;

}

为什么你想在客户端上使用 fork()?这很不寻常。 - Filipe Gonçalves
@Felipe:如果客户端是测试代码以模拟多个客户端连接到服务器,那么这是有意义的。 - Klas Lindbäck
1
如果您想学习网络编程,我建议您关注比fork更现代的概念。了解线程或事件驱动IO。分叉是网络程序的原始和过时模型。 - usr
Filipe: 这是我的老师让我做的。实际上,我不想打开多个窗口来运行多个客户端,这就是为什么我使用fork()来创建多个复制品的原因。是否有其他方法可以通过线程创建多个客户端并将它们连接到我的服务器程序? - Shikhar Deep
在 connection_handler 中你忘了释放 socket_desc。 - Chien-Wei Huang
2个回答

11

你可以使用线程创建多个客户端。为每个客户端创建一个单独的线程,然后从线程处理程序连接到服务器。我不确定这是否是一种好的方法。

代码:

#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <unistd.h>

#define MAX_SIZE 50
#define NUM_CLIENT 5
void *connection_handler(void *socket_desc);
int main()
{
    int socket_desc , new_socket , c , *new_sock, i;
    pthread_t sniffer_thread;
    for (i=1; i<=NUM_CLIENT; i++) {
        if( pthread_create( &sniffer_thread , NULL ,  connection_handler , (void*) i) < 0)
        {
            perror("could not create thread");
            return 1;
        }
        sleep(3);
    }
    pthread_exit(NULL);
    return 0;
}

void *connection_handler(void *threadid)
{
    int threadnum = (int)threadid;
    int sock_desc;
    struct sockaddr_in serv_addr;
    char sbuff[MAX_SIZE],rbuff[MAX_SIZE];

    if((sock_desc = socket(AF_INET, SOCK_STREAM, 0)) < 0)
        printf("Failed creating socket\n");

    bzero((char *) &serv_addr, sizeof (serv_addr));

    serv_addr.sin_family = AF_INET;
    serv_addr.sin_addr.s_addr = inet_addr("127.0.0.1");
    serv_addr.sin_port = htons(8888);

    if (connect(sock_desc, (struct sockaddr *) &serv_addr, sizeof (serv_addr)) < 0) {
        printf("Failed to connect to server\n");
    }

    printf("Connected successfully client:%d\n", threadnum);
    while(1)
    {
        printf("For thread : %d\n", threadnum);
        fgets(sbuff, MAX_SIZE , stdin);
        send(sock_desc,sbuff,strlen(sbuff),0);

        if(recv(sock_desc,rbuff,MAX_SIZE,0)==0)
            printf("Error");
        else
           fputs(rbuff,stdout);

        bzero(rbuff,MAX_SIZE);
        sleep(2);
    }
    close(sock_desc);
    return 0;
}

为了便于理解,我使用了sleep

参考文献:

http://www.amazon.com/UNIX-Network-Programming-Richard-Stevens/dp/0139498761

http://beej.us/guide/bgnet/

https://computing.llnl.gov/tutorials/pthreads/


sujin:出现以下警告和错误 mthclient.c: 在函数‘main’中:mthclient.c:18: 警告:从不同大小的整数转换为指针 mthclient.c: 在函数‘connection_handler’中:mthclient.c:31: 警告:从指针转换为不同大小的整数 /tmp/ccuPwTxp.o: 在函数‘main’中:mthclient.c:(.text+0x2d): 未定义对pthread_create的引用 collect2: ld返回1个退出状态 lnxrc186055-14:/shikhar # ./c -bash: ./c: 没有那个文件或目录 - Shikhar Deep
客户端:-连接成功 客户端:1 对于线程:1 你好 你好 嗨 $客户端连接成功:2 对于线程:2 嗨 ads $客户端连接成功:3 对于线程:3 ads adas asd $客户端连接成功:4 对于线程:4 adas $客户端连接成功:5 对于线程:5 asd ........我无法以正确的格式发布输出。 对此很抱歉...当客户端1连接时,我写的“hi”会在客户端2中回显。 为什么??? - Shikhar Deep
sujin:你能理解我发布的输出吗?还是我应该给你发一张输出的截图?我不知道如何在 Stack Overflow 上发布截图。 - Shikhar Deep
sujin: 是的,只是一个作业。但如果您能解决那个冲突,那就太好了。有5秒的休眠时间。所以在那5秒钟内输入的任何内容都应该被回显,但实际上只有第一条消息被回显,其余所有消息都在下一个客户端中被回显。这是唯一的问题...简而言之,对于每个客户端,我只能有一个回显消息,如果我写了多个消息,则这些消息会在下一个客户端中被回显。 - Shikhar Deep
有很多更改需要在客户端中进行,以满足您的要求。 我只是告诉你如何连接多个客户端。 多练习TCP、线程和同步程序,自然而然地您就会找到解决此问题的结论。 我能够意识到这段代码来自 binarytides.com,那里有很多例子。祝你好运成功。 - sujin
显示剩余4条评论

6
首先,如果你使用 fork(),你将创建额外的进程而不是线程。要创建额外的线程,你需要使用 pthread_create
其次,作为一名学生,标准答案是“阅读 Stephens”。这不仅是对于那些有经验编写套接字 I/O 例程的人来说是一个宝贵的工具,而且还包含了非线程和非 fork 异步 I/O 的示例,以及添加线程和 forking 的各种方法。我相信你需要的是: http://www.amazon.com/Programming-Environment-Addison-Wesley-Professional-Computing/dp/0321637739(如果我没记错的话,是第14章)。这本书应该在你的大学图书馆里。

1
推荐斯蒂文斯的《Unix网络编程》:http://www.amazon.de/Unix-Network-Programming-Richard-Stevens/dp/0139498761 - alk

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