Loading

18.5.2 多线程并发服务器端的实现

实现多个客户端之间可以交换信息的简单聊天程序

先上结果:

服务端代码

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

/*
服务端示例中,需要掌握临界区的构成,访问全局变量 clnt_cnt 和数组 clnt_sock_array 的代码将构
成临界区,添加和删除客户端时,变量 clnt_cnt 和数组 clnt_sock_array 将同时发生变化。
因此下列情形会导致数据不一致,从而引发错误:
1. 线程 A 从数组 clnt_sock_array 中删除套接字信息,同时线程 B 读取 clnt_cnt 变量
2. 线程 A 读取变量 clnt_cnt ,同时线程 B 将套接字信息添加到 clnt_sock_array 数组
*/

#define BUF_SIZE 256
#define MAX_CLEN 256

int clnt_cnt = 0;
int clnt_sock_array[MAX_CLEN];
pthread_mutex_t mutex;

void *handle_clnt(void *arg);
void error_handling(char *msg);
void send_msg(char *msg, int len);

int main(int argc, char *argv[])
{
    int clnt_sock = 0;
    int serv_sock = 0;
    struct sockaddr_in serv_addr;
    struct sockaddr_in clnt_addr;
    socklen_t clnt_addr_size = 0;
    pthread_t pid;

    if (argc != 2)
    {
        printf("Usage: %s <port>\n", argv[0]);
        exit(1);
    }
    //创建互斥锁
    pthread_mutex_init(&mutex, NULL);
    //创建套接字
    serv_sock = socket(AF_INET, SOCK_STREAM, 0);
    if (-1 == serv_sock)
    {
        error_handling("fail to socket!");
    }
    //初始化IP地址
    memset(&serv_addr, 0, sizeof(serv_addr));
    serv_addr.sin_family = AF_INET;
    serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
    serv_addr.sin_port = htons(atoi(argv[1]));

    if (-1 == bind(serv_sock, (struct sockaddr *)&serv_addr, sizeof(serv_addr)))
    {
        error_handling("fail to bind");
    }

    if (-1 == listen(serv_sock, 5))
    {
        error_handling("fail to bind");
    }

    while (1)
    {
        clnt_addr_size = sizeof(clnt_addr);
        clnt_sock = accept(serv_sock, (struct sockaddr *)&clnt_addr, &clnt_addr_size);

        //上锁
        pthread_mutex_lock(&mutex);
        //将新客户端存入数组
        clnt_sock_array[clnt_cnt++] = clnt_sock;
        //解锁
        pthread_mutex_unlock(&mutex);

        //创建线程为新客户端服务,并且把clnt_sock作为参数
        pthread_create(&pid, NULL, handle_clnt, &clnt_sock);
        //引导线程销毁,不会阻塞
        pthread_detach(pid);
        //打印客户端的ip地址
        printf("Connected client ID:%s\n", inet_ntoa(clnt_addr.sin_addr));
    }
    close(serv_sock);
    return 0;
}

void *handle_clnt(void *arg)
{
    int i = 0;
    int str_len = 0;
    int clnt_sock = *((int *)arg);
    char buf_msg[BUF_SIZE];

    while ((str_len = read(clnt_sock, buf_msg, sizeof(buf_msg))) != 0)
    {
        send_msg(buf_msg, str_len);
    }
    //接受到的信息为0,说明当前客户端已经断开连接
    pthread_mutex_lock(&mutex);
    //删除没有连接的客户端
    for (i=0; i<clnt_cnt; i++)
    {
        if (clnt_sock == clnt_sock_array[i])
        {
            while (i++ < clnt_cnt - 1)
            {
                clnt_sock_array[i] = clnt_sock_array[i + 1];
            }
            break;
        }
    }
    clnt_cnt--;
    pthread_mutex_unlock(&mutex);
    close(clnt_sock);
    return NULL;
}

//向所有客户端全发送信息
void send_msg(char *msg, int len)
{
    int i;
    pthread_mutex_lock(&mutex);
    for (i=0; i<clnt_cnt; i++)
    {
        write(clnt_sock_array[i], msg, len);
    }
    pthread_mutex_unlock(&mutex);
}

void error_handling(char *msg)
{
    fputs(msg, stderr);
    fputc('\n', stderr);
    exit(1);
}

客户端代码

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <pthread.h>

#define BUF_SIZE 256
#define NAME_SIZE 20

void *recv_msg(void *arg);
void *send_msg(void *arg);
void error_handling(char *msg);

char name[NAME_SIZE] = "[default]";
char msg[BUF_SIZE];

int main(int argc, char *argv[])
{
    int sock;
    struct sockaddr_in serv_addr;
    pthread_t send_thread;
    pthread_t recv_thread;
    void *thread_return;

    if (argc != 4)
    {
        printf("Usage : %s <IP> <port> <name>\n", argv[0]);
        exit(1);
    }

    sprintf(name, "[%s]", argv[3]);
    sock = socket(AF_INET, SOCK_STREAM, 0);
    if (-1 == sock)
    {
        error_handling("fail to socket");
    }

    memset(&serv_addr, 0, sizeof(serv_addr));
    serv_addr.sin_family = AF_INET;
    serv_addr.sin_port = htons(atoi(argv[2]));
    serv_addr.sin_addr.s_addr = inet_addr(argv[1]);

    if (-1 == connect(sock, (struct sockaddr *)&serv_addr, sizeof(serv_addr)))
    {
        error_handling("fail to connect");
    }

    //创建发送消息线程
    pthread_create(&send_thread, NULL, send_msg, (void *)&sock);
    //创建接受消息线程
    pthread_create(&recv_thread, NULL, recv_msg, (void *)&sock);

    //pthread_join()的作用可以这样理解:主线程等待子线程的终止
    pthread_join(send_thread, &thread_return);
    pthread_join(recv_thread, &thread_return);

    close(sock);
    return 0;
}

void *send_msg(void *arg)
{
    int sock = *((int *)arg);
    char name_msg[NAME_SIZE + BUF_SIZE];

    while (1)
    {
        fgets(msg, BUF_SIZE, stdin);
        if (!strcmp(msg, "q\n") || !strcmp(msg, "Q\n"))
        {
            close(sock);
            exit(0);
        }
        sprintf(name_msg, "%s %s", name, msg);
        write(sock, name_msg, strlen(name_msg));
    }
    return NULL;
}

void *recv_msg(void *arg)
{
    int sock = *((int *)arg);
    char name_msg[NAME_SIZE + BUF_SIZE];
    int str_len;

    while (1)
    {
        str_len = read(sock, name_msg, NAME_SIZE + BUF_SIZE -1);
        name_msg[str_len] = 0;
        fputs(name_msg, stdout);
    }
    return NULL;
}

void error_handling(char *msg)
{
    fputs(msg, stderr);
    fputc('\n', stderr);
    exit(1);
}

编译运行:

gcc chat_server.c -D_REENTRANT -o cserv -lpthread
gcc chat_clnt.c -D_REENTRANT -o cclnt -lpthread
./cserv 8180
./cclnt 127.0.0.1 8180 大郎
./cclnt 127.0.0.1 8180 小潘
posted @ 2021-05-08 17:43  小森林呐  阅读(147)  评论(0编辑  收藏  举报