zoukankan      html  css  js  c++  java
  • Linux Socket 多客户端通信

    搞了一下午的Linux套接字,实现了多客户端之间的TCP通信。不想再码字了,就简单描述一下代码流程,直接把代码贴出来吧。
    服务端多线程的思路主要参考了这篇:Linux C利用Socket套接字进行服务器与多个客户端进行通讯
    感觉自己对线程和TCP的理解也不是特别清晰,以下内容有不会的地方请大家指正

    程序说明

    1. 实现了多客户端之间的TCP通信
    2. 可以更改最大客户端数量
    3. 使用多线程处理客户端链路
    4. 使用父子进程实现客户端数据收发

    程序流程

    客户机

    • 客户端只需考虑如何连接目标服务器即可
    • 客户端首先创建套接字并向服务器附送网络连接请求
    • 连接到服务器之后,创建子进程
    • 父进程用来向服务器发送数据,子进程从服务器接收数据

    服务器

    • 服务器首先创建套接字并绑定网络信息
    • 之后创建一个子线程用于接收客户端的网络请求
    • 在子线程中接收客户端的网络请求,并为每一个客户端创建新的子线程,该子线程用于服务器接收客户端数据
    • 服务器的主线程用于向所有客户端循环发送数据
    • 服务端大概流程如下图所示
      服务器流程

    程序代码

    服务端程序

    /********************************************************************
    *   File Name: server.c
    *   Description: 用于实现多客户端通信
    *   Others: 
    *     1. 服务器首先创建套接字并绑定网络信息
    *     2. 之后创建一个子线程用于接收客户端的网络请求
    *     3. 在子线程中接收客户端的网络请求,并为每一个客户端创建新的子线程,该子线程用于服务器接收客户端数据
    *     4. 服务器的主线程用于向所有客户端循环发送数据
    *   Init Date: 2020/05/24
    *********************************************************************/
    #include "mysocket.h"
    
    int main()
    {
        ReadToSend = 0;
        conClientCount = 0;
        thrReceiveClientCount = 0;
    
        printf("Start Server...
    ");
    
        /* 创建TCP连接的Socket套接字 */
        int socketListen = socket(AF_INET, SOCK_STREAM, 0);
        if(socketListen < 0){
            printf("Fail to create Socket
    ");
            exit(-1);
        }else{
            printf("Create Socket successful.
    ");
        }
    
        /* 填充服务器端口地址信息,以便下面使用此地址和端口监听 */
        struct sockaddr_in server_addr;
        server_addr.sin_family = AF_INET;
        // 这里的地址使用所有本地网络设备的地址,表示服务器会接收任意地址的客户端信息
        server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
        server_addr.sin_port = htons(SERVER_PORT);
    
        /* 将套接字绑定到服务器的网络地址上 */
        if(bind(socketListen, (struct sockaddr *)&server_addr,sizeof(struct sockaddr)) != 0){
            perror("bind error");
    		exit(-1);
        }
        printf("call bind() successful
    ");
    
        /* 开始监听相应的端口 */
        if(listen(socketListen, 10) != 0){
            perror("call listen()");
            exit(-1);
        }
        printf("call listen() successful
    ");
    
        /* 创建一个线程用来接收客户端的连接请求 */
        pthread_t thrAccept;
        pthread_create(&thrAccept, NULL, fun_thrAcceptHandler, &socketListen);
    
        /* 主线程用来向所有客户端循环发送数据 */
        while(1){
            if(ReadToSend){
                // 判断线程存活数量
                int i;
                for(i = 0; i < thrReceiveClientCount; i++){
                    if(checkThrIsKill(arrThrReceiveClient[i])){
                        printf("A Thread has been killed
    ");
                        thrReceiveClientCount --;
                    }
                }
                printf("Number of connected client: %d
    ", thrReceiveClientCount);
                if(conClientCount <= 0){
                    printf("No Clients!
    ");
                }
    
                // 向所有客户端发送消息
                else{
                    printf("conClientCount = %d
    ", conClientCount);
                    for(i = 0; i < conClientCount; i++){
                        printf("socketCon = %d
    buffer is: %s
    ", arrConSocket[i].socketCon, buffer);
                        int sendMsg_len = send(arrConSocket[i].socketCon, buffer, strlen(buffer), 0);
                        if(sendMsg_len > 0){
                            printf("Send Message to %s:%d successful
    ", arrConSocket[i].ipaddr, arrConSocket[i].port);
                            ReadToSend = 0;
                        }else{
                            printf("Fail to send message to %s:%d
    ", arrConSocket[i].ipaddr, arrConSocket[i].port);
                        }
                    }
                }
            }
            sleep(0.5);
        }
    
        /* 等待子进程退出 */
        printf("Waiting for child thread to exit ....
    ");
        char *message;
        pthread_join(thrAccept,(void *)&message);
        printf("%s
    ",message);
    
        return 0;
    }
    
    
    
    /********************************************************************
    *   Function Name: void *fun_thrAcceptHandler(void *socketListen)
    *   Description: 监听客户端的连接请求,获取待连接客户端的网络信息,并为该客户端创建子线程.
    *   Called By: server.c[main]
    *   Input: socketListen -> 表示用于监听的被动套接字
    *   Date: 2020/05/24
    *********************************************************************/
    void *fun_thrAcceptHandler(void *socketListen){
        while(1){
            int sockaddr_in_size = sizeof(struct sockaddr_in);
            struct sockaddr_in client_addr;
            int _socketListen = *((int *)socketListen);
    
            /* 接收相应的客户端的连接请求 */
            int socketCon = accept(_socketListen, (struct sockaddr *)(&client_addr), (socklen_t *)(&sockaddr_in_size));
            if(socketCon < 0){
                printf("call accept()");
            }else{
                printf("Connected %s:%d
    ", inet_ntoa(client_addr.sin_addr), client_addr.sin_port);
            }
            printf("Client socket: %d
    ", socketCon);
    
            /* 获取新客户端的网络信息 */
            _MySocketInfo socketInfo;
            socketInfo.socketCon = socketCon;
            socketInfo.ipaddr = inet_ntoa(client_addr.sin_addr);
            socketInfo.port = client_addr.sin_port;
    
            /* 将新客户端的网络信息保存在 arrConSocket 数组中 */
            arrConSocket[conClientCount] = socketInfo;
            conClientCount++;
            printf("Number of users: %d
    ", conClientCount);
    
            /* 为新连接的客户端开辟线程 fun_thrReceiveHandler,该线程用来循环接收客户端的数据 */
            pthread_t thrReceive = 0;
            pthread_create(&thrReceive, NULL, fun_thrReceiveHandler, &socketInfo);
            arrThrReceiveClient[thrReceiveClientCount] = thrReceive;
            thrReceiveClientCount ++;
            printf("A thread has been created for the user.
    ");
     
            /* 让进程休息0.1秒 */
            usleep(100000);
        }
     
        char *s = "Safe exit from the receive process ...";
        pthread_exit(s);
    }
    
    
    
    /********************************************************************
    *   Function Name: void *fun_thrReceiveHandler(void *socketInfo)
    *   Description: 向服务器发送初始消息,从服务器循环接收信息.
    *   Called By: server.c[main]
    *   Input: socketInfo -> 表示客户端的网络信息
    *   Date: 2020/05/24
    *********************************************************************/
    void *fun_thrReceiveHandler(void *socketInfo){
    	int buffer_length;
        int con;
        int i;
    	_MySocketInfo _socketInfo = *((_MySocketInfo *)socketInfo);
    
        /* 向服务器发送握手消息 */
        send(_socketInfo.socketCon, HANDSHARK_MSG, sizeof(HANDSHARK_MSG), 0);
    
        /* 从服务器循环接收消息 */
        while(1){
    
        	// 将接收缓冲区buffer清空
        	bzero(&buffer,sizeof(buffer));
    
            // 接收服务器信息
            printf("Receiving messages from client %d ...
    ", _socketInfo.socketCon);
            buffer_length = recv(_socketInfo.socketCon, buffer, BUFSIZ, 0);
            if(buffer_length == 0){
                // 判断为客户端退出
                printf("%s:%d Closed!
    ", _socketInfo.ipaddr, _socketInfo.port);
                // 找到该客户端在数组中的位置
                for(con = 0; con < conClientCount; con++){
                    if(arrConSocket[con].socketCon == _socketInfo.socketCon){
                        break;
                    }
                }
                // 将该客户端的信息删除,重置客户端数组
                for(i = con; i < conClientCount-1; i++){
                    arrConSocket[i] = arrConSocket[i+1];
                }
                conClientCount --;
                break;
            }
            else if(buffer_length < 0){
                printf("Fail to call read()
    ");
                break;
            }
            buffer[buffer_length] = '';
            printf("%s:%d said:%s
    ", _socketInfo.ipaddr, _socketInfo.port, buffer);
            ReadToSend = 1;     // 发送标志置位,允许主线程发送数据
            usleep(100000);
        }
        printf("%s:%d Exit
    ", _socketInfo.ipaddr, _socketInfo.port);
        return NULL;
    }
    
    
    
    /********************************************************************
    *   Function Name: checkThrIsKill(pthread_t thr)
    *   Description: 检测当前线程是否存活.
    *   Called By: server.c[main]
    *   Input: thr -> 线程数组中的线程
    *   Date: 2020/05/24
    *********************************************************************/
    int checkThrIsKill(pthread_t thr){
        int res = 1;
        int res_kill = pthread_kill(thr, 0);
        if(res_kill == 0){
            res = 0;
        }
        return res;
    }
    

    客户端程序

    /********************************************************************
    *   File Name: client.c
    *   Description: 用于实现多客户端通信
    *   Others: 
    *     1. 客户端只需考虑如何连接目标服务器即可
    *     2. 客户端首先创建套接字并向服务器附送网络连接请求
    *     3. 连接到服务器之后,创建子进程
    *     4. 父进程用来向服务器发送数据,子进程从服务器接收数据
    *   Init Date: 2020/05/24
    *********************************************************************/
    #include "mysocket.h"
    
    int main(int argc, char *argv[]){
    	int client_sockfd;
    	int len;
    	pid_t pid;
        char buf_recv[BUFSIZ];                                 	//数据传送的缓冲区
    	char buf_send[BUFSIZ];
    	struct sockaddr_in remote_addr;                         //服务器端网络地址结构体
    
    	/* 初始化目标服务器的网络信息 */
    	memset(&remote_addr, 0, sizeof(remote_addr));           //数据初始化--清零
    	remote_addr.sin_family = AF_INET;                       //设置为IP通信
    	remote_addr.sin_addr.s_addr = inet_addr(SERVER_IP);     //服务器IP地址
    	remote_addr.sin_port = htons(SERVER_PORT);              //服务器端口号
    	
    	/*创建客户端套接字--IPv4协议,面向连接通信,TCP协议*/
    	if((client_sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0){
    		perror("socket error");
    		return 1;
    	}
    	
    	/*将套接字绑定到服务器的网络地址上*/
    	if(connect(client_sockfd, (struct sockaddr *)&remote_addr, sizeof(struct sockaddr)) < 0){
    		perror("connect error");
    		return 1;
    	}
    	printf("connected to server
    ");
    
    	/* 从服务器接收初始化的握手消息 */
    	len = recv(client_sockfd, buf_recv, BUFSIZ, 0);			//接收服务器端信息
        buf_recv[len] = '';
    	printf("%s", buf_recv);									//打印服务器端的欢迎信息
    	printf("Enter string to send: 
    ");
    	
    	/* 创建父子进程与服务器进行通信 */
    	if((pid = fork()) < 0){
    		printf("Fail to call fork()
    ");
    		return 1;
    	}
    	
    	/* 父进程用来发送数据 */
    	else if(pid > 0){
    		while(1){
    			scanf("%s", buf_send);
    			if(!strcmp(buf_send, "quit")){
    				kill(pid, SIGSTOP);
    				break;
    			}
    			len = send(client_sockfd, buf_send, strlen(buf_send), 0);
    		}
    	}
    	/* 子进程用来接收数据 */
    	else{
    		while(1){
    			memset(buf_recv, 0, sizeof(buf_recv));
    			if((len = recv(client_sockfd, buf_recv, BUFSIZ, 0)) > 0){
    				printf("Recive from server: %s
    ", buf_recv);
    			}
    			usleep(200000);
    		}
    	}
    
    	/* 关闭套接字 */
    	close(client_sockfd);
    
    	return 0;
    }
    
    

    头文件

    /********************************************************************
    *   File Name: mysocket.h
    *   Description: 用于实现多客户端通信
    *   Init Date: 2020/05/24
    *********************************************************************/
    #include <stdlib.h>
    #include <sys/types.h>
    #include <stdio.h>
    #include <sys/socket.h>
    #include <string.h>
    #include <signal.h>
    #include <netinet/in.h>
    #include <arpa/inet.h>
    #include <unistd.h>
    #include <pthread.h>
    
    #define SERVER_IP "127.0.0.1"       // 用于本地测试
    // #define SERVER_IP "47.95.13.239"	// 用于公网测试
    #define SERVER_PORT 18888
    #define HANDSHARK_MSG "Hello,Client!
    "
    #define MaxClientNum 10
    
    /* 套接字信息结构体,用于记录客户端信息 */
    typedef struct MySocketInfo{
        int socketCon;                  // 套接字描述符
        char *ipaddr;                   // 客户端IP地址
        uint16_t port;                  // 客户端端口号
    }_MySocketInfo;
    
    char buffer[BUFSIZ];                // 服务器数据收发缓冲区
    int ReadToSend;                     // 服务器准备发送标志位
    
    /* 用于记录客户端信息的数组 */
    struct MySocketInfo arrConSocket[MaxClientNum];
    int conClientCount;                 // 当前客户端数量
    
    /* 用来与客户端通信的线程数组 */
    pthread_t arrThrReceiveClient[MaxClientNum];
    int thrReceiveClientCount;          // 当前通信子线程数量
    
    /* 线程功能函数 */
    void *fun_thrReceiveHandler(void *socketInfo);
    void *fun_thrAcceptHandler(void *socketListen);
    int checkThrIsKill(pthread_t thr);
    
    

    Makefile

    all:
    	gcc server.c -o server -lpthread
    	gcc client.c -o client
    

    程序测试

    测试说明

    将服务端程序放到服务器上,即可实现多客户端之间的公网通信。
    修改头文件里的SERVER_IP宏定义,可以改变测试环境。
    修改头文件里的MaxClientNumber宏定义,可以改变最大客户端数量。
    我测试时将服务端程序放到了阿里云的服务器上,然后电脑连上手机热点,另一台小电脑连上家里的WiFi,运行程序测试通信功能。也就是说两台电脑不在同一个局域网中。

    测试视频

    测试视频如下。

    Linux Socket 多客户端通信测试

    点击这里可前往B站查看更清晰的测试视频

    视频里面左侧的终端是在Windows电脑开的虚拟机终端;中间的终端是同一个Windows电脑用FinalShell连接的服务器终端;右侧的终端是另一台Linux系统小电脑的终端。

    致谢

    特别感谢王文州同学,我们共同完成了该程序。

  • 相关阅读:
    织梦开发——相关阅读likeart应用
    织梦标签教程
    织梦专题调用代码
    HIT 2543 Stone IV
    POJ 3680 Intervals
    HIT 2739 The Chinese Postman Problem
    POJ 1273 Drainage Ditches
    POJ 2455 Secret Milking Machine
    SPOJ 371 Boxes
    HIT 2715 Matrix3
  • 原文地址:https://www.cnblogs.com/ZHJ0125/p/12968838.html
Copyright © 2011-2022 走看看