【Linux开发—群聊服务器(多线程并发)】

一,准备

  • 网络编程+多线程+线程同步实现群聊架构(CS架构):客户端和服务器
  • 群聊对于客户端是1对1,对于服务端是1对多。
  • 头文件准备:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <pthread.h>
#include <semaphore.h>

二,服务端

int clnt_socks[100] = {
    
     0 };//客户端socket数量
int clnt_cnt = 0;//客户端数量
pthread_mutex_t mutex;//创建互斥体,解决释放客户端socket问题,客户端数量变化

sem_t semid;//信号量,客户端socket生命周期延长

//服务端
void sev_send_msg(const char* msg, ssize_t str_len)
{
    
    
    pthread_mutex_lock(&mutex);

    for (int i = 0; i < clnt_cnt; i++)
    {
    
    
        if (clnt_socks[i] >= 0)
        {
    
    
            write(clnt_socks[i], msg, str_len);
        }

    }
    pthread_mutex_unlock(&mutex);
}

void* handle_clnt(void* arg)
{
    
    
    pthread_detach(pthread_self());
    int clnt_sock = *(int*)arg;//取出当前线程的socket
    char msg[1024] = "";
    ssize_t str_len = 0;
    while ((str_len = read(clnt_sock, msg, sizeof(msg))) > 0)//遍历,读取消息, 一直有消息就一直读,等于0:为关闭连接,小于0为出错
    {
    
    
        sev_send_msg(msg, str_len);
    } ;

   

    pthread_mutex_lock(&mutex);
    *(int*)arg = -1;

   /* for (int i = 0; i < clnt_cnt; i++)
    {
        if (clnt_sock == clnt_socks[i])
        {
            clnt_socks[i] = -1;
            break;
        }
    }*/
    pthread_mutex_unlock(&mutex);
    close(clnt_sock);
    pthread_exit(NULL);
}

void thread_more_server()
{
    
    
    int serv_sock, clnt_sock;
    struct sockaddr_in serv_adr, clnt_adr;
    socklen_t clnt_adr_sz = sizeof(clnt_adr);
    serv_sock = socket(PF_INET, SOCK_STREAM, 0);
    if (serv_sock == -1)
    {
    
    
        printf("create socket error:%d %s\n", errno, strerror(errno));
        return;
    }

    memset(&serv_adr, 0, sizeof(serv_adr));
    serv_adr.sin_family = AF_INET;
    serv_adr.sin_addr.s_addr = htonl(INADDR_ANY);
    serv_adr.sin_port = htons(9527);

    pthread_mutex_init(&mutex, NULL);

    if (bind(serv_sock, (struct sockaddr*)&serv_adr, sizeof(serv_adr)) == -1)
    {
    
    
        error_handling("thread server bind error");
        printf("bind error msg:%d %s\n", errno, strerror(errno));
        return;
    }

    if (listen(serv_sock, 5) == -1)
    {
    
    
        error_handling("thread server listen error");
        printf("listen error msg:%d %s\n", errno, strerror(errno));
        return;
    }

    printf("open thread server success!\n");

    while (true)
    {
    
    
        clnt_sock = accept(serv_sock, (struct sockaddr*)&clnt_adr, &clnt_adr_sz);
        if (clnt_sock == -1)
        {
    
    
            printf("accept error msg:%d %s\n", errno, strerror(errno));
            break;
        }

        pthread_mutex_lock(&mutex);
        clnt_socks[clnt_cnt++] = clnt_sock;
        pthread_mutex_unlock(&mutex);

        pthread_t thread;
        pthread_create(&thread, NULL, handle_clnt, &clnt_socks[clnt_cnt - 1]);
        printf("accept client count:%d", clnt_cnt - 1);

    }

    close(serv_sock);
    pthread_mutex_destroy(&mutex);

}

三,客户端

// 客户端 发送消息
char name[64] = "[MOON]";
void* client_send_msg(void* arg)
{
    
    
    pthread_detach(pthread_self());
    int clnt_sock = *(int*)arg;//取出当前线程的socket
    char msg[256] = "";
    char buffer[1024];
    while (1)
    {
    
    
        memset(buffer, 0, sizeof(buffer));
        fgets(msg, sizeof(msg), stdin);//对文件的标准输入流操作 读取buffer的256字节
        if (strcmp(msg, "q\n") == 0 || (strcmp(msg, "Q\n") == 0)) {
    
    
            break;
        }

        
        if (strcmp(msg, "") == 0)
        {
    
    
            continue;
        }

        snprintf(buffer, sizeof(buffer), "%s: %s", name, msg);
        size_t len = strlen(buffer);
        size_t send_len = 0;

        //当数据量很大时,并不能一次把所有数据全部发送完,因此需要分包发送
        while (send_len < len)
        {
    
    
            ssize_t ret = write(clnt_sock, buffer + send_len, len - send_len);//send_len 记录分包的标记
            if (ret <= 0) {
    
    //连接出了问题
                fputs("may be connect newwork failed,make client write failed!\n", stdout);
                break;
            }
            send_len += (size_t)ret;
        }       
    };

    sem_post(&semid);
    pthread_exit(NULL);
}
//客户端-接收消息
void* client_recv_msg(void* arg)
{
    
    
    pthread_detach(pthread_self());
    int clnt_sock = *(int*)arg;//取出当前线程的socket
    char buffer[1024] = "";
    while (1)
    {
    
    
        size_t ret = read(clnt_sock, buffer, sizeof(buffer));
        if (ret <= 0) {
    
    //连接出了问题
            fputs("client read failed!\n", stdout);
            break;
        }
        fputs(buffer, stdout);
        memset(buffer, 0, ret);//处理完消息及时重置内存
    };
    sem_post(&semid);
    pthread_exit(NULL);
}


void thread_more_client()
{
    
    
    struct sockaddr_in clnt_adr;

    socklen_t clnt_adr_sz = sizeof(clnt_adr);

    int clnt_sock = socket(PF_INET, SOCK_STREAM, 0);

    memset(&clnt_adr, 0, clnt_adr_sz);
    clnt_adr.sin_family = AF_INET;
    clnt_adr.sin_addr.s_addr = inet_addr("127.0.0.1");
    clnt_adr.sin_port = htons(9527);
    if (connect(clnt_sock, (struct sockaddr*) & clnt_adr, clnt_adr_sz) == -1)
    {
    
    
        printf("connect error msg:%d %s\n", errno, strerror(errno));
        return;
    }

    pthread_t thread_send, thread_recv;
    
    sem_init(&semid, 0, -1);
    pthread_create(&thread_send, NULL, client_send_msg, &clnt_sock);//消息clnt_sock为局部变量
    pthread_create(&thread_recv, NULL, client_recv_msg, &clnt_sock);//消息clnt_sock为局部变量


    sem_wait(&semid);

    close(clnt_sock);
}

四,调用示例

void thread_more_cs_connect(char*arg)
{
    
    
    if (strcmp(arg, "s") == 0)//如果输入s,走服务端路线
    {
    
    
        thread_more_server();
    }
    else
    {
    
    
        fputs("Please input your name:", stdout);
        scanf("%s", name);
        thread_more_client();
    }
}

int main(int argc, char* argv[])
{
    
    
    printf("%s %s!\n", "Welcome LinuxConsoleApp_server", __func__);
    thread_more_cs_connect(argv[1]);//多线程并发服务(线程)
    return 0;
}

五,结果

  • 实现群聊服务
    在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/MOON_YZM/article/details/131078049