浅谈linux下websockets 的开发

- 引言
WebSockets是HTML5支持的一种让浏览器与服务器全双工通信的协议,其以更小的开销高效的提供了Web连接。相较于经常推送实时数据到客户端甚至通过维护两个HTTP连接来模拟全双工连接的旧的轮询或长轮询来说,这将有效的减少网络流量与延迟。

- 实现原理
在实现websocket连线过程中,需要通过浏览器发出websocket连线请求,然后服务器发出回应,这个过程通常称为"握手" 。在 WebSocket API,浏览器和服务器只需要做一个握手的动作,然后,浏览器和服务器之间就形成了一条快速通道。两者之间就直接可以数据互相传送。在此WebSocket 协议中,为我们实现即时服务带来了两大好处:

  1. Header
    互相沟通的Header是很小的-大概只有 2 Bytes
  2. 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. 回调函数参数

回调函数用于处理对应个阶段的数据。

序号变量名类型含义
01contextstruct lws_context*含义:全局上下文,这是libwebsocket框架的上下文, 负责ws连接的维护和管理
02wsistruct wsi*含义:ws连接对象 每个ws连接均有一个wsi对象与之对应
03reasonenum lws_callback_reasons含义:调用回调的原因,一个协议只对应回调函数,但是调用函数的情况很多,且回调原因不同,参数in的含义也会发生变化。
04uservoid *含义:用户自定义数据,每个ws连接均会带有一个用户自定义数据,可在其中放入用户关心得信息。其大小与协议回调注册表对应配置项中的sizeof()项对应.
05invoid *含义:输入数据,根据调用的原因,该输入数据的含义发生变化。
06lensize_t含义:输入数据的长度
  • 回调函数参数中的reason指明了调用函数的原因,其也代表了ws连接正处于那个处理阶段或状态。比如:在ws连接创建成功后,应该进行自定义数据的初始化;在ws连接销毁阶段,应该释放自定义数据中用户分配的空间等。因此,要正确的编写协议回调函数就必须对reason各状态值有正确的理解。以下将对服务器端开发者需要关心的reason状态值的进行解释:
序号状态值含义
01LWS_CALLBACK_WSI_CREATE正在创建ws连接对象 。 备注:此时表1中的wsi对象和user对象依然为空指针,因此,还不能初始化用户自定义对象。回调函数的参数含义: context: 全局上下文 wsi: 空指针 user: 空指针in: 空指针 len: 0
02LWS_CALLBACK_FILTER_PROTOCOL_CONNECTION含义:使用lws库的人员可以在此过滤协议。备注:在此处返回非0值时,lws库将会关闭该链接;该处返回0时,表示ws连接已经建立成功。此时表1中的wsi对象和user对象已不为空,因此,此时可以对用户自定义对象user进行初始化处理。[注意:测试中发现同一个wsi可能多次由该reason调用回调,且该wsi对象的用户自定义数据的指针会发生变化,导致用户设置的数据丢失,造成严重后果。解决方案:不让lws维护对象,而是我们自己申请和维护数据!]
03LWS_CALLBACK_LOCK_POLL含义:添加保护ws连接状态的互斥锁 。 备注:当采用的是多线程编程,则在此添加互斥锁保护ws连接相关状态,防止冲突。如果是单进程方式,则无需做任何操作。
04LWS_CALLBACK_UNLOCK_POLL含义:解除保护ws连接状态的互斥锁。备注:当采用的是多线程编程,则在此解除互斥锁。如果是单进程方式,则无需做任何操作。
05LWS_CALLBACK_RECEIVE含义:收到一帧完整数据。 备注:表示WS服务端收到客户端发送过来的一帧完整数据,此时表1中的in表示收到的数据,len表示收到的数据长度。需要注意的是:指针in的回收、释放始终由LWS框架管理,只要出了回调函数,该空间就会被LWS框架回收。因此,开发者若想将接收的数据进行转发,则必须对该数据进行拷贝。
06LWS_CALLBACK_SERVER_WRITEABLE义:此ws连接为可写状态。备注:表示wsi对应的ws连接当前处于可写状态,即:可发送数据至客户端。
07LWS_CALLBACK_CLOSED含义:ws连接已经断开。备注:不能在此释放内存空间,否则存在内存泄漏的风险!!!因为连接断开时,并不总是会回调LWS_CALLBACK_CLOSED的处理!
08LWS_CALLBACK_WSI_DESTROY含义:正在销毁ws连接对象。备注:表示libwebsockets框架即将销毁wsi对象。此时如果用户自定义对象中存在动态分配的空间,则需要在此时进行释放。

参考原博链接有link:https://blog.csdn.net/qifengzou/article/details/50281545

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值