C++websocket使用总结

 

一.案例描述

Websocket是一种在单个TCP连接上进行全双工通信的协议。它使得客户端和服务器之间的数据交换变得更加简单,允许服务端主动向客户端推送数据。在WebSocket API中,浏览器和服务器只需要完成一次握手,两者之间就直接可以创建持久性的连接,并进行双向数据传输。

websocket在数据交互时有以下优点:

  • 较少的控制开销。在连接创建后,服务器和客户端之间交换数据时,用于协议控制的数据包头部相对较小。在不包含扩展的情况下,对于服务器到客户端的内容,此头部大小只有210字节(和数据包长度有关);对于客户端到服务器的内容,此头部还需要加上额外的4字节的掩码。相对于HTTP请求每次都要携带完整的头部,此项开销显著减少了。
  • 更强的实时性。由于协议是全双工的,所以服务器可以随时主动给客户端下发数据。相对于HTTP请求需要等待客户端发起请求服务端才能响应,延迟明显更少;即使是和Comet等类似的长轮询比较,其也能在短时间内更多次地传递数据。
  • 保持连接状态。与HTTP不同的是,Websocket需要先创建连接,这就使得其成为一种有状态的协议,之后通信时可以省略部分状态信息。而HTTP请求可能需要在每个请求都携带状态信息(如身份认证等)。
  • 更好的二进制支持。Websocket定义了二进制帧,相对HTTP,可以更轻松地处理二进制内容。
  • 可以支持扩展。Websocket定义了扩展,用户可以扩展协议、实现部分自定义的子协议。如部分浏览器支持压缩等。
  • 更好的压缩效果。相对于HTTP压缩Websocket在适当的扩展支持下,可以沿用之前内容的上下文,在传递类似的数据时,可以显著地提高压缩率。

注:http长连接与websocket的区别

http1.1 出了新头,如果请求头中包含 keep-alive,那么这个 http 请求发送收到返回之后,底层的 tcp 连接不会立马断掉,如果后续有 http 请求还是会利用。但是这个连接保持一来是没有硬性规定时间的,由浏览器和服务端实现来控制。二来这个连接不断是指底层 tcp 连接,不是说一次 http 请求收到返回之后不会断掉,还能再收服务端的返回(如果服务端对这次 http 请求立马返回,那么这次 http 请求就结束了。这种不是应用层面的长连接,其实和模拟 WebSocket 没啥关系。

coment技术是一种 hack 技术,即浏览器发送一个 http 请求,但是服务端不是立马返回,服务端一直不返回直到有浏览器需要的内容了在返回。期间这个 http 请求可以连着维持比较长的时间(在服务端返回之前)。这样模拟一种服务端推送机制。因为浏览器请求的时候等于先把连接建立好,等服务端有消息需要返回时再返回给浏览器。

说一下coment与websocket的区别:

comet 发送 http 请求后服务端如果没有返回则连接是一直连着的,等服务端有东西要“推送”给浏览器时,相当于给之前发送的这个 http 请求回了一个 http 响应。然后这个保持的时间比较长的 http 连接就断了。然后浏览器再次发送一个 http 请求,服务器端再 hold 住不返回,等待有东西需要“推送”给浏览器时,再给这个 http 请求一个响应,然后断开连接。循环往复。一旦浏览器不给服务器发送 http 请求,那么服务器是不能主动给浏览器推送消息的,因为根本没有连着的连接给你推。

WebSocket 则不同,它握手后建立的连接是不会断的(除了意外情况和程序主动掐断)。不需要浏览器在每次收到服务器推送的消息后再发起请求。而且服务器端可以随时给浏览器推送消息,不需要等浏览器发 http 请求,因为 WebSocket 的连接一直在没断。

为什么会有这样的区别呢?

这是协议层面的区别。http 协议规定了 http 连接是一个一来(request)一回(response)的过程。一个请求获得一个响应后必须断掉。而且只有先有请求才会有响应。拿 http1.1 keep-alive 来说,即使底层 tcp 连接没有断,服务端无缘无故给浏览器发一个 http 响应,浏览器是不收的,他找不到收的人啊,因为这个响应没有对应的请求。你看 ajax 必须先发请求才会有一个 onsuccess 回调来响应这个请求。这个 onsuccess 的回调是不会在你 ajax 不发送的情况下被调用到的。

 

二. 案例分析

1.环境搭建

在实际项目中后端服务器使用c++进行实现,故要使用到libwebsockets-master

libwebsocket github下载地址:

https://github.com/warmcat/libwebsockets

 使用流程:

unzip libwebsockets-master.zip

cd libwebsockets-master/

mkdir bulid

cd build/

cmake ../

make

make install

 

最终可以在build中的lib目录中得到以下文件

其中libwebsockets.so.16是我们需要的。

2.libwebsocket接口浅析

(1)注册协议回调表

struct TSessionData {

    long lContext;

    u8   byIsHttp;                             //是否是Http

};

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 }               /* 结束标识 */ 

};

注:libwebsockets支持http和ws同时使用,监听一个端口,但是protocols中0下标必须为http。

(2)回调函数

回调函数的原型:

ws_service_callback(

strcut lws *wsi,

 enum lws_callback_reasons reason,

void *user, 

void *in,

size_t len)

序号

变量名

类型

含义

1

context

struct lws*

全局上下文,负责ws连接的维护和管理

2

reason

enum lws_callback_reasons

调用回调回调函数的事件

3

pContext

void*

上下文,用户自定义结构

4

pData

void*

输入数据

5

len

size_t

输入数据长度

 

(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,但如果第一次发送的数据长度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连接加入可写事件监听

6

lws_callback_on_writable_all_protocol

将某个协议的所有ws连接加入可写事件监听

注:在网络中存在各种情况可能导致服务端并不知道与客户端的连接已经断开:比如客户端掉电。为了应对这种情况的存在。需要每隔一段事件执行该函数,再在协议的回调函数的LWS_CALLBACK_SERVER_WRITEABLE事件中判断一下是否超时,如果超时则返回非零值。

三.遇到的问题及分析

1.在调用lws_write时候会发生崩溃

原因分析: 在某一链路断开的时候,如果自己管理链路没有释放该wsi,回复消息的时候依然向该wsi的链路发送消息就会出现崩溃。在libwebsocket库中,可以在回复消息的时候,调用lws_callback_on_writable,然后在回调消息中会收到LWS_CALLBACK _SER VER _WRITEABLE消息,此时调用lws_write可以避免此问题。

 

2.在同步消息处理中阻塞问题

原因分析:在收到某条消息进行转发处理中,如果一直没有该处理接口出来,就会造成阻塞,导致新的wsi无法连接上,原来的wsi也会出现无法继续通信。解决方法:可以采用异步处理方法,采用消息队列,先存起来然后逐一处理。

 

3. LWS_CALLBACK_FILTER_PROTOCOL_CONNECTION中的问题

在同一个wsi可能多次由改reason调用回调,且该wsi对象的用户自定义数据的指针会发生变化,导致用户设置的数据丢失,造成严重后果。解决方案:不让lws维护对象,而是我们自己申请和维护数据。

 

4.ws_server集成http

libwebsocket该库中,支持监听一个端口,同时使用websocket与http协议。存在的问题:在http消息回复时候结束时候,一定要调用该接口lws_http_transaction_completed,否则http第二次请求会造成阻塞,因为http的状态没有刷新。第二点是在回复消息的时候需要带http头,在libwebsocket中有可以使用的接口——lws_add_http_header_by_name

此外HTTP 议有一个缺陷:通信只能由客户端发起,做不到服务器主动向客户端推送信息。websocket解决了这个问题。

 

 

四.总结

       当今客户端已经由C/S架构逐渐过渡到B/S架构,所以websocket显得日益重要,为web的后端奠定了基础,所以在链路处理的细节中决定了服务器的稳定性。

      

 

 

 

 

 

 

 

 

 

 

  • 4
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值