【Linux C】简易群聊 聊天室1.0
聊天室简介
本聊天室基于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相关推荐
- Java使用TCP实现群聊 聊天室(多线程和tcp的使用)
一:引言: 显示结果在控制台显示,未能实现图形界面的结合 二:上码 1.服务端 package com.wyj.talkhome; /** * 实现一个用户可以接发多条消息 * * */ import ...
- SpringBoot与webSocket实现在线聊天室——实现私聊+群聊+聊天记录保存
SpringBoot与webSocket实现在线聊天室--实现私聊+群聊+聊天记录保存 引用参考:原文章地址:https://blog.csdn.net/qq_41463655/article/det ...
- Java Socket实现简易多人聊天室传输聊天内容或文件
Java Socket实现简易多人聊天室传输聊天内容或文件 Java小练手项目:用Java Socket实现多人聊天室,聊天室功能包括传输聊天内容或者文件.相比于其它的聊天室,增加了传输文件的功能供参 ...
- 利用多线程实现linux下C语言的聊天室程序:
转载:http://www.360doc.com/content/16/0421/11/478627_552531090.shtml 利用多线程实现linux下C语言的聊天室程序: 客户端代码: th ...
- python实现简易聊天需要登录博客园zip下载_Python基于Socket实现简易多人聊天室的示例代码...
前言 套接字(Sockets)是双向通信信道的端点. 套接字可以在一个进程内,在同一机器上的进程之间,或者在不同主机的进程之间进行通信,主机可以是任何一台有连接互联网的机器. 套接字可以通过多种不同的 ...
- Linux环境下——C语言聊天室项目
由于使用了多线程操作,客户端进入程序后请先随便注册一次用户后再进行使用. 本程序默认第一个用户即ID为1的用户为超级管理员. 由于线程阻塞,最后的踢人操作有阻塞,需要在被踢出在线链表后手动下线. 看了 ...
- 基于Linux下的即时通讯聊天室项目(全代码 有注释 可直接运行)
基于Linux下的即时通讯聊天室项目 一.序言 二.具体功能 三.系统客户要求 四.具体代码 1.服务器代码 2.客户端代码 一.序言 最近在写一个基于Linux下的聊天工具 它适合于局域网内所有人进 ...
- JAVA网络编程NIO实现简易多人聊天室
BIO模型 BIO即blocking IO,顾名思义是一种阻塞模型.当没有客户端连接时,服务端会一直阻塞,当有客户端新建连接时,服务端会新开一个线程去响应(不用多线程的话服务端同一时刻最多只能接收一个 ...
- 网络编程套接字(上篇)UDP实现简易多人聊天室
目录 背景知识 主机间通信本质 socket 端口号特点: 为什么不用进程pid? 网络字节序 socket编程接口API sockaddr结构 编辑 简单UDP网络程序 了解UDP协议 简易多人聊 ...
最新文章
- 机器学习与深度学习常见面试问题与答案
- Python中range和xrange的区别
- java map输出中括号,从地图检索数据时获取双方括号
- python精要(71)-VMDK操作(1)
- Java开发技巧:Java如何编译运行?
- 三个变量中怎么找出中间值_一文理解神经网络中的偏差和方差
- 自检代码中trustmanager漏洞_2020-11微软漏洞通告
- 前端学习(557):css与百分比单位
- python网络通信框架_【python:flask-SocketIO】网络通信框架简单了解
- 眼控科技 实习算法工程师面试
- mysql infobright 缺点_infobright、mongodb优劣以及适用范围
- 使用百度编辑器--ueditor,后台接收提交编辑的内容,HTML不见了, 赋值不了,赋值之后,html暴露出来了??...
- C++面试题,平时面试不可缺少的!
- 一种一致性HASH算法的实现方法,附核心代码
- mysql各版本jar包下载
- 阿克曼函数求解(递归和非递归)
- 直播带货app源码,实现直播的秒开和优化
- Linux查看软件安装和系统信息操作
- python环境配置及参数_搭建python机器学习环境以及一个机器学习例子
- Android R(11)为自定义HIDL接口添加DMFCM(六)