Chat room login and registration implementation based on Linux system (03)

In the previous article, we have already talked about how to build a multi-threaded server model that can support multiple clients to connect to the server at the same time. In this article, we will implement multiple clients, how to register information with the server, and implement the login function.

data structure

Continue to add functions to the example code in the previous article. To implement the registration and login functions, we must allow the server and client to send and receive signaling in a unified format when exchanging data packets. Signaling format

//C/S通信的结构体
struct protocol{
 int cmd;     //命令
 int state;  //存储命令返回信息
 char name[32]; //用户名
 char data[64]; //数据
};

Command type: The commands in the signaling format are defined as follows:

/*cmd*/
#define BROADCAST 0X00000001   //广播数据
#define PRIVATE 0X00000002     //私聊
#define REGISTE 0X00000004     //注册账号
#define LOGIN 0X00000008       //登录
#define ONLINEUSER     0X00000010 //显示在线用户
#define LOGOUT     0X00000020    //退出

The online user information server needs to maintain all user information and needs to know whether the user is online and registered.

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

Registered client information needs to be stored on the server. For the sake of simplicity, we do not use database storage for the time being. We only define a global array to save client information, and only allow 64 clients to log in.

Server processing result return value

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

Functional flow chart

Now let's first draw a flow chart based on the function.

register

picture

As shown in the picture above:

  1. The server must be started first to monitor client connections;

  2. When the client starts, it first connects to the server and displays the login and registration interface;

  3. After the server receives the client connection, it will create a child thread dedicated to client communication;

  4. After selecting registration, you are prompted to enter the user name and password, encapsulate the registration information into the structure variable msg, and send the signaling to the server;

  5. After the server receives the client registration information, it enters the registration processing process;

  6. Registration function: first check whether the user name exists, the registered position in the array online[], the flag value is 1, otherwise it is -1; if the user name has been registered, the NAME_EXIST error message will be returned; if the user name has not been registered , then find a free location, save the username and password into the database online[], and return signaling of successful registration;

  7. After the client receives the server registration processing instruction, it will print a prompt message and display the menu in step 2.

Log in

picture

Insert image description here

  1. The server must be started first to monitor client connections;

  2. When the client starts, it first connects to the server and displays the login and registration interface;

  3. After the server receives the client connection, it will create a child thread dedicated to client communication;

  4. After selecting login, you are prompted to enter the user name and password, encapsulate the login information into the structure variable msg, and send the signaling to the server;

  5. After receiving the client registration information, the server enters the login process;

  6. Login function: First, check whether the username and password have a match in the array online[], find and return the corresponding subscript, and save the socket connected to the client to the corresponding entry, and return successful login Information to the client; if not found, -1 is returned, and 0X80000004 error message is returned to the client;

  7. After the client receives the server registration processing instruction, it will print a prompt message and set the client's online flag login_f to 1. At this time, the menu corresponding to the chat function will be displayed.

code

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

/*********************************************
          
*********************************************/
#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);
}

screenshot

Client 1 registration

Username: yikoulinux Password: qqqq client log

picture

Server log

picture

Client 2 registration

Username: yikoupeng Password: qqqq server/client log

picture

Client 1 login

Login log

picture

Press Enter, the client will hide the login and registration menus, and display the menus for public chat, private chat, and online list. As shown below:

picture

Insert image description here

Client 2 login

Login log

picture

Remark:

  1. This article only introduces the implementation of the login and registration function;

  2. Because this article only discusses the implementation of functions, many abnormal and error-prone operations are not fully completed;

  3. Online user information should be saved in a database [such as sqlite]. In order to facilitate readers' understanding, this article will temporarily use an array instead;

  4. Registration and login do not implement secondary verification and implicit input of passwords.

Guess you like

Origin blog.csdn.net/weixin_41114301/article/details/133384566