确认需求
实现的功能包含
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的\n
bzero(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位嘛