确认需求

实现的功能包含
1、私聊
2、群聊
3、打印在线信息
4、退出系统

需要注意

1、service中不能有重复名
2、发送的消息,如果对方不存在服务器需要给客户端返回错误信息
3、其他需要实现的功能,文件传输,聊天记录保存可根据自己完善。
4、代码只供参考阅读,一些可能存在的bug需要自己去修改

特此申明

作者自己本身也会进行完善代码和功能,对代码有问题,有更好想法的朋友欢迎评论区留言!!!希望共同学习,一起进步!

源码

头文件(也没封装啥,只是偷个懒,养成好习惯)
epoll_service_test.h
#ifndef NETWORK_EPOLL_SERVICE_TEST_H
#define NETWORK_EPOLL_SERVICE_TEST_H
#include <unistd.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <pthread.h>
#include <sys/epoll.h>
#include <string.h>
#include <ctype.h>
#include <stdlib.h>
#endif //NETWORK_EPOLL_SERVICE_TEST_H
服务器
epoll_service_test.c
#include "epoll_service_test.h"
#define MAX 1024
//定义消息结构体
typedef struct message{char type;//消息类型char name[30];//客户端姓名char dst_name[30];//目的客户姓名char text[MAX];//消息内容
}MSG;
MSG msg;//定义全局消息变量进行消息的接收和发送
//定义存储客户端信息链表
typedef struct  client_list{char name[30];//客户端的姓名int cfd;//对应的在树上的套接字struct client_list *next;
}linklist,*linkList ;
//定义头节点,设置为全局变量
linkList H;//创建头节点函数
linkList head_init(){linkList h=(linkList)malloc(sizeof(linklist));bzero(h,sizeof(linklist));h->next=NULL;return h;
}//创建结点插入函数
void insert_client(linkList H,MSG msg,int cfd){linkList p=(linkList)malloc(sizeof(linklist));bzero(p,sizeof(linklist));strncpy(p->name,msg.name,sizeof(msg.name));p->cfd=cfd;p->next=H->next;H->next=p;
}int listenfd_init(int port){int lfd;struct sockaddr_in service;bzero(&service,sizeof(service));//清空service.sin_family=AF_INET;//初始化service.sin_port=htons(port);service.sin_addr.s_addr=htonl(INADDR_ANY);if ((lfd=socket(AF_INET,SOCK_STREAM,0))==-1){//确定TCP连接perror("[socket]");return -1;}int opt=1;//使用端口复用if (setsockopt(lfd,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(opt))==-1){perror("[setsockopt]");return -1;}//绑定地址结构if (bind(lfd,(struct sockaddr *)&service,sizeof(service))==-1){perror("[bind]");return -1;}//设置最大监听数if (listen(lfd,128)==-1){perror("[listen]");return -1;}return lfd;
}//客户端连接函数
int connectfd_init(int lfd){int cfd;struct sockaddr_in client;bzero(&client,sizeof(client));socklen_t len=sizeof(client);if ((cfd=accept(lfd,(struct sockaddr *)&client,&len))==-1){perror("[accpet]");return -1;}printf("Have a brother join to us!\n");return cfd;
}int name_exist(linkList H ,MSG msg,int cfd){linkList s=H->next;while (s){//如果服务器中已经有这个人了,就不存,并给该客户端发送消息if (strncmp(msg.name,s->name,sizeof(msg.name))==0){return 0;}s=s->next;}return 1;
}int dstname_exist(linkList H,MSG msg,int cfd){linkList t=H->next;while (t){if (!strncmp(msg.dst_name,t->name,sizeof(msg.dst_name))){//表示有这个人return 1;}t=t->next;}return 0;//表示没有这个人
}int message_handler(int cfd){bzero(&msg,sizeof(msg));//清空int ret;ret=recv(cfd,&msg,sizeof(msg),0);if (ret==-1){perror("[recv]");return -1;}if (ret==0){//客户端断开连接printf("Has a brother disconnect!\n");return 0;}//对消息进行处理if (msg.type=='1'){//注册消息if (name_exist(H,msg,cfd)==0){bzero(msg.text,sizeof(msg.text));sprintf(msg.text,"The user %s has been registered!\n",msg.name);if (send(cfd,&msg,sizeof(msg),0)==-1){perror("[send_4]");return -1;}} else{insert_client(H,msg,cfd);//将这个客户添加到我们的链表中linkList q=H->next;sprintf(msg.text,"welcome to join us %s!\n",msg.name);while (q){if (send(q->cfd,&msg,sizeof(msg),0)==-1){perror("[send_1]");}q=q->next;}}}if (msg.type=='2'){//私聊消息,进行转发//进行检查该用户是否存在if (dstname_exist(H,msg,cfd)){//有这个人,将消息发送给这个人linkList p=H->next;//指向头节点while (p){if (!strncmp(p->name,msg.dst_name,sizeof(msg.dst_name))){if (send(p->cfd,&msg,sizeof(msg),0)==-1){perror("[send_2]");break;}printf("OK\n");}p=p->next;}} else{bzero(msg.text,sizeof(msg.text));sprintf(msg.text,"%s has been offline!\n",msg.dst_name);if (send(cfd,&msg,sizeof(msg),0)==-1){perror("[send_5]");return -1;}}}if (msg.type=='3'){//群聊,发送给每一个人linkList t=H->next;//创建一个临时结点用于遍历while (t){if (send(t->cfd,&msg,sizeof(msg),0)==-1){perror("[send_3]");break;}t=t->next;}}if (msg.type=='4'){//打印在线人员信息linkList u=H->next;while (u){bzero(msg.text,sizeof(msg.text));sprintf(msg.text,"%s is on line!\n",u->name);if (send(cfd,&msg,sizeof(msg),0)==-1){perror("[send_6]");return -1;}u=u->next;}}
}void epoll_init(int lfd){int ret,ret_r;//用于接收epoll_wait的返回值,表示事件的个数,第二个表示接收消息的返回值int cfd;//表示用于客户端连接的套接字struct epoll_event ev;struct epoll_event events[20];//设置监听事件,和用于存储发生事件的表ev.data.fd=lfd;//初始化ev.events=EPOLLIN;//读事件int epfd;//设置存放套接字的红黑树if ((epfd=epoll_create(20))==-1){//表示监听的最大的数量perror("[epoll_create]");return;}if (epoll_ctl(epfd,EPOLL_CTL_ADD,lfd,&ev)!=0){//将lfd添加上红黑树上,指定监听的事件为读事件perror("[epoll_ctl]");return;}printf("Listening.......\n");while (1){//进行事件的监听ret=epoll_wait(epfd,events,20,10);//timeout表示超时时长if (ret==-1){perror("[epoll_wait]");break;}for (int i = 0; i <ret ; ++i) {//循环处理事件if (!events[i].events & EPOLLIN){//如果不是读事件continue;}if (events[i].data.fd==lfd){//如果是读事件,且需要读的是lfd,表示的是需要进行连接cfd=connectfd_init(lfd);if (cfd==-1){return;}ev.data.fd=cfd;//初始化事件,将cfd对应的监听事件添加到监听对象中ev.events=EPOLLIN;if (epoll_ctl(epfd,EPOLL_CTL_ADD,cfd,&ev)==-1){//将结点cfd添加上树上perror("[epoll_ctl2]");return;}} else{//其他套接字的读事件ret_r=message_handler(events[i].data.fd);if (ret_r==-1){break;//出错直接跳出}if (ret_r==0){//客户端退出if (epoll_ctl(epfd,EPOLL_CTL_DEL,events[i].data.fd,NULL)==-1){//删除这个结点,从树上perror("[epoll_ctl3]");continue;//进行处理下一个事件}}}}}
}int main(int argc ,char *argv[]){int lfd;//进行套接字的初始化lfd=listenfd_init(8888);if (lfd==-1){return -1;}H=head_init();//进行epoll的连接初始化epoll_init(lfd);close(lfd);//关闭套接字return 0;
}
客户端 (耐住性子,按着思路去读,不清楚的可以看注释,你也可以copy下来在编译器打开,将功能收起来方便阅读)
epoll_client_test.c
#include "epoll_service_test.h"
#define MAX 1024
typedef struct message{char type;//消息类型char name[30];//自己的名字char dst_name[30];//想要聊天的对象char text[MAX];//消息内容
}MSG;
MSG msg1;//用于消息的发送
MSG msg;//全局变量,用于子线程接收消息
int i;//定义全局变量,进行功能选择
void message_handler(int *cfd){int ret;while (1){bzero(&msg,sizeof(msg));ret=recv(*cfd,&msg,sizeof(msg),0);if (ret==-1){perror("[recv]");break;}if (ret==0){printf("Service has disconnect!\n");break;}if (msg.type=='1'){printf("Service:%s\n",msg.text);}if (msg.type=='2' || msg.type=='3'){//如果是群发消息,或者是私聊消息就进行接收printf("%s : %s\n",msg.name,msg.text);}if (msg.type=='4'){printf("%s",msg.text);}}
}int connectfd_init(int port){int cfd;struct sockaddr_in service;bzero(&service,sizeof(service));service.sin_family=AF_INET;service.sin_port=htons(port);inet_pton(AF_INET,"192.168.200.134",&service.sin_addr.s_addr);if ((cfd=socket(AF_INET,SOCK_STREAM,0))==-1){perror("[socket]");return -1;}if (connect(cfd,(struct sockaddr *)&service,sizeof(service))==-1){perror("[connect]");return -1;}return cfd;
}int main(int argc ,char *argv[]){printf("--------------------------------\n");printf("Welcome to jacky's chat room!\n");printf("\n");printf("Please register first!\n");printf("\n");printf("\n");int cfd;//连接套接字pthread_t tid;int ret;//用于检查返回值,i用于进行功能的选择cfd=connectfd_init(8888);if (cfd==-1){return -1;}//创建线程用于接收消息pthread_create(&tid,NULL,(void *)message_handler,(void *)&cfd);pthread_detach(tid);//进行注册bzero(&msg1,sizeof(msg1));printf("What's your name?\n");fgets(msg1.name,sizeof(msg1.name),stdin);msg1.name[strlen(msg1.name)-1]=0;msg1.type='1';if (send(cfd,&msg1,sizeof(msg1),0)==-1){perror("[send]");return -1;}while (1){//对功能进行处理,子线程进行接收消息。//进行功能的选择,私聊还是群聊printf("\n");printf("1.私聊\n");printf("2.群聊\n");printf("3.打印在线信息\n");printf("4.退出系统\n");printf("\n");printf("Please enter your choice:");scanf("%d",&i);switch (i) {case 1:getchar();//用于吸收scanf的\nbzero(msg1.dst_name,sizeof(msg1.dst_name));//清空对象的名字printf("Who you want to chat?\n");fgets(msg1.dst_name,sizeof(msg1.dst_name),stdin);msg1.dst_name[strlen(msg1.dst_name)-1]=0;msg1.type='2';//更改消息类型,注册用户的名字就不用进行改了,一直保留while (1){//进行聊天bzero(msg1.text,sizeof(msg1.text));//清空消息内容printf("text:");fgets(msg1.text,sizeof(msg1.text),stdin);msg1.text[strlen(msg1.text)-1]=0;//进行消息的退出判断if (!strncmp(msg1.text,"quit",4)){break;}//进行消息的发送if (send(cfd,&msg1,sizeof(msg1),0)==-1){perror("[send]");printf("Disconnect! Please reconnect!\n");break;}}break;case 2://进行群聊getchar();bzero(msg1.dst_name,sizeof(msg1.dst_name));//清空msg1.type='3';//指定消息类型为群聊while (1){bzero(msg1.text,sizeof(msg1.text));printf("text:");fgets(msg1.text,sizeof(msg1.text),stdin);msg1.text[strlen(msg1.text)-1]=0;//进行退出判断if (!strncmp(msg1.text,"quit",4)){break;}if (send(cfd,&msg1,sizeof(msg1),0)==-1){perror("[send_2]");printf("Disconnect! Please reconnect!\n");break;}}break;case 3:getchar();//打印在线信息msg1.type='4';if (send(cfd,&msg1,sizeof(msg1),0)==-1){perror("[send_3]");break;}break;case 4:getchar();//退出系统printf("Goodbye!\n");exit(0);break;default:getchar();printf("Sorry, there is an error! please select again.\n");break;}}return 0;
}

结果展示

服务器工作状态

用户ABC通信

学习建议

这个注释还是非常的详细了。所以hxd们希望能坚持看下去,也非常希望大家能多多支持,给给意见一起来完善这个程序

上面更新了博客,在网络传输中,我们应该使用的字符型。而不建议使用int性。因为int型在网络传输中更容易出现错误。因为int32位,char8位嘛

Linux C 基于epoll的多人聊天室相关推荐

  1. 【Linux网络编程】基于UDP实现多人聊天室

    文章目录 一.UDP的概念 1.1 UDP 1.2 UDP特点 二. 采用UDP实现多人聊天室原因 三.多人聊天室项目功能 四.实现多人聊天室项目流程分析 4.1 前期准备 4.1.1 定义结构体 4 ...

  2. 基于python面向对象多人聊天室

    基于python面向对象多人聊天室 1.项目环境 项目名称:多人聊天室 项目模式:C/S 开发环境:win10+python3.8+pycharm 所需知识:python GUI编程,多线程编程,网络 ...

  3. 基于Python的多人聊天室的设计与实现

    基于Python的多人聊天室的设计与实现 摘要  本文介绍了基于即时通讯的Python实现web版多人聊天室的设计和实现.这个系统利用了多种先进的技术,如Django.Channels.WebSock ...

  4. linux多人聊天室 qt,Qt编程详解--网络通信之基于TCP的多人聊天室

    一.了解TCP的通信过程 Qt中封装了TCP协议 QTcpServer类负责服务端: 1.创建QTcpServer对象 2.监听listen需要的参数是地址和端口号 3.当有新的客户端连接成功时会发射 ...

  5. Linux下基于socket和多线程的聊天室小程序

    转载:http://blog.csdn.net/robot__man/article/details/52460733 要求:基于TCP编写,一个聊天室最多100人.  客户端:  1.用户需要登录, ...

  6. 基于epoll实现的c++聊天室(全代码)

    早些时候为了更加熟悉网络编程,所以写了一个聊天程序练练手,但那是纯linux终端实现的,没有界面,最近心血来潮翻出来加了个Qt的简单界面,成了一个简易的局域网聊天室,通过tcp服务器来转发消息,其实最 ...

  7. Java NIO基于控制台的多人聊天室

    闲来无事写了个基于NIO的聊天室项目,费话不说了,直接贴代码吧. Server端代码如下: package com.xz.helloworld.nettyt.nio.im;import java.io ...

  8. 多人聊天功能代码php,基于swoole实现多人聊天室

    本文实例为大家分享了swoole创建多人多房间聊天室的具体代码,供大家参考,具体内容如下 核心的swoole代码 基本的cs(client-sercer)结构不变,这里利用的是redis的哈希和set ...

  9. 基于udp的多人聊天室

    服务端 相当于一个服务器,接收用户发送的过来的消息(登录消息,文本消息,退出登录消息),然后将其转发给其用户. 基本功能: 1.把新注册用户登陆消息告诉其它用户 2.把新用户插入到用户链表中 3.服务 ...

  10. websocket多人聊天php,php-notes/基于websocket实现多人聊天室.md at master · dd-code-site/php-notes · GitHub...

    WebSocket 连接 断开 格式:ws://IP或域名:端口 发送 消息 var websocket; var wsUrl; function connect() { try { wsUrl = ...

最新文章

  1. 在IDEA 中为Maven 配置阿里云镜像源
  2. parentViewController
  3. wpf里的menu怎么用_股市里的两市成交量是什么,它反映了什么,我是怎么用它来定投的...
  4. Shell教程(六):函数、联机帮助
  5. Ambrosus宣布推出用于Web Apps、iOS、Android的源代码开发套件
  6. kill session-KILL_SESSION()
  7. 用python一行代码实现1—100之和,你会吗
  8. PBRNet:Progressive Boundary Refinement Network for Temporal Action Detection (AAAI 2020)
  9. 大话西游之Office应用实例系列! 19
  10. 湖南理工学院图像处理与计算机视觉,信息与通信工程一级学科硕士研究生培养方案...
  11. 高考改变命运,来自一个湖南贫困村的真实样本
  12. Flink系列:物理分区分组broadcast、global、shuffle、forward、rebalance、rescale理解与实战
  13. Specification for the Lab VIEW Measurement File
  14. 数字盲打怎么练_小键盘数字盲打练习
  15. 记一次微信小程序canvas 2d 生成海报问题
  16. Spring Security CSRF防御源码分析
  17. linux查看某个端口的流量_linux流量查看工具汇总
  18. 关于重定向和转发的理解
  19. postgresql 密码修改,忘记密码进行修改重置
  20. 2021-2027全球与中国360度鱼眼镜头市场现状及未来发展趋势

热门文章

  1. android4.2实现pwm,Android平台下AOA协议的PWM信号控制系统
  2. 点云标可视化+标注软件
  3. 浅析百度搜索引擎白皮书
  4. dcs与plc与c语言的联系,PLC与和DCS系统通讯的实现
  5. 学习笔记(5):2020华为HCIA/HCNA/数通/路由交换/实验/视频/教程/持续更新赠题库-HCIA数通IP地址编址及报头详解
  6. OpenCV3.1安装包下载
  7. MATLAB 拟合曲线
  8. Android经典的大牛博客推荐(排名不分先后)!!
  9. Android架构 armeabi、armeabi-v7a、arm64-v8a、x86详解
  10. Python 之 pip安装 及 使用详解