在NUC972上实现websocket客户端
? ??如果有問(wèn)題,請(qǐng)加QQ群 891339868 進(jìn)行交流
? ? 由于項(xiàng)目中要用到websocket協(xié)議實(shí)現(xiàn)一個(gè)websocket客戶(hù)端,而目前開(kāi)源的用C語(yǔ)言開(kāi)發(fā)的websocket庫(kù)貌似只有l(wèi)ibwebsockets,所以決定使用這個(gè)庫(kù)做開(kāi)發(fā)。websocket的具體協(xié)議和庫(kù)的移植就不記錄了,很多前輩已經(jīng)描述的很清楚了,今天主要是記錄一下libwebsockets的使用流程。
? ?websocket協(xié)議還是比較復(fù)雜的,不過(guò)使用庫(kù)了以后,就相對(duì)簡(jiǎn)單了一點(diǎn)兒。libwebsockets的使用主要是三個(gè)部分:
一、初始化后創(chuàng)建一個(gè)context,context的直譯是“上下文的意思”,我的理解就是一個(gè)“會(huì)話(huà)”的意思,context創(chuàng)建成功后,使用lws_service服務(wù)函數(shù)對(duì)該context提供各種服務(wù),其實(shí)就是一個(gè)奸細(xì),時(shí)時(shí)刻刻監(jiān)視著當(dāng)前context中的各種狀態(tài),并會(huì)很及時(shí)的向上級(jí)進(jìn)行匯報(bào);
二、配置客戶(hù)端的各種參數(shù)后連接服務(wù)器,具體的參數(shù)包括服務(wù)器的IP地址、端口號(hào)、協(xié)議名稱(chēng)等;
三、context中的各種狀態(tài)下對(duì)應(yīng)的回調(diào)函數(shù),lws_service服務(wù)函數(shù)監(jiān)視context的狀態(tài)有變化后會(huì)上報(bào)主程序,主程序會(huì)根據(jù)各種狀態(tài)調(diào)用相應(yīng)的回調(diào)函數(shù)。
下面詳細(xì)記錄一下上面提到的三個(gè)步驟:
首先說(shuō)一下初始化,即是websocket中context的創(chuàng)建。與其說(shuō)是初始化,不如說(shuō)是相關(guān)結(jié)構(gòu)體的填充很函數(shù)的調(diào)用,貌似使用庫(kù)的感覺(jué)都是這個(gè)樣子,只能說(shuō)是水平還是有限,比較喜歡用現(xiàn)成的開(kāi)源庫(kù)。這一步相關(guān)的主要的結(jié)構(gòu)體有:
1、struct lws_context_creation_info;
這個(gè)結(jié)構(gòu)體包括了創(chuàng)建context的相關(guān)信息,打開(kāi)這個(gè)結(jié)構(gòu)體詳細(xì)看一下:
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_SUPPORTSSL_CTX *provided_client_ssl_ctx; /* context */ #else /* maintain structure layout either way */void *provided_client_ssl_ctx; #endifshort 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]; };第一個(gè)參數(shù)是port,是配置成websocket服務(wù)器模式時(shí)的監(jiān)聽(tīng)端口,在這里使用的是客戶(hù)端模式,配置成CONTEXT_PORT_NO_LISTEN就可以了;
第二個(gè)參數(shù)是iface,接口的意思,就是制定在當(dāng)前系統(tǒng)中,當(dāng)前context監(jiān)聽(tīng)的是哪個(gè)網(wǎng)絡(luò)接口,比如eth1、eth2等,設(shè)置成為NULL時(shí),監(jiān)聽(tīng)所有接口;
第三個(gè)參數(shù)是protocols,指定當(dāng)前context使用的協(xié)議,這里需要注意一下,如果在客戶(hù)端配置使用的協(xié)議,服務(wù)器也必須配置相同名稱(chēng)的協(xié)議,要不然不能創(chuàng)建context,如果沒(méi)有配置,默認(rèn)使用服務(wù)器中的第一個(gè)協(xié)議進(jìn)行通信;
第四個(gè)參數(shù)是extensions,官方文檔說(shuō)是擴(kuò)展,僅僅用在websocket連接握手階段,目前很少用,在這里直接配置為NULL;
第五個(gè)參數(shù)是token_limits,直譯是標(biāo)志限制,也沒(méi)有搞明白是啥意思,直接配置為NULL;
接下來(lái)四個(gè)參數(shù)都是和ssl相關(guān)的,即加密傳輸先關(guān)的參數(shù),這里沒(méi)有用,直接配置為NULL;
第十個(gè)參數(shù)是http_proxy_address,第十一個(gè)參數(shù)是htttp_proxy_port分別是http代理地址和代理端口號(hào)的設(shè)置,這里不用http代理,都設(shè)置為NULL;
第十一個(gè)參數(shù)是gid,組ID;
第十二個(gè)參數(shù)是uid,用戶(hù)ID;
第十三個(gè)參數(shù)是options,作為客戶(hù)端時(shí)配置為0,作為服務(wù)器時(shí),對(duì)應(yīng)各種選項(xiàng),具體的沒(méi)有研究,后續(xù)有需要的話(huà)在仔細(xì)研究;
第十四個(gè)參數(shù)是user,
第十五個(gè)參數(shù)是ka_time,
第十六個(gè)參數(shù)是ka_probes,
第十七個(gè)參數(shù)是ka_interval,
第十八個(gè)參數(shù)是provided_client_ssl_ctx,
第十九個(gè)參數(shù)是max_http_header_data,在HTTP請(qǐng)求中,可以處理的http頭部最大的數(shù)據(jù)量;
第二十個(gè)參數(shù)是max_http_header_pool,在http請(qǐng)求頭中最大的連接數(shù)
第二十一個(gè)參數(shù)是count_threads,在一個(gè)隊(duì)列中,context的數(shù)量,配置成0的話(huà),只有一個(gè)context;
第二十二個(gè)參數(shù)是fd_limit_per_thread,在線程中對(duì)文件描述符數(shù)量的限制;
第二十三個(gè)參數(shù)是timeout_secs,與網(wǎng)絡(luò)相關(guān)的進(jìn)程超時(shí)保護(hù)的時(shí)間設(shè)置,以免造成僵尸進(jìn)程,配置成0的話(huà),是默認(rèn)配置;
第二十四個(gè)參數(shù)是ecdh_curve,
第二十五個(gè)參數(shù)是vhost_name,虛擬主機(jī)的名稱(chēng),如果用ip地址和端口號(hào)的話(huà),這個(gè)是不需要設(shè)置的,如果用網(wǎng)址的話(huà), 則需要匹配DNS的配置;
第二十六個(gè)參數(shù)是plugin_dirs,
第二十七個(gè)參數(shù)是pvo,
第二十八個(gè)參數(shù)是keepalive_timeout,http連接時(shí)間,默認(rèn)是0,代表60s;
第二十九個(gè)參數(shù)是log_filepath,日志路徑;
第三十個(gè)參數(shù)是mounts,可以選擇的虛擬主機(jī)列表;
第三十一個(gè)參數(shù)是server_string,在http協(xié)議中使用,在websocket協(xié)議中配置為NULL就可以了;
以上就是該結(jié)構(gòu)體的具體描述,其實(shí)對(duì)于websocket客戶(hù)端,里面的很多配置是不需要配置的,直接賦值NULL,就行了,下面的配置是一個(gè)基本的配置:
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);其實(shí)就是配置一下port和protocols,CONTEXT_PORT_NO_LISTEN是代表客戶(hù)端模式,如果是服務(wù)器模式,需要制定需要監(jiān)聽(tīng)的端口號(hào);protocols需要制定約定的協(xié)議;gid、uid都配置為-1,服務(wù)器隨機(jī)給客戶(hù)端配置組ID合用戶(hù)ID;最后調(diào)用lws_create_context接口創(chuàng)建一個(gè)context。
第二步就是配置客戶(hù)端連接服務(wù)器的先關(guān)參數(shù)并連接服務(wù)器,這一步主要的結(jié)構(gòu)體時(shí)struct lws_client_connect_info,詳細(xì)說(shuō)明如下:
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]; };第一個(gè)參數(shù)context,就是第一步創(chuàng)建的context;
第二個(gè)參數(shù)是address,需要連接的遠(yuǎn)程地址,就是服務(wù)器IP地址;
第三個(gè)參數(shù)是port,服務(wù)器端口號(hào);
第四個(gè)參數(shù)是ssl_connection,加密相關(guān)的,0不加密,1加密;
第五個(gè)參數(shù)是path,URI路徑;
第六個(gè)參數(shù)是host,host頭部的內(nèi)容,配置為服務(wù)器IP地址就可以了;
第七個(gè)參數(shù)是origin,origin頭部的內(nèi)容,配置為服務(wù)器IP地址就可以了;
第八個(gè)參數(shù)是protocol,需要配置為使用的協(xié)議的名稱(chēng);
第九個(gè)參數(shù)是ietf_version_or_minus_one,配置成0或者-1,目前沒(méi)有實(shí)際意義;
第十個(gè)參數(shù)是userdata,用戶(hù)數(shù)據(jù),一般配置為NULL;
第十一個(gè)參數(shù)是parent_wsi,是和當(dāng)前websocket連接的父連接相關(guān)的,就是和子進(jìn)程與父進(jìn)程之間的關(guān)系;
第十二個(gè)參數(shù)是uri_replace_from,配置為NULL;
第十三個(gè)參數(shù)是uri_replace_to,配置為NULL;
第十四個(gè)參數(shù)是vhost,bind的虛擬主機(jī)。
下面是一個(gè)struct lws_client_connect_info 的實(shí)例:
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);如上面所示,配置完成后通過(guò)lws_client_connect_via_info這個(gè)函數(shù)去連接服務(wù)器,正常的話(huà),就可以連接上服務(wù)器了。
第三部就是配置相關(guān)的各種回調(diào)函數(shù)來(lái)實(shí)現(xiàn)與服務(wù)器之間的數(shù)據(jù)交互。那么這個(gè)回調(diào)函數(shù)是在哪里配置的呢,在上面的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} }如上所示,這里定義了四個(gè)協(xié)議,第一個(gè)參數(shù)是協(xié)議的名稱(chēng),第二個(gè)參數(shù)就是對(duì)應(yīng)的回調(diào)函數(shù),再看一下這個(gè)回調(diào)函數(shù)的具體內(nèi)容:
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;//連接服務(wù)器失敗,需要重新連接服務(wù)器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;//服務(wù)器斷開(kāi),需要重新連接服務(wù)器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服務(wù)函數(shù)會(huì)實(shí)時(shí)的監(jiān)控當(dāng)前的context,根據(jù)相應(yīng)的網(wǎng)絡(luò)狀態(tài)和數(shù)據(jù)狀態(tài),與該回調(diào)函數(shù)進(jìn)行交互,進(jìn)而完成各種數(shù)據(jù)交互。
好了,說(shuō)到這里,一個(gè)簡(jiǎn)單的websocket客戶(hù)端就配置完成了。
總結(jié)
以上是生活随笔為你收集整理的在NUC972上实现websocket客户端的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 计算机找网络共享盘快捷键,电脑共享快捷键
- 下一篇: R语言中的函数20:parse(),ev