跑调的毕加索
发布于

libwebsocket(webosocket)客户端简易例程及接口浅析

libwebsockets 的周期流程

• 核心思想:

  1. 回调函数:libwebsockets 的回调函数(lws_callbacks.h)
  2. typedef int lws_callback_function(struct lws *wsi, enum lws_callback_reasons reason, void *user, void *in, size_t len);
    o 1
    lws 在初始化配置时,需要定一个回调函数,lws 会通过该回调函数返回给开发者当前的所有状态:初始化、连接建立、连接失败、数据读写等等,而状态类型通过枚举 reason 来反馈。
  3. 消息循环
    当开发者将所有的参数配置结束后,需要循环调用 lws_service,来反复进行 lws 内部逻辑并触发回调函数。这个循环就是消息循环。

• 基本流程:

  • 处理协议(定义回调函数)
  • 配置 lws_context_creation_info 参数
  • 创建 lws_context
  • 配置连接信息 lws_client_connect_info
  • 进入消息循环
  • 通过回调函数实时获取 ws 状态并进行下一步操作(发送、接受、断开、异常控制)

(1)注册协议回调表

struct libwebsocket_protocols protocols[] = 
{
    {
           "http",            /* 协议名:其与Sec-Websockets-Protocol字段对应 */  
           ProtobufCB,          /* 回调函数:协议对应的回调处理函数 */  
           DATA_BUFSIZE + LWS_PRE, /* 自定义数据空间大小:每个ws连接均会分配一个自定义数据空间 */  
           DATA_BUFSIZE,           /* 首发缓存 */  
	   NULL,                   /*固定格式*/
	   &Tsession                /*上下文,自定义结构体*/
    }
    {  
            "ws",            /* 协议名:其与Sec-Websockets-Protocol字段对应 */  
            ProtobufCB,          /* 回调函数:协议对应的回调处理函数 */  
            DATA_BUFSIZE + LWS_PRE, /* 自定义数据空间大小:每个ws连接均会分配一个自定义数据空间 */  
            DATA_BUFSIZE,           /* 首发缓存 */  
	        NULL,                   /*固定格式*/
	        &Tsession                /*上下文,自定义结构体*/
     },  
     { NULL, NULL, 0, 0 }               /* 结束标识 */  

}

(2)回调函数

回调函数的原型:
lws_callback_function(
strcut lws *wsi,
enum lws_callback_reasons reason,
void *user,
void *in,
size_t len)

lws_callback_function 组成部分

  1. 参数:
  • struct lws *wsi: 一个指向 WebSocket 实例的指针。它表示当前的连接或会话,所有的 WebSocket 操作和状态都是基于这个实例进行的。
  • enum lws_callback_reasons reason: 一个枚举值,指示触发回调的原因或事件类型。这帮助你识别发生了什么操作或状态变化,例如连接建立、数据接收、连接关闭等。
  • void *user: 指向为该会话分配的用户自定义数据的指针。这个数据区通常由用户在初始化时定义,用于存储会话相关的信息。
  • void *in: 与回调原因相关的数据指针。例如,如果回调原因是数据接收,则 in 指针会指向接收到的数据。
  • size_t len: in 指针指向的数据的长度。与数据传输相关的回调原因需要使用这个长度来确定数据大小。

(3)回调事件详解

序号 状态值 含义
1 LWS_CALLBACK_WSI_CREATE 正在创建 ws 连接对象。注:此时回调函数中的参数 wsi 对象与 user 对象依然为空指针,因此还不能初始化用户自定义对象回调函数的参数含义:context: 全局上下文 wsi: 空指针 user: 空指针  in: 空指针  len: 0
- - -
2 LWS_CALLBACK_FILTER_PROTOCOL_CONNECTION 此 reason 可以用来过滤协议。注:在此处返回非 0 值时,lws 库将会关闭该链接;该处返回 0 时,表示 ws 连接已经建立成功。此时表 1 中的 wsi 对象和 user 对象已不为空,因此,此时可以对用户自定义对象 user 进行初始化处理。
- - -
3 LWS_CALLBACK_LOCK_POLL 添加保护 ws 连接状态的互斥锁。注:当采用的是多线程编程,则在此添加互斥锁保护 ws 连接相关状态,防止冲突。如果是单进程方式,则无需做任何操作。
- - -
4 LWS_CALLBACK_UNLOCK_POLL 解除保护 ws 连接状态的互斥锁。注:当采用的是多线程编程,则在此解除互斥锁。如果是单进程方式,则无需做任何操作
- - -
5 LWS_CALLBACK_CLIENT_RECEIVE 数据出现在客户端连接的服务器上时,可以在通过*in 处找到,长度为 len 字节。
- - -
6 LWS_CALLBACK_RECEIVE 收到一帧完整数据。注:此时表 in 表示收到的数据,len 表示收到的数据长度。需要注意的是:指针 in 的回收、释放始终由 LWS 框架管理,只要出了回调函数,该空间就会被 LWS 框架回收。因此,开发者若想将接收的数据进行转发,则必须对该数据进行拷贝。
- - -
7 LWS_CALLBACK_SERVER_WRITEABLE 此 ws 连接为可写状态注:表示 wsi 对应的 ws 连接当前处于可写状态,即:可发送数据至客户端。
- - -
8 LWS_CALLBACK_CLOSED ws 连接已经断开。注:不能在此释放内存空间,否则存在内存泄漏的风险!!!因为连接断开时,并不总是会回调 LWS_CALLBACK_CLOSED 的处理!
- - -
9 LWS_CALLBACK_WSI_DESTROY 正在销毁 ws 连接对象。注:表示 libwebsockets 框架即将销毁 wsi 对象。此时如果用户自定义对象中存在动态分配的空间,则需要在此时进行释放。
- - -
10 LWS_CALLBACK_HTTP 收到 http 消息头
- - -
11 LWS_CALLBACK_HTTP_BODY 收到 http 消息体

(4)重要函数说明

序号 函数名 功能
1 lws_get_peer_write_allowance 该 ws 连接允许发送的字节数。备注:如果有发送字节限制,则返回正数;如果无发送字节限制,则返回-1。
- - -
2 lws_send_pipe_choked 判断 ws 连接是否阻塞。备注:如果 ws 连接阻塞,则返回 1,否则返回 0。
- - -
3 lws_write 将数据发送给对端备注:函数参数说明 wsi: ws 连接对象。 buf: 需要发送数据的起始地址。注意:必须在指针 buf 前预留长度为 LWS_SEND_BUFFER_PRE_PADDING 的空间,同时在指针 buf+len 后预留长度为 LWS_SEND_BUFFER_POST_PADDING 的空间。 len: 需要发送数据的长度 protocol: 如果该连接是 http 连接,则该参数的值为 LWS_WRITE_HTTP;如果该连接是 ws 连接,则该参数的值为 LWS_WRITE_BINARY 或 LWS_WRITE_TEXT,但如果第一次发送的数据长度 n < len,则发送后续长度为(len - n)字节的数据时,该参数值改为 LWS_WRITE_HTTP。
- - -
4 lws_http_transaction_completed 当前连接为 http 连接,而非 ws 连接时,如果当前 http 请求的应答数据发送完毕,则可使用该函数重置 http 连接的相关状态,只有收到新的 http 请求才能激活该 http 连接。备注:如果当前连接为 ws 连接,则千万不要调用此函数,其将导致服务端则无法再激活可写事件。
- - -
5 lws_callback_on_writable 将 ws 连接加入可写事件监听 注:调用此函数会触发一次回调里的 LWS_CALLBACK_CLIENT_WRITEABLE 事件
- - -
6 lws_callback_on_writable_all_protocol 将某个协议的所有 ws 连接加入可写事件监听。注:在网络中存在各种情况可能导致服务端并不知道与客户端的连接已经断开:比如客户端掉电。为了应对这种情况的存在。需要每隔一段事件执行该函数,再在协议的回调函数的 LWS_CALLBACK_SERVER_WRITEABLE 事件中判断一下是否超时,如果超时则返回非零值。
//注册协议回调表
static struct lws_protocols protocols[] = {
    {"ws",                       //协议名称“ws”
     WebsocketClient::Callback,  //协议的回调函数,用于处理事件
     0,                          //每个连接的会话数据大小,0 表示不分配额外数据
     1024},                      //接收缓冲区大小,设置为1024字节
    {NULL, NULL, 0, 0}           //协议列表的结束标记 
};

//连接函数
bool WebsocketClient::Connect(const std::string &url, int port)
{
    //websocket配置参数
    struct lws_context_creation_info info;
    memset(&info, 0, sizeof(info));
    info.port = CONTEXT_PORT_NO_LISTEN;
    info.protocols = protocols;
    info.gid = -1;
    info.uid = -1;
    
     //连接上下文
    context = lws_create_context(&info);

    if (!context)
    {
        std::cerr << "Failed to create WebSocket context" << std::endl;
        return false;
    }

    //连接信息
    struct lws_client_connect_info connect_info = {0};
    connect_info.context = context;     //设置上下文
    connect_info.address = url.c_str(); //设置目标主机ip/域名地址
    connect_info.port = port;           // 设置主机服务端口
    connect_info.path = "/";            //设置目标主机服务PATH
    connect_info.host = connect_info.address; //设置目标主机IP
    connect_info.origin = connect_info.address; //设置目标主机IP
    connect_info.protocol = protocols[0].name;  //设置连接协议
    connect_info.ssl_connection = 0; // Set to 1 if using SSL/TLS
    //以下可优化方案
    // bool ssl = protocol == "wss" ? true : false; //确认是否进行SSL加密
    // ci.ssl_connection = ssl ? (LCCSCF_USE_SSL | LCCSCF_ALLOW_SELFSIGNED | LCCSCF_SKIP_SERVER_CERT_HOSTNAME_CHECK | LCCSCF_ALLOW_INSECURE) : 0;
   
    //是连接信息生效
    wsi = lws_client_connect_via_info(&connect_info);

   
    if (!wsi)
    {
        std::cerr << "Client failed to connect" << std::endl;
        return false;
    }

    // 处理网络事件,保证连接成功,这里的写法没连接成功会阻塞
    while (!GetInstance().is_connected)
    {
        lws_service(context, 100);
        std::cerr << "is_conneted " << GetInstance().is_connected << std::endl;
    }

   //连接成功,创建线程进入消息循环,持续处理网络事件
    int result = SafeCreateThread("lws_service_th", WebsocketClient::lws_peroid_work, this);
    if (result != 0)
    {
        std::cerr << "Failed to create thread" << std::endl;
        return false;
    }

    return true;
}

//消息回调
int WebsocketClient::Callback(struct lws *wsi, enum lws_callback_reasons reason, void *user, void *in, size_t len)
{
    switch (reason)
    {
    case LWS_CALLBACK_CLIENT_ESTABLISHED:
        std::cout << "WebSocket connection established!" << std::endl;
        std::cout << "11111111111111!" << std::endl;

        GetInstance().is_connected = true;
        //调用一次lws_callback_on_writeable,会触发一次callback的LWS_CALLBACK_CLIENT_WRITEABLE,之后可进行一次发送数据操作
        lws_callback_on_writable(wsi);
        break;

    case LWS_CALLBACK_CLIENT_RECEIVE:
        //获取到服务端的数据
        std::cout << "Received: " << string((char *)in, len) << std::endl;
        lws_callback_on_writable(_wsi);
        break;

    case LWS_CALLBACK_CLIENT_WRITEABLE:
        std::cout << "Client is writable!" << std::endl;
        break;
    case LWS_CALLBACK_CLIENT_CONNECTION_ERROR:
        //连接失败、异常
        std::cerr << "WebSocket connection error!" << std::endl;
        GetInstance().is_connected = false;
        // 处理连接错误,可能是服务器不可达
        // Reconnect();
        break;
    case LWS_CALLBACK_CLOSED:
        // 客户端主动断开、服务端断开都会触发此reason
        std::cout << "Connection closed" << std::endl;
        GetInstance().is_connected = false;
        break;

    default:
        break;
    }
    return 0;
}
浏览 (415) 点赞 (1) 收藏 分享
评论