日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 运维知识 > linux >内容正文

linux

Linux C 基于epoll的多人聊天室

發布時間:2023/12/31 linux 19 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Linux C 基于epoll的多人聊天室 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

確認需求

實現的功能包含
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的多人聊天室的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。