#网页IM

项目名称:网页IM即时通信
项目描述:实现通信系统,使用户能够通过浏览器进行用户的注册登录,进行即时聊天的系统
开发环境:Centos7.5-vim/g++/gdb/makefile/git

概要设计:采用mvc框架
model数据管理模块,使用mysql数据库进行用户信息管理,设计用户信息表tb_user 管理 用户数据,封装数据库表访问类TableUser实现用户信息的增删改查,用户验证以及用户是否存在
controller业务处理模块,搭建http服务器实现网络通信,设计通信接口进行系统的业务逻辑处理,静态网页,注册,登录,协议切换,聊天消息
view前端界面模块,使用html+css+js设计前端展示界面,注册登录界面和聊天界面

详细设计
数据管理模块,使用mysql数据库进行用户信息管理,设计用户信息表tb_user 管理 用户数据,封装数据库表访问类TableUser实现用户信息的增删改查,用户验证以及用户是否存在
设计用户信息表tb_user 管理 用户数据
id,用户名,密码,状态
db.sql
create database if not exists im_system;
use im_system;
create table if not exists tb_user(
  id int primary key auto_increment,
  name varchar(32) not null unique comment '用户名',
  passwd varchar(32) not null comment '密码',
  status varchar(8) not null comment '状态信息-offline/online'
);

mysql -uroot<db.sql

封装数据库表访问类TableUser实现用户信息的增删改查,用户验证以及用户是否存在
用户信息的插入,
bool Insert(const string &name, const string &passwd)
用户信息的删除,
bool Delete(const string &name)
用户状态的修改,
bool UpdateStatus(const string &name, const string &status)
用户密码的修改,
bool UpdatePasswd(const string &name, const string &passwd)
查询单个用户信息,通过用户名和json接收单个用户信息
bool SelectOne(const string &name, Json::Value *user)
查询所有用户信息,通过json接收所有用户信息
bool SelectAll(Json::Value *users)
用户信息的验证,用于登录
bool VerifyUser(const string &name, const string &passwd)
用户是否存在,用于注册时判断用户名是否被占用
bool Exists(const string &name)
im.hpp
#include <cstdio>
#include <iostream>
#include <sstream> //stringstream保存cookie
#include <mutex>//互斥锁
#include <list>//链表保存session
#include <mysql/mysql.h>//mysql
#include <jsoncpp/json/json.h>//json
#include "mongoose.h"


using namespace std;
//设计im命名空间,防止函数和变量命名冲突,防止命名污染
namespace im
{
//定义宏用于连接mysql服务器的参数设置
#define MYSQL_HOST "127.0.0.1"
#define MYSQL_USER "root"
#define MYSQL_PASS ""
#define MYSQL_DB "im_system"
//
#define ONLINE "online"
#define OFFLINE "offline"
  //封装数据库用户访问类
  class TableUser
  {
  public:
    //构造函数
    //完成数据库操作的初始化
    //使用参数列表进行初始化,mysql句柄默认为NULL
    TableUser() : _mysql(NULL)
    {
      //初始化mysql句柄
      _mysql = mysql_init(NULL);
      if (_mysql == NULL)
      {
        printf("init mysql instance failed!\n");
        exit(-1);//初始化失败退出
      }
      //连接mysql服务器
      if (mysql_real_connect(_mysql, MYSQL_HOST, MYSQL_USER, MYSQL_PASS, MYSQL_DB, 0, NULL, 0) == NULL)
      {
        printf("connect mysql server failed!\n");
        mysql_close(_mysql);
        exit(-1);
      }
      //设置当前客户端的字符集
      if (mysql_set_character_set(_mysql, "utf8") != 0)
      {
        printf("set client character failed:%s\n", mysql_error(_mysql));//打印错误信息
        mysql_close(_mysql);
        exit(-1);
      }
      //已经默认选择了数据库,不需要这个接口,切换时使用
      //选择操作的数据库
      //mysql_select_db(_mysql,MYSQL_DB);
    }
    
    //析构函数
    //完成数据库句柄的销毁
    ~TableUser()
    {
      if (_mysql)
        mysql_close(_mysql);
    }


    //用户信息的插入
    bool Insert(const string &name, const string &passwd)
    {
      //对密码进行MD5加密
#define INSERT_USER "insert tb_user value(null,'%s',MD5('%s'),'%s');"
      char tmp_sql[4096] = {0};
      //sprintf按照指定格式组织一个字符串放到tmp_sql缓冲区中
      sprintf(tmp_sql, INSERT_USER, name.c_str(), passwd.c_str(), OFFLINE);//默认为offline
      return QuerySql(tmp_sql);//执行语句
    }


    //用户信息的删除
    bool Delete(const string &name)
    {
#define DELETE_USER "delete from tb_user where name='%s';"
      char tmp_sql[4096] = {0};
      sprintf(tmp_sql, DELETE_USER, name.c_str());
      return QuerySql(tmp_sql);
    }


    //用户状态的修改
    bool UpdateStatus(const string &name, const string &status)
    {
#define UPDATE_USER_STATU "update tb_user set status='%s' where name='%s';"
      char tmp_sql[4096] = {0};
      sprintf(tmp_sql, UPDATE_USER_STATU, status.c_str(), name.c_str());
      return QuerySql(tmp_sql);
    }


    //用户密码的修改
    bool UpdatePasswd(const string &name, const string &passwd)
    {
      //对密码进行MD5加密
#define UPDATE_USER_PASS "update tb_user set passwd=MD5('%s') where name='%s';"
      char tmp_sql[4096] = {0};
      sprintf(tmp_sql, UPDATE_USER_PASS, passwd.c_str(), name.c_str());
      return QuerySql(tmp_sql);
    }


    //查询单个用户信息,通过用户名和json接收单个用户信息
    bool SelectOne(const string &name, Json::Value *user)
    {
#define SELECT_USER_ONE "select id,passwd,status from tb_user where name='%s';"
      char tmp_sql[4096] = {0};
      sprintf(tmp_sql, SELECT_USER_ONE, name.c_str());
      //执行语句和保存结果集并非原子操作,在多线程操作的时候可能会出问题
      _mutex.lock(); //加锁保护,将结果集保护起来,防止中间被打断
      if (QuerySql(tmp_sql) == false)
      {
        _mutex.unlock(); //失败解锁
        return false;
      }
      //执行成功,获取结果集到本地
      MYSQL_RES *res = mysql_store_result(_mysql);
      _mutex.unlock(); //正常解锁
      if (res == NULL)
      {
        printf("select one user store result failed:%s\n", mysql_error(_mysql));//打印错误原因
        return false;
      }
      //获取结果集中的行数
      int num_row = mysql_num_rows(res);
      if (num_row != 1)//!=1,因为获取的是单个用户,防止出现0的情况
      {
        printf("one user result count error!\n");
        mysql_free_result(res);
        return false;
      }
      for (int i = 0; i < num_row; i++)
      {
        //遍历结果集
        MYSQL_ROW row = mysql_fetch_row(res);
        //*的优先级比[]低,使用括号括起来
        (*user)["id"] = stoi(row[0]);
        (*user)["name"] = name.c_str();
        (*user)["passwd"] = row[1];
        (*user)["status"] = row[2];
      }
      mysql_free_result(res);
      return true;
    }


    //查询所有用户信息,通过json接收所有用户信息
    bool SelectAll(Json::Value *users)
    {
#define SELECT_ALL_USER "select id,name,passwd,status from tb_user;"
      _mutex.lock();
      if (QuerySql(SELECT_ALL_USER) == false)
      {
        _mutex.unlock();
        return false;
      }
      MYSQL_RES *res = mysql_store_result(_mysql);
      _mutex.unlock();
      if (res == NULL)
      {
        printf("select all user store result failed:%s\n", mysql_error(_mysql));
        return false;
      }
      int num_row = mysql_num_rows(res);
      for (int i = 0; i < num_row; i++)
      {
        MYSQL_ROW row = mysql_fetch_row(res);
        Json::Value user;
        user["id"] = stoi(row[0]);
        user["name"] = row[1];
        user["passwd"] = row[2];
        user["status"] = row[3];
        users->append(user);
      }
      mysql_free_result(res);
      return true;
    }


    //用户信息的验证,用于登录
    bool VerifyUser(const string &name, const string &passwd)
    {
      //对密码进行MD5加密,MD5是sql中的一个聚合函数
#define VERIFY_USER "select *from tb_user where name='%s'and passwd=MD5('%s');"
      char tmp_sql[4096] = {0};
      sprintf(tmp_sql, VERIFY_USER, name.c_str(), passwd.c_str());
      _mutex.lock();
      if (QuerySql(tmp_sql) == false)
      {
        _mutex.unlock();
        return false;
      }
      MYSQL_RES *res = mysql_store_result(_mysql);
      _mutex.unlock();
      if (res == NULL)
      {
        printf("verify user store result failed:%s\n", mysql_error(_mysql));
        return false;
      }
      int num_row = mysql_num_rows(res);
      if (num_row != 1)
      {
        printf("verify user failed!\n");
        return false;
      }
      mysql_free_result(res);
      return true;
    }


    //用户是否存在,用于注册时判断用户名是否被占用
    bool Exists(const string &name)
    {
#define EXISTS_USER "select *from tb_user where name='%s';"
      char tmp_sql[4096] = {0};
      sprintf(tmp_sql, EXISTS_USER, name.c_str());
      _mutex.lock();
      if (QuerySql(tmp_sql) == false)
      {
        _mutex.unlock();
        return false;
      }
      MYSQL_RES *res = mysql_store_result(_mysql);
      _mutex.unlock();
      if (res == NULL)
      {
        printf("exists user store result failed:%s\n", mysql_error(_mysql));
        return false;
      }
      int num_row = mysql_num_rows(res);
      if (num_row != 1)
      {
        printf("have no user!\n");
        mysql_free_result(res);
        return false;
      }
      mysql_free_result(res);
      return true;
    }


  private:
    //封装语句的执行,代码复用,设置为private
    bool QuerySql(const string &sql)
    {
      if (mysql_query(_mysql, sql.c_str()) != 0)
      {
        printf("query sql:[%s] failed:%s\n", sql.c_str(), mysql_error(_mysql));
        return false;
      }
      return true;
    }


  private:
    MYSQL *_mysql;//数据库的操作句柄
    mutex _mutex;
  };
}

im.cpp
#include "im.hpp"


void sql_test()
{
  //实例化一个user对象
  im::TableUser user;


  //测试用户信息的插入
  user.Insert("lisi","111111");
  user.Insert("zhangsan","111111");


  //测试修改密码
  user.UpdatePasswd("lisi","111112");//1


  //测试验证用户信息
  cout<<user.VerifyUser("zhangsan","111111")<<endl;//1


  //测试验证用户是否存在
  cout<<user.Exists("wangwu")<<endl;//have no user;0;
  cout<<user.Exists("lisi")<<endl;//1
  //测试查询单个用户信息
  Json::Value val;
  user.SelectOne("zhangsan",&val);
  Json::StyledWriter writer;
  cout<<writer.write(val)<<endl;


  //测试查询所有用户信息
  Json::Value val;
  user.SelectAll(&val);
  Json::StyledWriter writer;
  cout<<writer.write(val)<<endl;


  //测试用户信息的删除
  user.Delete("zhangsan");
}


int main(int argc, char *argv[])
{
  sql_test();
  return 0;
}

Makefile
im:im.cpp im.hpp
    g++ -std=c++11 $^ -o $@ -L/usr/lib64/mysql -lpthread -lmysqlclient -ljsoncpp
#-L/usr/lib64/mysql
#指定mysql库文件的路径,这个路径不是默认路径,所以需要显式指定

make
./im

业务处理模块,搭建http服务器实现网络通信,设计通信接口, 对各个请求进行对应的业务处理,静态网页+注册+登录+协议切换+聊天消息
1.网络通信模块,实现网络通信
http协议是一种简单的请求响应协议,客户端发送请求,服务器针对请求进行响应,在实现网页IM聊天中,一个用户发送消息后,其他用户都需要能够获取到这条消息,如果使用http协议实现比较麻烦,因为每个用户都需要每隔几秒钟从服务器上拉一下当前的聊天信息,因此网页IM系统使用websocket协议实现聊天信息的推送
http协议只能轮询不断发送请求获取响应
websocket协议允许服务端主动向客户端推送数据,浏览器和服务器只需要完成一次握手,两者之间就可以创建持久性的连接,并进行双向数据传输
websocket通过HTTP/1.1协议的101状态码进行握手
使用mongoose库搭建http服务器,mongoose库支持http协议升级
2.业务处理模块, 静态网页+注册+登录+协议切换+聊天消息
静态网页,注册,登录功能通过HTTP协议实现传输
聊天消息通过websocket协议进行传输
接口设计
1.静态网页,正文返回页面数据
2.注册功能,检测用户名是否已经被占用,如果没有占用则添加数据库,然后进行响应
3.登录功能,数据库验证登录信息,验证成功则创建会话,然后进行响应
登录成功之后,客户端请求聊天页面进行聊天,但是聊天就必须知道是谁在发消息,因此需要进行客户端的状态维护,使用cookie+session进行状态管理
用户登录成功之后,服务端为客户端创建session会话,记录用户名以及对应状态信息,生成session_id,通过cookie将session_id和name返回给客户端
浏览器客户端下次请求的时候,就会自动将这些cookie信息发送给服务端,服务端收到后就可以分辨消息是谁发送的
4.websocket握手请求,确认升级websocket协议
5.使用websocket协议进行聊天消息的传输和推送
网页IM功能流程
1.客户端浏览器请求注册页面(静态页面请求)---返回页面文件
2.填写用户注册信息,点击注册提交(提交用户名和密码到服务器)---检测用户名是否已经被占用,如果没有占用则添加数据库,然后进行响应
3.客户端浏览器请求登录页面(静态页面请求)--返回页面文件
4.界面中填写登录信息,点击登录提交(提交用户名和密码到服务器)---数据库验证登录信息,验证成功则创建会话,然后进行响应
5.登录成功,客户端浏览器请求聊天界面(静态网页请求)---返回页面文件
6.建立websocket聊天通道(http协议切换,websocket握手请求)---确认升级websocket协议
7.使用websocket协议进行聊天通信发送消息---将聊天消息广播给其他已经建立了websocket的用户
im.hpp
struct session
  {
    string name;
    string status;
    uint64_t session_id;
    double login_time;
    double last_atime;
    struct mg_connection *conn;//哪个连接发送的消息
  };


  class IM
  {
  public:
    //析构函数
    ~IM()
    {
      ///关闭所有连接,并释放所有资源
      mg_mgr_free(&_mgr);
    }


    //初始化
    static bool Init(const string &port = "9000")
    {
      //实例化一个对象
      _tb_user = new TableUser();
      //初始化句柄
      mg_mgr_init(&_mgr);
      string addr = "0.0.0.0:";
      addr += port;
      //创建http监听连接
      //操作句柄 监听地址 回调函数 传入参数
      _lst_http = mg_http_listen(&_mgr, addr.c_str(), callback, &_mgr);
      if (_lst_http == NULL)
      {
        cout<<"http listen failed!\n";
        return false;
      }
      return true;
    }


    //程序运行
    static bool Run()
    {
      while (1)
      {
        //轮询监听
        mg_mgr_poll(&_mgr, 1000);
      }
      return true;
    }


  private:
    //分割字符串
    //Cookie:SESSION_ID-12312535; NAME=zhangsan; path=/
    static int Split(const string &str,const string &sep,vector<string>*list){
      //string::substr(),从pos位置开始截取指定长度字符串
      //string::find(),从pos位置开始找_s分隔符,返回所在位置
      int count=0;
      size_t pos=0,idx=0;//idx起始找寻位置
      while(1){
        pos=str.find(sep,idx);//从str字符串的idx位置开始找sep分隔符
        if(pos==string::npos){
          break;
        }
        list->push_back(str.substr(idx,pos-idx));
        idx=pos+sep.size();
        count++;
      }
      if(idx<str.size()){
        list->push_back(str.substr(idx));
        count++;
      }
      return count;
    }
    
    //获取cookie,通过分割字符串获取
    static bool GetCookie(const string &cookie,const string &key,string *val){
      vector<string>list;
      int count=Split(cookie,"; ",&list);
      for(auto s:list){
        vector<string>arry_cookie;
        Split(s,"=",&arry_cookie);
        if(arry_cookie[0]==key){
          *val=arry_cookie[1];
          return true;
        }
      }
      return false;;
    }
    
    //创建session
    static void CreateSession(struct session *s,struct mg_connection*c,const string &name){
      s->name=name;
      s->session_id=(uint64_t)(mg_time()*1000000);//session_id唯一
      s->login_time=mg_time();
      s->last_atime=mg_time();
      s->conn=c;
      return;
    }
    
    //删除session
    static void DeleteSession(struct mg_connection*c){
      auto it=_list.begin();
      for(;it!=_list.end();it++){
        if(it->conn==c){
          cout<<"delete session:"<<it->name<<endl;
          _list.erase(it);
          return;
        }
      }
      return;
    }
    
    //获取session-byconn
    static struct session *GetSessionByConn(struct mg_connection*c){
      auto it=_list.begin();
      for(;it!=_list.end();it++){
        if(it->conn==c){
          return&(*it);
        }
      return NULL;
      }
    }


    //获取session-byname
    static struct session *GetSessionByName(const string &name){
      auto it=_list.end();
      for(;it!=_list.end();it++){
        if(it->name==name){
          return&(*it);
        }
      return NULL;
      }
    }


    //注册
    static bool reg(struct mg_connection *c, struct mg_http_message *hm)
    {
      int status = 200;
      string header = "Content-Type:application/json\r\n";
      //从正文中获取提交的用户信息,json格式的字符串
      string body;
      body.assign(hm->body.ptr, hm->body.len);
      //解析进行反序列化得到用户名和密码
      Json::Value user;
      Json::Reader reader;
      bool ret = reader.parse(body, user);
      if (ret == false)
      {
        status = 400;
        mg_http_reply(c, status, header.c_str(), "{\"reason\":\"请求格式错误\"}");
        return false;
      }
      //判断这个用户名是否已经被占用
      ret = _tb_user->Exists(user["name"].asString());
      if (ret == true)
      {
        status = 400;
        mg_http_reply(c, status, header.c_str(), "{\"reason\":\"用户名被占用\"}");
        return false;
      }
      //将用户信息插入到数据库中
      ret = _tb_user->Insert(user["name"].asString(), user["passwd"].asString());
      if (ret == false){
        status = 500;
        mg_http_reply(c, status, header.c_str(), "{\"reason\":\"数据库访问错误\"}");
        return false;
      }
      mg_http_reply(c, status, header.c_str(), "{\"reason\":\"注册成功\"}");
      return true;
    }


    //登录
    static bool login(struct mg_connection *c, struct mg_http_message *hm)
    {
      int rsp_status = 200;
      string rsp_body = "{\"reason\":\"登录成功\"}";
      string rsp_header = "Content-Type:application/json\r\n";
      string req_body;
      req_body.assign(hm->body.ptr, hm->body.len);
      Json::Value user;
      Json::Reader reader;
      bool ret = reader.parse(req_body, user);
      if (ret == false)
      {
        rsp_status = 400;
        rsp_body = "{\"reason\":\"请求格式错误\"}";
        mg_http_reply(c, rsp_status, rsp_header.c_str(), rsp_body.c_str());
        return false;
      }
      //进行验证,验证用户名和密码
      ret = _tb_user->VerifyUser(user["name"].asString(), user["passwd"].asString());
      if (ret == false)
      {
        rsp_status = 403;
        rsp_body = "{\"reason\":\"用户名或密码错误\"}";
        mg_http_reply(c, rsp_status, rsp_header.c_str(), rsp_body.c_str());
        return false;
      }
      //用户登录成功之后创建session,以及设置客户端cookie,并且设置用户处于在线状态
      //#
      //设置用户处于在线状态
      ret=_tb_user->UpdateStatus(user["name"].asString(),ONLINE);
      if(ret==false){
        rsp_status = 500;
        rsp_body="{\"reason\":\"修改用户状态出错\"}";
        mg_http_reply(c, rsp_status, rsp_header.c_str(), rsp_body.c_str());
        return false;
      }
      //登录成功后创建session
      struct session s;
      CreateSession(&s,c,user["name"].asString());
      _list.push_back(s);
      stringstream cookie;
      cookie<<"Set-Cookie:SESSION_ID="<<s.session_id<<"; path=/\r\n";
      cookie<<"Set-Cookie:NAME="<<s.name<<"; path=/\r\n";
      rsp_header+=cookie.str();
      //#
      mg_http_reply(c, rsp_status, rsp_header.c_str(), rsp_body.c_str());
      return true;
    }


    //进行广播,遍历链表就能获取所有的连接
    static void Broadcast(const string &msg)
    {
      struct mg_connection *c;
      //遍历链表
      for (c = _mgr.conns; c != NULL; c = c->next)
      {
        //如果c是一个websocket连接,则调用mg_ws_send接口发送数据
        if (c->is_websocket){
          mg_ws_send(c, msg.c_str(), msg.size(), WEBSOCKET_OP_TEXT);
          //WEBSOCKET_OP_TEXT,选项标志位
        }
      }
      return;
    }


    //回调函数,设置为static函数,没有this指针,因为回调函数只有4个参数
    //回调函数:当前连接,触发事件,处理完毕的数据,传入参数
    static void callback(struct mg_connection *c, int ev, void *ev_data, void *fn_data)
    {
      //http
      struct mg_http_message *hm = (struct mg_http_message *)ev_data;
      //websocket
      struct mg_ws_message *wm = (struct mg_ws_message *)ev_data;    
      switch (ev)
      {
      case MG_EV_HTTP_MSG:
        //注册的提交表单数据请求
        if (mg_http_match_uri(hm, "/reg"))
        {
          //对注册封装一个函数reg
          reg(c, hm);
        }
        //登录的提交表单数据请求
        else if (mg_http_match_uri(hm, "/login"))
        {
          //对登录封装一个函数login
          login(c, hm);
        }
        //websocket的握手请求
        //在建立websocket聊天通道的时候,应该检测这个客户端是否已经登录
        //获取到请求头部中的cookie,通过cookie中的session或者name查找session
        else if (mg_http_match_uri(hm, "/websocket"))
        {
          //GetCookie(const string &cookie,const string &key,string *val)
          struct mg_str* cookie_str=mg_http_get_header(hm,"Cookie");//hm-头部信息
          if(cookie_str==NULL){
            //现在处于未登录状态,未登录用户
            //c++11,R"()",括号中的数据是一个原始字符串,没有特殊含义
            string body=R"({"reson":"未登录"})";
            string header="Content-Type:application/json\r\n";
            mg_http_reply(c,403,header.c_str(),body.c_str());
            return;
          }
          string tmp;
          tmp.assign(cookie_str->ptr,cookie_str->len);
          string name;
          GetCookie(tmp,"NAME",&name);
          string msg=name+" 加入聊天室...welcome!";
          Broadcast(msg);
          mg_ws_upgrade(c, hm, NULL);
        }
        //静态页面请求
        //除了登录界面,过来的时候,都应该检测一下Cookie,判断是否登录成功了
        //如果没有检测到session,则应该跳转到登录页面
        else
        {
          if (hm->uri.ptr!= "/login.html")
          {
            //获取一下cookie,根据name找session,没找到就意味着没有登录,
            //但是这里存在一个问题,login.html依赖的其他静态资源(图片,css代码),
            //在没有登录成功的状态下,就获取不到这些资源


          }
          struct mg_http_serve_opts opts = {.root_dir = "./web_root"};
          mg_http_serve_dir(c, hm, &opts);
        }
        break;
      //收到一条聊天消息进行广播
      case MG_EV_WS_MSG:
      {
        string msg;
        msg.assign(wm->data.ptr, wm->data.len); //wm是已经解析好的websocket消息
        Broadcast(msg);
      }
        break;
      //连接断开
      //当一个连接断开的时候,删除当前用户session,设置用户为下线状态
      case MG_EV_CLOSE:
      //加个{}保证局部变量不会超出作用域
      {
        struct session *ss=GetSessionByConn(c);
        if(ss!=NULL){
          string msg=ss->name+"退出聊天室...";
          Broadcast(msg);
          _tb_user->UpdateStatus(ss->name,OFFLINE);
          DeleteSession(c);
        }
      }
        break;
      default:
        break;
    }
    return;
  }


  private:
    string _addr; //监听地址信息  
    static TableUser *_tb_user;//定义一个数据库表访问类的指针
    static struct mg_mgr _mgr;//句柄              
    static struct mg_connection *_lst_http;//监听连接
    //使用链表保存session-需要频繁插入删除-不用map和vector-使用list快速插入删除
    static list<struct session> _list;//cookie需要频繁插入删除,所以用链表     
};


//在类外进行static成员的初始化
TableUser* IM::_tb_user = NULL;
struct mg_mgr IM::_mgr;
struct mg_connection* IM::_lst_http = NULL;
list<struct session> IM::_list;

im.cpp
#include "im.hpp"
int main(int argc, char *argv[])
{
  // sql_test();
  im::IM im_server;//实例化一个对象
  im_server.Init();//初始化
  im_server.Run();//运行
  return 0;
}

Makefile
im:im.cpp im.hpp mongoose.c
    g++ -std=c++11 $^ -o $@ -L/usr/lib64/mysql -lpthread -lmysqlclient -ljsoncpp

make
./im

前端界面模块,使用html+css+js设计前端展示界面,即注册登录界面和聊天界面
ajax,向服务器发送请求,得到响应后渲染页面,相当于 在浏览器后台创建了一个http客户端,用来和进行服务器数据传输
login.html
<body>
    <div id="bg">
        <!-- //vue对象,略过背景 -->
        <div id="app">
            <div id="login_wrap">
                <div id="login"><!-- 登录注册切换动画 -->
                    <div id="status">
                        <!-- //设置login/sigin显示其一 -->
                        <i style="top: 0" v-show="show_flag=='login'">login</i>
                        <i style="top: 0" v-show="show_flag=='sigin'">sigin</i>
                    </div>
                    <span>
                        <form action="post">
                            <!-- //注册和登录都会有 -->
                            <p class="form">
                                <!-- //数据双向绑定 -->
                                <input type="text" id="user" placeholder="用户名" v-model="username">
                            </p>
                            <p class="form">
                                <!-- //数据双向绑定 -->
                                <input type="password" id="passwd" placeholder="密码" v-model="password">
                            </p>
                            
                            <!-- //sigin显示 -->
                            <p class="form" v-show="show_flag=='sigin'">
                                <!-- //数据双向绑定 -->
                                <input type="password" id="confirm-passwd" placeholder="确认密码" v-model="confirm_password">
                            </p>
                            
                            <!-- //设置点击事件 -->
                            <input type="button" value="登录" class="btn"  style="margin-right: 20px;" v-on:click="login()">
                            <input type="button" value="注册" class="btn"  id="btn" v-on:click="sigin()">
                        </form>
                    </span>
                </div>


                <div id="login_img"><!-- 图片绘制框 -->
                    <span class="circle">
                        <span></span>
                        <span></span>
                    </span>
                    <span class="star">
                        <span></span>
                        <span></span>
                        <span></span>
                        <span></span>
                        <span></span>
                        <span></span>
                        <span></span>
                        <span></span>
                    </span>
                    <span class="fly_star">
                        <span></span>
                        <span></span>
                    </span>
                    <p id="title">CLOUD</p>
                </div>
            </div>
        </div>
    </div>
</body>


<!-- //vue.js -->
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
<!-- 链接jquery ajax -->
<script src="https://cdn.staticfile.org/jquery/1.10.2/jquery.min.js"></script>
<script>
    //创建vue对象
    var app = new Vue({
        el: '#app',
        data: {
            show_flag:"login",//login-登录显示,sigin-注册显示
            username:"",
            password:"",
            confirm_password:""
        },
        methods:{
            //设置sigin注册点击事件
            sigin:function(){
                //登录状态下点击注册显示注册界面
                if(this.show_flag=="login"){
                    this.show_flag="sigin";
                    //切换页面清空输入框
                    this.username="";
                    this.password="";
                    this.confirm_password="";
                //注册状态下,点击注册就是数据提交
                }else{
                    //数据双向绑定
                    if(this.username.lenght<6){
                        alert("用户名长度不能低于6个字符");
                        return;
                    }
                    if(this.password.length<6){
                        alert("密码长度不能低于6个字符");
                        return;
                    }
                    if(this.password!=this.confirm_password){
                        alert("密码和确认密码不一致");
                        return;
                    }
                    var userinfo={
                        name:this.username,
                        passwd:this.password
                    }
                    console.log("发起注册请求到服务器");//打印日志
                    $.ajax({
                        type:"post",//请求方法
                        url:"/reg",//资源路径
                        //正文数据
                        data:JSON.stringify(userinfo),//js对象转为json格式字符串
                        context:this,//这里的this就是vue对象
                        //success回调函数
                        success:function(data,status,xhr){
                            alert("注册成功");
                            this.show_flag="login";//注册成功跳转到登录界面
                            //清空界面信息
                            this.username="";
                            this.password="";
                            this.confirm_password="";
                        },
                        error:function(xhr){
                            alert("注册失败:"+xhr.status+xhr.responseText);//打印状态码和响应正文
                            //清空界面信息
                            this.username="";
                            this.password="";
                            this.confirm_password="";
                        }
                    })
                }
            },
            //设置login登录点击事件
            login:function(){
                //切换
                if(this.show_flag=="sigin"){
                    this.show_flag="login";
                    //切换页面清空输入框
                    this.username="";
                    this.password="";
                    this.confirm_password="";
                //登录数据提交
                }else{
                    //数据双向绑定
                    //username是vue对象中的数据,函数内部认为是访问局部变量了,所以加上this
                    if(this.username.lenght<6){
                        alert("用户名长度不能低于6个字符");
                        return;
                    }
                    if(this.password.length<6){
                        alert("密码长度不能低于6个字符");
                        return;
                    }
                }
                var userinfo={
                        name:this.username,
                        passwd:this.password
                }
                $.ajax({
                    type:"post",//请求方法
                    url:"/login",//资源路径
                    data:JSON.stringify(userinfo),
                    context:this,//这里的this是vue对象
                    success:function(data,status,xhr){
                        alert("登录成功");
                        window.location.href="/index.html";//重新跳转一个新界面,跳转到聊天页面
                    },
                    error:function(xhr){
                        alert("登录失败:"+xhr.status+xhr.responseText);
                    }
                })
            }
        }
    })
</script>

index.html
<body>
  <div id="app">
    <div class="content">
      <h1>welcome to syc's IM system</h1>


      <p>
      </p>


      <div id="messages">
      </div>


      <p>
        <!-- //v-model双向绑定send_msg -->
        <input type="text" id="send_input" v-model="send_msg" />
        <!-- //设置点击事件 -->
        <button id="send_button" v-on:click="send()"> 发送 </button>
        <button id="quit_button" v-on:click="quit()"> 退出 </button>
      </p>
    </div>
  </div>
</body>
<!-- //vue.js -->
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
<!-- //ajax -->
<script src="https://cdn.staticfile.org/jquery/1.10.2/jquery.min.js"></script>
<script>
//创建一个vue对象
var app = new Vue({
        //vue对象选择器
        el: '#app',
        data: {
          send_msg:"",//发送的消息
          websock:null,
          real_msg:""
        },
        methods:{
          //初始化
          init:function(){
            var ws_url="ws://"+window.location.host+"/websocket";//主机地址+请求资源路径
            this.websock=new WebSocket(ws_url);//new一个websocket对象,创建一个websocket连接
            //设置回调函数
            this.websock.onopen=this.wsonopen;//连接建立成功
            this.websock.onmessage=this.wsonmessage;//收到消息
            this.websock.onerror=this.wsonerror;//出错
            this.websock.onclose=this.wsonclose;//连接关闭
          },
          //websocket连接建立成功后触发的回调函数
          wsonopen:function(){
            alert("聊天通道建立成功")
          },
          //通信发生错误时触发的回调函数
          wsonerror:function(){
            alert("通信错误")
            this.init();//出错重新初始化
          },
          //连接关闭时触发的回调函数
          wsonclose:function(){
            alert("连接关闭")
          },
          //收到消息后触发的回调函数,e.data就是收到的数据
          wsonmessage:function(e){
            var com_div=document.createElement("div");
            com_div.innerHTML=e.data;
            var html_div=document.getElementById("messages");//获取message控件
            html_div.appendChild(com_div);//添加一个子标签
          },
          //分割字符串
          get_cookie_name:function(){
            var cookie=document.cookie;
            var cookie_arry=cookie.split("; ");
            for(var i=0;i<cookie_arry.length;i++){
              var arry=cookie_arry[i].split("=");
              if(arry[0]=="NAME"){
                return arry[1];
              }
            }
            return "匿名";
          },
          //send点击事件
          send:function(){
            console.log("call send function");//打印日志
            if(this.send_msg.length==0){
              alert("消息不能为空")
              return;
            }
            var username=this.get_cookie_name();
            this.real_msg="<p>"+username+":"+this.send_msg+"</p>";//将消息组织为一个html标签
            this.websock.send(this.real_msg);//发送
            this.send_msg="";
          },
          //quit点击事件
          quit:function(){
            this.websock.close();//关闭websocket连接
            window.location.href="/login.html"//退出返回登录界面
          }
        }
      });
      app.init();//vue对象创建成功后,调用init接口,创建websocket连接
</script>

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值