libwebsockets库

libwebsockets( https://libwebsockets.org/ )是一款轻量级用来开发服务器和客户端的C库。它不仅支持ws,wss还同时支持http与https,可以轻轻松松结合openssl等库来实现ssl加密。

  • libwebsockets的接口非常底层,很多逻辑需要自己封装实现
  • libwebsockets不是线程安全的库,如果想要将其进行封装为类,那么线程、信号量、锁的控制一定要非常严谨

使用的基本流程:

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

常用的api

struct lws_context* lws_create_context(const struct lws_context_creation_info *info);
void lws_context_destroy(struct lws_context *context);
struct lws* lws_client_connect_via_info(const struct lws_client_connect_info *ccinfo);
int lws_service(struct lws_context *context, int timeout_ms);
int lws_callback_on_writable(struct lws *wsi);
int lws_write(struct lws *wsi, unsigned char *buf, size_t len, enum lws_write_protocol protocol);
  1. lws_create_context:创建上下文对象,管理ws
    lws_create_context是libwebsockets库中的函数,用于创建一个WebSocket上下文。WebSocket上下文是一个结构体,包含了与WebSocket相关的所有信息和状态。在创建WebSocket连接之前,需要先创建一个WebSocket上下文。lws_create_context函数的作用是初始化WebSocket上下文,并返回一个指向WebSocket上下文的指针。

  2. lws_context_destroy:销毁上下文对象
    lws_context_destroy 是一个函数,用于销毁一个 libwebsockets 应用程序的上下文。它会释放分配给上下文的所有资源,包括套接字、线程、缓冲区等。调用此函数后,应用程序将无法再使用上下文,因此应该确保在不需要上下文时才调用此函数。

  3. lws_client_connect_via_info:连接到WebSocket服务器
    lws_client_connect_via_info是libwebsockets库中的一个函数,它的作用是通过提供的连接信息来建立一个客户端连接。这个函数可以用于在客户端应用程序中连接到服务器。

    • 具体来说,lws_client_connect_via_info函数需要提供以下信息:

      • 连接协议的名称,例如http、https、websocket等。
      • 服务器的主机名或IP地址
      • 服务器的端口号
      • 连接选项,例如SSL/TLS证书等。
      • 回调函数,用于处理连接状态和接收数据。

      通过提供这些信息,lws_client_connect_via_info函数可以建立一个客户端连接,并在连接建立后调用回调函数来处理连接状态和接收数据。

  4. lws_service:处理所有未处理的事件和数据
    在libwebsockets库中,lws_service是一个函数,它用于处理所有未处理的事件和数据。它在一个无限循环中运行,不断地检查是否有新的事件或数据需要处理。当有新的事件或数据到达时,它会调用相应的回调函数来处理它们。lws_service函数是libwebsockets库中最重要的函数之一,它确保了网络连接的稳定和可靠性。

    • lws_service返回值不小于0:返回值表示发生事件的数量
    • lws_service返回值小于0
      • LWS_ERROR: 通用错误
      • LWS_EINTR: 调用被中断
      • LWS_ENOMEM: 内存不足
      • LWS_EAGAIN:操作被阻塞,需要重试
  5. lws_callback_on_writable:提醒libwebsockets可以发送数据
    lws_callback_on_writable 是 libwebsockets 库中的一个函数,它的含义是将一个 websocket 连接的 writable 回调函数加入到事件循环中。当该连接的网络缓冲区变为空闲状态时,就会触发这个回调函数。这个函数通常用于在 websocket 连接中发送数据。调用该函数可以确保在网络缓冲区可写时立即发送数据,从而提高网络传输效率。
    简单来说,调用一次lws_callback_on_writeable,会触发一次callback的LWS_CALLBACK_CLIENT_WRITEABLE,之后可进行一次发送数据操作。

  6. lws_write:将数据写入WebSocket连接
    wsi是指向WebSocket连接的指针,buf是要发送的数据的缓冲区,len是要发送的数据的长度,protocol是数据的协议类型。在WebSocket中,数据可以使用不同的协议类型进行传输,如文本类型、二进制类型等。lws_write函数的返回值为发送的数据长度,如果发送失败则返回一个小于等于0的值。

    数据协议类型:

    • LWS_WRITE_TEXT:发送WebSocket的文本消息。指针必须指向有效的UTF-8编码的数据。

    • LWS_WRITE_BINARY:发送WebSocket的二进制消息。指针必须指向有效的二进制数据。

    • LWS_WRITE_CONTINUATION:继续之前的WebSocket消息。指针必须指向有效的数据。

    • LWS_WRITE_HTTP:发送HTTP内容。

    • LWS_WRITE_PING:发送WebSocket的Ping消息。

    • LWS_WRITE_PONG:发送WebSocket的Pong消息。

    • LWS_WRITE_HTTP_FINAL:与LWS_WRITE_HTTP类似,但表示此写入结束了事务。

    • LWS_WRITE_HTTP_HEADERS:发送HTTP头部。在HTTP/2中,该载荷与LWS_WRITE_HTTP的载荷编码方式不同。

    • LWS_WRITE_HTTP_HEADERS_CONTINUATION:HTTP/2头部的继续部分。

    • LWS_WRITE_BUFLIST:将数据添加到输出缓冲区列表中,而不实际写入。如果你已经有要写入的数据,但写入的时机与连接是否可写无关,且你本来还需要分配一个临时缓冲区并稍后写入,那么这个选项非常有用。

    • LWS_WRITE_NO_FIN:消息的一部分,而不是消息的结尾。

    • LWS_WRITE_H2_STREAM_END:如果允许在HTTP/2的DATA或HEADERS帧上发送STREAM_END,设置该标志以表示该数据包应该带有STREAM_END。

    • LWS_WRITE_CLIENT_IGNORE_XOR_MASK:客户端数据包的有效载荷将不会被编码。这仅对安全测试有用,因为正常的服务器无法解码使用该选项的内容。

  7. lws_get_error:获取错误码
    函数返回一个整数值,表示最近一次发生在给定WebSocket连接上的错误码。如果返回值为0,则表示没有错误发生。调用lws_get_error()函数通常用于在lws_callback_on_writable等函数返回失败时,进一步了解问题的原因。例如,如果lws_callback_on_writable返回负数,可以使用lws_get_error()函数获取错误码,然后根据错误码来诊断问题。
    需要注意的是,错误码是一个全局变量,因此在调用lws_callback_on_writable等函数之前,应该先调用lws_set_log_level()函数来设置日志级别,以避免错误码被其他线程修改

  8. 常见reason

    • LWS_CALLBACK_ESTABLISHED:WebSocket连接已经建立。
    • LWS_CALLBACK_CLOSED:WebSocket连接已经关闭。
    • LWS_CALLBACK_CLIENT_CONNECTION_ERROR:WebSocket客户端连接错误。
    • LWS_CALLBACK_CLIENT_ESTABLISHED:WebSocket客户端连接已经建立。
    • LWS_CALLBACK_CLIENT_RECEIVE:WebSocket客户端接收到数据。
    • LWS_CALLBACK_CLIENT_WRITEABLE:WebSocket客户端可以发送数据。
    • LWS_CALLBACK_SERVER_NEW_CLIENT_INSTANTIATED:WebSocket服务器收到新的客户端连接请求。
    • LWS_CALLBACK_RECEIVE:WebSocket服务器接收到客户端数据。
    • LWS_CALLBACK_SERVER_WRITEABLE:WebSocket服务器可以向客户端发送数据。

常用的结构体

struct lws;
struct lws_context;
struct lws_context_creation_info;
struct lws_protocols;
struct lws_client_connect_info;
void* m_pUser;
  • struct lws:WebSocket 连接的句柄,保存了与连接相关的信息,如连接状态、协议版本、接收和发送缓冲区等。

    • 代表一个特定WebSocket连接的对象。每当新的WebSocket连接建立时,都会创建一个新的lws实例。lws实例包含该连接的所有信息,如连接状态、关联的用户数据等。
  • struct lws_context:WebSocket 的上下文结构体,保存了 WebSocket 服务器的全局配置信息,如端口号、协议列表、SSL证书等。

    • 使用 lws_create_context 创建,参数是 struct lws_context_creation_info
    • 是处理事件函数 lws_service 的主要参数
    • 不需要上下文的时候调用销毁函数 lws_context_destory
    • 在应用程序的生命周期中,一个 lws_context 可以包含多个 lws 实例,每个 lws 实例代表一个活动的 WebSocket 连接。 lws_context 和 lws 的创建和维护由libwebsockets库自身负责,但应用程序可以通过调用特定的API来影响它们的状态。
  • struct lws_context_creation_info:用于创建WebSocket上下文的结构体,包括了WebSocket服务器的配置信息,如日志记录级别、端口号、协议列表、SSL证书等。

    • protocols:一个指向lws_protocol_vhost_options结构体数组的指针,表示支持的协议列表。

    • port:一个整数值,表示监听的端口号。

    • giduid:两个整数值,表示运行lws_context对象的用户和组。

    • options:一个函数指针,表示在创建lws_context对象时需要应用的选项。该函数需要返回0表示成功,或者返回非0值表示失败。

    • ssl_cert_filepathssl_private_key_filepath:两个字符串,表示SSL证书和私钥的文件路径。

    • ssl_ca_filepath:一个字符串,表示SSL证书颁发机构(CA)证书的文件路径。

    • ssl_cipher_list:一个字符串,表示SSL加密算法名称的列表。

    • count_threads:一个整数值,表示使用的线程数。

      // demo
      struct lws_context_creation_info ctx_info = {0};
      ctx_info.port = 8000;
      ctx_info.iface = NULL;  // 在所有网络接口上监听
      ctx_info.protocols = protocols;
      ctx_info.gid = -1;
      ctx_info.uid = -1;
      ctx_info.options = LWS_SERVER_OPTION_VALIDATE_UTF8;
      
      /*
          ctx_info.ssl_ca_filepath = "../ca/ca-cert.pem";
          ctx_info.ssl_cert_filepath = "./server-cert.pem";
          ctx_info.ssl_private_key_filepath = "./server-key.pem";
          ctx_info.options |= LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT;
          // ctx_info.options |=
         LWS_SERVER_OPTION_REQUIRE_VALID_OPENSSL_CLIENT_CERT;
      */
      
  • struct lws_protocols:WebSocket协议的定义结构体,用于描述WebSocket服务器支持的协议类型和回调函数等信息。

    struct lws_protocols {
        const char *name;
        lws_callback_function *callback;
        size_t per_session_data_size;
        size_t rx_buffer_size;
        unsigned int id;
        void *user;
        size_t tx_packet_size;
    };
    
    • name: 协议名称,也是 URI 的一部分。

    • callback: 回调函数指针,用于处理协议事件。

    • per_session_data_size: 每个会话数据的大小,用于分配存储空间。

    • rx_buffer_size: 接收缓冲区的大小。

    • id: 协议 ID,由 libwebsockets 自动分配。

    • user: 用户自定义数据的指针,可以在回调函数中使用。

    • tx_packet_size: 发送数据包的最大大小。如果不指定,则默认为 0,表示使用默认大小。

      // demo
      struct lws_protocols protocols[] = {
          {
              // 协议名称,协议回调,接收缓冲区大小
              "ws",
              protocol_my_callback,
              sizeof(struct session_data),
              MAX_PAYLOAD_SIZE,
          },
          {
              NULL, NULL, 0  // 最后一个元素固定为此格式
          }};
      
  • void* m_pUser:是指向返回回调的对象的指针 *void user,在WebSocket连接创建时可以将用户数据与连接句柄关联起来,方便用户在后续的处理中使用。

    // demo
    struct session_data {
        int msg_count;
        unsigned char buf[LWS_PRE + MAX_PAYLOAD_SIZE];
        int len;
        bool bin;
        bool fin;
    };
    
    // 回调函数内
    struct session_data *data = (struct session_data *)user;
    
  • struct lws_client_connect_info:用于创建WebSocket客户端连接的结构体,包括了客户端连接的配置信息,如服务器地址、端口号、协议类型等。

    当使用 struct lws_client_connect_info 结构体连接服务器时,需要指定客户端连接的目标服务器、连接方式、协议等信息,这些信息可以分为以下 3 类:

    1. 连接相关信息:

      • *struct lws_context context: 指向客户端连接所属的上下文的指针。
      • *const char address: 要连接的服务器的地址,可以是 IP 地址或主机名。
      • int port: 要连接的服务器的端口号。
      • int ssl_connection: 是否使用 SSL 进行连接。设置为 0 表示不使用 SSL,设置为非 0 值表示使用 SSL。
      • *const char path: 要连接的服务器上的路径,通常是“/”。
      • *const char host: 要连接的服务器的主机名。
      • *const char origin: 发起连接的来源。
      • *const char protocol: 要使用的协议名称。
      • int ietf_version_or_minus_one: 要使用的 IETF 版本号。如果没有特定版本要求,则设置为 -1。
      • *const char iface: 指定客户端连接要绑定到的网络接口的名字或 IP 地址。设置为 NULL 表示绑定到任意接口。
      • int local_port: 指定本地绑定的端口号。设置为 0 表示使用系统分配的临时端口号。
      • *const char alpn: 指定要使用的 ALPN 协议名称。如果设置为 NULL,则使用默认列表中的协议。
    2. 客户端身份验证信息:

      • *const char user: 指定要使用的用户名进行身份验证。

      • *const char password: 指定要使用的密码进行身份验证。

      这些字段指定了客户端连接时可能需要使用的用户名和密码,用于进行身份验证。

    3. HTTP 代理服务器相关信息:

      • int http_proxy_port: HTTP 代理服务器的端口号。

      • *const char http_proxy_address: HTTP 代理服务器的地址。

      • *const char http_proxy_username: HTTP 代理服务器的用户名。

      • *const char http_proxy_password: HTTP 代理服务器的密码。

      这些字段指定了客户端连接时可能需要使用的 HTTP 代理服务器相关信息。如果客户端连接需要通过 HTTP 代理服务器进行连接,则需要设置这些字段。

// demo
struct lws_client_connect_info ci = {0};     //websocket 连接信息
//初始化连接信息
ci.context = context;      //设置上下文
ci.address = host.c_str(); //设置目标主机IP
ci.port = port;            //设置目标主机服务端口
ci.path = path.c_str();    //设置目标主机服务PATH
ci.host = ci.address;      //设置目标主机IP
ci.origin = ci.address;    //设置目标主机IP
ci.pwsi = &wsi;            //设置wsi句柄
ci.userdata = NULL;        //userdata 指针会传递给callback的user参数,一般用作自定义变量传入
ci.protocol = lwsprotocol[0].name;

//ws/wss需要不同的配置
ci.ssl_connection = ssl ? (LCCSCF_USE_SSL | LCCSCF_ALLOW_SELFSIGNED | LCCSCF_SKIP_SERVER_CERT_HOSTNAME_CHECK | LCCSCF_ALLOW_INSECURE) : 0;
lws_client_connect_via_info(&ci); //使连接信息生效

连接选项

服务器选项

  • LWS_SERVER_OPTION_REQUIRE_VALID_OPENSSL_CLIENT_CERT:需要客户端提供合法的OpenSSL证书才能连接。
  • LWS_SERVER_OPTION_SKIP_SERVER_CANONICAL_NAME:跳过获取服务器规范主机名。
  • LWS_SERVER_OPTION_ALLOW_NON_SSL_ON_SSL_PORT:允许在SSL监听的端口上接收非SSL(明文)连接。
  • LWS_SERVER_OPTION_LIBEV:使用libev事件循环。
  • LWS_SERVER_OPTION_DISABLE_IPV6:禁用IPv6支持。
  • LWS_SERVER_OPTION_DISABLE_OS_CA_CERTS:不加载操作系统CA证书,需要自行加载CA证书。
  • LWS_SERVER_OPTION_PEER_CERT_NOT_REQUIRED:接受无效证书(如自签名证书)的连接。
  • LWS_SERVER_OPTION_VALIDATE_UTF8:检查UT-8的正确性。
  • LWS_SERVER_OPTION_SSL_ECDH:初始化ECDH加密。
  • LWS_SERVER_OPTION_LIBUV:使用libuv事件循环。
  • LWS_SERVER_OPTION_REDIRECT_HTTP_TO_HTTPS:使用HTTP重定向强制客户端使用HTTPS。
  • LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT:初始化SSL库。
  • LWS_SERVER_OPTION_EXPLICIT_VHOSTS:仅在调用create api时创建context,用户代码自行创建vhosts。
  • LWS_SERVER_OPTION_UNIX_SOCK:使用Unix Socket。
  • LWS_SERVER_OPTION_STS:发送严格传输安全头,强制客户端后续使用HTTPS。
  • LWS_SERVER_OPTION_IPV6_V6ONLY_MODIFY:启用IPV6_V6ONLY_VALUE选项生效。
  • LWS_SERVER_OPTION_IPV6_V6ONLY_VALUE:如果设置,vhost上仅允许IPv6。
  • LWS_SERVER_OPTION_UV_NO_SIGSEGV_SIGFPE_SPIN:Libuv only: 不处理SIGSEGV/SIGFPE信号。默认会spin等待调试器连接。
  • LWS_SERVER_OPTION_JUST_USE_RAW_ORIGIN:默认会在origin前加"http://",此选项将直接使用连接信息中的origin字符串。
  • LWS_SERVER_OPTION_FALLBACK_TO_APPLY_LISTEN_ACCEPT_CONFIG:如果收到无效HTTP请求,放弃HTTP处理,应用listen_accept_role/listen_accept_protocol配置。
  • LWS_SERVER_OPTION_LIBEVENT:使用libevent事件循环。
  • LWS_SERVER_OPTION_ADOPT_APPLY_LISTEN_ACCEPT_CONFIG:所有连接采用listen_accept_role/listen_accept_protocol配置。
  • LWS_SERVER_OPTION_ALLOW_LISTEN_SHARE:允许一个接口+地址+端口上打开多个监听socket。默认一个端口只能一个监听socket。
  • LWS_SERVER_OPTION_CREATE_VHOST_SSL_CTX:即使未提供证书信息,也强制创建vhost SSL_CTX。在callback中将提供SSL_CTX。
  • LWS_SERVER_OPTION_SKIP_PROTOCOL_INIT:跳过vhost的LWS_CALLBACK_PROTOCOL_INIT调用。
  • LWS_SERVER_OPTION_IGNORE_MISSING_CERT:如果证书keyfile不存在,不报错继续启动。
  • LWS_SERVER_OPTION_VHOST_UPG_STRICT_HOST_CHECK:升级连接时检查Host头并验证。
  • LWS_SERVER_OPTION_HTTP_HEADERS_SECURITY_BEST_PRACTICES_ENFORCE:发送Mozilla Observatory建议的安全HTTP头。
  • LWS_SERVER_OPTION_ALLOW_HTTP_ON_HTTPS_LISTENER:允许HTTPS监听器上接收HTTP,不推荐。
  • LWS_SERVER_OPTION_FAIL_UPON_UNABLE_TO_BIND:端口已占用时bind失败返回NULL。
  • LWS_SERVER_OPTION_H2_JUST_FIX_WINDOW_UPDATE_OVERFLOW:忽略错误的h2窗口更新,自行修正。
  • LWS_SERVER_OPTION_VH_H2_HALF_CLOSED_LONG_POLL:半关闭的h2远程客户端进入永久长轮询。
  • LWS_SERVER_OPTION_GLIB:使用glib事件循环。
  • LWS_SERVER_OPTION_H2_PRIOR_KNOWLEDGE:明文HTTP作为h2 priori知识(无升级请求)。
  • LWS_SERVER_OPTION_NO_LWS_SYSTEM_STATES:禁用lws_system状态。
  • LWS_SERVER_OPTION_SS_PROXY:作为安全流代理的监听socket。
  • LWS_SERVER_OPTION_SDEVENT:使用sd-event事件循环。
  • LWS_SERVER_OPTION_ULOOP:使用uloop事件循环。
  • LWS_SERVER_OPTION_DISABLE_TLS_SESSION_CACHE:禁止使用客户端TLS缓存。

客户端选项

  • LCCSCF_USE_SSL:用于指示使用 SSL 连接的标志。
  • LCCSCF_ALLOW_SELFSIGNED:用于指示允许自签名证书的标志。
  • LCCSCF_SKIP_SERVER_CERT_HOSTNAME_CHECK:用于指示跳过服务器证书主机名检查的标志。
  • LCCSCF_ALLOW_EXPIRED:用于指示允许过期证书的标志。
  • LCCSCF_ALLOW_INSECURE:用于指示允许不安全连接的标志。
  • LCCSCF_H2_QUIRK_NGHTTP2_END_STREAM:用于指示 H2 协议的 NGHTTP2_END_STREAM 问题的标志。
  • LCCSCF_H2_QUIRK_OVERFLOWS_TXCR:用于指示 H2 协议的 TXCR 溢出问题的标志。
  • LCCSCF_H2_AUTH_BEARER:用于指示使用 Bearer 授权的 H2 协议标志。
  • LCCSCF_H2_HEXIFY_AUTH_TOKEN:用于指示对 H2 授权令牌进行十六进制编码的标志。
  • LCCSCF_H2_MANUAL_RXFLOW:用于指示手动控制 H2 接收流的标志。
  • LCCSCF_HTTP_MULTIPART_MIME:用于指示使用多部分 MIME 的 HTTP 标志。
  • LCCSCF_HTTP_X_WWW_FORM_URLENCODED:用于指示使用 x-www-form-urlencoded 格式的 HTTP 标志。
  • LCCSCF_HTTP_NO_FOLLOW_REDIRECT:用于指示不跟随重定向的 HTTP 标志。
  • LCCSCF_HTTP_NO_CACHE_CONTROL:用于指示禁用缓存控制的 HTTP 标志。
  • LCCSCF_ALLOW_REUSE_ADDR:用于指示允许地址重用的标志。
  • LCCSCF_PIPELINE:用于指示在可能的情况下对多个客户端连接进行序列化/流水线处理的标志。
  • LCCSCF_MUXABLE_STREAM:用于指示可复用的流的标志。
  • LCCSCF_H2_PRIOR_KNOWLEDGE:用于指示 H2 协议的先验知识的标志。
  • LCCSCF_WAKE_SUSPEND__VALIDITY:用于指示唤醒挂起的连接的标志。
  • LCCSCF_PRIORITIZE_READS:用于指示优先处理读取操作的标志。
  • LCCSCF_SECSTREAM_CLIENT:用于标记客户端 wsi 绑定到安全流的标志。
  • LCCSCF_SECSTREAM_PROXY_LINK:用于标记客户端作为安全流代理链接的标志。
  • LCCSCF_SECSTREAM_PROXY_ONWARD:用于标记客户端作为安全流代理链接的继续连接的标志。
  • LCCSCF_IP_LOW_LATENCY:用于在此连接的 IP 数据包上设置"低延迟"位的标志。
  • LCCSCF_IP_HIGH_THROUGHPUT:用于在此连接的 IP 数据包上设置"高吞吐量"位的标志。
  • LCCSCF_IP_HIGH_RELIABILITY:用于在此连接的 IP 数据包上设置"高可靠性"位的标志。
  • LCCSCF_IP_LOW_COST:用于在此连接的 IP 数据包上设置"最小化费用"位的标志。
  • LCCSCF_CONMON:如果构建时启用了 LWS_WITH_CONMON,则用于保留 getaddrinfo 结果的副本,以便以后查询。
  • LCCSCF_ACCEPT_TLS_DOWNGRADE_REDIRECTS:用于允许 HTTPS 重定向到 HTTP 的标志。
  • LCCSCF_CACHE_COOKIES:如果使用-DLWS_WITH_CACHE_NSCOOKIEJAR进行构建,则用于在此连接上存储和重新应用 Netscape Cookie Jar 中的 HTTPCookie 的标志。

心跳机制

  1. libwebsockets支持心跳机制,为了解决连接链路上长时间不跑数据导致链路被释放问题,在初始化libwebsockets库时,设置一下心跳参数就可以了。

  2. 心跳参数struct lws_context_creation_info

    creation_info_.ka_time = 10;     // 心跳包间的时间间隔
    creation_info_.ka_interval = 10; // 发出心跳包后没有收到ACK确认包时重发心跳包的超时时间
    increation_info_.ka_probes = 3;  // 心跳探测次数,对于windows操作系统
                                     // 此设置是无效的,Windows系统时固定为10次,不可修改
    
  3. libwebsockets的心跳机制最终调用的是lws_plat_set_socket_options接口,该接口内部最终是给对应的socket套接字设置心跳参数的,使用的还是TCP/IP协议栈的心跳,不是应用层自己实现的心跳机制,只能保证连接可用,不能确保业务层是可用的,需要按照自己实际情况来按需选择是否实现业务层心跳。


客户端会触发的一些事件

01. LWS_CALLBACK_OPENSSL_LOAD_EXTRA_CLIENT_VERIFY_CERTS //ssl
1. LWS_CALLBACK_PROTOCOL_INIT
2. LWS_CALLBACK_CLIENT_HTTP_BIND_PROTOCOL
3. LWS_CALLBACK_CONNECTING
4. LWS_CALLBACK_SERVER_NEW_CLIENT_INSTANTIATED
5. LWS_CALLBACK_GET_THREAD_ID
6. LWS_CALLBACK_EVENT_WAIT_CANCELLED
7. LWS_CALLBACK_WSI_CREATE
02. LWS_CALLBACK_OPENSSL_PERFORM_SERVER_CERT_VERIFICATION //ssl
03. LWS_CALLBACK_OPENSSL_LOAD_EXTRA_CLIENT_VERIFY_CERTS //ssl
8. LWS_CALLBACK_CLIENT_APPEND_HANDSHAKE_HEADER
9. LWS_CALLBACK_ESTABLISHED_CLIENT_HTTP
10. LWS_CALLBACK_CLIENT_FILTER_PRE_ESTABLISH
11. LWS_CALLBACK_CLIENT_ESTABLISHED

12. LWS_CALLBACK_CLIENT_RECEIVE
13. LWS_CALLBACK_CLIENT_WRITEABLE
              ...

04. LWS_CALLBACK_VHOST_CERT_AGING //ssl
14. LWS_CALLBACK_WS_CLIENT_DROP_PROTOCOL
15. LWS_CALLBACK_CLIENT_CLOSED
16. LWS_CALLBACK_WSI_DESTROY
17. LWS_CALLBACK_EVENT_WAIT_CANCELLED

服务端会触发的一些事件

1. LWS_CALLBACK_GET_THREAD_ID
2. LWS_CALLBACK_PROTOCOL_INIT
3. LWS_CALLBACK_EVENT_WAIT_CANCELLED
4. LWS_CALLBACK_FILTER_NETWORK_CONNECTION
5. LWS_CALLBACK_WSI_CREATE
6. LWS_CALLBACK_SERVER_NEW_CLIENT_INSTANTIATED
7. LWS_CALLBACK_EVENT_WAIT_CANCELLED
8. LWS_CALLBACK_HTTP_CONFIRM_UPGRADE
9. LWS_CALLBACK_HTTP_BIND_PROTOCOL
10. LWS_CALLBACK_FILTER_PROTOCOL_CONNECTION
11. LWS_CALLBACK_ADD_HEADERS
12. LWS_CALLBACK_ESTABLISHED

13. LWS_CALLBACK_SERVER_WRITEABLE
14. LWS_CALLBACK_RECEIVE
            ...

15. LWS_CALLBACK_WS_SERVER_DROP_PROTOCOL
16. LWS_CALLBACK_CLOSED
17. LWS_CALLBACK_WSI_DESTROY

可能遇到的一些坑

  1. 发包要加在前面留LWS_PRE字节,后面要留LWS_SEND_BUFFER_POST_PADDING字节(虽然是0),写入数据的时候要从&data+LWS_PRE处写起。

    uint8_t data[LWS_PRE + MAX_PAYLOAD_SIZE + LWS_SEND_BUFFER_POST_PADDING];
    
  2. 使用ssl要在lws_context_creation_info的options中加入LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT,来进行全局初始化,不然openssl会报错(E: SSL_new failed: error:00000063:lib(0):func(0):reason(99))。

  3. user指针地址,客户端在struct lws_client_connect_info.userdata传入,不是在lws_protocols

  4. 异步写数据要确认连接已经建立

  5. lws_client_connect_info.address不需要写协议,只写域名lws_client_connect_info.path写请求的路径,不要写到address去

  6. http自定义头要保证key':'结尾

  7. lws_protocols要多申请一个空间,最好以LWS_PROTOCOL_LIST_TERM结尾

  8. 传入的user的指针在这两个reason还没有被初始化,使用会报错

    LWS_CALLBACK_OPENSSL_LOAD_EXTRA_CLIENT_VERIFY_CERTS

    LWS_CALLBACK_PROTOCOL_INIT

  9. ios下移动网、WIFI网络变化,收到LWS_CALLBACK_CLIENT_CLOSED会有延迟 相关链接

  10. lws_write要在enum lws_callback_reasons:LWS_CALLBACK_CLIENT_WRITEABLE中使用,不要异步调用,会有崩溃的危险

  11. 断线重连不应该销毁上下文再重新创建: 相关链接

  12. 对端主动关闭连接将触发LWS_CALLBACK_WS_PEER_INITIATED_CLOSE,而非LWS_CALLBACK_CLIENT_CLOSEDLWS_CALLBACK_CLOSED


定时器

  1. lws_set_timeout(struct lws *wsi, enum pending_timeout reason, int secs)

    用于连接超时的定时器,有更精细的接口lws_set_timeout_us(),另外有两个宏LWS_TO_KILL_ASYNCLWS_TO_KILL_SYNC可以用于强行断开连接

  2. lws_set_timer_usecs(struct lws *wsi, lws_usec_t usecs)

    用于连接中的定时器,单个wsi只会同时存在一个定时器,超时后会在连接callback中返回enum lws_callback_reasons:LWS_CALLBACK_TIMER,可用作业务层心跳保活。宏LWS_SET_TIMER_USEC_CANCEL可用于取消定时器,LWS_MS_PER_SECLWS_US_PER_SEC可用来时间单位转换

  3. 定时器需要进入事件循环(lws_service())才能正常运作


进阶:(GPT生成原文,未验证)

lws_pollfd

struct lws_pollfd结构体通常与lws_service_fd()函数一起使用,用于轮询检查是否有数据可读或可写。使用方法如下:

  1. 创建struct lws_pollfd结构体:
struct lws_pollfd poll_fd = { 0 };
poll_fd.fd = socket_fd; // socket文件描述符
poll_fd.events = POLLOUT | POLLIN; // 监听可读和可写事件
poll_fd.revents = 0;

在上述代码中,socket_fd是一个已经创建好的socket文件描述符,events字段表示要监听的事件,可以是POLLIN(可读事件)或POLLOUT(可写事件)等,revents字段表示已经发生的事件,初始值为0。

  1. 调用lws_service_fd()函数:
int ret = lws_service_fd(context, &poll_fd);

在上述代码中,context是WebSocket上下文,poll_fd是要监听的struct lws_pollfd结构体,lws_service_fd()函数将检查传入的poll_fd结构体中的文件描述符是否有事件发生,如果有,则会调用相应的回调函数进行处理。

  1. 处理事件:
if (poll_fd.revents & POLLOUT) {
    // socket可写事件处理
}

if (poll_fd.revents & POLLIN) {
    // socket可读事件处理
}

在上述代码中,revents字段表示已经发生的事件,可以使用&运算符与POLLINPOLLOUT等常量进行按位与操作,以判断是否有相应的事件发生。如果有,则可以调用相应的处理函数进行处理。

请注意,在使用struct lws_pollfd结构体时,需要先创建好socket文件描述符,并将其赋值给fd字段。同时,需要在每次调用lws_service_fd()函数之前,将revents字段重置为0,以便下一次检查。

lws_create_vhost

lws_create_vhost是libwebsockets库中的一个函数,用于创建一个虚拟主机(vhost)对象。虚拟主机是libwebsockets的一个核心概念,它允许您在单个程序中承载多个独立的WebSocket服务。

函数原型如下:

struct lws_vhost *lws_create_vhost(struct lws_context *context, const struct lws_context_creation_info *info);

参数说明:

  • context:指向lws_context对象的指针,表示libwebsockets上下文。
  • info:指向lws_context_creation_info结构的指针,包含有关虚拟主机创建的详细信息。

该函数的主要目的是创建一个虚拟主机对象,并将其添加到libwebsockets上下文中。虚拟主机对象代表一个独立的WebSocket服务,可以具有自己的配置选项、协议处理和事件回调。

在调用lws_create_vhost之前,您需要首先创建一个lws_context对象,通过调用lws_create_context函数。

以下是一个简单的示例代码,演示了如何使用lws_create_vhost创建一个虚拟主机:

#include <libwebsockets.h>

int main() {
    // 创建上下文
    struct lws_context_creation_info info;
    struct lws_context *context;

    memset(&info, 0, sizeof(info));
    info.port = CONTEXT_PORT_NO_LISTEN;  // 不监听任何端口
    info.protocols = NULL;  // 使用默认协议
    context = lws_create_context(&info);
    if (context == NULL) {
        // 上下文创建失败
        return -1;
    }

    // 创建虚拟主机
    struct lws_vhost *vhost;
    vhost = lws_create_vhost(context, &info);
    if (vhost == NULL) {
        // 虚拟主机创建失败
        lws_context_destroy(context);
        return -1;
    }

    // 使用虚拟主机进行后续操作...

    // 销毁虚拟主机和上下文
    lws_context_destroy(context);

    return 0;
}

大概还会更新…

参考链接:

https://blog.csdn.net/shihoongbo/article/details/113929963

https://blog.csdn.net/bigger_belief/article/details/131111430

https://zhuanlan.zhihu.com/p/559460155

https://libwebsockets.org/lws-api-doc-master/html/group__usercb.html

https://github.com/warmcat/libwebsockets/issues/566

https://github.com/warmcat/libwebsockets/issues/1076

https://blog.csdn.net/yetyongjin/article/details/131082375

https://github.com/warmcat/libwebsockets/issues/2386

Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐