项目:即时群聊通信系统

项目简介

● 首先,使用开源websocket框架mongoose编写网页版本的群聊即时通信工具
● 然后,先完成基本的聊天逻辑,再能够让我们访问数据库,访问我们之前所建立的表;
● 最后,完成登录逻辑。其中,还要涉及到对cookie和session的理解以及http+mongoose+session+mysql+jsoncpp的技术的构成,在代码中进行添加,完成IM工具。

项目技术点

  • C++11 STL
  • http协议
  • websocket协议
  • session和cookie理解
  • mysql c connect
  • 登录注册,session管理
  • mongoose框架理解
  • jsoncpp

http && websocket协议

  • HTTP只能被动接受用户的请求,而WebSocket可以主动向服务器推送消息
  • WebSocket是应用层协议,是TCP/IP协议的子集,可实现全双工双向通信,此功能需要浏览器与服务器的支持。
  • WebSocket协议的建立需要先借助HTTP协议,在服务器返回101状态码之后,就可以进行websocket全双工双向通信了,就没有HTTP协议什么事情了。

客户端请求

   Get / HTTP/1.1
   Upgrade:websocket
   //Upgrade必须设置为WebSocket,表示在取得服务器响应之后,使用HTTP升级将HTTP协议转换(升级)为WebSocket协议
   Connection:Upgrade
   //Connection必须设置为Upgrade,表示客户端希望连接升级
   Host:example.com
   origin:http://example.com
   sec-WebSocket-Key:sN9cRrP/n9NdMgdcy2VJFQ==
   //随机字符串,用于验证协议是否为WebSocket协议而非HTTP协议
   sec-WebSocket-Version:13

服务器回应

   HTTP/1.1 101 Switching Protocols
   //101状态码表示升级协议,在返回101状态码后,HTTP协议完成工作,转换为WebSocket协议。此时就可以进行全双工双向通信
   Upgrade:websocket
   Connection:Upgrade
   sec-WebSocket-Accept:fFBooB7FAkLlXgRSzOBT3v4hq5s=
   //根据Sec-WebSocket-Accept和特殊字符串计算,验证协议是否为WebSocket协议 
   sec-Web

websocket框架mongoose:嵌入式Web服务器/嵌入式网络库

定义

  • Mongoose是c语言写成的网络库
  • 它为TCP、UDP、HTTP、WebSocket、CoAP、MQTT实现了事件驱动型的非阻塞 api

特性:

  • 具有跨平台、原生支持PicoTCP的嵌入式TCP/IP协议栈、支持LWIP嵌入式TCP/IP协议栈;
  • 单线程、异步、非阻塞核心与简单的基于事件的API特性
  • 使嵌入式网络编程快速,健壮,轻松。

代码实现

1.mongoose基本使用

#include "mongoose.h" 
static sig_atomic_t s_signal_received = 0; 
static const char *s_http_port = "8000"; 
static struct mg_serve_http_opts s_http_server_opts; 
 
static void signal_handler(int sig_num) {   
signal(sig_num, signal_handler);  // Reinstantiate signal handler   
s_signal_received = sig_num; 
} 
 
static int is_websocket(const struct mg_connection *nc) { 
 return nc->flags & MG_F_IS_WEBSOCKET; //检测链接是否是websocket的长链接 
 }
 static void broadcast(struct mg_connection *nc, const struct mg_str msg) {
 //将消息msg通过链接*nc发送出去。   
 struct mg_connection *c;   
 char buf[500];   
 char addr[32];   
 mg_sock_addr_to_str(&nc->sa, addr, sizeof(addr),MG_SOCK_STRINGIFY_IP | MG_SOCK_STRINGIFY_PORT);//我们不用 
 
 snprintf(buf, sizeof(buf), "%s %.*s", addr, (int) msg.len, msg.p);//字符串格式化输出   
 printf("%s\n", buf); /* Local echo. */   
 for (c = mg_next(nc->mgr, NULL); c != NULL; c = mg_next(nc->mgr, c)) {
 //遍历事件管理器链接的头部     
 //if (c == nc) continue; //这里是不会发给自己,我们要将消息推送给自己,注释掉即可     
 mg_send_websocket_frame(c, WEBSOCKET_OP_TEXT, buf, strlen(buf));   
 } 
 } 
 
static void ev_handler(struct mg_connection *nc/*那个链接有事件*/,\
                        int ev/*什么事件*/, \ 
                        void *ev_data/*对应事件相关的数据*/) {   
 switch (ev) {     
  case MG_EV_WEBSOCKET_HANDSHAKE_DONE: { //websocket握手事件完成      
  /* New websocket connection. Tell everybody. */       
  broadcast(nc, mg_mk_str("++ joined")); //给所有链接广播消息       
  break;     
  }     
  case MG_EV_WEBSOCKET_FRAME: { //正常的websocket数据       
  struct websocket_message *wm = (struct websocket_message *) ev_data;       
  /* New websocket message. Tell everybody. */       
  struct mg_str d = {(char *) wm->data, wm->size};       
  broadcast(nc, d);
  break;     
  }     
  case MG_EV_HTTP_REQUEST: { //正常的http请求       
  mg_serve_http(nc, (struct http_message *) ev_data, s_http_server_opts);       
  break;     
  }     
  case MG_EV_CLOSE: {       /* Disconnect. Tell everybody. */       
  if (is_websocket(nc)) {         
  broadcast(nc, mg_mk_str("-- left"));       
  }       
  break;     
  }   
  }
} 
 
int main(void) {   
struct mg_mgr mgr; //事件管理器   
struct mg_connection *nc;
//捕捉退出信号,程序退出   
signal(SIGTERM, signal_handler);    
signal(SIGINT, signal_handler);
mg_mgr_init(&mgr, NULL); //创建并初始化事件管理器 
 
 nc = mg_bind(&mgr, s_http_port, ev_handler);//绑定端口号,设置回调函数,生成监听链接,类比                   
 //listen socket,只不过框架将链接(fd)进行了封装                                               
 //所有后续链接,共享同一 ev_handler   
 mg_set_protocol_http_websocket(nc);//设置监听链接,支持websocket        
 s_http_server_opts.document_root = ".";  //设置服务器当前工作目录   
 s_http_server_opts.enable_directory_listing = "yes"; 
 
  printf("Started on port %s\n", s_http_port);   
  while (s_signal_received == 0) {     
  mg_mgr_poll(&mgr, 200); //进入事件监听,底层采用select进行事件监听,我们只要知道,有IO事件到来
//时,会触发我们之前设置的回调函数ev_handler   
}   
  mg_mgr_free(&mgr); 
  return 0; 
  } 

2.理解mongoose框架

struct mg_mgr

 //关于事件管理器,我们只要知道它是将所有mg_connection使用链表链接起来即可  
  struct mg_mgr {          
    struct mg_connection *active_connections;//可以理解成链表头部  
  #if MG_ENABLE_HEXDUMP  
    const char *hexdump_file; /* Debug hexdump file path */  
  #endif  
  #if MG_ENABLE_BROADCAST  
    sock_t ctl[2]; /* Socketpair for mg_broadcast() */  
  #endif  
    void *user_data; /* User data */  
    int num_ifaces;   
    int num_calls;  
    struct mg_iface **ifaces; /* network interfaces */  
    const char *nameserver;   /* DNS server to use */  
  };

struct mg_connection

   struct mg_connection { 
   struct mg_connection *next, *prev; /* 双链表组织所有链接 */ 
   struct mg_connection *listener;    /* 指向监听链接 */ 
   struct mg_mgr *mgr;                /* 指向事件管理器,可以理解成指向链表头部 */ 
   
   sock_t sock; /* 封装的该链接对应的socket */ 
   int err; 
   union socket_address sa; /* Remote peer address */ 
   size_t recv_mbuf_limit;  /* Max size of recv buffer */ 
   struct mbuf recv_mbuf;   /* 链接的接收缓冲区 */ 
   struct mbuf send_mbuf;   /* 链接的发送缓冲区 */ 
   time_t last_io_time;     /* Timestamp of the last socket IO */ 
   double ev_timer_time;    /* 链接的相关时间事件,后面会使用 */ 
 #if MG_ENABLE_SSL 
   void *ssl_if_data; /* SSL library data. */ 
 #endif 
   mg_event_handler_t proto_handler; /* Protocol-specific event handler */ 
   void *proto_data;                 /* Protocol-specific data */ 
   void (*proto_data_destructor)(void *proto_data); 
   mg_event_handler_t handler; /* 链接事件发生之后对应的回调函数 */ 
   void *user_data;            /* 用户数据 */ 
   union { 122     void *v; 
     /* 
      * the C standard is fussy about fitting function pointers into 
      * void pointers, since some archs might have fat pointers for functions. 
      */ 
     mg_event_handler_t f; 
   } priv_1; 
   void *priv_2; 
   void *mgr_data; /* Implementation-specific event manager's data. */ 
   struct mg_iface *iface; 
   unsigned long flags; //链接特性,注意,这些所有细节全部都是mongoose框架主动或被动设置的 
 /* Flags set by Mongoose , 全都是宏,使用时设置,我们不关心设置*/ 
 #define MG_F_LISTENING (1 << 0)          /* This connection is listening */ 
 #define MG_F_UDP (1 << 1)                /* This connection is UDP */ 
 #define MG_F_RESOLVING (1 << 2)          /* Waiting for async resolver */ 
 #define MG_F_CONNECTING (1 << 3)         /* connect() call in progress */ 
 #define MG_F_SSL (1 << 4)                /* SSL is enabled on the connection */ 
 #define MG_F_SSL_HANDSHAKE_DONE (1 << 5) /* SSL hanshake has completed */ 
 #define MG_F_WANT_READ (1 << 6)          /* SSL specific */ 
 #define MG_F_WANT_WRITE (1 << 7)         /* SSL specific */ 
 #define MG_F_IS_WEBSOCKET (1 << 8)       /* Websocket 标记,注意!!*/ 
 #define MG_F_RECV_AND_CLOSE (1 << 9) /* Drain rx and close the connection. */ 
   
 /* Flags that are settable by user */ 
 #define MG_F_SEND_AND_CLOSE (1 << 10)      /* 发送剩余数据,然后关闭链接 */ 
 #define MG_F_CLOSE_IMMEDIATELY (1 << 11)   /* Disconnect */ 
 #define MG_F_WEBSOCKET_NO_DEFRAG (1 << 12) /* Websocket specific */ 
 #define MG_F_DELETE_CHUNK (1 << 13)        /* HTTP specific */ 
 #define MG_F_ENABLE_BROADCAST (1 << 14)    /* Allow broadcast address usage */ 
   
 #define MG_F_USER_1 (1 << 20) /* Flags left for application */ 
 #define MG_F_USER_2 (1 << 21) 
 #define MG_F_USER_3 (1 << 22) 
 #define MG_F_USER_4 (1 << 23)
 #define MG_F_USER_5 (1 << 24) 
 #define MG_F_USER_6 (1 << 25) 
 }; 
 
链接flags:     
  每个链接都有flasg位域。有些flags是由mongoose设置的,例如如果用户使用udp://1.2.3.4:5678创建一个出站
  的UDP链接。mongoose会为此链接设置MG_F_UDP标记。其他标志只能由用户事件处理程序设置,告诉mongoose做何种操 作。下面是由事件处理程序设置的链接flags列表:     
  MG_F_FINISHED_SENDING_DATA:告诉mongoose所有数据已经追加到send_mbuf,只要mongoose将数据写入 socket,此链接就会关闭。      MG_F_BUFFER_BUT_DONT_SEND:告诉mongoose追加数据到send_mbuf,但数据要马上发送,因为此数据稍后会被修 改。然后通过清除MG_F_BUFFER_BUT_DONT_SEND标志将数据发送出去。     
  MG_F_CLOSE_IMMEDIATELY:告诉mongoose立即关闭链接,通常在产生错误后发送此事件。     
  MG_USER_1,MG_USER_2,MG_USER_3,MG_USER_4:开发者可用它来存储特定应用程序的状态 下面是由mongoose设置的flags:        MG_F_SSL_HANDSHAKE_DONE:仅使用ssl,在ssl握手完成时设置     
  MG_F_CONNECTING:mg_connect()调用后链接处于链接状态但未完成链接时设置。     
  MG_F_LISTENING:设置所有监听链接     
  MG_F_UDP:链接是udp时设置。       
  MG_F_WEBSOCKET:链接是websocket时设置。     
  MG_F_WEBSOCKET_NO_DEFRAG:如果用户想关闭websocket的自动帧碎片整理功能,则由用户设置此标记。 

● struct mbuf: vim mongoose/mongoose.h +2354
● struct websocket_message
● vim mongoose/mongoose.h +2223
● vim mongoose/mongoose.c + 4245

3.mongoose基本接口

#define MG_CB(cb, ud) cb, ud 
//初始化事件管理器 
void mg_mgr_init(struct mg_mgr *m, void *user_data)//绑定端口,设置回调 
struct mg_connection *mg_bind(struct mg_mgr *srv, const char *address,\                                     MG_CB(mg_event_handler_t event_handler, \                                     
void *user_data)); 
//设置websocket处理回调函数,我们调用即可 
void mg_set_protocol_http_websocket(struct mg_connection *nc); 
//注册对特定页面的处理动作,常见登录和注册 
void mg_register_http_endpoint(struct mg_connection *nc, const char *uri_path,\           
MG_CB(mg_event_handler_t handler, void *user_data)); 
//进入事件循环,事件到来,触发回调,处理事件 
int mg_mgr_poll(struct mg_mgr *m, int timeout_ms); 
//释放事件管理器 
void mg_mgr_free(struct mg_mgr *m); 
//完成http重定向 
void mg_http_send_redirect(struct mg_connection *nc, int status_code,\           
const struct mg_str location,\           
const struct mg_str extra_headers); //完成http静态网页请求 
void mg_serve_http(struct mg_connection *nc, struct http_message *hm,\                          
struct mg_serve_http_opts opts); //返回当前链接的下一个链接 
struct mg_connection *mg_next(struct mg_mgr *s, struct mg_connection *conn); //发送websocket数据帧 
void mg_send_websocket_frame(struct mg_connection *nc, int op, const void *data, size_t len); //设置超时闹钟,超时时,特定链接会受到超时事件:
MG_EV_TIMER double mg_set_timer(struct mg_connection *c, double timestamp);
//获取当前系统时间戳 
double mg_time(void); 
//获取指定http header中的value,比如:Content_Length:1234,可以通过Content_Length,获取1234 
struct mg_str *mg_get_http_header(struct http_message *hm, const char *name); 
//解析头部属性信息 
int mg_http_parse_header2(struct mg_str *hdr, const char *var_name, char **buf,\                           
     size_t buf_size); 
//返回你想返回的http 
response int mg_printf(struct mg_connection *conn, const char *fmt, ...); 

C++编写mongoose基本框架

  • 首先,设计基本框架
  • 然后,先完成基本的聊天逻辑
  • 在能够让我们访问数据库,访问我们之前所建立的表
  • 最后,完成登录逻辑,这里还要涉及到对cookie和session的理解,我们在代码中体现

代码实现

IM相关代码
● IM_Server.hpp IM核心代码
● ImServer.cc 调用
● Util.hpp 工具类
● Makefile
● 前端代码,寻找模板灵活处理

你可能感兴趣的:(Linux)