简易聊天室
Linux系統項目之簡易聊天室
- 聊天室簡介
- 客戶端
- 服務器端
- makefile
- 總結
聊天室簡介
通過C語言寫了一段代碼,實現在一個局域網下實現了群聊和私聊,可以通過輸入switch實現群聊和私聊的切換,通過輸入seeyou來退出聊天室。本文存在的問題,當昵稱相同時輸入需要私聊的昵稱時只能和第一個進入聊天室的人私聊。
客戶端
客戶端通過TCP通訊協議和服務器端進行通訊,首先產生套接字然后與服務器建立連接。進入時輸入昵稱并且把昵稱發送給服務器。手動輸入switch來切換群聊私聊,輸入seeyou實現客戶端的退出。通過一個線程來接收來自群聊或者私聊的消息。代碼如下:
/*************************************************************************> 文件路徑:client.c> 作者: Moliam> 郵箱: 2515826079@qq.com > 文件創建時間: 2019年03月17日 星期日 17時26分02秒************************************************************************/ #include <stdio.h> #include <string.h> #include <stdlib.h> #include <netinet/in.h> #include <arpa/inet.h> #include <sys/socket.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <unistd.h> #include <pthread.h>int sockfd;//客戶端sockfd char *IP = "192.168.1.190";//服務器IP,這個IP可以在服務器終端輸入ifconfig查詢 short PORT = 9709;//端口號, typedef struct sockaddr AD;//方便閱讀和書寫的類型定義 char name[30];//客戶端名字 int flag_chat;//群聊與私聊標志 int text_fd;//用來承接函數返回值,判斷是否出錯void err(const char *str,int line);//出錯函數,并退出客戶端 void Init(void);//根據TCP協議初始化客戶端 void start(void);// void *recv_pthread(void *p);//接收線程服務函數int main(void) {Init();memset(name,0,sizeof(name));printf("請輸入你的昵稱:\n");scanf("%s",name);start();return 0; } void start(void) {char buf2[120]={0};int send_fd;pthread_t id;pthread_create(&id,0,recv_pthread,0);//創建線程用來接收消息text_fd = send(sockfd,name,strlen(name),0);if (text_fd == -1)err("send",__LINE__);printf("連接成功!聊天過程中可以按""switch""切換私聊和群聊\n");//與服務器鏈接成功while(1){char buf[100] = {0};//群聊狀態 all: scanf("%s",buf);//輸入需要發送的消息text_fd = send(sockfd,buf,strlen(buf),0);if (text_fd == -1)err("send",__LINE__);if(strcmp(buf,"seeyou") == 0)//如果發送的是seeyou則退出服務器{text_fd = send(sockfd,"seeyou",strlen("seeyou"),0);if (text_fd == -1)err("send",__LINE__);break;}if((strcmp(buf,"switch") == 0) && (flag_chat == 0))//如果輸入switch且為群聊狀態{printf("請輸入你想要私聊的昵稱:\n");scanf("%s",name);text_fd = send(sockfd,name,strlen(name),0);if (text_fd == -1)err("send",__LINE__);printf("%s\n",buf2);if(strcmp(buf2,"error") == 0){printf("昵稱輸入錯誤,已自動回到群聊\n");memset(buf,0,sizeof(buf));goto all;}else {printf("切換成功\n");printf("請輸入聊天內容:\n");memset(buf,0,sizeof(buf));}flag_chat = 1;//切換成功,私聊標志}if((strcmp(buf,"switch") == 0) && (flag_chat == 1))//如果輸入switch且為私聊狀態{printf("你已回到群聊狀態\n");flag_chat = 0;//群聊標志} }close(sockfd);//關閉套接字 } void *recv_pthread(void *p)//接收線程 {while(1){char buf[100] = {0};if(recv(sockfd,buf,sizeof(buf),0) <=0 )return (void *)0;printf("%s\n",buf);} } void Init(void) {sockfd = socket(PF_INET,SOCK_STREAM,0);if(sockfd == -1)err("socket",__LINE__);struct sockaddr_in addr;addr.sin_family = PF_INET;addr.sin_port = htons(PORT);addr.sin_addr.s_addr = inet_addr(IP);if(connect(sockfd,(AD*)&addr,sizeof(addr)) == -1)err("connect",__LINE__);printf("客戶端初始化成功!\n"); } void err(const char *str,int line) {fprintf(stderr,"出錯行號為%d\n",line);//格式化輸出perror(str);exit(-1); }服務器端
服務器端通過接收來自客戶端的信號,接收客戶端的昵稱并存放在指針數組中,當有客戶端連接時,為該客戶端開辟一個線程,進入線程服務函數,當客戶端發出的消息為switch時,該線程轉化為群聊或者私聊,當客戶端打出seeyou退出,然后下一個客戶端連接時,取代該客戶端的i值與指針。
/*************************************************************************> 文件路徑: service.c> 作者: Moliam> 郵箱: 2515826079@qq.com > 文件創建時間: 2019年03月18日 星期日 15時08分00秒************************************************************************/ #include <stdio.h> #include <string.h> #include <stdlib.h> #include <netinet/in.h> #include <arpa/inet.h> #include <sys/socket.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <unistd.h> #include <pthread.h>#define MAX_CLI 256//設置最大連接數為256 #define SET_PORT 9709//設置端口號值 int sockfd;//服務器socket int fds[MAX_CLI] = {0};//客戶端socketfd int size = MAX_CLI;//聊天室容量 char *IP = "192.168.1.190";//服務器IP地址,ifconfig查詢 short PORT = SET_PORT;//端口號 int count = 0;//計算當前在線人數 //int flag_chat;//私聊 or 群聊 char *name[MAX_CLI];//指針數組存放名字; char **n = name;//雙重指針取昵稱 int text_fd ;//承接函數返回值typedef struct sockaddr AD;//類型定義減少繁瑣重復內容void err(const char *buf,int line);//出錯打印函數 void Init(void);//服務器初始化,然后進入服務模式 void Service(void);//服務器模式 void *service_thread(void* p);//線程服務函數 void SendMsgToAll(char * buf);//給所有人發消息 void SendMsgToSin(int name,char *buf);//私聊消息int main(void) {Init();Service();return 0; } //TCP協議前面的步驟初始化 void Init(void) {sockfd = socket(PF_INET,SOCK_STREAM,0);//IPV4if(sockfd == -1)err("socket",__LINE__);struct sockaddr_in addr;addr.sin_family = PF_INET;addr.sin_port = htons(PORT);//端口號addr.sin_addr.s_addr = inet_addr(IP);//服務器為本機if(bind(sockfd,(AD *)&addr,sizeof(addr)) == -1)err("bind",__LINE__);if(listen(sockfd,MAX_CLI) == -1)err("listen",__LINE__);printf("服務器初始化成功\n"); } void Service(void) {char buf_join[130];while(1){printf("%d inline\n",count);struct sockaddr_in fromaddr;int len = sizeof(fromaddr);int fd = accept(sockfd,(AD*)&fromaddr,&len);if(fd == -1){ //err("accept",__LINE__);printf("CONNECT ERROR!\n");continue;}char *buf1;buf1 = (char *)malloc(30);char buf[30];memset(buf,0,sizeof(buf));text_fd = recv(fd,buf,sizeof(buf),0);//接收到的name*if(text_fd == -1)err("recv",__LINE__);strcpy(buf1,buf);int i = 0;for(i = 0;i<size;i++)//{if(fds[i] == 0)//查詢沒有使用的數,fds數組為存放fd,沒使用的fd值為0{fds[i] = fd;//新建立連接的客戶端fdname[i] = buf1;//name定義為 char *name[size];char **n=name;sprintf(buf_join,"%s加入聊天室!",*(n+i));SendMsgToAll(buf_join);//群發消息pthread_t id;pthread_create(&id,0,service_thread,&fd);//為該客戶端開辟一個線程count++;break;}}if(size == i)//此時已經沒有空余{char *str = "FULL WITH CLIENTS!";//給客戶端發消息客戶達到最大值text_fd = send(fd,str,sizeof(str),0);if(text_fd == -1)err("send",__LINE__);close(fd);}} } void *service_thread(void* p) {int fd = *(int *)p;char name_buf[30]= {0};char buf[100]={0};//存放接收到的數據int i,j;//i記錄自己fd所在位置,j記錄私聊對象fd所在位置 all: for(i = 0;i < size;i++)//群聊狀態{if(fds[i] == fd)//找到當前客戶所在的位置break;}while(1){int recv_fd;text_fd = recv(fd,buf,sizeof(buf),0);if (text_fd <= 0)//消息出錯,減去其客戶{fds[i]==0;count--;printf("%d inline\n",count);pthread_exit((void *)i);}if (strcmp(buf,"seeyou") == 0)//接收到seeyou消息時結束該線程{char buf_exit[128] = {0};sprintf(buf_exit,"%s退出了聊天室!",*(n+i));SendMsgToAll(buf_exit);fds[i]==0;//將該客戶fd退掉count--;printf("%d inline\n",count);pthread_exit((void *)i);//退出該線程}if (strcmp(buf,"switch") == 0)//接收到switch時進入到私聊{goto sin;}char buf_all[128] = {0};sprintf(buf_all,"%s:%s",*(n+i),buf);SendMsgToAll(buf_all);//沒出錯,發送消息} sin:text_fd = recv(fd,name_buf,sizeof(name_buf),0);if (text_fd == -1)err("recv",__LINE__);for(j = 0;j < size;j++)//找到該私聊昵稱的fd,記錄下位置{if((fds[j] != 0)&&(strcmp(*(n+j),name_buf) == 0))//找到該昵稱所對應的fd{break;}}if(size == j)//未找到,繼續回到群聊狀態{text_fd = send(fd,"error",sizeof("error"),0);if (text_fd == -1)perror("send");//不能調用err函數,err函數會退出進程printf("昵稱輸入錯誤!\n");goto all;}text_fd = send(fd"correct",sizeof("correct"),0);if (text_fd == -1)perror("send");while(1){int recv_sin_fd;char buf_sin_msg[100] = {0};//接收到的私聊信息char buf_sin[131]={0};//將名字與信息放在一起recv_sin_fd = recv(fd,buf_sin_msg,sizeof(buf_sin_msg),0);//接收私聊信息printf("%s\n",buf_sin_msg);if (recv_sin_fd == -1)err("recv",__LINE__);if (strcmp(buf_sin_msg,"switch") == 0){goto all;//接收到switch繼續回到群聊}sprintf(buf_sin,"來自%s私聊:%s",*(n+i),buf_sin_msg);//將來自i的消息存放,i為私聊的客戶fd,j為被私聊的客戶fdSendMsgToSin(j,buf_sin);//發送給j} } void SendMsgToSin(int name,char *buf) {text_fd = send(fds[name],buf,strlen(buf),0);if (text_fd == -1)err("send",__LINE__); } void SendMsgToAll(char * buf) {int i;for(i = 0;i < size;i++){if (fds[i] != 0){text_fd = send(fds[i],buf,strlen(buf),0);if (text_fd == -1)err("send",__LINE__);}} } void err(const char *buf,int line) {fprintf(stderr,"出錯行號為 : %d",line);perror(buf);exit(-1); }makefile
由于我是用的ubuntu系統,所以并沒有線程的庫,多以每次編譯時總是有些麻煩,所以我寫了一個makefile。
all:@gcc 3vservice.c -lpthread -o s@gcc 3vclient.c -lpthread -o c clean:@rm *.out s c -rf #帶@為只執行不回顯,-lpthread鏈接線程庫總結
該簡易聊天室在首先說的不足之處在我寫的下篇文章關于銀行管理系統有解決,通過服務器的鏈表來存儲賬號密碼,這樣不會出現賬號重復問題,該聊天室還可以客戶端發送昵稱通過指針數組中有沒有該昵稱來解決。
總結
- 上一篇: elementUI表格无数据设置
- 下一篇: Flask-蓝图