基于Linux 系统聊天室登录与注册实现(03)

news2025/1/1 23:24:27

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

数据结构

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

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

功能流程图

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

注册

图片

由上图所示:

  1. 服务器要先启动,监听客户端的连接;

  2. 客户端启动,首先连接服务器,并显示登陆、注册界面;

  3. 服务器接收到客户端连接后,会创建一个子线程专门用于于客户端的通信;

  4. 选择注册后,提示输入用户名、密码,封装注册信息到结构体变量msg中,并发送该信令给服务器;

  5. 服务器接收到客户端注册信息后,进入注册处理流程;

  6. 注册功能:首先查找该用户名是否存在,数组online[]中注册的位置,flage值为1,否则为-1;如果该用户名已经注册,则返回NAME_EXIST 错误信息;如果该用户名没有被注册,则找一个空闲位置,将该用户名密码保存到数据库online[]中,并返回注册成功的信令;

  7. 客户端接收到服务器注册处理指令后,会打印提示信息,并显示步骤2的菜单。

登录

图片

在这里插入图片描述

  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 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);
}

截图

客户端1注册

用户名:yikoulinux 密   码:qqqq客户端log

图片

服务器log

图片

客户端2注册

用户名:yikoupeng 密   码:qqqq服务器/客户端log

图片

客户端1登录

登录log

图片

按下回车,客户端会隐藏登录、注册的菜单,并显示公聊、私聊、在线列表的菜单。如下图所示:

图片

在这里插入图片描述

客户端2登录

登录log

图片

备注:

  1. 本篇只介绍登陆注册功能的实现;

  2. 因为本文只讨论功能的实现,对于很多异常出错的操作并没有全部完善;

  3. 在线用户的信息应该保存到数据库中【比如sqlite】,本篇为了便于读者理解,暂时用数组替代;

  4. 注册登录没有实现密码的二次校验和隐式输入。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1049869.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

(Java)关于easyExcel合并单元格

今天过客遇到一个需要合并单元格的业务&#xff0c;但是之前过客用的一直是easyExcel框架&#xff0c;所以这次也不想去使用其他的框架&#xff0c;今天就跟大家讲讲easyExcel怎么进行单元格的合并。 首先使用easyExcel进行导出的实体类一样是依据之前那样写&#xff0c;之后在…

宠物玩具在欧洲销售CE认证EN71测试标准

CE认证的EN71测试宠物玩具办理&#xff1a; 宠物玩具是用来给宠物玩耍&#xff0c;基于将宠物作为人类伙伴关系而诞生的一种玩具类型&#xff0c;这种玩具的存在就是让人类和自己的宠物真正的互动起来&#xff0c;在情感上面得到更大的交流和互动。 那么宠物玩具出口到欧盟市场…

支付宝电脑网站支付,异步通知

一&#xff1a;异步通知是支付宝回调商户的服务器&#xff0c;所以这个地址需要通过外网访问&#xff0c;在真实项目中都会有对应的服务器&#xff0c;但是在测试中只有使用内网穿透工具 推荐使用NATAPP-内网穿透 基于ngrok的国内高速内网映射工具 配置好内网穿透之后不要忘记…

目标检测如何演变:从区域提议和 Haar 级联到零样本技术

目录 一、说明 二、目标检测路线图 2.1 路线图&#xff08;一般&#xff09; 2.2 路线图&#xff08;更传统的方法&#xff09; 2.3 路线图&#xff08;深度学习方法&#xff09; 2.4 对象检测指标的改进 三、传统检测方法 3.1 维奥拉-琼斯探测器 (2001) 3.2 HOG探测器…

最大内切圆算法计算裂缝宽度

本文这里是对CSDN上另一位博主的代码进行了整理&#xff1a; 基于opencv的裂缝宽度检测算法&#xff08;计算轮廓最大内切圆算法&#xff09; 我觉得这位博主应该是上传了一个代码草稿&#xff0c;我对其进行了重新整理&#xff0c;并添加了详细的注释。 import cv2 import …

产业园区中工业厂房的能源综合配置

安科瑞 崔丽洁 园区工业地产中能源综合配置存在的问题 我国园区工业地产建设已历经近40年的发展, 园区在区域经济发展、产业集聚方面发挥了重要的载体和平台作用, 有力推动了我国社会经济的高质量发展。园区工业地产是国民经济的发展的重要载体, 但同时也是集中的环境污染源。…

大数据Doris(一):Doris概述篇

文章目录 Doris概述篇 一、前言 二、Doris简介

Norms and Inner Products

See https://ai.stanford.edu/~gwthomas/notes/norms-inner-products.pdf

Jenkins 权限管理

关于Role-based Authorization Strategy 使用Jenkins自身的权限管理过于粗糙&#xff0c;无法对单个、一类项目做管理&#xff0c;我们可以使用 Role-based Authorization Strategy插件来管理项目、角色。 首先安装该插件&#xff1a;在Jenkins查看该插件有无安装 在Jenkins-…

C++ 类和对象 (5) 析构函数

用构造函数创建对象后&#xff0c;程序负责跟踪该对象&#xff0c;直到对象过期为止。对象过期时&#xff0c;程序将自动调用一个特殊的成员函数&#xff0c;该函数的名称——析构函数。析构函数完成清理工作&#xff0c;实际上还是很有用的。例如&#xff0c;用new来分配一个构…

八、【漏洞复现】jupyter-notebook 命令执行(CVE-2019-9644)

8.0、基础知识 1、测试功能点 &#xff08;这种情况基本上很难遇到&#xff09; 8.1、漏洞原理 ​Jupyter Notebook是一套用于创建、共享代码和说明性文本文档的开源Web应用程序。 Jupyter Notebook可直接使用命令行执行任意命令。​ 8.2、影响范围 未授权开启终端权限的…

嵌入式Linux应用开发-基础知识-第七章 具体单板的 LED驱动程序

嵌入式Linux应用开发-基础知识-第七章 具体单板的 LED驱动程序 第七章 具体单板的 LED 驱动程序7.1 怎么写 LED 驱动程序&#xff1f;7.2 AM335X的 LED驱动程序7.2.1 原理图 XXXXXX_AM335X开发板结构为&#xff1a;7.2.2 所涉及的寄存器操作7.2.3 写程序7.2.4 配置内核去掉原有…

PyTorch meshgrid 生成网格坐标

torch.meshgrid(*tensors, indexingNone) 使用输入的 1-D 张量创建网格坐标 示例 indexing‘xy’ import torcha torch.arange(3) b torch.arange(3, 6)x, y torch.meshgrid(a, b, indexingxy) print(x , x, sep\n) print(y , y, sep\n)输出: x tensor([[0, 1, 2],[0, …

麒麟信安组织开展国产操作系统技术赋能专题培训

近日&#xff0c;为学习国产操作系统基本概念、使用与运维知识&#xff0c;应对用户单位内部信息系统国产化需求&#xff0c;来自国营洛阳丹城无线电厂的运维工程师们走进麒麟信安&#xff0c;进行了为期一周的操作系统课程学习。 针对客户此次培训需求&#xff0c;结合学员实…

uni-app:canvas-图形实现1

效果 代码 <template><view><!-- 创建了一个宽度为300像素&#xff0c;高度为200像素的canvas元素。canvas-id属性被设置为"firstCanvas"&#xff0c;可以用来在JavaScript中获取该canvas元素的上下文对象。 --><canvas style"width:200p…

nodejs+vue电影网站elementui

第3章 系统分析 5 3.1 需求分析 5 3.2 系统可行性分析 5 3.2.1技术可行性&#xff1a;技术背景 5 3.2.2经济可行性 6 3.2.3操作可行性&#xff1a; 6 3.3 项目设计目标与原则 6 3.4系统流程分析 7 3.4.1操作流程 7 3.4.2添加信息流程 8 3.4.3删除信息流程 9 第4章 系统设计 11 …

助力古彝文保护,AI文字识别技术推动文化传承

文章目录 一、写在前面古彝文为何物&#xff1f;古彝文的保护背景 二、古彝文识别有何难点&#xff1f;如何解决&#xff1f;三、合合信息的强劲技术四、古文识别的重要意义 一、写在前面 古彝文为何物&#xff1f; 彝文指的是云南、贵州、四川等地的彝族人使用的文字&#x…

【Java 进阶篇】MySQL外键约束详解

在数据库设计和管理中&#xff0c;外键约束是一项重要的功能&#xff0c;它用于维护表与表之间的关联关系&#xff0c;保证数据的完整性和一致性。本文将详细介绍MySQL外键约束的概念、用法以及一些最佳实践&#xff0c;以帮助您更好地理解和应用外键约束。 1. 什么是外键约束…

使用领域引导图卷积神经网络GCNN增强基于脑电图EEG的神经疾病诊断完整代码

一种基于图卷积神经网络&#xff08;GCNN&#xff09;的新方法&#xff0c;用于改进使用头皮脑电图&#xff08;EEG&#xff09;进行神经系统疾病诊断。尽管脑电图是神经系统疾病诊断中主要使用的检测方法之一&#xff0c;但基于EEG的专家视觉诊断的敏感性仍然只有约50&#xf…

ubuntu18.04 OpenGL开发(显示YUV)

源码参考&#xff1a;https://download.csdn.net/download/weixin_55163060/88382816 安装opengl库 sudo apt install libglu1-mesa-dev freeglut3-dev mesa-common-dev 安装opengl工具包 sudo apt install mesa-utils 检查opengl版本信息&#xff08;桌面终端执行&#xff09…