网页聊天室

网页IM:网页聊天室

框架:不严谨MVC框架

​ M-model:数据管理

​ V-view:界面

​ C-controller:业务控制

在这里插入图片描述

详细设计


数据管理模块:

  1. 数据存储的管理:使用mysql数据库进行存储管理

  2. 数据访问的管理

    1. 用户信息:用户ID,用户名,密码(被加密的字符串),昵称,创建时间
    2. 聊天信息:消息ID,用户ID,消息内容,消息时间
  3. 对信息进行的操作

    1. 用户信息操作

      1. 新增用户
      2. 用户名密码的验证
      3. 修改密码
      4. 删除用户
      5. 修改昵称
    2. 聊天信息操作

      1. 新增消息
      2. 获取历史消息

业务处理模块

  1. 网络通信服务器模块:使用第三方库(mongoose)来实现,搭建HTTP服务器,实现网络通信。

  2. 业务处理模块:分析请求,明确客户端请求目的,然后根据具体的业务功能处理。

网络通信接口设计:约定的是请求格式

restful风格的请求(规范性)

​ restful一套基于HTTP协议的API接口风格

  • GET请求表示获取资源

  • POST请求表示新增资源

  • PUT请求表示修改资源

  • DELETE请求表示删除资

    资源路径:要操作的资源

​ JSON:一种轻量的数据交换格式

  • 字符串:使用双引号括起来的数据

  • 数字:连续的数字字符

  • 数组:使用[]括起来的元素

  • 对象:使用{}括起来的元素

    示例:小明,男,18岁,大一,数学32,英语54,语文98

    {

    ​ “姓名”:“小明”,

    ​ “年龄”:18,

    ​ “年级”:“大一”,

    ​ “成绩”:[32,54,98],

    ​ “对象”:{“姓名”:“小红”,“年龄”:18…}

    }

接口设计:

​ 1.新增用户
在这里插入图片描述
​ 2.用户名密码验证—使用常规的html表单数据格式
在这里插入图片描述
3.修改密码
在这里插入图片描述
4.修改昵称
在这里插入图片描述
5.删除用户
在这里插入图片描述

因为HTTP协议是一个请求-相应协议:是客户端请求了服务器,服务器进行业务处理返回结果。

但是这种模式不适合在群聊天,因为一个客户端发送的信息,需要服务器主动的推送给其他所有的客户端。

但HTTP并不支持主动推送,无法主动给客户端发送信息。除非客户端不断的请求服务器,每隔1s请求服务器获取一下新消息。

但是这样有缺陷

​ 1.消息不够及时

​ 2.对服务器的消耗负载过大(不管有无消息,总有大量的客户端与服务器进行通信)

因此,一旦用户成功登录,群聊天就不在使用HTTP协议了,使用websocket协议。websocket协议就是在此需求的基础上,在HTTP协议之上设计的双向长连接通信协议。

在这里插入图片描述

服务器的搭建,并不自己实现,使用第三方库:mongoose

消息操作格式约定

​ 1.客户端将要发送的消息发送给服务器
在这里插入图片描述
2.历史消息的获取
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-OsTN2uVo-1681792981067)(C:\Users\简自豪\AppData\Roaming\Typora\typora-user-images\image-20230403190628364.png)]

在这里插入图片描述

​ session管理

​ 在HTTP通信中,维护客户端状态信息,让服务器能够知道当前通信的连接对应的是哪一个客户端。

服务器上为每一个客户端都创建一个session:sid,user,conn…客户端登录成功后,将对应创建的session的ssid通过cookie发送个客户端。

cookie:让客户端保存的信息,在下一次请求服务器的时候发送给服务器,客户端在下一次通信的时候,将ssid通过cookie发送给服务器,服务器在管理所有的session中,找到对应的session中,通过session就能知道当前对应的是哪一个客户端。

​ session管理:

  • 客户端登录成功则建立session
  • 客户端关闭连接时删除session
  • 获取连接对应的session

cookie与session详情请看:


前端模块

拿到一个网页模板,然后进行修改,最终完成所需的界面和操作。

开发环境搭建:

  1. 编译器升级

    yum install centos-release-scl-rh centos-release-scl

    yum install devtoolset-7-gcc devtoolset-gcc-c++\

    source/opt/rh/devtoolset-7/enable

    echo"source /opt/rh/devtoolset-7/enable">>~/.bashrc

  2. 安装jsoncpp

    yum -y install epel-release

    yum install jsoncpp-devel

  3. 安装mariadb数据库

    sudo yum install -y mariadb mariadb-server mariadb-client mariadb-devel

    vi /etc/my.cnf.d/server.cnf
    [mysqld]
    collation-server = utf8 general ci
    character-set-server = utf8
    init-connect='SET NAMES utf8!
    sql-mode = TRADITIONAL

    vi /etc/my.cnf.d/mysql-clients.cnf
    [mysql]
    default-character-set=utf8

    vi /etc/my.cnf.d/client.cnf
    [client]
    default-character-set = utf8
    systemctl restart mariad

  4. 安装mongoose库

​ 第三方库的认识:

jsoncpp:

​ Json::Value类:json的数据类,用于实例化一个管理所有序列化数据的对象

  1. Json::Value val;

    []的重载:val[“姓名”]=“张三”;val[“年龄”]=18

  2. append(Value&);数组元素的添加

    val[“成绩”].apend(55); val[“成绩”].apend(88);

  3. asString()将保存的元素以string形式返回

    std:cout<<val[“姓名”].asString()<<std::endl;

  4. alsnt()

    std::cout<<val[“年龄”].aslnt()<<std::endl;

  5. size() 数组成员的元素个数

    for(int i=0;i<val[“成绩”].size();i++)

    ​ std::cout<<val[“成绩”] [i].asFloat()<<std::endl;

Json序列化:

​ Json::StreamWriter:

	int write(Value const &root,std::ostream *sout)

Json::StreamWriterBuilder

Json::StreamWriter * newStreamWriter();
#include <iostream>
#include <string>
#include <sstream>
#include <memory>
#include <jsoncpp/json/json.h>
//序列化
void serialize(Json::Value &value, std::string &body)
{
    Json::StreamWriterBuilder swb;
    // 输出风格
    swb["commentStyle"] = "None";

    // Json::StreamWriter *sw = swb.newStreamWriter();
    // 智能指针
    std::unique_ptr<Json::StreamWriter> sw(swb.newStreamWriter());
    std::stringstream ss;
    int ret = sw->write(value, &ss);
    if (ret != 0)
    {
        std::cout << "serialize failed!\n";
        // delete sw;
        return;
    }
    body = ss.str();
    // delete sw;
    return;
}
int main()
{
    Json::Value val;
    val["姓名"] = "小明";
    val["年龄"] = 18;
    val["成绩"].append(45);
    val["成绩"].append(79);
    val["成绩"].append(67);cl

    std::string body;
    serialize(val, body);

    std::cout << body << std::endl;

    return 0;
}

在这里插入图片描述

反序列化:

Json::CharReader

bool parse(char *beginDoc,char *endDoc,Value *root,std::string *errs)

Json::CharReaderBuilder

chatReader* newCharReader();
#include <iostream>
#include <string>
#include <sstream>
#include <memory>
#include <jsoncpp/json/json.h>
// 反序列化
bool unserialize(std::string &body, Json::Value &value)
{
    Json::CharReaderBuilder crb;
    std::unique_ptr<Json::CharReader> cr(crb.newCharReader());

    std::string err;
    bool ret = cr->parse(body.c_str(), body.c_str() + body.size(), &value, &err);

    if (ret == false)
    {
        std::cout << "unserialize failed!" << std::endl;
        return false;
    }
    return true;
}


int main()
{
    Json::Value val;
    val["姓名"] = "小明";
    val["年龄"] = 18;
    val["成绩"].append(45);
    val["成绩"].append(79);
    val["成绩"].append(67);

    std::string body;
    serialize(val, body);

    std::cout << body << std::endl;

    Json::Value stu;
    unserialize(body, stu);
    std::cout << stu["姓名"].asString() << std::endl;
    std::cout << stu["年龄"].asInt() << std::endl;
    if (stu["成绩"].isArray())
    {
        for (int i = 0; i < stu["成绩"].size(); i++)
        {
            std::cout << stu["成绩"][i].asFloat() << std::endl;
        }
    }

    return 0;
}	

在这里插入图片描述

mysql数据库的操作

mysql数据库采用的是C/S架构

mysql的操作都是通过sql(structure query language)语句完成的。

库的操作:

​ 在mysql数据库中可以管理多个库,每个库可以单独存储自己的数据,mysql数据库是一个关系型数据库–以行列关系模型管理数据,在每一个库中以表作为存储单元。

  1. 创建一个库

    create database if not exists My_Test;

  2. 查看mysql都管理哪些库

    show databases;

  3. 选择要操作的库

    use My_Test;

  4. 删除一个库

    drop database My_Test;

数据类型:

​ int 整形

​ varchar(255) 字符串类型,中间的数字表示字段最大存储的字符个数。

​ decimal(4,2) 浮点型 4表示总的数字个数,2表示小数点后数字的个数 8.888->8.89 88.888->88.89 888.888->888.8

​ datatime 日期类型 “2023-04-11 11:38:03” now()表示当前时间

表的操作:

​ 创建表:creat table ifnot exists tbstu()

create table if not exists tbstu(

sn int,(primary key 主键约束:非空且唯一,创建主键索引(加快查询速度)),(auto_increment 自增属性:默认从1开始)

name varchar(32),

age int,

ch decimal(4,2),

en decimal(4,2));

​ 查看库中都有什么表:show tables;

​ 查看指定表的结构:desc tbstu;

​ 删除表:drop table tbstu;

在这里插入图片描述

数据操纵:增删改查

增:insert into

insert into tbstu values(null,'张三',18,99.5,39,"2005-6-1 16:58:39");
insert into tbstu values(null,'李四',0,0,0,now());

在这里插入图片描述

删:delete

delete from tbstu where name="张三";

改:update

update tbstu set age=40;(默认为所有成员进行修改)

update tbstu set age=20 where name="张三";

在这里插入图片描述

查:select

select* from tbstu;
select sn,name,from tbstu;

在这里插入图片描述
​ 查询60s内添加的用户

select *from tbstu where timestampdiff(second,brith,now())<60;

在这里插入图片描述


通过mysql的API接口,自己实现一个自己所需的客户端,完场数据库操作。

看手册

初始化:

mysql_init

mysql_real_connect

mysql_set_character_set

mysql_select_db

执行语句:

mysql_query

查询结果获取:

mysql_store_result

mysql_num_rows

mysql_num_fields

mysql_num_row

mysql_fecth_row

mysql_free_result

释放:

mysql_close

错误原因获取:

mysql_error
MYSQL *mysql_init(MYSQL *mysql);

功能:初始化mysql操作句柄,如果参数为NULL,则会动态申请空间然后进行初始化,并返回首地址。

返回值:成功返回句柄空间首地址,失败返回NULL

	MYSQL* mysql_real_connect(

MYSQL* mysql//mysql初始化返回的句柄首地址,

char *host,//mysql服务器IP地址

char *user,//访问mysql服务器的用户名,通常为root

char *passwd,//访问mysqld服务器的用户密码,""

char *dbname,//默认选择库的名称

int port,//mysql服务器的端口号,默认为0,表示3306

char* unix_socket,//可以为自己指定的套接字文件或管道,通常为NULL

int client_flag//选择标准,通常为0

)

功能:连接mysql服务器

返回值:成功返回句柄首地址,失败返回NULL

int mysql_select(MYSQL *mysql,char* dbname);

功能:选择要操作的库

返回值:成功返回0,失败返回错误编号

int mysql_set_character_set(MYSQL *mysql, char *encode);

功能:设置客户端字符集,最好与服务器保持一致 “utf8”

返回值:成功返回0,失败返回非0

int mysql_query(MYSQL,char* sql);

功能:执行sql语句

返回值:成功返回0,失败返回非0

MYSQL_RES* mysql_store_result(MYSQL* mysql);

功能:将查询结果,保存到本地

返回值:成功返回结果首地址,失败返回NULL

int mysql_num_rows(MYSQL_RES* res);

功能:获取结果集中所有查询结果的调速

返回值:查询结果条数

int mysql_num_fields(MYSQL_RES* res);

功能:获取结果集中一条数据有多少列

MYSQL_ROW mysql_fetch_row(MYSQL_RES* res)

功能:逐条取出结果集中每一条数据的查询结果

MYSQL_ROW char **(二级指针)

在这里插入图片描述

int mysql_free_result(MYSQL_RES *res);

功能:释放结果集

11…

int mysql_close(MYSQL *mysql);

释放mysql的操作句柄

char *mysql_error(MYSQL *mysql);

功能:通过句柄获取将上一次mysql接口操作失败的原因字符串

#include <iostream>
#include <mysql/mysql.h>

int main()
{
    // 初始化句柄
    MYSQL *mysql = mysql_init(NULL);
    if (mysql == NULL)
    {
        std::cout << "mysql init failed!\n";
        return -1;
    }
    // 连接服务器
    // mysql_real_connect(mysql,host,user,passwd,dbname,port,socket,flag)
    if (mysql_real_connect(mysql, "127.0.0.1", "root", "", "db888", 0, NULL, 0) == NULL)
    {
        std::cout << "connect server failed: " << mysql_errno(mysql) << std::endl;
        return -1;
    }
    // 设置字符集
    mysql_set_character_set(mysql, "utf8");
    // 选择数据库
    // mysql_select_db(mysql,"db888");

    // 执行语句
    // 插入://const char *sql = "insert into tbstu values(null,'孙八',19,65,73,now());";
    // 修改://const char* sql="update tbstu set age=100 where sn =2";
    // 删除://const char *sql = "delete from tbstu where name='李四'";
    // 查询:
    const char *sql = "select * from tbstu;";

    int ret = mysql_query(mysql, sql);
    if (ret != 0)
    {
        std::cout << sql << std::endl;
        std::cout << "query faild!"
                  << " " << mysql_error(mysql) << std::endl;
        return -1;
    }

    // 保存结果集到本地
    MYSQL_RES *res = mysql_store_result(mysql);
    if (res == NULL)
    {
        std::cout << "store result failed!"
                  << " " << mysql_error(mysql) << std::endl;
        return -1;
    }
    // 获取结果集条数,列数,
    int num_row = mysql_num_rows(res);
    int num_col = mysql_num_fields(res);
    // 根据条数和列数,遍历结果集
    for (int i = 0; i < num_row; i++)
    {
        MYSQL_ROW row = mysql_fetch_row(res);
        for (int j = 0; j < num_col; j++)
        {
            printf("%s\t", row[j]);
        }
        printf("\n");
    }
    // 释放结果集
    mysql_free_result(res);

    mysql_close(mysql);

    return 0;
}

makefile文件要链接库

mysql:mysql.cpp
        g++ -g -std=c++11 $^ -o $@ -L/usr/lib64/mysql/ -ljsoncpp -lmysqlclient
json:json.cpp
        g++ -std=c++11 $^ -o $@ -ljsoncpp


使用mongoose库搭建HTTP服务器以及Websocket服务器

http服务器收到的请求有两种:

​ 静态资源请求:首页index.html

​ 动态的功能请求:登录验证

一个用于搭建服务器的库:

不需要让用户关系服务器如何搭建,用户只关心针对收到的请求如何处理。

mongoose库中只需要设置的点主要有两个:

  • 静态资源根目录:告诉服务器静态资源文件存放在哪个目录下

    当服务器收到了静态资源请求,就会到目录下找到文件,读取数据进行响应。

  • 功能回调函数:告诉服务器功能性请求使用哪个函数进行处理

    当服务器收到功能性请求,只需要调用回调函数即可

enum {
  MG_EV_ERROR,       // Error                        char *error_message
  MG_EV_OPEN,        // Connection created           NULL
  MG_EV_POLL,        // mg_mgr_poll iteration        uint64_t *uptime_millis
  MG_EV_RESOLVE,     // Host name is resolved        NULL
  MG_EV_CONNECT,     // Connection established       NULL
  MG_EV_ACCEPT,      // Connection accepted          NULL
  MG_EV_TLS_HS,      // TLS handshake succeeded      NULL
  MG_EV_READ,        // Data received from socket    long *bytes_read
  MG_EV_WRITE,       // Data written to socket       long *bytes_written
  MG_EV_CLOSE,       // Connection closed            NULL
  MG_EV_HTTP_MSG,    // HTTP request/response        struct mg_http_message *
  MG_EV_HTTP_CHUNK,  // HTTP chunk (partial msg)     struct mg_http_message *
  MG_EV_WS_OPEN,     // Websocket handshake done     struct mg_http_message *
  MG_EV_WS_MSG,      // Websocket msg, text or bin   struct mg_ws_message *
  MG_EV_WS_CTL,      // Websocket control msg        struct mg_ws_message *
  MG_EV_MQTT_CMD,    // MQTT low-level command       struct mg_mqtt_message *
  MG_EV_MQTT_MSG,    // MQTT PUBLISH received        struct mg_mqtt_message *
  MG_EV_MQTT_OPEN,   // MQTT CONNACK received        int *connack_status_code
  MG_EV_SNTP_TIME,   // SNTP time received           uint64_t *epoch_millis
  MG_EV_USER         // Starting ID for user events
};
struct mg_http_message {
  struct mg_str method, uri, query, proto;             // Request/response line
  struct mg_http_header headers[MG_MAX_HTTP_HEADERS];  // Headers
  struct mg_str body;                                  // Body
  struct mg_str head;                                  // Request + headers
  struct mg_str chunk;    // Chunk for chunked encoding,  or partial body
  struct mg_str message;  // Request + headers + body
};

在这里插入图片描述

mongoose库:

​ 单线程+多路转接模型实现的服务器

​ 提前给监听连接设置好回调函数–业务处理回调函数

​ 开始监控事件

1.如果监听连接触发了可读事件,–过去新连接,给新连接设置回调函数(就是给监听套接字设置回调函数–业务处理函数),然后给新连接有加入到事件监控中

2.如果通信套接字触发了可读事件–

​ 1.接受socket数据

​ 2.对数据进行解析

​ 3.调用设置的回调函数进行

数据库访问操作的封装:

​ 思想:针对每一张表,都封装一个类

​ 一个类实例化的对象,可以提供对指定表中的数据的访问操作

​ 表:

​ 用户信息表:ID,用户名,昵称,密码,创建时间

​ 新增用户,用户登录,密码修改,昵称修改,删除用户,

​ 用户信息的获取:通过用户名,通过用户ID

​ 聊天信息表:

​ 新增聊天信息:

​ 获取历史聊天信息(30s):

数据库访问操作的封装:

​ 连接成功并初始化,执行语句,关闭句柄

JsonUtil
{
    serialize(value,body);//序列化
	unserialize(value,bdy);//反序列化
}
	
MysqlUtil
{

    mysql_create(host,user,pass,db,port)
	{	
        //1.初始化 
    	mysql_real_connect();//与系统上的MYSQL服务器建立连接
   	 	mysql_ecex(mysql,sql);
    	mysql_query(mysql,sql.c_ctr());//查询
    //成功返回0,失败返回错误编号
    
    //2.设置字符集
	}
}
UserTable
{
private:
    MYSQL* _mysql;
    mutex _mutex;
public:
 	UserTable(host,user,pass,db,port)
    {
        _mysql=MysqlUtil::mysql_create();
	}
    ~UserTable();
    insert(const::Vaule &user)
    {
        //将格式化的输出写入缓冲区中
        snprintf(sql,4095,INSERT_USER,
                user["..."].aCSting()/*获取c语言字符串集*/...);
        return MysqlUtil::mysql_esec(mysql,sql);
    }
    select_by_id()
    {
        snprintf(...);
        {
            //加锁,lock对象被释放后,会自动给解锁
            //加锁的意义:下面两步操作不是原子操作,二义性
            std::unique_lock<std::mutex> lock(_mutex);
            /*1.*/
            bool ret=MysqlUtil::mysql_exec(_mysql,sql);
            /*2.*/
            //返回MYSQL_RES结果集,失败返回NULL
            res=mysql_store_result(_mysql);
            
        }
	}
}
MsgTable
{
    //操作与UserTable类似
}

bt 调用函数栈

break 设置断点 run

p显示变量内容


业务处理模块

  1. 服务器的搭建:mogoose

    1. 定义服务器句柄

      struct mg_mgr mgr;
      
    2. 对句柄进行初始化

      mg_mgr_init(struct mg_mgr* mgr);
      
    3. 给句柄设置要绑定的监听地址,以及业务处理的回调函数

      mg_http_listen(struct mg_mgr* mgr,const char* addr,void(*entry)(struct mg_connection *c,int ev,void *msg,void *data),void *data);
      
    4. 开始服务器监听

      mg_mgr_poll(struct mg_mgr *mgr,int ms);
      

监听套接字的连接

HTTP连接

session:

0.登陆时候创建一个session

1.获取用户信息时,获取session(通过session_id来获取用户相关信息)

2.协议切换升级:HTTP-websocket

session的删除:

之前:在连接关闭时删除,但如果是短链接,将session删除后,客户端在建立一个连接,服务器端依然不知道客户端是谁,这样是不合理的,

所以,session与连接是解耦合的,两者是无关的,session创建的目的是让客户端的状态和连接脱离关系,因为http的短连接是无法通过持续的连接获取客户端状态,创建之后,不能再连接断开后。

解决:定时器

=mysql_store_result(_mysql);

    }
}

}
MsgTable
{
//操作与UserTable类似
}




bt 调用函数栈

break 设置断点 run

p显示变量内容

------

### 业务处理模块

1. 服务器的搭建:mogoose

   1. 定义服务器句柄

      ```C
      struct mg_mgr mgr;
      ```

   2. 对句柄进行初始化

      ```c
      mg_mgr_init(struct mg_mgr* mgr);
      ```

   3. 给句柄设置要绑定的监听地址,以及业务处理的回调函数

      ```C
      mg_http_listen(struct mg_mgr* mgr,const char* addr,void(*entry)(struct mg_connection *c,int ev,void *msg,void *data),void *data);
      ```

   4. 开始服务器监听

      ```C
      mg_mgr_poll(struct mg_mgr *mgr,int ms);
      ```

      

监听套接字的连接 

HTTP连接





session:

0.登陆时候创建一个session

1.获取用户信息时,获取session(通过session_id来获取用户相关信息)

2.协议切换升级:HTTP-websocket

session的删除:

之前:在连接关闭时删除,但如果是短链接,将session删除后,客户端在建立一个连接,服务器端依然不知道客户端是谁,这样是不合理的,

所以,session与连接是解耦合的,两者是无关的,session创建的目的是让客户端的状态和连接脱离关系,因为http的短连接是无法通过持续的连接获取客户端状态,创建之后,不能再连接断开后。

解决:定时器



  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Exile_001

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值