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()应答之前,允许在进入队列中等待的连接数目是10if(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 = 533if(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 100typedef 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()应答之前,允许在进入队列中等待的连接数目是10if(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 = 533if(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目录,此时没有群聊文件

在这里插入图片描述

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

在这里插入图片描述

两个客户端正常通信。此时再查看record目录,发现多了一个111111.txt文件,证明此文件是用户加入群聊后自动创建的。
在这里插入图片描述

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

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

在这里插入图片描述

可以发现,在222222群聊中发消息,消息只会出现在222222的群聊中,而111111中并没有,如此也证明用户在不同群聊中发送消息,消息只会被广播给与发送消息的用户在同一群聊中的在线用户这一功能实现了。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/19696.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

不要拿 ChatGPT 干这 6 件事

虽然 ChatGPT 是一个强大的 AI 工具&#xff0c;能够生成连贯和相关性极强的回复&#xff0c;但它也有其局限性。它不是敏感信息的安全渠道&#xff0c;也不是法律或医疗咨询的可靠来源&#xff0c;不能替代人类的决策或专业的心理健康支持&#xff0c;也不是事实信息的权威来源…

宽客挑战赛: 量化投资基础知识12题

跨界知识聚会系列文章&#xff0c;“知识是用来分享和传承的”&#xff0c;各种会议、论坛、沙龙都是分享知识的绝佳场所。我也有幸作为演讲嘉宾参加了一些国内的大型会议&#xff0c;向大家展示我所做的一些成果。从听众到演讲感觉是不一样的&#xff0c;把知识分享出来&#…

为了做宽客选择读一个PhD值得吗

转 为了做宽客选择读一个PhD值得吗&#xff1f; 编者按语&#xff1a;接触不少想往quant方向走的童鞋,对于量化交易是很感兴趣也觉得未来大有可为&#xff0c;但是quant由于其行业性质其门槛具有一定的专业要求。现在很多投行、银行、公募、私募等招聘职位基本都要求硕士学历&…

“宽客鼻祖”的诞生——爱德华·索普

本文摘自《算法交易员&#xff1a;会赚钱的人工智能》 1961 年某个炎热的夜晚&#xff0c;美国拉斯维加斯赌场里有个男人正在玩“21点”&#xff0c;他的身后围着数十名看客。虽然这个男人已经淡定地玩了整整 6 小时&#xff0c;但从他戴着的大黑框眼镜和淋漓的汗水中还是可以…

克里夫·阿斯内斯:量化天才的宽客人生

克里夫阿斯内斯 &#xff08;Clifford Asness&#xff09;&#xff0c;1966年10月1日出生在美国纽约皇后区的一个普通中产阶级家庭。父亲是一名律师&#xff0c;母亲独立经营一家医疗教育公司。在宾夕法尼亚大学取得商学院和工程与应用科学院的两个学士学位之后&#xff0c;他进…

宽客人物事件图谱

看完了《宽客》这本书&#xff0c;描述的是20世纪美国华尔街对冲基金、股票市场、证券市场从早期的投资者靠直觉交易进化为数学家、物理学家靠数学模型进行交易的发展历程&#xff0c;这些进入金融界的数学家、物理学家依靠大数据分析、自动化交易找到市场中稍纵即逝的交易机会…

量化交易创干合送给每一位爱习宽客quat

原 量化交易原创干货合集&#xff0c;送给每一位爱学习的宽客quant 序号标题传送链接1双均线策略(期货) 量化策略源码https://www.myquant.cn/docs/python_strategyies/1532alpha对冲(股票期货) 量化策略源码https://www.myquant.cn/docs/python_strategyies/1013集合竞价选股…

量化交易创干货合送每位爱习宽客quan

原 量化交易原创干货合集,送给每一位爱学习的宽客quant 序号标题传送链接1双均线策略(期货) 量化策略源码https://www.myquant.cn/docs/python_strategyies/1532alpha对冲(股票+期货) 量化策略源码https://www.myquant.cn/docs/python_strategyies/1013集合竞价选股(股票)

惊恐 !ChatGPT通过谷歌L3入职面试,拿到18万美元offer,人类码农危?

上一篇&#xff1a;单男福利&#xff1f;程序猿用ChatGPT创造的虚拟老婆&#xff0c;被真女友强制「安乐死」 来源&#xff1a;新智元 【导读】ChatGPT已经通过谷歌面试&#xff0c;拿下offer了。看来&#xff0c;替代全部码农它还做不到&#xff0c;但替代一部分&#xff0c;已…

18.3 万美元offer到手!ChatGPT 通过谷歌 L3 面试:留给谷歌的时间不多了

微软投资、OpenAI 发布的 ChatGPT 去年底爆红后&#xff0c;“传统搜索引擎要亡”的言论甚嚣尘上&#xff0c;搜索巨头谷歌也拉响了“红色代码”警报&#xff0c;内部正在进行各种开发测试&#xff0c;应对这个看起来可能是几十年来谷歌价值 1490 亿美元搜索业务的第一个显著威…

ChatGPT助力校招----面试问题分享(十一)

1 ChatGPT每日一题&#xff1a;PCB布线&#xff0c;高速信号线走直角的后果 问题&#xff1a;PCB布线&#xff0c;高速信号线走直角的后果 ChatGPT&#xff1a;对于高速信号线来说&#xff0c;最好避免使用直角布线。直角布线会引入反射和信号损耗&#xff0c;从而导致信号完…

chatgpt赋能python:Python教程:如何打开和读取文件

Python教程&#xff1a;如何打开和读取文件 作为一名具有10年Python编程经验的工程师&#xff0c;我经常会发现自己需要处理和操作文件。因此&#xff0c;在本文中&#xff0c;我将向你展示如何使用Python在程序中打开和读取文件。 1. 文件路径 在打开文件之前&#xff0c;我…

ChatGPT+Midjourney融合

&#x1f31f;亲爱的小伙伴们&#xff01;今天我要向大家强烈推荐一种绝妙的工具&#xff0c;它将会给你的生活带来巨大的改变和便利。就是——ChatGPTMidjourney融合&#xff01;&#x1f525;&#x1f525;&#x1f525; ✨让我们来探索一下这个神奇的组合带来的惊喜吧&…

【ChatGPT】《ChatGPT “最强打工人” 7x24 随时待命》- 知识点目录

《ChatGPT “最强打工人” 7x24 随时待命》 编程 阅读篇 &#x1f4bb; 编程&#xff1a; &#x1f5a5;️ Bash 脚本生成器&#x1f468;‍&#x1f4bb;‍ Web Dev Q&A&#x1f468;‍&#x1f4bb; 代码逐行解释器 &#x1f4da; 阅读&#xff1a; &#x1f520; …

ChatGPT提问,BLIP-2回答:图像描述自动提问

ChatGPT Asks, BLIP-2 Answers: Automatic Questioning Towards Enriched Visual Descriptions (ChatGPT提问&#xff0c;BLIP-2回答&#xff1a;图像描述自动提问) https://arxiv.org/pdf/2303.06594.pdf https://github.com/Vision-CAIR/ChatCaptioner ChatCaptioner是一种…

微软发布基于ChatGPT打造的 Bing 搜索服务

微软推出了全新的Bing搜索体验&#xff0c;它是基于ChatGPT技术的。现在&#xff0c;所有人都可以使用桌面版的"Bing有限预览"。只需访问Bing.com&#xff0c;你就可能会看到全新的搜索界面&#xff0c;微软还提供了一些问题示例供参考。点击"试试吧"&…

前端已死? 2023 年前端十大 Web 趋势

点击“开发者技术前线”&#xff0c;选择“星标” 让一部分开发者看到未来 作者 | Robin Wiruch 来自|infoQ 译者 | 核子可乐 策划 | 丁晓昀 虽然就个人观点&#xff0c;我觉得 Web 开发的前景已经好几年没什么进展&#xff08;2016 年至 2021 年&#xff09;&#xff0c;但在…

两种开源聊天机器人的性能测试(一)——ChatterBot

因为最近在学习自然语言处理的相关知识&#xff0c;QQ小冰这个东西最近又很热&#xff0c;所以就试着玩了下两个开源聊天机器人&#xff0c;在这里分享一点小经验&#xff0c;希望对有共同兴趣的人能起到那么一点作用。 我主要测试了两个聊天机器人&#xff0c;一个是ChatterBo…

聊聊chatbot那些事

1. 生活中的chatbot ​ 现在社会&#xff0c;随着AI的迅猛发展&#xff0c;各种新技术层出不穷&#xff0c;大大改变了我们的生活&#xff0c;其中&#xff0c;有很多技术已经走入了我们的日常生活&#xff0c;比如CV领域的人脸识别 &#xff0c;NLP 领域的智能助手等。本次&a…

Chatbot(五)

一、走进聊天机器人 目标 知道常见的bot的分类知道企业中常见的流程和方法 1.1 目前企业中的常见的聊天机器人 QA BOT (问答机器人) : 回答问题 1.代表:智能名服 2.比如: 提问和回答TASK BOT(任务机器人): 助人们做事情 1.代表: siri 2.比如:设五明天早上9点的闹钟CHAT BOT…