nopoll是linux常用的开源的websocket的实现。可用于websocket的解决方案和已有的TCP的应用提供websocket的支持。
http://www.aspl.es/nopoll/html/nopoll_core_library_manual.html#installing_nopoll
1.安装
nopoll的安装之前需要安装一个依赖OpenSSL,用于通信协议本身要求的加密需求。
2.使用
安装成功后,只需要在我们的源文件中包含头文件nopoll.h
#include <nopoll.h>
如果在多个线程中同时调用nopoll的api,需要设置4个回调用以执行nopoll的创建,销毁和上锁和解锁。
在使用nopoll的API之前需要创建一个noPollCtx的对象,这个对象代表了一个单独的nopoll库的实例。
如下操作:
noPollCtx *ctx = nopoll_ctx_new();
if(!ctx)
{
error
}
//do some websocket ooperation(as client or server)
在用户终止的数据的传输,链接,需要释放这个context通过调用如下接口:
nopoll_ctx_unref(ctx);
3.使用nopoll提供的API创建一个基本的websocket服务器
// create a listener to receive connections on port 1234
noPollConn * listener = nopoll_listener_new (ctx, "0.0.0.0", "1234");
if (! nopoll_conn_is_ok (listener)) {
// some error handling here
}
// now set a handler that will be called when a message (fragment or not) is received
nopoll_ctx_set_on_msg (ctx, listener_on_message, NULL);
// now call to wait for the loop to notify events
nopoll_loop_wait (ctx, 0);
现在,每次只要有数据帧被接收到,就会调用listener_on_message这个处理函数。
void listener_on_message (noPollCtx * ctx, noPollConn * conn, noPollMsg * msg, noPollPtr user_data) {
// print the message (for debugging purposes) and reply
printf ("Listener received (size: %d, ctx refs: %d): (first %d bytes, fragment: %d) '%s'\n",
nopoll_msg_get_payload_size (msg),
nopoll_ctx_ref_count (ctx), shown, nopoll_msg_is_fragment (msg), example);
// reply to the message
nopoll_conn_send_text (conn, "Message received", 16);
return;
}
4.使用nopoll搭建一个基本的websocket客户端。
当创建了一个context(noPollCtx)后就能够通过如下的方式,连接到server
// call to create a connection
noPollConn * conn = nopoll_conn_new (ctx, "localhost", "1234", NULL, NULL, NULL, NULL);
if (! nopoll_conn_is_ok (conn)) {
// some error handling here
}
这之后可以调用nopoll_conn_is_ready 返回一个nopoll_true或者使用nopoll_conn_wait_until_connection_ready.确定网路已经连接。可以通过如下的方式发送数据。
// send a message
if (nopoll_conn_send_text (conn, "Hello there! this is a test", 27) != 27) {
// send a message
}
从websocket的链接上接收数据:
使用nopoll_loop_wait()循环等待,设置消息接收的处理函数(nopoll_ctx_set_on_msg 和nopoll_conn_set_on_msg).
nopoll是否会自动的整合信息帧片段成为一个单独的消息。所有的websocket都必须设计成一个面向流的socket设计。一般来说,nopoll调用接口nopoll_conn_get_msg()尝试接收整合一个完整的消息,当接收到了一个消息片段后,这个消息片段可能是消息的头或者消息体的一部分,然后接口返回一个NULL,等待用户再次调用这个接口用以得到完整的消息。
写操作重复尝试失败,用户每次执行的而写操作(调用nopoll_conn_send_text或者nopoll_conn_send_text_fragment)都有失败的可能,因为socket不能够保持持续的数据接收。
出现这种情况时error=11将会被返回,或者是NOPOLL_EWOULDBLOCK,所以在每次执行写操作时都应该检查。
因为websocket发送的headers中包含了发送消息的长度。所以不能通过调用nopoll_conn_send_text这些发送的接口再次尝试发送数据,
int websocket_write (noPollConn * conn, const char * content, int length) {
// FIRST PART: normal send operation
int tries = 0;
int bytes_written;
// do write operation and check
bytes_written = nopoll_conn_send_text (conn, content, length);
if (bytes_written == length) {
// operation completed, just return bytes written
return bytes_written;
}
// SECOND PART: retry in the case of failure
// some failure found, check errno
while (tries < 5 && errno == NOPOLL_EWOULDBLOCK && nopoll_conn_pending_write_bytes (conn) > 0) {
// ok, unable to write all data but that data is waiting to be flushed
// you can return here and then make your application to retry again or
// try it right now, but with a little pause before continue
nopoll_sleep (10000); // lets wait 10ns
// flush and check if write operation completed
if (nopoll_conn_complete_pending_write (conn) == 0)
return length;
// limit loop
tries++;
}
// failure, return error code reported by the first call or the last retry
return bytes_written;
}
实现协议端口的共享:在同一个端口运行websocket协议或者先前的协议。
由于多个协议共享端口,所以在使用之前一般会确定使用的哪一种协议:
- 通过调用socket的标准接口accept()
- 然后调用接口recv(socket,buffer[3],3,MSG_PEEK);读取没有从队列上移除的3个字节
- 然后按如下的方式检查链接是否为websocket
// detect tls conn
nopoll_bool is_tls_conn = bytes[0] == 22 && bytes[1] == 3 && bytes[2] == 1;
// detect then both values (TLS WebSocket and just WebScoket)
if (! axl_memcmp ("GET", bytes, 3) && ! is_tls_conn)
return nopoll_false; // nothing detected here (it doesn't seems
// to be a websocket connection) continue as normal
// nice, it seems we've found an incoming WebSocket connection
- 如果什么也没有探测到,就需要停止,并用先前的协议。如果探测到的是可能的websocket,需要做如下的参测试:
// Create a noPollConn listener object that presents the legacy listener where the connection was
// received. In general is recommended to reuse these references to avoid creating over and over
// again new references as new WebSocket connections are received.
//
noPollConn * nopoll_listener = nopoll_listener_from_socket (nopoll_ctx, listener_socket);
// Create an accepted listener connection reusing the socket received
// where nopoll_ctx is a reference to a noPollCtx object created and reused by all
// connections accepted, and _socket represents the socket file descriptor
//
noPollConn * conn = nopoll_listener_from_socket (nopoll_ctx, _socket);
if (! nopoll_conn_is_ok (conn)) {
// ok, something failed, release and return
nopoll_conn_close (conn);
return;
}
// Now, initiate the entire WebSocket accept process (including TLS one)
// where nopoll_ctx is the context we used before, nopoll_listener is a listener where
// the connection was received, conn is the connection accepted and is_tls_conn
// is an indication about what to expect about TLS process.
//
if (! nopoll_conn_accept_complete (nopoll_ctx, nopoll_listener, conn, _socket, is_tls_conn)) {
// failed to accept connection, release connection
nopoll_conn_close (conn);
// optionally close listener reference if it is not reused
nopoll_conn_close (nopoll_listener);
return;
}
// now process incoming messages as configured (either because you've configured an onMessage handler)
// or because you are handling directly all incoming content (streaming API).
nopoll提供了peer verify的功能。
nopoll同样可适用于安卓系统。
在linux的SDK中,websocket的实现是使用的开源的nopoll,所以根据实际情况,梳理nopoll在每个接口中的调用和实现。