上一篇我们已经讲了如何搭建一个多线程的服务器模型,可以支持多个客户端同时连接服务器,本篇我们来实现多个客户端,如何实现向服务器注册信息,并实现登录的功能。


数据结构

接着上一篇的实例代码继续增加功能。
要实现注册和登录功能,那么我们就必须要让服务器和客户端在交互数据包的时候按照统一的格式收发信令。
信令格式

//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    //退出

在线用户信息
服务器需要维护所有用户信息,需要知道用户是否在线,是否注册。

//在线用户 struct ONLINE{int fd;  //-1:该用户下线   >0:该用户已经登录,对应的套接字int flage; //-1 该条目没有用户信息  1:该条目有用户注册信息char name[32]; //注册的用户名字char passwd[32];  //用户名密码}; struct ONLINE online[MAX_USER_NUM];

注册的客户端信息需要存储在服务器,为了简单起见,我们暂时不用数据库存储,只定义一个全局的数组保存客户端信息,并且规定只允许64个客户端登录。

服务器处理结果返回值

/*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  //登录时,提示用户没有注册

功能流程图

现在我们根据功能,首先画一个流程图。

注册

从0实现基于Linux socket聊天室-实现聊天室的登录、注册功能-3_Linux 由上图所示:

  1. 服务器要先启动,监听客户端的连接;
  2. 客户端启动,首先连接服务器,并显示登陆、注册界面;
  3. 服务器接收到客户端连接后,会创建一个子线程专门用于于客户端的通信;
  4. 选择注册后,提示输入用户名、密码,封装注册信息到结构体变量msg中,并发送该信令给服务器;
  5. 服务器接收到客户端注册信息后,进入注册处理流程;
  6. 注册功能:首先查找该用户名是否存在,数组online[]中注册的位置,flage值为1,否则为-1;
    如果该用户名已经注册,则返回NAME_EXIST 错误信息;
    如果该用户名没有被注册,则找一个空闲位置,将该用户名密码保存到数据库online[]中,并返回注册成功的信令;
  7. 客户端接收到服务器注册处理指令后,会打印提示信息,并显示步骤2的菜单。

登录

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

  1. 服务器要先启动,监听客户端的连接;
  2. 客户端启动,首先连接服务器,并显示登陆、注册界面;
  3. 服务器接收到客户端连接后,会创建一个子线程专门用于于客户端的通信;
  4. 选择登陆后,提示输入用户名、密码,封装登陆信息到结构体变量msg中,并发送该信令给服务器;
  5. 服务器接收到客户端注册信息后,进入登陆处理流程;
  6. 登陆功能:首先查找该用户名、密码是否在数组online[]中存在匹配项,找到返回对应的下标,并将于该客户端相连接的套接字保存到对应的条目中,返回登陆成功信息给客户端;
    如果没有找到,则返回-1,并返回0X80000004错误信息给客户端;
  7. 客户端接收到服务器注册处理指令后,会打印提示信息,并设置客户端在线的标记login_f 为1,此时会显示 聊天功能对应的菜单。
代码

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 notchar 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

/*********************************************
           公众号:一口Linux
*********************************************/#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[64]={0};while(1){if(login_f != 1){continue;}	
	
		len = read(sockfd,buf,sizeof(buf));if(len<=0){close(sockfd);return;}
		buf[len]='\0';printf("%s\n",buf);}}void broadcast(int fd){}void private(int fd){}void list_online_user(sockfd){}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);case 0:logout(sockfd);break;default:break;}if(sel == 0){exit(0);}}}

server.c

/*********************************************
           公众号:一口Linux
*********************************************/#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 find_dest_user_online(int sockfd,int *index,struct protocol*msg){int i;for(i=0;i<MAX_USER_NUM;i++){//this pos not useif(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 logedprintf("%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){}void list_online_user(int index){}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);//进入聊天了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);}

截图

客户端1注册

用户名:yikoulinux
密 码: qqqq
客户端log
从0实现基于Linux socket聊天室-实现聊天室的登录、注册功能-3_Linux _03服务器log
从0实现基于Linux socket聊天室-实现聊天室的登录、注册功能-3_Linux _04

客户端2注册

用户名:yikoupeng
密 码: qqqq
服务器/客户端log从0实现基于Linux socket聊天室-实现聊天室的登录、注册功能-3_Linux _05

客户端1登录

登录log

从0实现基于Linux socket聊天室-实现聊天室的登录、注册功能-3_Linux _06按下回车,客户端会隐藏登录、注册的菜单,并显示公聊、私聊、在线列表的菜单。如下图所示:

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

客户端2登录

登录log

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

  1. 本篇只介绍登陆注册功能的实现;
  2. 因为本文只讨论功能的实现,对于很多异常出错的操作并没有全部完善;
  3. 在线用户的信息应该保存到数据库中【比如sqlite】,本篇为了便于读者理解,暂时用数组替代;
  4. 注册登录没有实现密码的二次校验和隐式输入。