最近在看mongoose(版本6.13)的源码,作为网络编程入门,以此记录学习笔记,特此分享,有错还望各位指正。
下面是官网上的一个入门例子:一个简单的TCP服务器,它接收客户端的消息并回传给客户端。
其中主函数分为以下几步:
1.创建一个连接管理器(mg_mgr)
2.新建一个监听连接(mg_connection),绑定端口和回调函数,加入到事件管理器中
3.进入循环,轮询所有连接,如果有事件发生,则进行处理
#include "mongoose.h" // Include Mongoose API definitions
// Define an event handler function
static void ev_handler(struct mg_connection *nc, int ev, void *ev_data) {
struct mbuf *io = &nc->recv_mbuf;
switch (ev) {
case MG_EV_RECV:
// This event handler implements simple TCP echo server
mg_send(nc, io->buf, io->len); // Echo received data back
mbuf_remove(io, io->len); // Discard data from recv buffer
break;
default:
break;
}
}
int main(void) {
struct mg_mgr mgr;
mg_mgr_init(&mgr, NULL); // Initialize event manager object
// Note that many connections can be added to a single event manager
// Connections can be created at any point, e.g. in event handler function
mg_bind(&mgr, "1234", ev_handler); // Create listening connection and add it to the event manager
for (;;) { // Start infinite event loop
mg_mgr_poll(&mgr, 1000);
}
mg_mgr_free(&mgr);
return 0;
}
在阅读源码之前,首先要了解两个结构体:mg_mgr和mg_connection
- mg_mgr:连接管理器(又或者叫事件管理器?),我觉得它更像是连接管理器,每次循环都检查所有连接,找出活动的连接,然后依次处理。这里说的连接不仅是指客户端和服务器建立的连接,也包括监听连接。
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_mgr mgr;
mg_mgr_init(&mgr, NULL); // Initialize event manager object
就是创建了一个新的连接管理器。
mg_mgr_init的源码也很简单,即对结构体内的数据做一些赋值操作,创建网络接口以及ssl初始化等。(一开始没搞懂ifaces是个啥,其实就是网络端口,Linux下 ifconfig -a指令就可以查看。。)
//初始化事件管理器外部接口
void mg_mgr_init(struct mg_mgr *m, void *user_data) {
struct mg_mgr_init_opts opts;
memset(&opts, 0, sizeof(opts));
mg_mgr_init_opt(m, user_data, opts);
}
//初始化事件管理器
void mg_mgr_init_opt(struct mg_mgr *m, void *user_data,
struct mg_mgr_init_opts opts) {
memset(m, 0, sizeof(*m));
#if MG_ENABLE_BROADCAST
m->ctl[0] = m->ctl[1] = INVALID_SOCKET;
#endif
m->user_data = user_data;
#ifdef _WIN32
{
WSADATA data;
WSAStartup(MAKEWORD(2, 2), &data);
}
#elif defined(__unix__)
/* Ignore SIGPIPE signal, so if client cancels the request, it
* won't kill the whole process. */
signal(SIGPIPE, SIG_IGN);
#endif
{
int i;
if (opts.num_ifaces == 0) {
opts.num_ifaces = mg_num_ifaces;
opts.ifaces = mg_ifaces;
}
if (opts.main_iface != NULL) {
opts.ifaces[MG_MAIN_IFACE] = opts.main_iface;
}
m->num_ifaces = opts.num_ifaces;
m->ifaces =
(struct mg_iface **) MG_MALLOC(sizeof(*m->ifaces) * opts.num_ifaces);
for (i = 0; i < opts.num_ifaces; i++) {
m->ifaces[i] = mg_if_create_iface(opts.ifaces[i], m);
m->ifaces[i]->vtable->init(m->ifaces[i]);//vtable:虚函数表,保存所有的网络端口的操作函数
}
}
if (opts.nameserver != NULL) {
m->nameserver = strdup(opts.nameserver);
}
DBG(("=================================="));
DBG(("init mgr=%p", m));
#if MG_ENABLE_SSL
{
static int init_done;
if (!init_done) {
mg_ssl_if_init();
init_done++;
}
}
#endif
}
- mg_connection
对于保存在连接管理器中的每一个连接,都用一个结构体来表示,即mg_connection
struct mg_connection {
struct mg_connection *next, *prev; /* mg_mgr::active_connections linkage 双向链表,指向前一个和后一个连接*/
struct mg_connection *listener; /* Set only for accept()-ed connections 当连接为监听连接时,设置为0;当连接是由监听连接创建的连接时,设置为1*/
struct mg_mgr *mgr; /* Pointer to containing manager 该连接所属的连接管理器*/
sock_t sock; /* Socket to the remote peer本机套接字 */
int err;
union socket_address sa; /* Remote peer address 客户端地址*/
size_t recv_mbuf_limit; /* Max size of recv buffer 最大接收缓存量*/
struct mbuf recv_mbuf; /* Received data 接收的数据*/
struct mbuf send_mbuf; /* Data scheduled for sending 发送的数据*/
time_t last_io_time; /* Timestamp of the last socket IO 上一次读写数据*/
double ev_timer_time; /* Timestamp of the future MG_EV_TIMER 下一次出发的时间?*/
#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; /* Event handler function 事件处理函数*/
void *user_data; /* User-specific data 用户数据*/
union {
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;
/* 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 specific */
/* Flags that are settable by user */
#define MG_F_SEND_AND_CLOSE (1 << 10) /* Push remaining data and close */
#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)
};
接下来一行
mg_bind(&mgr, "1234", ev_handler);
在端口1234上创建一个监听连接,并绑定一个回调函数,然后加入到刚才创建的连接管理器中。
接下来看代码:
struct mg_connection *mg_bind(struct mg_mgr *srv, const char *address,
MG_CB(mg_event_handler_t event_handler,
void *user_data)) {
struct mg_bind_opts opts;
memset(&opts, 0, sizeof(opts));
return mg_bind_opt(srv, address, MG_CB(event_handler, user_data), opts);
}
struct mg_connection *mg_bind_opt(struct mg_mgr *mgr, const char *address,
MG_CB(mg_event_handler_t callback,
void *user_data),
struct mg_bind_opts opts) {
union socket_address sa;
struct mg_connection *nc = NULL;
int proto, rc;
struct mg_add_sock_opts add_sock_opts;
char host[MG_MAX_HOST_LEN];
#if MG_ENABLE_CALLBACK_USERDATA
opts.user_data = user_data;
#endif
if (callback == NULL) {
MG_SET_PTRPTR(opts.error_string, "handler is required");
return NULL;
}
MG_COPY_COMMON_CONNECTION_OPTIONS(&add_sock_opts, &opts);
//解析地址,这里由于只有端口号,则直接使用本机地址
if (mg_parse_address(address, &sa, &proto, host, sizeof(host)) <= 0) {
MG_SET_PTRPTR(opts.error_string, "cannot parse address");
return NULL;
}
//创建一个连接,并设置该连接的回调函数
nc = mg_create_connection(mgr, callback, add_sock_opts);
if (nc == NULL) {
return NULL;
}
nc->sa = sa;
//将标志位设置为监听状态
nc->flags |= MG_F_LISTENING;
if (proto == SOCK_DGRAM) nc->flags |= MG_F_UDP;
#if MG_ENABLE_SSL
DBG(("%p %s %s,%s,%s", nc, address, (opts.ssl_cert ? opts.ssl_cert : "-"),
(opts.ssl_key ? opts.ssl_key : "-"),
(opts.ssl_ca_cert ? opts.ssl_ca_cert : "-")));
if (opts.ssl_cert != NULL || opts.ssl_ca_cert != NULL) {
const char *err_msg = NULL;
struct mg_ssl_if_conn_params params;
if (nc->flags & MG_F_UDP) {
MG_SET_PTRPTR(opts.error_string, "SSL for UDP is not supported");
mg_destroy_conn(nc, 1 /* destroy_if */);
return NULL;
}
memset(¶ms, 0, sizeof(params));
params.cert = opts.ssl_cert;
params.key = opts.ssl_key;
params.ca_cert = opts.ssl_ca_cert;
params.cipher_suites = opts.ssl_cipher_suites;
if (mg_ssl_if_conn_init(nc, ¶ms, &err_msg) != MG_SSL_OK) {
MG_SET_PTRPTR(opts.error_string, err_msg);
mg_destroy_conn(nc, 1 /* destroy_if */);
return NULL;
}
nc->flags |= MG_F_SSL;
}
#endif /* MG_ENABLE_SSL */
//绑定套接字并将套接字设置为监听状态
if (nc->flags & MG_F_UDP) {
rc = nc->iface->vtable->listen_udp(nc, &nc->sa);
} else {
rc = nc->iface->vtable->listen_tcp(nc, &nc->sa);
}
if (rc != 0) {
DBG(("Failed to open listener: %d", rc));
MG_SET_PTRPTR(opts.error_string, "failed to open listener");
mg_destroy_conn(nc, 1 /* destroy_if */);
return NULL;
}
mg_add_conn(nc->mgr, nc);
return nc;
}
到这一步一个连接管理器和一个连接就创建好了。
下面就开始循环遍历每个连接,并处理各种事件。
for ( ; ; ) { // Start infinite event loop
mg_mgr_poll(&mgr, 1000);
}
//轮询所有网络接口
int mg_mgr_poll(struct mg_mgr *m, int timeout_ms) {
int i, num_calls_before = m->num_calls;
for (i = 0; i < m->num_ifaces; i++) {
m->ifaces[i]->vtable->poll(m->ifaces[i], timeout_ms);
}
return (m->num_calls - num_calls_before);
}
其中虚函数表中的poll函数其实是mg_socket_if_poll
定义如下
/* clang-format off */
#define MG_SOCKET_IFACE_VTABLE \
{ \
mg_socket_if_init, \
mg_socket_if_free, \
mg_socket_if_add_conn, \
mg_socket_if_remove_conn, \
mg_socket_if_poll, \
mg_socket_if_listen_tcp, \
mg_socket_if_listen_udp, \
mg_socket_if_connect_tcp, \
mg_socket_if_connect_udp, \
mg_socket_if_tcp_send, \
mg_socket_if_udp_send, \
mg_socket_if_tcp_recv, \
mg_socket_if_udp_recv, \
mg_socket_if_create_conn, \
mg_socket_if_destroy_conn, \
mg_socket_if_sock_set, \
mg_socket_if_get_conn_addr, \
}
/* clang-format on */
mg_socket_if_poll源码如下:
time_t mg_socket_if_poll(struct mg_iface *iface, int timeout_ms) {
struct mg_mgr *mgr = iface->mgr;
double now = mg_time();
double min_timer;
struct mg_connection *nc, *tmp;
struct timeval tv;
//创建文件描述符清单
fd_set read_set, write_set, err_set;
sock_t max_fd = INVALID_SOCKET;
int num_fds, num_ev, num_timers = 0;
#ifdef __unix__
int try_dup = 1;
#endif
FD_ZERO(&read_set);
FD_ZERO(&write_set);
FD_ZERO(&err_set);
#if MG_ENABLE_BROADCAST
mg_add_to_set(mgr->ctl[1], &read_set, &max_fd);
#endif
/*
* Note: it is ok to have connections with sock == INVALID_SOCKET in the list,
* e.g. timer-only "connections".
*/
min_timer = 0;
for (nc = mgr->active_connections, num_fds = 0; nc != NULL; nc = tmp) {
tmp = nc->next;
//处理所有的活动连接
if (nc->sock != INVALID_SOCKET) {
num_fds++;//统计有效的连接
#ifdef __unix__
/* A hack to make sure all our file descriptos fit into FD_SETSIZE. */
if (nc->sock >= (sock_t) FD_SETSIZE && try_dup) {
int new_sock = dup(nc->sock);
if (new_sock >= 0) {
if (new_sock < (sock_t) FD_SETSIZE) {
closesocket(nc->sock);
DBG(("new sock %d -> %d", nc->sock, new_sock));
nc->sock = new_sock;
} else {
closesocket(new_sock);
DBG(("new sock is still larger than FD_SETSIZE, disregard"));
try_dup = 0;
}
} else {
try_dup = 0;
}
}
#endif
//加入到监听清单中
//如果接受缓存没写满,并且不是udp连接或者是监听连接,加入读清单中
if (nc->recv_mbuf.len < nc->recv_mbuf_limit &&
(!(nc->flags & MG_F_UDP) || nc->listener == NULL)) {
mg_add_to_set(nc->sock, &read_set, &max_fd);
}
//如果是客户端连接服务器的连接并且不希望读标志位置位,或者发送缓存大于0且不是连接服务器连接
if (((nc->flags & MG_F_CONNECTING) && !(nc->flags & MG_F_WANT_READ)) ||
(nc->send_mbuf.len > 0 && !(nc->flags & MG_F_CONNECTING))) {
mg_add_to_set(nc->sock, &write_set, &max_fd);
mg_add_to_set(nc->sock, &err_set, &max_fd);
}
}
//取最小的轮询时间
if (nc->ev_timer_time > 0) {
if (num_timers == 0 || nc->ev_timer_time < min_timer) {
min_timer = nc->ev_timer_time;
}
num_timers++;
}
}
/*
* If there is a timer to be fired earlier than the requested timeout,
* adjust the timeout.
*/
if (num_timers > 0) {
double timer_timeout_ms = (min_timer - mg_time()) * 1000 + 1 /* rounding */;
if (timer_timeout_ms < timeout_ms) {
timeout_ms = (int) timer_timeout_ms;
}
}
if (timeout_ms < 0) timeout_ms = 0;
tv.tv_sec = timeout_ms / 1000;
tv.tv_usec = (timeout_ms % 1000) * 1000;
//select轮询查看每个链接是否活动
num_ev = select((int) max_fd + 1, &read_set, &write_set, &err_set, &tv);
now = mg_time();
#if 0
DBG(("select @ %ld num_ev=%d of %d, timeout=%d", (long) now, num_ev, num_fds,
timeout_ms));
#endif
#if MG_ENABLE_BROADCAST
if (num_ev > 0 && mgr->ctl[1] != INVALID_SOCKET &&
FD_ISSET(mgr->ctl[1], &read_set)) {
mg_mgr_handle_ctl_sock(mgr);
}
#endif
//遍历每一个链接,并检查是否有活动发生
for (nc = mgr->active_connections; nc != NULL; nc = tmp) {
int fd_flags = 0;
if (nc->sock != INVALID_SOCKET) {
if (num_ev > 0) {
//如果连接有变化,将fd_flags置为1
fd_flags = (FD_ISSET(nc->sock, &read_set) &&
(!(nc->flags & MG_F_UDP) || nc->listener == NULL)
? _MG_F_FD_CAN_READ
: 0) |
(FD_ISSET(nc->sock, &write_set) ? _MG_F_FD_CAN_WRITE : 0) |
(FD_ISSET(nc->sock, &err_set) ? _MG_F_FD_ERROR : 0);
}
#if MG_LWIP
/* With LWIP socket emulation layer, we don't get write events for UDP */
if ((nc->flags & MG_F_UDP) && nc->listener == NULL) {
fd_flags |= _MG_F_FD_CAN_WRITE;
}
#endif
}
tmp = nc->next;
//处理每个事件
mg_mgr_handle_conn(nc, fd_flags, now);
}
return (time_t) now;
}
上面代码的思路也很简单,及检查每个有效连接,对每个连接调用mg_mgr_handle_conn处理函数
void mg_mgr_handle_conn(struct mg_connection *nc, int fd_flags, double now) {
int worth_logging =
fd_flags != 0 || (nc->flags & (MG_F_WANT_READ | MG_F_WANT_WRITE));
if (worth_logging) {
DBG(("%p fd=%d fd_flags=%d nc_flags=0x%lx rmbl=%d smbl=%d", nc, nc->sock,
fd_flags, nc->flags, (int) nc->recv_mbuf.len,
(int) nc->send_mbuf.len));
}
//如果是poll事件
if (!mg_if_poll(nc, now)) return;
//如果是出站连接事件(连接服务器)
if (nc->flags & MG_F_CONNECTING) {
if (fd_flags != 0) {
int err = 0;
#if !defined(MG_ESP8266)
if (!(nc->flags & MG_F_UDP)) {//如果是TCP连接
socklen_t len = sizeof(err);
int ret =
getsockopt(nc->sock, SOL_SOCKET, SO_ERROR, (char *) &err, &len);
if (ret != 0) {
err = 1;
} else if (err == EAGAIN || err == EWOULDBLOCK) {
err = 0;
}
}
#else
/*
* On ESP8266 we use blocking connect.
*/
err = nc->err;
#endif
mg_if_connect_cb(nc, err);//调用连接事件的回调函数
} else if (nc->err != 0) {
mg_if_connect_cb(nc, nc->err);
}
}
//如果是可读事件
if (fd_flags & _MG_F_FD_CAN_READ) {
if (nc->flags & MG_F_UDP) {
mg_if_can_recv_cb(nc);//如果是UDP连接
} else {//TCP连接
if (nc->flags & MG_F_LISTENING) {//如果是监听到TCP连接
/*
* We're not looping here, and accepting just one connection at
* a time. The reason is that eCos does not respect non-blocking
* flag on a listening socket and hangs in a loop.
*/
mg_accept_conn(nc);
} else {
mg_if_can_recv_cb(nc);
}
}
}
//如果是可写事件
if (fd_flags & _MG_F_FD_CAN_WRITE) mg_if_can_send_cb(nc);
if (worth_logging) {
DBG(("%p after fd=%d nc_flags=0x%lx rmbl=%d smbl=%d", nc, nc->sock,
nc->flags, (int) nc->recv_mbuf.len, (int) nc->send_mbuf.len));
}
}
对于本例,监听连接上有客户端连接申请到来时,调用mg_accept_conn(nc);创建一个新的连接,并进行初始化,然后加入到连接管理器中。这个创建的新的连接使用监听连接的回调函数,即
static void ev_handler(struct mg_connection *nc, int ev, void *ev_data) {
struct mbuf *io = &nc->recv_mbuf;
switch (ev) {
case MG_EV_RECV:
// This event handler implements simple TCP echo server
mg_send(nc, io->buf, io->len); // Echo received data back
mbuf_remove(io, io->len); // Discard data from recv buffer
break;
default:
break;
}
}
至此,连接的创建和初始化流程全部列出。下面就是如何发送和接收数据了。