聊天室简介

​ 本聊天室基于LinuxC进行编写,使用到的有tcp协议、多线程、互斥量、条件变量等知识,实现一个最大可接入20个用户的群聊聊天室;服务端运行后,用户运行用户端接入,输入用户名即可接入;用新用户接入或者下线时,均会群发消息提醒其他在线用户;运行效果如下所示:

由于时间仓促,本聊天室为1.0版本,仍有许多地方需要优化,如并发机制、接入客户端数量、客户端下线后编号的复用(可考虑用链表代替数组)、代码简洁性等问题,后续会再优化更新;

聊天室源码

服务端

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <pthread.h>
#include <signal.h>#define BUFSIZE 512
#define ClientMax 20int s_fd;
int c_fd[ClientMax] = {0}; //最大可接收20个客户端的套接字pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t  w_msg = PTHREAD_COND_INITIALIZER;char sendBuf[BUFSIZE] = {0};//构造一个结构体用于存储客户端用户的名字及套接字编号
struct Userinfo
{int num;char username[20];
};//用于发送信息的线程函数
void *sendmsg_func(void *p)
{int i;printf("启动信息发送线程:\n");while(1){pthread_mutex_lock(&lock);//利用条件变量,当收到任一客户端的信息后,就群发该信息到全体客户端中pthread_cond_wait(&w_msg,&lock);//给所有在线的客户端发送信息for(i = 0;c_fd[i] != 0 && i < ClientMax;i++){if (c_fd[i] == -1){continue;    //如果是已退出的客户端,则不发送信息}else{if(write(c_fd[i],sendBuf,BUFSIZE) < 0 ){perror("write");exit(-1);}}}pthread_mutex_unlock(&lock);   }
}//用于接收客户端信息的函数
void *recv_func(void *p)
{//将传递进来的用户姓名、socket编号存到局部变量中,方便使用int tmp_cnt = ((struct Userinfo *)p)->num;char tmp_username[20] = {0};strcpy(tmp_username,((struct Userinfo *)p)->username);char readBuf[BUFSIZE] = {0};int n_read = 0;printf("启动%d号线程用于接收信息\n",tmp_cnt);//通知聊天室内所有用户有新用户上线pthread_mutex_lock(&lock);memset(sendBuf,0,BUFSIZE);sprintf(sendBuf,"%s上线了\n",tmp_username);pthread_cond_signal(&w_msg);pthread_mutex_unlock(&lock);//不断接收对应套接字用户发来的信息    while(1){memset(readBuf,0,BUFSIZE);n_read = read(c_fd[tmp_cnt],readBuf,sizeof(readBuf));if(n_read == -1){perror("read");exit(1);}else if(n_read == 0){//用户下线了,群发信息告知其他用户pthread_mutex_lock(&lock);memset(sendBuf,0,BUFSIZE);sprintf(sendBuf,"%s下线了\n",tmp_username);pthread_cond_signal(&w_msg);pthread_mutex_unlock(&lock);c_fd[tmp_cnt] = -1;        //如果对应的客户端退出,则令对应的c_fd的值为-1,表示掉线pthread_exit(NULL);   //如果对方关闭,结束线程}else {printf("#%s\n",readBuf);   //将用户发送的信息打印在服务端,若有数据库,这里可以将聊天记录存在数据库}//给群发信息的线程发送信号,群发用户发送进来的消息pthread_mutex_lock(&lock);memset(sendBuf,0,BUFSIZE);strcpy(sendBuf,readBuf);pthread_cond_signal(&w_msg);pthread_mutex_unlock(&lock);}
}//信号中断函数,若本程序被外来信号打断,可以及时清理现场,释放资源
void int_handler(int s)
{int i;pthread_mutex_destroy(&lock);pthread_cond_destroy(&w_msg);for(i=0;c_fd[i] != 0 && i < ClientMax;i++){if(c_fd[i] == -1)continue;elseclose(c_fd[i]);}close(s_fd);exit(0);
}void main()
{struct sockaddr_in s_addr;struct sockaddr_in c_addr[ClientMax];pthread_t tid[ClientMax] = {0};int addr_len = sizeof(struct sockaddr_in);int err; int c_cnt = 0;struct Userinfo userinfo[ClientMax] = {0};  //创建sockets_fd = socket(AF_INET,SOCK_STREAM,0);    if (s_fd < 0){perror("socket");exit(1);}//取消关闭socket后的wait等待int val =1;if (setsockopt(s_fd,SOL_SOCKET,SO_REUSEADDR,&val,sizeof(val)) < 0){perror("setsockopt");exit(1);}//bind()s_addr.sin_family = AF_INET;s_addr.sin_port = htons(8899);     //host to net shortinet_aton("0.0.0.0",&s_addr.sin_addr); //sin_addr即为in_addr格式if(bind(s_fd,(struct sockaddr*)&s_addr,sizeof(s_addr)) == -1){perror("bind");exit(-1);} //listen()if(listen(s_fd,200) < 0)  //暴露s_fd指向的socket给客户端连接,最多接受200个连接{perror("listen");exit(-1);}//创建一个线程用来发送消息给所有客户端pthread_t send_tid;err = pthread_create(&send_tid,NULL,sendmsg_func,NULL);if(err){fprintf(stderr,"Create pthread fail:%s\n",strerror(err));exit(1);}sleep(1); //让发送信息线程有足够时间加锁signal(SIGINT,int_handler); //不断等待是否有新客户端接入while(1){userinfo[c_cnt].num = c_cnt;   //给新接入的客户端套接字分配一个编号//等待新客户端接入c_fd[c_cnt] = accept(s_fd,(struct sockaddr*)(c_addr+c_cnt),&addr_len);   //接收客户端的连接if (c_fd[c_cnt] < 0){perror("accept()");exit(-1);}printf("get connect %s\n",inet_ntoa(c_addr[c_cnt].sin_addr));//将客户端发送来的姓名存入userinfo结构体中err = read(c_fd[c_cnt],userinfo[c_cnt].username,sizeof(userinfo[c_cnt].username));if(err == -1){perror("read");exit(1);}//创建一个新线程用来发送信息err = pthread_create((tid+c_cnt),NULL,recv_func,userinfo+c_cnt);if (err){fprintf(stderr,"Create pthread fail:%s\n",strerror(err));exit(1);}  c_cnt++;  //客户端计数器+1}//close()//回收资源close(s_fd);pthread_mutex_destroy(&lock);pthread_cond_destroy(&w_msg);exit(0);
}

客户端

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <pthread.h>
#include <signal.h>
#include <time.h>
#define BUFSIZE 512int c_fd;
int n_read;
char readBuf[BUFSIZE] = {0};//用来发送信息的线程函数
void *rcvmsg_func(void *p)
{while(1){memset(readBuf,0,BUFSIZE);    //清空readBuf//读取来自服务端的信息n_read = read(c_fd,readBuf,sizeof(readBuf));if(n_read == -1){perror("read");exit(1);}else if(n_read == 0) {pthread_exit(NULL);    //如果对方或者自己关闭套接字,则退出 }else{printf("#%s\n",readBuf); }}
}//该函数用于组合待发送的信息
void msg_merge(char *sendBuf,char *username,char *time,char *str)
{char title[20] = {"骚气聊天室 "};char say[10] = {"说:"};strcpy(sendBuf,title);strcat(sendBuf,time);strcat(sendBuf,username);    strcat(sendBuf,say);strcat(sendBuf,str);
}void main()
{struct sockaddr_in c_addr;int addr_len = sizeof(struct sockaddr_in);char str[BUFSIZE] = {0};     //存放输入的信息char sendBuf[BUFSIZE] = {0};int err;char username[20] = {0};struct tm *timeptr;       //关于日期的一个结构体指针time_t timeval;          //关于时间的一个结构体变量  char tm[50];//创建socketc_fd = socket(AF_INET,SOCK_STREAM,0);    if (c_fd < 0){perror("socket");exit(1);}c_addr.sin_family = AF_INET;c_addr.sin_port  = htons(8899);inet_aton("127.0.0.1",&c_addr.sin_addr);//输入用户姓名printf("欢迎来到骚气聊天室,输入你的英文名:\n");scanf("%s",username);getchar();       //吸收掉scanf的换行符//connect()if((connect(c_fd,(struct sockaddr*)&c_addr,sizeof(c_addr))) == -1){perror("connect");exit(-1);} //创建一个线程用于接收信息pthread_t tid;err = pthread_create(&tid,NULL,rcvmsg_func,NULL);if (err){fprintf(stderr,"Create pthread fail:%s\n",strerror(err));exit(1);}//send you name to serverif((err = write(c_fd,username,sizeof(username))) < 0 ){perror("write");exit(1);}printf("连接成功,退出聊天室请输入“quit”\n");printf("聊天可直接输入信息:\n");//while循环用于发送信息while(1){//清空buf中的信息                                            memset(str,0,BUFSIZE);                  memset(sendBuf,0,BUFSIZE);           //获取当前时间(void)time(&timeval);strcpy(tm,ctime(&timeval));            fgets(str,BUFSIZE,stdin);   //接收用户输入的信息//判断是否为退出命令if(strcmp(str,"quit\n") == 0){printf("退出聊天室");break;    }msg_merge(sendBuf,username,tm,str);     //将姓名、时间、发送的信息组合到sendBuf中                      //发送信息到socket中                                  if((err = write(c_fd,sendBuf,BUFSIZE)) < 0 )    {perror("write");exit(1);}else if (err == 0){break;}        }//close()close(c_fd);exit(0);
}

【Linux C】简易群聊 聊天室1.0相关推荐

  1. Java使用TCP实现群聊 聊天室(多线程和tcp的使用)

    一:引言: 显示结果在控制台显示,未能实现图形界面的结合 二:上码 1.服务端 package com.wyj.talkhome; /** * 实现一个用户可以接发多条消息 * * */ import ...

  2. SpringBoot与webSocket实现在线聊天室——实现私聊+群聊+聊天记录保存

    SpringBoot与webSocket实现在线聊天室--实现私聊+群聊+聊天记录保存 引用参考:原文章地址:https://blog.csdn.net/qq_41463655/article/det ...

  3. Java Socket实现简易多人聊天室传输聊天内容或文件

    Java Socket实现简易多人聊天室传输聊天内容或文件 Java小练手项目:用Java Socket实现多人聊天室,聊天室功能包括传输聊天内容或者文件.相比于其它的聊天室,增加了传输文件的功能供参 ...

  4. 利用多线程实现linux下C语言的聊天室程序:

    转载:http://www.360doc.com/content/16/0421/11/478627_552531090.shtml 利用多线程实现linux下C语言的聊天室程序: 客户端代码: th ...

  5. python实现简易聊天需要登录博客园zip下载_Python基于Socket实现简易多人聊天室的示例代码...

    前言 套接字(Sockets)是双向通信信道的端点. 套接字可以在一个进程内,在同一机器上的进程之间,或者在不同主机的进程之间进行通信,主机可以是任何一台有连接互联网的机器. 套接字可以通过多种不同的 ...

  6. Linux环境下——C语言聊天室项目

    由于使用了多线程操作,客户端进入程序后请先随便注册一次用户后再进行使用. 本程序默认第一个用户即ID为1的用户为超级管理员. 由于线程阻塞,最后的踢人操作有阻塞,需要在被踢出在线链表后手动下线. 看了 ...

  7. 基于Linux下的即时通讯聊天室项目(全代码 有注释 可直接运行)

    基于Linux下的即时通讯聊天室项目 一.序言 二.具体功能 三.系统客户要求 四.具体代码 1.服务器代码 2.客户端代码 一.序言 最近在写一个基于Linux下的聊天工具 它适合于局域网内所有人进 ...

  8. JAVA网络编程NIO实现简易多人聊天室

    BIO模型 BIO即blocking IO,顾名思义是一种阻塞模型.当没有客户端连接时,服务端会一直阻塞,当有客户端新建连接时,服务端会新开一个线程去响应(不用多线程的话服务端同一时刻最多只能接收一个 ...

  9. 网络编程套接字(上篇)UDP实现简易多人聊天室

    目录 背景知识 主机间通信本质 socket 端口号特点: 为什么不用进程pid? 网络字节序 socket编程接口API sockaddr结构 ​编辑 简单UDP网络程序 了解UDP协议 简易多人聊 ...

最新文章

  1. 机器学习与深度学习常见面试问题与答案
  2. Python中range和xrange的区别
  3. java map输出中括号,从地图检索数据时获取双方括号
  4. python精要(71)-VMDK操作(1)
  5. Java开发技巧:Java如何编译运行?
  6. 三个变量中怎么找出中间值_一文理解神经网络中的偏差和方差
  7. 自检代码中trustmanager漏洞_2020-11微软漏洞通告
  8. 前端学习(557):css与百分比单位
  9. python网络通信框架_【python:flask-SocketIO】网络通信框架简单了解
  10. 眼控科技 实习算法工程师面试
  11. mysql infobright 缺点_infobright、mongodb优劣以及适用范围
  12. 使用百度编辑器--ueditor,后台接收提交编辑的内容,HTML不见了, 赋值不了,赋值之后,html暴露出来了??...
  13. C++面试题,平时面试不可缺少的!
  14. 一种一致性HASH算法的实现方法,附核心代码
  15. mysql各版本jar包下载
  16. 阿克曼函数求解(递归和非递归)
  17. 直播带货app源码,实现直播的秒开和优化
  18. Linux查看软件安装和系统信息操作
  19. python环境配置及参数_搭建python机器学习环境以及一个机器学习例子
  20. Android R(11)为自定义HIDL接口添加DMFCM(六)

热门文章

  1. PMO如何建立项目管理文化?
  2. Internet,Intranet和Extrants
  3. MVP模式理解与使用
  4. 逻辑斯蒂回归分类算法
  5. A complete log of this run can be found in no such file or directory 前端项目一直启动不了
  6. 回归分析:逻辑斯蒂回归模型,非线性分类任务案例
  7. 计算机如何访问玩客云,肿么通过电脑访问玩客云
  8. 【图像隐藏】基于LDPC编码译码改进DCT变换算法实现水印嵌入提取matlab源码
  9. Java判断多个时间段是否重叠
  10. Note7爆炸答案揭晓,三星重树市场新形象