- 引言
WebSockets是HTML5支持的一种让浏览器与服务器全双工通信的协议,其以更小的开销高效的提供了Web连接。相较于经常推送实时数据到客户端甚至通过维护两个HTTP连接来模拟全双工连接的旧的轮询或长轮询来说,这将有效的减少网络流量与延迟。
- 实现原理
在实现websocket连线过程中,需要通过浏览器发出websocket连线请求,然后服务器发出回应,这个过程通常称为"握手" 。在 WebSocket API,浏览器和服务器只需要做一个握手的动作,然后,浏览器和服务器之间就形成了一条快速通道。两者之间就直接可以数据互相传送。在此WebSocket 协议中,为我们实现即时服务带来了两大好处:
- Header
互相沟通的Header是很小的-大概只有 2 Bytes - Server Push
服务器的推送,服务器不再被动的接收到浏览器的请求之后才返回数据,而是在有新数据时就主动推送给浏览器。
-Websockets 使用说明
1.在linux下使用websockets需要C语言实现libwebsockets进一步开发和相关库及工具的安装。可以参考以下:
link:[添加链接描述](https://github.com/warmcat/libwebsockets/blob/master/READMEs/README.build.md)
2.使用LWS
2.1.首先,要使用libwebsockets,请包含头文件:
#include "libwebsockets.h"
///
2.2.构造lws协议回调
struct libwebsocket_protocols protocols [] = {
{
"http-only", / *协议名称, * /
callback_http, / *回调函数* /
sizeof(struct per_session_data__http), / * per_session_data_size * /
0, / * max buffer * /
},
{
"dumb-increment-protocol",
callback_dumb_increment,
sizeof(struct per_session_data__dumb_increment),
0,
},
{
"lws-command-protocol",
callback_lws_command,
sizeof(struct per_session_data__lws_command),
0,
},
{NULL,NULL,0,0 } / *终止* /
};
ibwebsocket_protocols是libwebsockets中定义的协议结构:
-
http-only是协议的名称。您可以定义您喜欢的任何协议名称,但第一个协议必须始终是HTTP处理程序。
-
callback_http是您实现自己的业务逻辑的回调函数。我们将在下面介绍它。
-
sizeof(struct per_session_data__http)是您要传输的用户数据结构大小。
-
[0]表示最大帧大小。
因此,这里可以定义一组这样的协议,并libwebsockets中处理。剩下的唯一工作就是定义自己的回调函数。典型的http回调函数可能是这样的:
static int callback_http(struct libwebsocket_context *context, struct libwebsocket *wsi, enum libwebsocket_callback_reasons reason, void *user, void *in, size_t len)
{
int n, m;
unsigned char *p;
static unsigned char buffer[4096];
TCHAR cache_file[MAX_PATH];
//struct stat stat_buf;
unsigned int fileSize = 0;
struct per_session_data__http *pss =
(struct per_session_data__http *)user;
DWORD readLen = 0;
char client_name[128];
char client_ip[128];
switch (reason) {
case LWS_CALLBACK_HTTP:
lwsl_notice("http.\n");
/* check for the "send a big file by hand" example case */
sprintf(cache_file, "%s\\cache.png", g_cache_path);
p = buffer;
pss->hFile = CreateFile(cache_file, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_FLAG_SEQUENTIAL_SCAN, NULL);
if (pss->hFile == NULL || pss->hFile == INVALID_HANDLE_VALUE)
return -1;
/*
* we will send a big jpeg file, but it could be
* anything. Set the Content-Type: appropriately
* so the browser knows what to do with it.
*/
p += sprintf((char *)p,
"HTTP/1.0 200 OK\x0d\x0a"
"Server: libwebsockets\x0d\x0a"
"Content-Type: image/png\x0d\x0a"
"Content-Length: %u\x0d\x0a\x0d\x0a",
GetFileSize(pss->hFile, 0));
/*
* send the http headers...
* this won't block since it's the first payload sent
* on the connection since it was established
* (too small for partial)
*/
n = libwebsocket_write(wsi, buffer,
p - buffer, LWS_WRITE_HTTP);
if (n < 0) {
CloseHandle(pss->hFile);
return -1;
}
/*
* book us a LWS_CALLBACK_HTTP_WRITEABLE callback
*/
libwebsocket_callback_on_writable(context, wsi);
break;
case LWS_CALLBACK_HTTP_FILE_COMPLETION:
// lwsl_info("LWS_CALLBACK_HTTP_FILE_COMPLETION seen\n");
/* kill the connection after we sent one file */
return -1;
case LWS_CALLBACK_HTTP_WRITEABLE:
/*
* we can send more of whatever it is we were sending
*/
do {
if (ReadFile(pss->hFile, buffer, sizeof buffer, &readLen, NULL) == INVALID_HANDLE_VALUE)
{
CloseHandle(pss->hFile);
return -1;
}
/* problem reading, close conn */
if (readLen <= 0)
{
CloseHandle(pss->hFile);
return -1;
}
/*
* because it's HTTP and not websocket, don't need to take
* care about pre and postamble
*/
m = libwebsocket_write(wsi, buffer, readLen, LWS_WRITE_HTTP);
if (m < 0)
{
CloseHandle(pss->hFile);
return -1;
}
if (m != readLen)
/* partial write, adjust */
SetFilePointer(pss->hFile, m - readLen, 0, FILE_CURRENT);
} while (!lws_send_pipe_choked(wsi));
libwebsocket_callback_on_writable(context, wsi);
break;
/*
* callback for confirming to continue with client IP appear in
* protocol 0 callback since no websocket protocol has been agreed
* yet. You can just ignore this if you won't filter on client IP
* since the default uhandled callback return is 0 meaning let the
* connection continue.
*/
case LWS_CALLBACK_FILTER_NETWORK_CONNECTION:
libwebsockets_get_peer_addresses(context, wsi, (int)(long)in, client_name, sizeof(client_name) ,client_ip, sizeof(client_ip));
fprintf(stderr, "Received network connect from %s (%s)\n",
client_name, client_ip);
/* if we returned non-zero from here, we kill the connection */
break;
default:
break;
}
return 0;
}
- 实际上,libwebsockets会在收到任何http请求后调用此函数。通过判断switch(reason)中的调用reason,我们可以在正确的case中实现我们的功能。
2.3. 回调函数参数
回调函数用于处理对应个阶段的数据。
序号 | 变量名 | 类型 | 含义 |
---|---|---|---|
01 | context | struct lws_context* | 含义:全局上下文,这是libwebsocket框架的上下文, 负责ws连接的维护和管理 |
02 | wsi | struct wsi* | 含义:ws连接对象 每个ws连接均有一个wsi对象与之对应 |
03 | reason | enum lws_callback_reasons | 含义:调用回调的原因,一个协议只对应回调函数,但是调用函数的情况很多,且回调原因不同,参数in的含义也会发生变化。 |
04 | user | void * | 含义:用户自定义数据,每个ws连接均会带有一个用户自定义数据,可在其中放入用户关心得信息。其大小与协议回调注册表对应配置项中的sizeof()项对应. |
05 | in | void * | 含义:输入数据,根据调用的原因,该输入数据的含义发生变化。 |
06 | len | size_t | 含义:输入数据的长度 |
- 回调函数参数中的reason指明了调用函数的原因,其也代表了ws连接正处于那个处理阶段或状态。比如:在ws连接创建成功后,应该进行自定义数据的初始化;在ws连接销毁阶段,应该释放自定义数据中用户分配的空间等。因此,要正确的编写协议回调函数就必须对reason各状态值有正确的理解。以下将对服务器端开发者需要关心的reason状态值的进行解释:
序号 | 状态值 | 含义 |
---|---|---|
01 | LWS_CALLBACK_WSI_CREATE | 正在创建ws连接对象 。 备注:此时表1中的wsi对象和user对象依然为空指针,因此,还不能初始化用户自定义对象。回调函数的参数含义: context: 全局上下文 wsi: 空指针 user: 空指针in: 空指针 len: 0 |
02 | LWS_CALLBACK_FILTER_PROTOCOL_CONNECTION | 含义:使用lws库的人员可以在此过滤协议。备注:在此处返回非0值时,lws库将会关闭该链接;该处返回0时,表示ws连接已经建立成功。此时表1中的wsi对象和user对象已不为空,因此,此时可以对用户自定义对象user进行初始化处理。[注意:测试中发现同一个wsi可能多次由该reason调用回调,且该wsi对象的用户自定义数据的指针会发生变化,导致用户设置的数据丢失,造成严重后果。解决方案:不让lws维护对象,而是我们自己申请和维护数据!] |
03 | LWS_CALLBACK_LOCK_POLL | 含义:添加保护ws连接状态的互斥锁 。 备注:当采用的是多线程编程,则在此添加互斥锁保护ws连接相关状态,防止冲突。如果是单进程方式,则无需做任何操作。 |
04 | LWS_CALLBACK_UNLOCK_POLL | 含义:解除保护ws连接状态的互斥锁。备注:当采用的是多线程编程,则在此解除互斥锁。如果是单进程方式,则无需做任何操作。 |
05 | LWS_CALLBACK_RECEIVE | 含义:收到一帧完整数据。 备注:表示WS服务端收到客户端发送过来的一帧完整数据,此时表1中的in表示收到的数据,len表示收到的数据长度。需要注意的是:指针in的回收、释放始终由LWS框架管理,只要出了回调函数,该空间就会被LWS框架回收。因此,开发者若想将接收的数据进行转发,则必须对该数据进行拷贝。 |
06 | LWS_CALLBACK_SERVER_WRITEABLE | 义:此ws连接为可写状态。备注:表示wsi对应的ws连接当前处于可写状态,即:可发送数据至客户端。 |
07 | LWS_CALLBACK_CLOSED | 含义:ws连接已经断开。备注:不能在此释放内存空间,否则存在内存泄漏的风险!!!因为连接断开时,并不总是会回调LWS_CALLBACK_CLOSED的处理! |
08 | LWS_CALLBACK_WSI_DESTROY | 含义:正在销毁ws连接对象。备注:表示libwebsockets框架即将销毁wsi对象。此时如果用户自定义对象中存在动态分配的空间,则需要在此时进行释放。 |
参考原博链接有link:https://blog.csdn.net/qifengzou/article/details/50281545