查看原文
其他

从0实现基于Linux socket聊天室-增加公聊、私聊-4

土豆居士 一口Linux 2021-11-05

前面文章链接如下:

从0实现基于Linux socket聊天室-多线程服务器模型-1

从0实现基于Linux socket聊天室-多线程服务器一个很隐晦的错误-2

从0实现基于Linux socket聊天室-实现聊天室的登录、注册功能-3

上文中,我们基于多线程的框架,实现了注册和登录的功能,这一章,我们在此基础上来实现公聊、私聊、显示在线用户列表功能。

公聊

接着上几篇的流程图我们详细讲解公聊流程图如下:

如上图所示,我们去掉了网络连接和客户端登录、注册等功能,直接进入聊天的流程:

  1. 客户端从菜单选择公聊功能;
  2. 输入要聊天信息;
  3. 回车发送聊天信息;
  4. 服务器的子线程收到公聊数据之后,进入公聊流程;
  5. 查找所有在线用户,向所有的在线用户发送该公聊信息;
  6. 客户端进入聊天后会创建一个子线程,该子线程会循环接收所有服务器发送的数据信息。

私聊

如上图所示:

  1. 客户端从菜单选择私聊功能;
  2. 输入要聊天的对象和聊天信息;
  3. 发送聊天信息给服务器;
  4. 服务器的子线程收到公聊数据之后,进入公聊流程;
  5. 查找所有在线用户,向所有的在线用户发送该公聊信息;
  6. 客户端子线程会循环接收所有服务器发送的数据信息。

显示在线用户

如上图所示:

  1. 客户端从菜单选择显示在线用户功能;
  2. 封装显示在线用户数据包,并发送该数据包给服务器;
  3. 服务器收到数据包后,进入显示在线用户功能模块;
  4. 检查在线用户数据信息数据库,将在线用户【fd不为-1】名称封装到数据包中,一次只填充一个,stat字段填充ONLINEUSER_OK;
  5. 所有用户发送完毕,补充一个数据包,stat填充ONLINEUSER_OVER;
  6. 收到服务器发送的在线用户数据包后,客户端子线程进入显示在线用户子模块;提取数据包中在线用户名字并打印,判断该数据包stat是否为 ONLINEUSER_OVER,如果不是则继续接收下一个数据包,如果是,则提示用户显示完毕。

运行截图

现在预设:客户A:yikoulinux 客户B:yikoupeng

公聊

客户B发公聊:

客户A收到信息:

服务器log:

私聊

客户B私发信息给A:客户A收到的消息:

显示在线用户信息


chat.h
#ifndef _TCP_CHAT#define _TCP_CHAT
#include <sys/socket.h>#include <netinet/in.h>#include <arpa/inet.h>#include <stdio.h>#include <sys/types.h>#include <sys/stat.h>#include <fcntl.h>#include <string.h>#include <pthread.h>#include <stdlib.h> #include <string.h> #define SERVER_PORT 8888//在线用户 struct ONLINE{ int fd; //-1 int flage; //registed or not char name[32]; char passwd[32];}; #define MAX_USER_NUM 64
//C/S通信的结构体struct protocol{ int cmd; int state; char name[32]; char data[64];}; /*cmd*/#define BROADCAST 0X00000001#define PRIVATE 0X00000002#define REGISTE 0X00000004#define LOGIN 0X00000008#define ONLINEUSER 0X00000010#define LOGOUT 0X00000020
/*return code*/#define OP_OK 0X80000000#define ONLINEUSER_OK 0X80000001#define ONLINEUSER_OVER 0X80000002#define NAME_EXIST 0X80000003#define NAME_PWD_NMATCH 0X80000004#define USER_LOGED 0X80000005#define USER_NOT_REGIST 0X80000006

#endif
client.c
#include "chat.h"
int sockfd;int addrlen;struct sockaddr_in server_addr;
pthread_t pid;
int login_f = -1; void *func(void *arg){ int len; char buf[128]={0}; struct protocol *msg; while(1) { if(login_f != 1) { continue; } memset(buf,0,sizeof(buf)); len = read(sockfd,buf,sizeof(buf)); if(len<=0) { close(sockfd); return; } msg = (struct protocol *)buf; // this is show online user, if((msg->state == ONLINEUSER_OK)&&(msg->cmd == ONLINEUSER)) { printf("%s\t",msg->name); continue; } if((msg->state == ONLINEUSER_OVER)&&(msg->cmd == ONLINEUSER)) { printf("\n"); continue; } buf[len]='\0'; printf("%s\n",buf); } }void broadcast(int fd){ struct protocol msg; msg.cmd = BROADCAST; printf("say:\n#"); scanf("%s",msg.data); write(fd,&msg,sizeof(msg));}void private(int fd){ struct protocol msg; msg.cmd = PRIVATE; printf("input name you want to talk:\n#"); scanf("%s",msg.name); printf("say:\n#"); scanf("%s",msg.data);
write(fd,&msg,sizeof(msg));}void list_online_user(sockfd){ struct protocol msg; msg.cmd = ONLINEUSER; write(sockfd,&msg,sizeof(msg)); getchar(); getchar(); /* while(1) { read(sockfd ,&msg,sizeof(msg)); if(msg.state == ONLINEUSER_OK) { printf("%s\t",msg.name); }else{ break; } }*/ }int registe(int fd){ struct protocol msg,msgback;
msg.cmd = REGISTE; printf("input your name\n"); scanf("%s",msg.name); printf("input your passwd\n"); scanf("%s",msg.data);
write(sockfd,&msg,sizeof(msg)); read(sockfd,&msgback,sizeof(msgback)); if(msgback.state != OP_OK) { printf("Name had exist,try again!\n"); getchar(); getchar(); return -1; }else{ printf("Regist success!\n"); getchar(); getchar(); return 0 ; }}int login(int fd){ struct protocol msg,msgback;
msg.cmd = LOGIN; printf("input your name\n"); scanf("%s",msg.name); printf("input your passwd\n"); scanf("%s",msg.data);
write(sockfd,&msg,sizeof(msg)); read(sockfd,&msgback,sizeof(msgback)); if(msgback.state != OP_OK) { printf("Name had exist,try again!\n"); getchar(); getchar(); login_f = -1; return NAME_PWD_NMATCH; }else{ printf("Login success!\n"); getchar(); getchar(); login_f = 1; return OP_OK ; }}int logout(int fd){ close(fd); login_f = -1;}int main(int argc, char **argv){ int sel; int ret; int min_sel,max_sel; int portnumber; struct protocol msg; if(argc<3) { printf("cmd: %s ip portnumber\n",argv[0]); return; } //argv2 存放的是端口号 ,读取该端口,转换成整型变量 if((portnumber=atoi(argv[2]))<0) { fprintf(stderr,"Usage:%s hostname portnumber\a\n",argv[0]); exit(1); } sockfd = socket(PF_INET,SOCK_STREAM,0); if(sockfd<0) { perror("socket() fail\n"); return; } server_addr.sin_family = PF_INET; server_addr.sin_port = htons(portnumber); server_addr.sin_addr.s_addr = inet_addr(argv[1]); addrlen = sizeof(struct sockaddr_in); connect(sockfd,(struct sockaddr* )&server_addr,addrlen); pthread_create(&pid, NULL,func, NULL); while(1) { //getchar(); system("clear"); if(login_f == -1){ printf("\t 1 娉ㄥ唽 \n"); printf("\t 2 鐧诲綍 \n"); }else if(login_f == 1){ printf("\t 3 鍏亰 \n"); printf("\t 4 绉佽亰 \n"); printf("\t 5 鍦ㄧ嚎鍒楄〃 \n"); } printf("\t 0 閫€鍑?\n"); fflush(stdin); scanf("%d",&sel); if(sel == 0) { break; } if(login_f == 1) { min_sel = 3; max_sel = 5; }else if(login_f == -1){ min_sel = 1; max_sel = 2; } if(sel<min_sel || sel > max_sel) { printf("Valid choice ,try again\n"); continue; } switch(sel) { case 1: registe(sockfd); break; case 2: ret = login(sockfd); break; case 3: broadcast(sockfd); break; case 4: private(sockfd); break; case 5: list_online_user(sockfd); break; case 0: logout(sockfd); break; default: break; } if(sel == 0) { exit(0); } }
}
server.c

#include "chat.h"
struct ONLINE online[MAX_USER_NUM];

void del_user_online(int index){ int i; char buf[128]={0};
if(index <0) { return; } online[index].fd = -1; sprintf(buf,"%s offline\n",online[index].name); //通知所有客户端,某个用户下线了 for(i=0;i<MAX_USER_NUM;i++) { if(online[i].fd == -1) { continue; } write(online[i].fd,buf,strlen(buf)); } return;}int add_user(int sockfd,struct protocol*msg){ int i,index = -1; char buf[128]={0}; for(i=0;i<64;i++)//添加到在线用户列表 { if(online[i].flage == -1) { online[i].flage= 1; strcpy(online[i].name,msg->name); strcpy(online[i].passwd,msg->data); printf("regist %s to %d \n",msg->name,i); index = i; return index; } } return index;}void broadcast(int index,struct protocol*msg){ int i; char buf[128]={0}; sprintf(buf,"%s say:%s\n",online[index].name ,msg->data); for(i=0;i<MAX_USER_NUM;i++) {// jump offline and sender self if((online[i].fd == -1)||(i == index)) { continue; } write(online[i].fd,buf,strlen(buf)); } }int find_dest_user_online(int sockfd,int *index,struct protocol*msg){ int i; for(i=0;i<MAX_USER_NUM;i++) { //this pos not use if(online[i].flage== -1) { continue; } if((strcmp(msg->name,online[i].name)==0)&&(strcmp(msg->data,online[i].passwd)==0)) { if(online[i].fd == -1) { online[i].fd = sockfd; *index = i ; return OP_OK; }else{ //user had loged printf("%s had login\n",online[i].name); return USER_LOGED; } } } return NAME_PWD_NMATCH;}int find_dest_user(char *name){ int i; for(i=0;i<MAX_USER_NUM;i++) { if(online[i].flage == -1) { continue; } if(strcmp(name,online[i].name)==0) { return i; } } return -1;}
void private(int index,struct protocol*msg){ int dest_index; char buf[128]; //找到那个人 dest_index = find_dest_user(msg->name); if(dest_index == -1) { sprintf(buf,"there is no user :%s \n",msg->name); write(online[index].fd,buf,strlen(buf)); return; }else{ sprintf(buf,"%s say to %s:%s\n",online[index].name ,online[dest_index].name,msg->data); write(online[dest_index].fd,buf,strlen(buf)); return; } }void list_online_user(int index){ int i; struct protocol msg; for(i=0;i<MAX_USER_NUM;i++) { if(online[i].fd == -1) { continue; } memset(&msg,0,sizeof(msg)); msg.cmd = ONLINEUSER; msg.state = ONLINEUSER_OK; strcpy(msg.name,online[i].name); printf("list online[i].name =%s \n",online[i].name); write(online[index].fd ,&msg,sizeof(msg)); } msg.cmd = ONLINEUSER; msg.state = ONLINEUSER_OVER; write(online[index].fd ,&msg,sizeof(msg));}
void registe(int sockfd,int *index,struct protocol*msg){ int dest_index; char buf[128]; struct protocol msg_back;
msg_back.cmd = REGISTE; //找到那个人 dest_index = find_dest_user(msg->name);
if(dest_index == -1) { // this user can registe *index = add_user(sockfd,msg); online[*index].flage = 1; msg_back.state = OP_OK; printf("user %s regist success!\n",msg->name); write(sockfd,&msg_back,sizeof(msg_back)); return; }else{ msg_back.state = NAME_EXIST; printf("user %s exist!\n",msg->name);
write(sockfd,&msg_back,sizeof(msg_back)); return; } }void login(int sockfd,int *index,struct protocol*msg){ int i; int ret; char buf[128]; struct protocol msg_back;
msg_back.cmd = LOGIN; //找到那个人 ret = find_dest_user_online(sockfd,index,msg); if(ret != OP_OK) { msg_back.state = ret; strcpy(buf,"there is no this user\n"); printf("user %s login fail!\n",msg->name); write(sockfd,&msg_back,sizeof(msg_back)); return; }else{ msg_back.state = OP_OK; strcpy(msg_back.data,"login success\n"); printf("user %s login success!index =%d \n",msg->name,*index); write(online[*index].fd,&msg_back,sizeof(msg_back)); } //通知所有客户端,某个用户上线了 sprintf(buf,"%s online\n",online[*index].name); for(i=0;i<MAX_USER_NUM;i++) { if(online[i].fd != -1) { write(online[i].fd,buf,strlen(buf)); } } }void *func(void *arg){ int sockfd = *((int*)arg); char buf[64]; int len; int index = -1;//该用户在在线用户列表的下标 struct protocol msg; free(arg); //客户端连接后 必须先输入一个名字,不考虑数据类型出错,以及重名问题,也不考虑人数满了的情况// read(sockfd,&msg,sizeof(msg)); // index = add_user_online(sockfd,&msg);
//进入聊天了 while(1) { len = read(sockfd,&msg,sizeof(msg)); if(len<=0) {//下线 printf("%s offline\n",online[index].name); //从在线列表中删除 del_user_online(index); close(sockfd); return; } switch(msg.cmd) { case REGISTE: registe(sockfd,&index,&msg); break; case LOGIN: login(sockfd,&index,&msg); break; case BROADCAST: broadcast(index,&msg); break; case PRIVATE: private(index,&msg); break; case ONLINEUSER: list_online_user(index); break; default: break; } } }
int main(int argc, char **argv){ int lsfd,newfd; int addrLen,cliaddrlen; struct sockaddr_in my_addr; struct sockaddr_in cli_adr; char buf[64]="xuezhiqian fuhele\n"; pthread_t pid; int *arg; int i; int portnumber; if(argc<2) { printf("cmd: %s portnumber\n",argv[0]); return; }/*׋ࠚۅһהì΋Զ*/ if((portnumber=atoi(argv[1]))<0) { fprintf(stderr,"Usage:%s portnumber\a\n",argv[0]); exit(1); } lsfd = socket(PF_INET,SOCK_STREAM,0); if(lsfd<0) { perror("socket() fail\n"); return; } bzero(&my_addr,sizeof(struct sockaddr_in)); my_addr.sin_family = PF_INET; my_addr.sin_port = htons(portnumber); my_addr.sin_addr.s_addr = htonl(INADDR_ANY); addrLen = sizeof(struct sockaddr_in); if(bind(lsfd,(struct sockaddr* )&my_addr ,addrLen)<0) { perror("bind() fail\n"); return; } listen(lsfd,5); cliaddrlen = sizeof(struct sockaddr_in); for(i=0;i<64;i++) { online[i].fd = -1; online[i].flage= -1; } while(1) { newfd = accept(lsfd,(struct sockaddr *)&cli_adr,&cliaddrlen); printf("client:ip:%s port:%d \n", inet_ntoa(cli_adr.sin_addr),cli_adr.sin_port); arg = malloc(sizeof(int)); *arg = newfd;//必须搞清楚为什么要申请内存 pthread_create(&pid,NULL,func, (void*)arg); } close(newfd); close(lsfd);}



一口君个人微信


添加一口君个人微信即送Linux、嵌入式等独家入门视频


→ 精选技术资料共享

→ 高手如云交流社群






推荐阅读



【1】基于ARM UART裸机驱动详解
【2】Linux 自旋锁spinlock,教你如何把ubuntu弄死锁
【3】
看了这几个C语言例子,你一定和我一样连说5个卧槽,声音一次比一次大【必读】
【4】手把手教Linux驱动7-内核互斥锁
【5】
Linux信号量(1)-SYSTEM V
【6】I2C干货-基于Cortex-A9(重新整理)  【必读】【7】一文搞懂ADC裸机和基于Linux驱动编写方法
【9】从0实现基于Linux socket聊天室-多线程服务器模型-1   【必读】
【10】从0实现基于Linux socket聊天室-多线程服务器一个很隐晦的错误-2 【必读】
【11】从0实现基于Linux socket聊天室-实现聊天室的登录、注册功能-3【必读】


本公众号全部原创干货已整理成一个目录,请在公众号里回复「m」获取!或者关注进入后台点击左下角「干货」!





后台回复进群」,即可加入技术交流群,进群福利:免费赠送Linux学习资料


: . Video Mini Program Like ,轻点两下取消赞 Wow ,轻点两下取消在看

您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存