在NUC972上实现websocket客户端

    如果有问题,请加QQ群 891339868 进行交流

    由于项目中要用到websocket协议实现一个websocket客户端,而目前开源的用C语言开发的websocket库貌似只有libwebsockets,所以决定使用这个库做开发。websocket的具体协议和库的移植就不记录了,很多前辈已经描述的很清楚了,今天主要是记录一下libwebsockets的使用流程。

   websocket协议还是比较复杂的,不过使用库了以后,就相对简单了一点儿。libwebsockets的使用主要是三个部分:

一、初始化后创建一个context,context的直译是“上下文的意思”,我的理解就是一个“会话”的意思,context创建成功后,使用lws_service服务函数对该context提供各种服务,其实就是一个奸细,时时刻刻监视着当前context中的各种状态,并会很及时的向上级进行汇报;

二、配置客户端的各种参数后连接服务器,具体的参数包括服务器的IP地址、端口号、协议名称等;

三、context中的各种状态下对应的回调函数,lws_service服务函数监视context的状态有变化后会上报主程序,主程序会根据各种状态调用相应的回调函数。

下面详细记录一下上面提到的三个步骤:

首先说一下初始化,即是websocket中context的创建。与其说是初始化,不如说是相关结构体的填充很函数的调用,貌似使用库的感觉都是这个样子,只能说是水平还是有限,比较喜欢用现成的开源库。这一步相关的主要的结构体有:

1、struct lws_context_creation_info;

这个结构体包括了创建context的相关信息,打开这个结构体详细看一下:

struct lws_context_creation_info {
	int port;					/* VH */
	const char *iface;				/* VH */
	const struct lws_protocols *protocols;		/* VH */
	const struct lws_extension *extensions;		/* VH */
	const struct lws_token_limits *token_limits;	/* context */
	const char *ssl_private_key_password;		/* VH */
	const char *ssl_cert_filepath;			/* VH */
	const char *ssl_private_key_filepath;		/* VH */
	const char *ssl_ca_filepath;			/* VH */
	const char *ssl_cipher_list;			/* VH */
	const char *http_proxy_address;			/* VH */
	unsigned int http_proxy_port;			/* VH */
	int gid;					/* context */
	int uid;					/* context */
	unsigned int options;				/* VH + context */
	void *user;					/* context */
	int ka_time;					/* context */
	int ka_probes;					/* context */
	int ka_interval;				/* context */
#ifdef LWS_OPENSSL_SUPPORT
	SSL_CTX *provided_client_ssl_ctx;		/* context */
#else /* maintain structure layout either way */
	void *provided_client_ssl_ctx;
#endif

	short max_http_header_data;			/* context */
	short max_http_header_pool;			/* context */

	unsigned int count_threads;			/* context */
	unsigned int fd_limit_per_thread;		/* context */
	unsigned int timeout_secs;			/* VH */
	const char *ecdh_curve;				/* VH */
	const char *vhost_name;				/* VH */
	const char * const *plugin_dirs;		/* context */
	const struct lws_protocol_vhost_options *pvo;	/* VH */
	int keepalive_timeout;				/* VH */
	const char *log_filepath;			/* VH */
	const struct lws_http_mount *mounts;		/* VH */
	const char *server_string;			/* context */

	/* Add new things just above here ---^
	 * This is part of the ABI, don't needlessly break compatibility
	 *
	 * The below is to ensure later library versions with new
	 * members added above will see 0 (default) even if the app
	 * was not built against the newer headers.
	 */

	void *_unused[8];
};

第一个参数是port,是配置成websocket服务器模式时的监听端口,在这里使用的是客户端模式,配置成CONTEXT_PORT_NO_LISTEN就可以了;

第二个参数是iface,接口的意思,就是制定在当前系统中,当前context监听的是哪个网络接口,比如eth1、eth2等,设置成为NULL时,监听所有接口;

第三个参数是protocols,指定当前context使用的协议,这里需要注意一下,如果在客户端配置使用的协议,服务器也必须配置相同名称的协议,要不然不能创建context,如果没有配置,默认使用服务器中的第一个协议进行通信;

第四个参数是extensions,官方文档说是扩展,仅仅用在websocket连接握手阶段,目前很少用,在这里直接配置为NULL;

第五个参数是token_limits,直译是标志限制,也没有搞明白是啥意思,直接配置为NULL;

接下来四个参数都是和ssl相关的,即加密传输先关的参数,这里没有用,直接配置为NULL;

第十个参数是http_proxy_address,第十一个参数是htttp_proxy_port分别是http代理地址和代理端口号的设置,这里不用http代理,都设置为NULL;

第十一个参数是gid,组ID;

第十二个参数是uid,用户ID;

第十三个参数是options,作为客户端时配置为0,作为服务器时,对应各种选项,具体的没有研究,后续有需要的话在仔细研究;

第十四个参数是user,

第十五个参数是ka_time,

第十六个参数是ka_probes,

第十七个参数是ka_interval,

第十八个参数是provided_client_ssl_ctx,

第十九个参数是max_http_header_data,在HTTP请求中,可以处理的http头部最大的数据量;

第二十个参数是max_http_header_pool,在http请求头中最大的连接数

第二十一个参数是count_threads,在一个队列中,context的数量,配置成0的话,只有一个context;

第二十二个参数是fd_limit_per_thread,在线程中对文件描述符数量的限制;

第二十三个参数是timeout_secs,与网络相关的进程超时保护的时间设置,以免造成僵尸进程,配置成0的话,是默认配置;

第二十四个参数是ecdh_curve,

第二十五个参数是vhost_name,虚拟主机的名称,如果用ip地址和端口号的话,这个是不需要设置的,如果用网址的话, 则需要匹配DNS的配置;

第二十六个参数是plugin_dirs,

第二十七个参数是pvo,

第二十八个参数是keepalive_timeout,http连接时间,默认是0,代表60s;

第二十九个参数是log_filepath,日志路径;

第三十个参数是mounts,可以选择的虚拟主机列表;

第三十一个参数是server_string,在http协议中使用,在websocket协议中配置为NULL就可以了;

以上就是该结构体的具体描述,其实对于websocket客户端,里面的很多配置是不需要配置的,直接赋值NULL,就行了,下面的配置是一个基本的配置:

    data->context_info = calloc(1,sizeof(struct lws_context_creation_info));
	
	data->context_info->port = CONTEXT_PORT_NO_LISTEN;
	data->context_info->iface = NULL;
	data->context_info->protocols = data->protocols;
	data->context_info->ssl_cert_filepath = NULL;
	data->context_info->ssl_private_key_filepath = NULL;
	//data->context_info->extensions = lws_get_internal_extensions();
	data->context_info->gid = -1;
	data->context_info->uid = -1;
	data->context_info->options =0;


	struct lws_context *context;

	context = lws_create_context(data->context_info);

其实就是配置一下port和protocols,CONTEXT_PORT_NO_LISTEN是代表客户端模式,如果是服务器模式,需要制定需要监听的端口号;protocols需要制定约定的协议;gid、uid都配置为-1,服务器随机给客户端配置组ID合用户ID;最后调用lws_create_context接口创建一个context。

第二步就是配置客户端连接服务器的先关参数并连接服务器,这一步主要的结构体时struct lws_client_connect_info,详细说明如下:

struct lws_client_connect_info {
	struct lws_context *context;
	const char *address;
	int port;
	int ssl_connection;
	const char *path;
	const char *host;
	const char *origin;
	const char *protocol;
	int ietf_version_or_minus_one;
	void *userdata;
	const struct lws_extension *client_exts;
	const char *method;
	struct lws *parent_wsi;
	const char *uri_replace_from;
	const char *uri_replace_to;
	struct lws_vhost *vhost;

	/* Add new things just above here ---^
	 * This is part of the ABI, don't needlessly break compatibility
	 *
	 * The below is to ensure later library versions with new
	 * members added above will see 0 (default) even if the app
	 * was not built against the newer headers.
	 */

	void *_unused[4];
};

第一个参数context,就是第一步创建的context;

第二个参数是address,需要连接的远程地址,就是服务器IP地址;

第三个参数是port,服务器端口号;

第四个参数是ssl_connection,加密相关的,0不加密,1加密;

第五个参数是path,URI路径;

第六个参数是host,host头部的内容,配置为服务器IP地址就可以了;

第七个参数是origin,origin头部的内容,配置为服务器IP地址就可以了;

第八个参数是protocol,需要配置为使用的协议的名称;

第九个参数是ietf_version_or_minus_one,配置成0或者-1,目前没有实际意义;

第十个参数是userdata,用户数据,一般配置为NULL;

第十一个参数是parent_wsi,是和当前websocket连接的父连接相关的,就是和子进程与父进程之间的关系;

第十二个参数是uri_replace_from,配置为NULL;

第十三个参数是uri_replace_to,配置为NULL;

第十四个参数是vhost,bind的虚拟主机。

下面是一个struct lws_client_connect_info 的实例:

    struct lws_client_connect_info conn_info = {0};
	conn_info.context = context;
	conn_info.address = data->host_address;
	conn_info.port = data->host_port;
	conn_info.ssl_connection = 0;
	conn_info.path = "./";
	conn_info.host = data->host_address;
	conn_info.origin = data->host_address;
	conn_info.protocol = data->protocols[0].name;
	conn_info.ietf_version_or_minus_one = -1;
	conn_info.method = NULL;

	struct lws *wsi;	
	re_connect_server:wsi = lws_client_connect_via_info(&conn_info);

如上面所示,配置完成后通过lws_client_connect_via_info这个函数去连接服务器,正常的话,就可以连接上服务器了。

第三部就是配置相关的各种回调函数来实现与服务器之间的数据交互。那么这个回调函数是在哪里配置的呢,在上面的protocol里,首先看一下具体的例子:

struct lws_protocols protocols[] = {
	{
		NULL,
		ws_client_service_callback, 
		sizeof(struct session_data), 
		1024
	},
	{
		"dumb-increment-protocol",
		ws_client_service_callback, 
		sizeof(struct session_data), 
		1024
	},
	{
		"lws-mirror-protocol",
		ws_client_service_callback, 
		sizeof(struct session_data), 
		1024
	},
	{
		"my-echo-protocol",
		ws_client_service_callback, 
		sizeof(struct session_data), 
		1024
	},
	{
		NULL, NULL, 0, 0
	}
}

如上所示,这里定义了四个协议,第一个参数是协议的名称,第二个参数就是对应的回调函数,再看一下这个回调函数的具体内容:

int ws_client_service_callback(struct lws *wsi, enum lws_callback_reasons reason,
						void *user, void *in, size_t len)
{
	switch (reason) {
		case LWS_CALLBACK_CLIENT_ESTABLISHED:
			printf(KYEL"[Main Service] Connect with server success.\n"RESET);
			connection_flag = 1;
			break;

		case LWS_CALLBACK_CLIENT_CONNECTION_ERROR:
			printf(KRED"[Main Service] Connect with server error.\n"RESET);
			//destroy_flag = 1;
			re_connect_server_flag = 1;//连接服务器失败,需要重新连接服务器
			connection_flag = 0;
			break;
			
		case LWS_CALLBACK_CLOSED:
			printf(KYEL"[Main Service] LWS_CALLBACK_CLOSED\n"RESET);
			//destroy_flag = 1;
			re_connect_server_flag = 1;//服务器断开,需要重新连接服务器
			connection_flag = 0;
			break;
			
		case LWS_CALLBACK_CLIENT_RECEIVE:
			printf(KCYN_L"[Main Service] Client recvived:%s\n"RESET, (char *)in);
			/*if (writeable_flag)
				destroy_flag = 1;*/
			break;

		case LWS_CALLBACK_CLIENT_WRITEABLE:
			printf(KYEL"[Main Service] On writeable is called. send byebye message\n"RESET);
			websocket_write_back(wsi, "Byebye! See you later", -1);
			writeable_flag = 1;
			break;

		default:
			break;
		
	}

	return 0;
}

如上所示,lws_service服务函数会实时的监控当前的context,根据相应的网络状态和数据状态,与该回调函数进行交互,进而完成各种数据交互。

好了,说到这里,一个简单的websocket客户端就配置完成了。

  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值