现在的位置: 首页 > 自动控制 > 工业·编程 > 正文

libwebsockets(三)实现简易websocket服务器

2019-09-04 17:53 工业·编程 ⁄ 共 4937字 ⁄ 字号 暂无评论

实现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 \
    }

最后实现的效果,当一个窗口发送消息时,打开的页面都会收到。

给我留言

留言无头像?