【多线程】聊天室的实现

 

【目标实现】

模拟一个聊天室,任意一个客户端窗口可以发送消息,同时也可以接收聊天室内所有人的消息。

 

【服务器端】

 

#include <stdio.h>
#include <cstring>
#include <algorithm>
#include <arpa/inet.h>
#include <pthread.h>
#include <unistd.h>
using namespace std;
int cli_socks[100], num = 0, judge = 0;
pthread_mutex_t mutex;
void *t_main(void *arg)
{
    char str[100];
    int sock = *(int *)arg;
    while(1)
    {
        int len = read(sock, str, 100);
        if(!len) break;
        str[len] = 0;
        pthread_mutex_lock(&mutex);
        //向所有客户端发送消息
        for(int i = 1; i <= num; i++)
        {
            write(cli_socks[i], str, sizeof(str));
        }
        pthread_mutex_unlock(&mutex);
    }
    int i;
    //线程同步
    pthread_mutex_lock(&mutex);
    for(i = 1; i <= num; i++)
    {
        if(cli_socks[i] == sock)
            break;
    }
    while(i < num) 
    {
        cli_socks[i] = cli_socks[i+1];
        i++;
    }
    num--;
    pthread_mutex_unlock(&mutex);
    printf("close connect is %d\n", sock);
    close(sock);
    if(!num && judge) exit(0);
}
int main(int argc, char ** argv)
{
    int ser_sock, cli_sock;
    sockaddr_in ser_addr, cli_addr;
    ser_sock = socket(PF_INET, SOCK_STREAM, 0);//创建套接字
    if(ser_sock == -1) 
        puts("socket error");

    //地址再分配
    int opt = 1;
    setsockopt(ser_sock, SOL_SOCKET, SO_REUSEADDR, &opt, 4);

    memset(&ser_addr, 0, sizeof(ser_addr));
    ser_addr.sin_family = AF_INET;
    ser_addr.sin_addr.s_addr = htonl(INADDR_ANY);
    ser_addr.sin_port = htons(atoi("9190"));
    //分配地址信息
    bind(ser_sock, (sockaddr *)&ser_addr, sizeof(ser_addr));
    //转化可接受连接状态
    listen(ser_sock, 5);

    pthread_mutex_init(&mutex, NULL);//创建互斥量
    pthread_t id;
    puts("等待进入...");
    while(1)
    {
        socklen_t cli_len = sizeof(cli_addr);
        //请求受理
        cli_sock = accept(ser_sock, (sockaddr *)&cli_addr, &cli_len);
        judge = 1;
        //利用互斥量锁住临界区
        pthread_mutex_lock(&mutex);
        cli_socks[++num] = cli_sock;
        pthread_mutex_unlock(&mutex);

        pthread_create(&id, NULL, t_main, (void *)&cli_sock);//创建线程
        pthread_detach(id);//线程运行结束自动释放所有资源
        printf("connect is %d\n", cli_sock);
    }
    pthread_mutex_destroy(&mutex);//销毁互斥量
    close(ser_sock);
    return 0;
}

 

 

【客户端】

 

#include <stdio.h>
#include <unistd.h>
#include <cstring>
#include <algorithm>
#include <arpa/inet.h>
#include <pthread.h>
#include <iostream>
using namespace std;
char name[100], na[100];
void *wr(void *arg)
{
    char s[100], mes[100];
    int sock = *(int *)arg;
    int x = sizeof(s);
    while(1)
    {
        fgets(s, sizeof(s), stdin);
        s[strlen(s) - 1] = 0;
        if(!strcmp(s, "q")) break;
        sprintf(mes, "%s %s", name, s);
        write(sock, mes, strlen(mes));
    }
    sprintf(mes, "【系统消息】%s退出聊天室", na);
    write(sock, mes, strlen(mes));
    close(sock);
    exit(0);
}
void *rd(void *arg)
{
    int sock = *(int *)arg;
    char s[100];
    while(1)
    {
        int len = read(sock, s, 100);
        s[len] = 0;
        puts(s);
    }
}
int main(int argc, char **argv)
{
    puts("请输入用户名!");
    char mes[50];
    cin >> na;
    getchar();//防止fgets读回车
    int sock = socket(PF_INET, SOCK_STREAM, 0);//创建套接字

    sockaddr_in addr;
    memset(&addr, 0, sizeof(addr));
    addr.sin_family = AF_INET;
    addr.sin_addr.s_addr = inet_addr("127.0.0.1");
    addr.sin_port = htons(atoi("9190"));
    sprintf(name, "[%s]", na);
    connect(sock, (sockaddr *)&addr, sizeof(addr));//请求连接
    sprintf(mes, "【系统消息】%s进入聊天室", na);
    write(sock, mes, strlen(mes));

    pthread_t id_rd, id_wr;
    //创建读写线程
    pthread_create(&id_wr, NULL, wr, (void *)&sock);
    pthread_create(&id_rd, NULL, rd, (void *)&sock);

    pthread_join(id_wr, NULL);//等待写线程终止
    //pthread_join(id_rd, NULL);
    close(sock);
    return 0;
}

 

 

【效果截图】

 

 

 

 

【发现问题】

1.exit和return的区别:传送门

2.linux用gets会出现警告,由于他没有指定输入字符的大小,如果输入字符大于定义的数组长度的时候,那么就会发生内存越界问题。 而用fgets函数则可以根据定义数组的长度自动截断字符,而消除一些安全隐患。 但是fgets会读入最后的回车。

3.客户端代码的21行是把数据送到输出缓冲,34行是从输入缓冲读取,不用担心两个会同时进行,因为并不在一个通道上。

4.全局变量num和cli_socks所在的代码行构成临界区,因为如果不同的线程同时运行可能会引发错误。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值