● 首先,使用开源websocket框架mongoose编写网页版本的群聊即时通信工具;
● 然后,先完成基本的聊天逻辑,再能够让我们访问数据库,访问我们之前所建立的表;
● 最后,完成登录逻辑。其中,还要涉及到对cookie和session的理解以及http+mongoose+session+mysql+jsoncpp的技术的构成,在代码中进行添加,完成IM工具。
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
●定义:
● 特性:
#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;
}
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
#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基本框架
IM相关代码
● IM_Server.hpp IM核心代码
● ImServer.cc 调用
● Util.hpp 工具类
● Makefile
● 前端代码,寻找模板灵活处理