Linux-Socket实现模拟群聊(多人聊天室)

这篇具有很好参考价值的文章主要介绍了Linux-Socket实现模拟群聊(多人聊天室)。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

Linux-Socket实现模拟群聊(多人聊天室)

简单版本
服务端源码
#include<stdio.h>
#include<stdlib.h>
#include<pthread.h>
#include<unistd.h>
#include<string.h>
#include<sys/socket.h>
#include<arpa/inet.h>
#include<netinet/in.h>

#define MAX 100
typedef struct Client{
    //socket文件描述符
    int cfd;
    //客户端名称
    char name[50];
}Client;
//设置最多群聊人数
Client client[MAX] = {};
size_t count = 0;

//初始化互斥锁
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;

//广播函数
void broadcast(char *msg, Client c){
    pthread_mutex_lock(&mutex);
    //给除了当前客户端的其他所有客户端发消息
    for(size_t i = 0; i < count; i++){
        if(client[i].cfd != c.cfd){
            if(send(client[i].cfd,msg,strlen(msg),0) <= 0){
                break;
            }
        }
    }
    pthread_mutex_unlock(&mutex);
}

//处理与每个客户端的交互
void *pthread_run(void *arg){
    Client c = *(Client*)(arg);
    while(1){
        char buf[1024] = {};
        strcpy(buf,c.name);
        strcat(buf," :");
        int ret = recv(c.cfd,buf + strlen(buf), 1024 - strlen(buf), 0);
        //如果没有接收到该客户端的消息,说明该客户端离线
        if(ret <= 0){
            for(size_t i = 0; i < count; i++){
                if(client[i].cfd == c.cfd){
                    //把该客户端的信息从客户端列表中删除
                    client[i] = client[count - 1];
                    count--;
                    strcpy(buf,c.name);
                    strcat(buf,"已退出群聊");
                    break;
                }
            }
            broadcast(buf,c);
            close(c.cfd);
            return NULL;
        }else{
            //接收到了客户端消息,则广播该消息
            broadcast(buf,c);
        }
    }
}

int main(int argc, char *argv[]){
    const char *ip;
    unsigned short int port;
    //如果没有指定ip地址和端口号,则使用默认ip地址(本机)和端口号
    if(argc < 3){
        ip = "127.0.0.1";
        port = 533;
    }else{
        ip = argv[1];
        port = atoi(argv[2]);
    }
    //使用TCP/IP(V4)协议
    int sfd = socket(AF_INET,SOCK_STREAM,0);
    if(sfd == -1){
        perror("socket err\n");
        return -1;
    }

    struct sockaddr_in addr;
    addr.sin_family = AF_INET;
    //将port转换为网络字节序(大端模式)
    addr.sin_port = htons(port);
    //将点分十进制的IPv4地址转换成网络字节序列的长整型
    addr.sin_addr.s_addr = inet_addr(ip);
    socklen_t addrlen = sizeof(addr);
    //将ip地址绑定套接字
    int ret = bind(sfd,(struct sockaddr*)(&addr), addrlen);
    if( ret == -1){
        perror("bind error\n");   
        return -1;
    }
    //监听链接请求队列,accept()应答之前,允许在进入队列中等待的连接数目是10
    if(listen(sfd,10) == -1){
        perror("listen error\n");
        return -1;
    }
    printf("服务器已启动...\n");
    while(1){
        struct sockaddr_in caddr;
        socklen_t len = sizeof(caddr);
       
        int cfd = accept(sfd,(struct sockaddr*)(&caddr),&len);
        if(cfd == -1){
            perror("accept error\n");
            return -1;
        }
        //单次通信最大数据长度
        char buf[100] = {};
        recv(cfd,&client[count].name,50,0);
        //将该客户端保存到客户端列表
        client[count].cfd = cfd;
        //创建一个线程处理此次连接
        pthread_t tid;
        strcpy(buf,client[count].name);
        strcat(buf,"已加入群聊");
        broadcast(buf,client[count]);
        ret = pthread_create(&tid,NULL,pthread_run,(void*)(&client[count]));
        count++;
        if(ret != 0){
            printf("pthread_create: %s\n",strerror(ret));
            continue;
        }
        printf("有一个客户端成功连接:ip <%s> port [%hu]\n",inet_ntoa(caddr.sin_addr),ntohs(caddr.sin_port));
    }
    
    return 0;
}

//编译代码
//gcc server.c -o server -lpthread
客户端源码
#include<stdio.h>
#include<stdlib.h>
#include<time.h>
#include<string.h>
#include<arpa/inet.h>
#include<unistd.h>
#include<sys/socket.h>
#include<netinet/in.h>

int main(int argc, char *argv[]){
    const char *ip;
    unsigned short int port;
    //如果没指明,默认是ip = "127.0.0.1",port = 533
    if(argc < 3){
        ip = "127.0.0.1";
        port = 533;
    }else{
        ip = argv[1];
        port = atoi(argv[2]);
    }
    int sfd = socket(AF_INET,SOCK_STREAM,0);
    if(sfd == -1){
        perror("socket error\n");
        return -1;
    }
    struct sockaddr_in addr;
    addr.sin_family = AF_INET;
    addr.sin_port = htons(port);
    addr.sin_addr.s_addr = inet_addr(ip);
    socklen_t addrlen = sizeof(addr);

    int ret = connect(sfd,(const struct sockaddr*)(&addr),addrlen);
    if(ret == -1){
        perror("connect error\n");
        return -1;
    }
    char name[50];
    printf("请输入你的群聊昵称:");
    fgets(name,49,stdin);
    send(sfd,name,strlen(name) - 1, 0);
    //创建两个进程,父进程负责收消息,子进程负责发消息
    pid_t pid = fork();
    if(pid == -1){
        perror("fork error\n");
    }else if(pid == 0){
        while(1){
            char buf[1024] = {};
            fgets(buf,1023,stdin);
            if(send(sfd,buf,strlen(buf) + 1,0) <= 0){
                break;
            }
        }
    }else{
        while(1){
            char buf[1024] = {};
            if(recv(sfd,buf,1024,0) <= 0){
                break;
            }
            time_t current_time;
            time(&current_time);
            printf("%s\n",ctime(&current_time));
            printf("%s\n",buf);
        }
    }
    close(sfd);
    return 0;
}
//编译代码
//gcc client.c -o client

服务器可以在特定的端口监听客户端的连接请求,若连接成功,服务器采用广播的形式向当前所有连接客户端发送该客户端登录成功消息多个客户端可以同时登录,在源码文件中可以配置最多群聊同时在线人数。服务端接收到客户端发送的群聊信息后,也会采用广播的形式通知其他客户端,其他客户端接收后打印输出信息。这样就实现了简单版本模拟群聊。

这个版本有几个痛点

  1. 只有一个群,如果想同时在多个群群聊怎么办?
  2. 退出群聊后,群聊信息就没有了,如果想查看历史群聊信息怎么办?
  3. 用户在不同的群聊发送信息,服务端怎么将用户发送的信息广播给当前在线的且与发送信息的用户在同一群聊的用户?
更新版本

问题1解决方案:

给每个群聊设置一个群聊标识(群号),在启动客户端时,通过输入不同的群聊标识来进入不同的群。

问题2解决方案:

服务端为每一个群聊创建一个文本文件放入record目录中,以此文本文件存储群聊信息

在服务端Client结构体中加入address属性来记录当前群聊所对应的文本文件的地址

用户运行客户端程序,输入群号来加入群聊,如果该群号所对应的群不存在,那么服务端就为该群创建一个文本文件。如果该群号所对应的文本文件存在于record目录中,那么客户端程序就加载并打印该文件的内容

如此便实现了查看历史信息

问题3解决方案:

在服务端Client结构体中增加一个属性pid来记录当前客户端连接所加入的群聊的群号。用户每次运行客户端程序,需要输入要加入的群的群号,然后发送给服务端,服务端将此号赋值给表示当前连接的Client结构体中的pid属性。同一用户打开多个窗口运行客户端程序,在服务器的角度是创建了多个连接,会被当做不同用户看待,但是在用户的角度,就相当于在多个群进行聊天。 只要客户端与服务端每次通信时携带当前所在群的群号,在由服务端解析出群号,使用广播函数时判断当前所有客户端连接对应的群号和解析出的群号是否一致,一致就转发消息。

如此便实现了用户在群聊中发消息,服务端转发消息时只转发给与发送消息的用户在同一个群中的在线用户

核心概念是一个客户端与服务器的连接只能加入一个群,而同一用户通过打开不同的窗口运行客户端程序来创建多个连接,以此来加入不同的群

服务端代码
#include<stdio.h>
#include<stdlib.h>
#include<pthread.h>
#include<unistd.h>
#include<string.h>
#include<sys/socket.h>
#include<arpa/inet.h>
#include<netinet/in.h>
#include<sys/types.h>
#include<dirent.h>
#include<sys/stat.h>
#include<time.h>
#include<fcntl.h>

#define MAX 100

typedef struct Client{
    //socket文件描述符
    int cfd;
    //客户端名称
    char name[50];
    //群号,6位
    char id[7];
    //群聊信息文件地址
    char address[128];
}Client;
//设置最多群聊人数
Client client[MAX] = {};
size_t count = 0;

//初始化互斥锁
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
//保存聊天记录
void save(char *msg, Client c){
    char record[1024] = {};
    time_t current_time;
    time(&current_time);
    char *str = ctime(&current_time);
    int fd;
    fd = open(c.address,O_APPEND | O_WRONLY);
    if(fd == -1){
        perror("server open record error\n");
        return;
    }
    sprintf(record,"%s%s\n\n",str,msg);
    int ret = write(fd,record,strlen(record));
    if(ret == -1){
        perror("wirte record error\n");
        return;
    }
    close(fd);
}

//广播函数
void broadcast(char *msg, Client c){
    pthread_mutex_lock(&mutex);
    save(msg,c);
    //广播给与当前用户在同一群聊中的所有其他用户
    for(size_t i = 0; i < count; i++){
        if(client[i].cfd != c.cfd && strcmp(client[i].id,c.id) == 0){
            if(send(client[i].cfd,msg,strlen(msg),0) <= 0){
                break;
            }
        }
    }
    pthread_mutex_unlock(&mutex);
}
//判断群号是否存在于record目录里,否创建文件
void exits(Client c){
    DIR *db;
    struct dirent *p;
    db = opendir("/root/linux/communicate/record");
    char temp[20];
    sprintf(temp,"%s%s",c.id,".txt");
    int flag = 0;
    while((p = readdir(db))){
        if(strcmp(p->d_name,temp) == 0){
            flag = 1;
              break;
        }
    }
    if(flag == 0){
        umask(0);
        int ret = creat(c.address,0666);
        if(ret == -1) perror("creat record error\n");
    }
    closedir(db);
}
//对每一个客户端连接都创建一个线程处理
void *pthread_run(void *arg){
    Client c = *(Client*)(arg);
    exits(c);
    //单次通信最大数据长度
    char buf[100] = {};
    strcpy(buf,c.name);
    strcat(buf,"已加入群聊");
    broadcast(buf,c);
    while(1){
        char buf[1024] = {};
        strcpy(buf,c.name);
        strcat(buf," :");
        int ret = recv(c.cfd,buf + strlen(buf), 1024 - strlen(buf), 0);
        //如果没有接收到该客户端的消息,说明该客户端离线
        if(ret <= 0){
            for(size_t i = 0; i < count; i++){
                if(client[i].cfd == c.cfd){
                    //把该客户端的信息从客户端列表中删除
                    client[i] = client[count - 1];
                    count--;
                    strcpy(buf,c.name);
                    strcat(buf,"已退出群聊");
                    break;
                }
            }
            broadcast(buf,c);
            close(c.cfd);
            return NULL;
        }else{
            //接收到了客户端消息,则广播该消息
            broadcast(buf,c);
        }
    }
}

//接收用户要加入的群号和用户昵称,并将客户端保存到客户端列表
void receive(int cfd){
    char temp[128] = {};
    recv(cfd,temp,128,0);
    int i = 0;
    while(i < 6){
        client[count].id[i] = temp[i];
        i++;
    }
    client[count].id[i] = '\0';
    int j = 0;
    while(i < strlen(temp)){
        client[count].name[j] = temp[i];
        i++;
        j++;
    }
    client[count].name[i] = '\0';
    sprintf(client[count].address,"%s/%s%s","/root/linux/communicate/record",client[count].id,".txt");
    client[count].cfd = cfd;
}

//服务端socket初始化
int inet_init(const char *ip, unsigned short int port){
    //使用TCP/IP(V4)协议
    int sfd = socket(AF_INET,SOCK_STREAM,0);
    if(sfd == -1){
        perror("socket err\n");
        return -1;
    }

    struct sockaddr_in addr;
    addr.sin_family = AF_INET;
    //将port转换为网络字节序(大端模式)
    addr.sin_port = htons(port);
    //将点分十进制的IPv4地址转换成网络字节序列的长整型
    addr.sin_addr.s_addr = inet_addr(ip);
    socklen_t addrlen = sizeof(addr);
    //将ip地址绑定套接字
    int ret = bind(sfd,(struct sockaddr*)(&addr), addrlen);
    if( ret == -1){
        perror("bind error\n");   
        return -1;
    }
    //监听链接请求队列,accept()应答之前,允许在进入队列中等待的连接数目是10
    if(listen(sfd,10) == -1){
        perror("listen error\n");
        return -1;
    }
    return sfd;
}

int main(int argc, char *argv[]){
    const char *ip;
    unsigned short int port;
    //如果没有指定ip地址和端口号,则使用默认ip地址(本机)和端口号
    if(argc < 3){
        ip = "127.0.0.1";
        port = 533;
    }else{
        ip = argv[1];
        port = atoi(argv[2]);
    }

    int sfd = inet_init(ip, port);
    if(sfd == -1){
        perror("server socket init error\n");
        return -1;
    }
    printf("服务器已启动...\n");
    while(1){
        struct sockaddr_in caddr;
        socklen_t len = sizeof(caddr);
       
        int cfd = accept(sfd,(struct sockaddr*)(&caddr),&len);
        if(cfd == -1){
            perror("accept error\n");
            return -1;
        }
        receive(cfd);
        //创建一个线程处理此次连接
        pthread_t tid;
        int ret = pthread_create(&tid,NULL,pthread_run,(void*)(&client[count]));
        count++;
        if(ret != 0){
            printf("pthread_create: %s\n",strerror(ret));
            continue;
        }
        printf("有一个客户端成功连接:ip <%s> port [%hu]\n",inet_ntoa(caddr.sin_addr),ntohs(caddr.sin_port));
    }
    
    return 0;
}
//编译代码
//gcc server.c -o server -lpthread
客户端代码
#include<stdio.h>
#include<stdlib.h>
#include<time.h>
#include<string.h>
#include<arpa/inet.h>
#include<unistd.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<fcntl.h>
#include<dirent.h>
#include<sys/stat.h>
//打印历史信息
void print_history(char *id){
    char filename[128] = {};
    sprintf(filename,"%s/%s%s","/root/linux/communicate/record",id,".txt");
    int fd;
    fd = open(filename,O_RDONLY);
    if(fd == -1){
        perror("client open record error\n");
    }
    int len;
    char buf[1024];
    while((len = read(fd,buf,1024)) != 0){
        printf("%s",buf);
        memset(buf,'\0',1024);
    }
    printf("------------历史群聊信息-----------\n");
    close(fd);
}
int main(int argc, char *argv[]){
    const char *ip;
    unsigned short int port;
    //如果没指明,默认是ip = "127.0.0.1",port = 533
    if(argc < 3){
        ip = "127.0.0.1";
        port = 533;
    }else{
        ip = argv[1];
        port = atoi(argv[2]);
    }
    int sfd = socket(AF_INET,SOCK_STREAM,0);
    if(sfd == -1){
        perror("socket error\n");
        return -1;
    }
    struct sockaddr_in addr;
    addr.sin_family = AF_INET;
    addr.sin_port = htons(port);
    addr.sin_addr.s_addr = inet_addr(ip);
    socklen_t addrlen = sizeof(addr);

    int ret = connect(sfd,(const struct sockaddr*)(&addr),addrlen);
    if(ret == -1){
        perror("connect error\n");
        return -1;
    }
    char name[50];
    char id[8];
    printf("请输入群号:");
    fgets(id,8,stdin);
    printf("请输入你的群聊昵称:");
    fgets(name,49,stdin);
    char temp[128];
    strncat(temp,id,6);
    strncat(temp,name,strlen(name) - 1);
    send(sfd, temp, strlen(temp), 0);
    char cutid[7] = {};
    strncat(cutid,id,6);
    sleep(1);
    print_history(cutid);
    //创建两个进程,父进程负责收消息,子进程负责发消息
    pid_t pid = fork();
    if(pid == -1){
        perror("fork error\n");
    }else if(pid == 0){
        while(1){
            char buf[1024] = {};
            fgets(buf,1023,stdin);
            if(send(sfd,buf,strlen(buf) + 1,0) <= 0){
                break;
            }
            printf("\n");
        }
    }else{
        while(1){
            char buf[1024] = {};
            if(recv(sfd,buf,1024,0) <= 0){
                break;
            }
            time_t current_time;
            time(&current_time);
            printf("%s",ctime(&current_time));
            printf("%s\n\n",buf);
        }
    }
    close(sfd);
    return 0;
}
//编译代码
//gcc client.c -o client
程序演示

先看一下record目录,此时没有群聊文件

Linux-Socket实现模拟群聊(多人聊天室)

创建两个线程模拟两个客户端,并加入到群号为111111的群里

Linux-Socket实现模拟群聊(多人聊天室)

两个客户端正常通信。此时再查看record目录,发现多了一个111111.txt文件,证明此文件是用户加入群聊后自动创建的。
Linux-Socket实现模拟群聊(多人聊天室)

并且从上图可以看到每个客户端在进入群聊后都会去加载当前群的历史群聊信息

此时,再运行两个客户端,加入群号为222222的群中

Linux-Socket实现模拟群聊(多人聊天室)

可以发现,在222222群聊中发消息,消息只会出现在222222的群聊中,而111111中并没有,如此也证明用户在不同群聊中发送消息,消息只会被广播给与发送消息的用户在同一群聊中的在线用户这一功能实现了。文章来源地址https://www.toymoban.com/news/detail-497356.html

到了这里,关于Linux-Socket实现模拟群聊(多人聊天室)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 多人聊天室(带私聊功能)Linux网络编程基础

    在和同学一起努力下终于完成了期末作业哈哈哈哈 文章目录 目录 前言 一、需求分析 二、功能设计 1.服务器端: 2.客户端: 三、流程图: 编程流程图: 服务器流程图: 客户端流程图: 四、运行效果: 项目源码: 服务器源码 客户端源码: 总结: Linux网络编程是我们这学

    2024年02月09日
    浏览(59)
  • C语言实现--基于UDP的多人在线聊天室

    目录 实现功能 实现思想 实现代码(部分及详解) 服务端部分代码 客户端部分代码 实现效果 项目中出现的问题和解决方法 项目整体代码展示 代码优化思路 服务端代码 客户端代码 服务端可以同时连接多个客户端; 新的客户端连接服务端时,可以在服务端显示自己的名字并

    2024年02月04日
    浏览(63)
  • 基于Python guI的多人聊天室的设计与实现

    现在,即时聊天系统已成为 Internet 上的主要交流工具,并且涌现出大量的AP和平台。这些AP和平台都拥有更加完善的交换机制,使得人们可以更加便捷地进行沟通和交换信息。 广域网的聊天系统多重多样,知名的软件主要有 Facebook、腾讯 QQ 等。局域网聊天通信软件也有很多,

    2024年02月05日
    浏览(53)
  • 使用Linux系统IO多路复用中eopll创建基于TCP通信协议的多人聊天室

    一.1.搭建好TCP的通信模型 2.创建红黑树根节点 3.将套接字事件添加到红黑树中,使其被监听 4.当套接字事件发生,表示有客户端连接,将连接事件加入到红黑树节点当中 5.每当连接事件发生时,表示客户端发送信息到服务器 6.每当有事件准备就绪时,将对应的红黑树节点信息

    2024年02月13日
    浏览(44)
  • Linux socket聊天室

    目录 一、运行效果 1、分别编译客户端和服务端代码 2、运行 3、使用效果  二、代码 chat.h 服务端代码  客户端代码 gcc client.c -o C -lpthread gcc server.c -o S -lpthread 先运行服务器端,8888为端口号 ./S 8888  再运行客户端,这里创建两个客户端,端口号要和服务端的一样 ./C 127.0.0.1

    2024年01月22日
    浏览(46)
  • WebSocket+Vue实现简易多人聊天室 以及 对异步调用的理解

    代码仓库:github   HTTP是不支持长连接的,WebSocket是一种通信协议,提供了在单一、长连接上进行全双工通信的方式。它被设计用于在Web浏览器和Web服务器之间实现,但也可以用于任何需要实时通信的应用程序。使用ws作为协议标识符,如果需要加密则使用wss作为协议标识符

    2024年01月17日
    浏览(60)
  • Python web实战 | 使用 Flask 实现 Web Socket 聊天室

        今天我们学习如何使用 Python 实现 Web Socket,并实现一个实时聊天室的功能。本文的技术栈包括 Python、Flask、Socket.IO 和 HTML/CSS/JavaScript。   Web Socket 是一种在单个 TCP 连接上进行全双工通信的协议。它是 HTML5 中的一部分,并且可以在浏览器和服务器之间创建实时的交互式

    2024年02月14日
    浏览(54)
  • .NET编程——利用C#实现基于Socket类的聊天室(WinForm)

    在学习C#和MySQL实现注册登录和TCP协议的Socket通信后,本文将介绍如何利用Socket类中的异步通信函数来实现本地聊天室功能, Socket通信限制了客户端与客户端之间的通信,客户端只能接收来自服务器的消息而不能接收到客户端发送的消息,因此服务器最佳的选择是起到一个中

    2023年04月21日
    浏览(90)
  • 【你的第一个socket应用】Vue3+Node实现一个WebSocket即时通讯聊天室

    这篇文章主要是用WebSocket技术实现一个 即时通讯聊天室 ,首先先要了解为什么使用WebSocket而不是普通的HTTP协议,如果使用HTTP协议它是下面这种情况: 我发送一条消息,发送一个发送消息的请求;* 一直轮询接收别人发送的消息,不管有没有发送都要定时去调用接口。这里明

    2023年04月20日
    浏览(62)
  • Python多人聊天室

    链接:https://pan.baidu.com/s/1kzxiLTkvdxGAMgF3SQzcaw?pwd=vb9h 提取码:vb9h 利用socket方式编写一个多人聊天室程序,可以实现多个用户之间的群聊功能,私聊功能,显示当前用户功能 在聊天室程序中增加利用ftp实现文件的上传,下载,删除,查看当前文件功能 在聊天室程序中增加利用

    2024年02月03日
    浏览(59)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包