实现websocket服务器本身也是libwebsockets库的初衷,本篇博客将介绍如何利用libwebsockets库来实现一个简单的ws服务器。
1、添加websocket协议
这里创建服务器句柄的流程与http一致,需要修改的地方只有在创建服务器时传入的协议数组,即
struct lws_context_creation_info info;
struct lws_context *context;
static struct lws_protocols protocols[] =
{
/*http服务器库中已做实现,直接使用lws_callback_http_dummy即可*/
{ "http", lws_callback_http_dummy, 0, 0 },
LWS_PLUGIN_PROTOCOL_MINIMAL,
{ NULL, NULL, 0, 0 } /* 结束标志 */
};
/*初始化内存*/
memset(&info, 0, sizeof info);
/*设置服务器端口*/
info.port = 7681;
/*设置http服务器的配置*/
info.mounts = &mount;
/*添加协议*/
info.protocols = protocols;
...
struct lws_protocols的结构如下
struct lws_protocols {
/*协议名称*/
const char *name;
/*服务回调,协议事件处理*/
lws_callback_function *callback;
/*服务建立和断开时申请内存大小,也是callback中user的内存*/
size_t per_session_data_size;
/*接收缓存区大小*/
size_t rx_buffer_size;
/*协议id,可以用来区分协议*/
unsigned int id;
/*自定义数据*/
void *user;
/*发送缓存大小,为0则与rx_buffer_size相同*/
size_t tx_packet_size;
};
这里我们重点关注的是callback成员,它是一个lws_callback_function类型的函数指针,协议的的数据交互处理都会使用该回调函数。该回调函数的原型是
/*
* wsi: 连接的websocket的实例
* reason: 回调的原因
* user:用户自定的数据,数据大小为per_session_data_size,需在连接初始化时申请内存
* in: 回调的传入数据
* len: in指向的内存大小
*/
typedef int
lws_callback_function(struct lws *wsi, enum lws_callback_reasons reason,
void *user, void *in, size_t len);
其中常用的reason值如下:
/*协议初始化,只调用一次*/
LWS_CALLBACK_PROTOCOL_INIT
/*连接已建立*/
LWS_CALLBACK_ESTABLISHED
/*连接关闭*/
LWS_CALLBACK_CLOSED
/*可写*/
LWS_CALLBACK_SERVER_WRITEABLE
/*有数据到来*/
LWS_CALLBACK_RECEIVE
下面我们以官方的一个例子来说明如何写回调函数。
2、websocket服务器实例
这里我们将实现一个简单的聊天室,即当一个页面发送消息时,所有的连接的页面都会收到该消息。
(1) 服务器结构体
struct per_vhost_data__minimal
{
/*服务器,可由vhost与protocol获取该结构体*/
struct lws_vhost *vhost;
/*使用的协议*/
const struct lws_protocols *protocol;
/*客户端链表*/
struct per_session_data__minimal *pss_list;
/*接收到的消息,缓存大小为一条数据*/
struct msg amsg;
/*当前消息编号,用来同步所有客户端的消息*/
int current;
};
(2) 客户端的结构体
struct per_session_data__minimal
{
/*下一个客户端结点*/
struct per_session_data__minimal *pss_list;
/*客户端连接句柄*/
struct lws *wsi;
/*当前接收到的消息编号*/
int last;
};
(3) 消息结构
struct msg
{
/*内存地址*/
void *payload;
/*大小*/
size_t len;
};
整体代码如下:
/*消息释放*/
static void
__minimal_destroy_message(void *_msg)
{
struct msg *msg = _msg;
free(msg->payload);
msg->payload = NULL;
msg->len = 0;
}
/*回调函数*/
static int
callback_minimal(struct lws *wsi, enum lws_callback_reasons reason,
void *user, void *in, size_t len)
{
/*获取客户端结构*/
struct per_session_data__minimal **ppss, *pss =
(struct per_session_data__minimal *)user;
/*由vhost与protocol还原lws_protocol_vh_priv_zalloc申请的结构*/
struct per_vhost_data__minimal *vhd =
(struct per_vhost_data__minimal *)
lws_protocol_vh_priv_get(lws_get_vhost(wsi),
lws_get_protocol(wsi));
int m;
switch (reason) {
/*初始化*/
case LWS_CALLBACK_PROTOCOL_INIT:
/*申请内存*/
vhd = lws_protocol_vh_priv_zalloc(lws_get_vhost(wsi),
lws_get_protocol(wsi),
sizeof(struct per_vhost_data__minimal));
vhd->protocol = lws_get_protocol(wsi);
vhd->vhost = lws_get_vhost(wsi);
break;
/*建立连接,将客户端放入客户端链表*/
case LWS_CALLBACK_ESTABLISHED:
pss->pss_list = vhd->pss_list;
vhd->pss_list = pss;
pss->wsi = wsi;
pss->last = vhd->current;
break;
/*连接关闭,将客户端从链表中移除*/
case LWS_CALLBACK_CLOSED:
/*遍历客户端链表*/
lws_start_foreach_llp(struct per_session_data__minimal **,
ppss, vhd->pss_list) {
if (*ppss == pss) {
*ppss = pss->pss_list;
break;
}
} lws_end_foreach_llp(ppss, pss_list);
break;
/*客户端可写*/
case LWS_CALLBACK_SERVER_WRITEABLE:
if (!vhd->amsg.payload)
break;
if (pss->last == vhd->current)
break;
/* notice we allowed for LWS_PRE in the payload already */
m = lws_write(wsi, vhd->amsg.payload + LWS_PRE, vhd->amsg.len,
LWS_WRITE_TEXT);
if (m < vhd->amsg.len) {
lwsl_err("ERROR %d writing to di socket\n", n);
return -1;
}
pss->last = vhd->current;
break;
/*客户端收到数据*/
case LWS_CALLBACK_RECEIVE:
if (vhd->amsg.payload)
__minimal_destroy_message(&vhd->amsg);
vhd->amsg.len = len;
/* notice we over-allocate by LWS_PRE */
vhd->amsg.payload = malloc(LWS_PRE + len);
if (!vhd->amsg.payload) {
lwsl_user("OOM: dropping\n");
break;
}
memcpy((char *)vhd->amsg.payload + LWS_PRE, in, len);
vhd->current++;
/*
*遍历所有的客户端,将数据放入写入回调
*/
lws_start_foreach_llp(struct per_session_data__minimal **,
ppss, vhd->pss_list) {
lws_callback_on_writable((*ppss)->wsi);
} lws_end_foreach_llp(ppss, pss_list);
break;
default:
break;
}
return 0;
}
#define LWS_PLUGIN_PROTOCOL_MINIMAL \
{ \
"lws-minimal", \
callback_minimal, \
sizeof(struct per_session_data__minimal), \
128, \
0, NULL, 0 \
}
最后实现的效果,当一个窗口发送消息时,打开的页面都会收到。