天天看點

從0實作基于Linux socket聊天室-實作聊天室的登入、注冊功能-3

上一篇我們已經講了如何搭建一個多線程的伺服器模型,可以支援多個用戶端同時連接配接伺服器,本篇我們來實作多個用戶端,如何實作向伺服器注冊資訊,并實作登入的功能。

資料結構

接着上一篇的執行個體代碼繼續增加功能。

要實作注冊和登入功能,那麼我們就必須要讓伺服器和用戶端在互動資料包的時候按照統一的格式收發信令。

信令格式

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

由上圖所示:

  1. 伺服器要先啟動,監聽用戶端的連接配接;
  2. 用戶端啟動,首先連接配接伺服器,并顯示登陸、注冊界面;
  3. 伺服器接收到用戶端連接配接後,會建立一個子線程專門用于于用戶端的通信;
  4. 選擇注冊後,提示輸入使用者名、密碼,封裝注冊資訊到結構體變量msg中,并發送該信令給伺服器;
  5. 伺服器接收到用戶端注冊資訊後,進入注冊處理流程;
  6. 注冊功能:首先查找該使用者名是否存在,數組online[]中注冊的位置,flage值為1,否則為-1;

    如果該使用者名已經注冊,則傳回NAME_EXIST 錯誤資訊;

    如果該使用者名沒有被注冊,則找一個空閑位置,将該使用者名密碼儲存到資料庫online[]中,并傳回注冊成功的信令;

  7. 用戶端接收到伺服器注冊處理指令後,會列印提示資訊,并顯示步驟2的菜單。

登入

從0實作基于Linux socket聊天室-實作聊天室的登入、注冊功能-3
  1. 選擇登陸後,提示輸入使用者名、密碼,封裝登陸資訊到結構體變量msg中,并發送該信令給伺服器;
  2. 伺服器接收到用戶端注冊資訊後,進入登陸處理流程;
  3. 登陸功能:首先查找該使用者名、密碼是否在數組online[]中存在比對項,找到傳回對應的下标,并将于該用戶端相連接配接的套接字儲存到對應的條目中,傳回登陸成功資訊給用戶端;

    如果沒有找到,則傳回-1,并傳回0X80000004錯誤資訊給用戶端;

  4. 用戶端接收到伺服器注冊處理指令後,會列印提示資訊,并設定用戶端線上的标記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 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

/*********************************************
           公衆号:一口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 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)
{

}
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

伺服器log

從0實作基于Linux socket聊天室-實作聊天室的登入、注冊功能-3

用戶端2注冊

使用者名:yikoupeng

伺服器/用戶端log

從0實作基于Linux socket聊天室-實作聊天室的登入、注冊功能-3

用戶端1登入

登入log

從0實作基于Linux socket聊天室-實作聊天室的登入、注冊功能-3

按下回車,用戶端會隐藏登入、注冊的菜單,并顯示公聊、私聊、線上清單的菜單。如下圖所示:

從0實作基于Linux socket聊天室-實作聊天室的登入、注冊功能-3

用戶端2登入

從0實作基于Linux socket聊天室-實作聊天室的登入、注冊功能-3

備注:

  1. 本篇隻介紹登陸注冊功能的實作;
  2. 因為本文隻讨論功能的實作,對于很多異常出錯的操作并沒有全部完善;
  3. 線上使用者的資訊應該儲存到資料庫中【比如sqlite】,本篇為了便于讀者了解,暫時用數組替代;
  4. 注冊登入沒有實作密碼的二次校驗和隐式輸入。

歡迎關注公衆号:一口Linux

繼續閱讀