server.c
#include "libwebsockets.h"
#include
#include
static volatile int exit_sig = 0;
#define MAX_PAYLOAD_SIZE 10 * 1024
void sighdl( int sig ) {
lwsl_notice( "%d traped", sig );
exit_sig = 1;
}
/**
* 会话上下文对象,结构根据需要自定义
*/
struct session_data {
int msg_count;
unsigned char buf[LWS_PRE + MAX_PAYLOAD_SIZE];
int len;
bool bin;
bool fin;
};
static int protocol_my_callback( struct lws *wsi, enum lws_callback_reasons reason, void *user, void *in, size_t len ) {
struct session_data *data = (struct session_data *) user;
switch ( reason ) {
case LWS_CALLBACK_ESTABLISHED: // 当服务器和客户端完成握手后
printf("Client connect!\n");
break;
case LWS_CALLBACK_RECEIVE: // 当接收到客户端发来的帧以后
// 判断是否最后一帧
data->fin = lws_is_final_fragment( wsi );
// 判断是否二进制消息
data->bin = lws_frame_is_binary( wsi );
// 对服务器的接收端进行流量控制,如果来不及处理,可以控制之
// 下面的调用禁止在此连接上接收数据
lws_rx_flow_control( wsi, 0 );
// 业务处理部分,为了实现Echo服务器,把客户端数据保存起来
memcpy( &data->buf[ LWS_PRE ], in, len );
data->len = len;
printf("recvied message:%s\n",in);
// 需要给客户端应答时,触发一次写回调
lws_callback_on_writable( wsi );
break;
case LWS_CALLBACK_SERVER_WRITEABLE: // 当此连接可写时
lws_write( wsi, &data->buf[ LWS_PRE ], data->len, LWS_WRITE_TEXT );
// 下面的调用允许在此连接上接收数据
lws_rx_flow_control( wsi, 1 );
break;
}
// 回调函数最终要返回0,否则无法创建服务器
return 0;
}
/**
* 支持的WebSocket子协议数组
* 子协议即JavaScript客户端WebSocket(url, protocols)第2参数数组的元素
* 你需要为每种协议提供回调函数
*/
struct lws_protocols protocols[] = {
{
//协议名称,协议回调,接收缓冲区大小
"ws", protocol_my_callback, sizeof( struct session_data ), MAX_PAYLOAD_SIZE,
},
{
NULL, NULL, 0 // 最后一个元素固定为此格式
}
};
int main(int argc,char **argv)
{
// 信号处理函数
signal( SIGTERM, sighdl );
struct lws_context_creation_info ctx_info = { 0 };
ctx_info.port = 8000;
ctx_info.iface = NULL; // 在所有网络接口上监听
ctx_info.protocols = protocols;
ctx_info.gid = -1;
ctx_info.uid = -1;
ctx_info.options = LWS_SERVER_OPTION_VALIDATE_UTF8;
ctx_info.ssl_ca_filepath = "../ca/ca-cert.pem";
ctx_info.ssl_cert_filepath = "./server-cert.pem";
ctx_info.ssl_private_key_filepath = "./server-key.pem";
ctx_info.options |= LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT;
//ctx_info.options |= LWS_SERVER_OPTION_REQUIRE_VALID_OPENSSL_CLIENT_CERT;
struct lws_context *context = lws_create_context(&ctx_info);
while ( !exit_sig ) {
lws_service(context, 1000);
}
lws_context_destroy(context);
return 0;
}
编译脚本,compile.sh
#########################################################################
# File Name: compile.sh
# Author: loon
# mail: [email protected]
# Created Time: 2018年09月07日 星期五 10时08分52秒
#########################################################################
#!/bin/bash
libdir=libwebsockets
g++ -g -o server server.c -I$libdir/include -L$libdir/lib -lwebsockets
client.c
#include "libwebsockets.h"
#include
static volatile int exit_sig = 0;
#define MAX_PAYLOAD_SIZE 10 * 1024
void sighdl( int sig ) {
lwsl_notice( "%d traped", sig );
exit_sig = 1;
}
/**
* 会话上下文对象,结构根据需要自定义
*/
struct session_data {
int msg_count;
unsigned char buf[LWS_PRE + MAX_PAYLOAD_SIZE];
int len;
};
/**
* 某个协议下的连接发生事件时,执行的回调函数
*
* wsi:指向WebSocket实例的指针
* reason:导致回调的事件
* user 库为每个WebSocket会话分配的内存空间
* in 某些事件使用此参数,作为传入数据的指针
* len 某些事件使用此参数,说明传入数据的长度
*/
int callback( struct lws *wsi, enum lws_callback_reasons reason, void *user, void *in, size_t len ) {
struct session_data *data = (struct session_data *) user;
switch ( reason ) {
case LWS_CALLBACK_CLIENT_ESTABLISHED: // 连接到服务器后的回调
lwsl_notice( "Connected to server ok!\n" );
break;
case LWS_CALLBACK_CLIENT_RECEIVE: // 接收到服务器数据后的回调,数据为in,其长度为len
lwsl_notice( "Rx: %s\n", (char *) in );
break;
case LWS_CALLBACK_CLIENT_WRITEABLE: // 当此客户端可以发送数据时的回调
if ( data->msg_count < 3 ) {
// 前面LWS_PRE个字节必须留给LWS
memset( data->buf, 0, sizeof( data->buf ));
char *msg = (char *) &data->buf[ LWS_PRE ];
data->len = sprintf( msg, "你好 %d", ++data->msg_count );
lwsl_notice( "Tx: %s\n", msg );
// 通过WebSocket发送文本消息
lws_write( wsi, &data->buf[ LWS_PRE ], data->len, LWS_WRITE_TEXT );
}
break;
}
return 0;
}
/**
* 支持的WebSocket子协议数组
* 子协议即JavaScript客户端WebSocket(url, protocols)第2参数数组的元素
* 你需要为每种协议提供回调函数
*/
struct lws_protocols protocols[] = {
{
//协议名称,协议回调,接收缓冲区大小
"ws", callback, sizeof( struct session_data ), MAX_PAYLOAD_SIZE,
},
{
NULL, NULL, 0 // 最后一个元素固定为此格式
}
};
int main() {
// 信号处理函数
signal( SIGTERM, sighdl );
// 用于创建vhost或者context的参数
struct lws_context_creation_info ctx_info = { 0 };
ctx_info.port = CONTEXT_PORT_NO_LISTEN;
ctx_info.iface = NULL;
ctx_info.protocols = protocols;
ctx_info.gid = -1;
ctx_info.uid = -1;
//ssl支持(指定CA证书、客户端证书及私钥路径,打开ssl支持)
ctx_info.ssl_ca_filepath = "../ca/ca-cert.pem";
ctx_info.ssl_cert_filepath = "./client-cert.pem";
ctx_info.ssl_private_key_filepath = "./client-key.pem";
ctx_info.options |= LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT;
// 创建一个WebSocket处理器
struct lws_context *context = lws_create_context( &ctx_info );
char address[] = "127.0.0.1";
int port = 8000;
char addr_port[256] = { 0 };
sprintf(addr_port, "%s:%u", address, port & 65535 );
// 客户端连接参数
struct lws_client_connect_info conn_info = { 0 };
conn_info.context = context;
conn_info.address = address;
conn_info.port = port;
conn_info.ssl_connection = 1;
conn_info.path = "./";
conn_info.host = addr_port;
conn_info.origin = addr_port;
conn_info.protocol = protocols[ 0 ].name;
// 下面的调用触发LWS_CALLBACK_PROTOCOL_INIT事件
// 创建一个客户端连接
struct lws *wsi = lws_client_connect_via_info( &conn_info );
while ( !exit_sig ) {
// 执行一次事件循环(Poll),最长等待1000毫秒
lws_service( context, 1000 );
/**
* 下面的调用的意义是:当连接可以接受新数据时,触发一次WRITEABLE事件回调
* 当连接正在后台发送数据时,它不能接受新的数据写入请求,所有WRITEABLE事件回调不会执行
*/
lws_callback_on_writable( wsi );
}
// 销毁上下文对象
lws_context_destroy( context );
return 0;
}
编译脚本:
#########################################################################
# File Name: compile.sh
# Author: loon
# mail: [email protected]
# Created Time: 2018年09月07日 星期五 10时22分58秒
#########################################################################
#!/bin/bash
libdir=libwebsockets
g++ -g -o client client.c -I$libdir/include -L$libdir/build/lib -lwebsockets
其实下载了官网源码后minimal-examples下面有很多的例子,里面已经写好了cmake的文件,看一下对应的README后直接可以在对应位置编译,我这里复制minimal-ws-client和一个minimal-ws-server的例子,我基本只改了服务地址和端口,然后增加了打印(还有tx和rx客户端分别代表发送和接收,都可以看一下):
客户端:
/*
* lws-minimal-ws-client-tx
*
* Written in 2010-2019 by Andy Green
*
* This file is made available under the Creative Commons CC0 1.0
* Universal Public Domain Dedication.
*
* This demonstrates a ws "publisher" to go with the minimal-ws-broker
* example.
*
* Two threads are spawned that produce messages to be sent to the broker,
* via a local ringbuffer. Locking is provided to make ringbuffer access
* threadsafe.
*
* When a nailed-up client connection to the broker is established, the
* ringbuffer is sent to the broker, which distributes the events to all
* connected clients.
*/
#include
#include
#include
#include
static int interrupted;
/* one of these created for each message */
struct msg {
void *payload; /* is malloc'd */
size_t len;
};
struct per_vhost_data__minimal {
struct lws_context *context;
struct lws_vhost *vhost;
const struct lws_protocols *protocol;
pthread_t pthread_spam[2];
pthread_mutex_t lock_ring; /* serialize access to the ring buffer */
struct lws_ring *ring; /* ringbuffer holding unsent messages */
uint32_t tail;
struct lws_client_connect_info i;
struct lws *client_wsi;
int counter;
char finished;
char established;
};
#if defined(WIN32)
static void usleep(unsigned long l) { Sleep(l / 1000); }
#endif
static void
__minimal_destroy_message(void *_msg)
{
struct msg *msg = _msg;
free(msg->payload);
msg->payload = NULL;
msg->len = 0;
}
static void *
thread_spam(void *d)
{
struct per_vhost_data__minimal *vhd =
(struct per_vhost_data__minimal *)d;
struct msg amsg;
int len = 128, index = 1, n;
do {
/* don't generate output if client not connected */
if (!vhd->established)
goto wait;
pthread_mutex_lock(&vhd->lock_ring); /* --------- ring lock { */
/* only create if space in ringbuffer */
n = (int)lws_ring_get_count_free_elements(vhd->ring);
if (!n) {
lwsl_user("dropping!\n");
goto wait_unlock;
}
amsg.payload = malloc(LWS_PRE + len);
if (!amsg.payload) {
lwsl_user("OOM: dropping\n");
goto wait_unlock;
}
n = lws_snprintf((char *)amsg.payload + LWS_PRE, len,
"tid: %p, msg: %d",
(void *)pthread_self(), index++);
amsg.len = n;
n = lws_ring_insert(vhd->ring, &amsg, 1);
if (n != 1) {
__minimal_destroy_message(&amsg);
lwsl_user("dropping!\n");
} else
/*
* This will cause a LWS_CALLBACK_EVENT_WAIT_CANCELLED
* in the lws service thread context.
*/
lws_cancel_service(vhd->context);
wait_unlock:
pthread_mutex_unlock(&vhd->lock_ring); /* } ring lock ------- */
wait:
usleep(100000);
} while (!vhd->finished);
lwsl_notice("thread_spam %p exiting\n", (void *)pthread_self());
pthread_exit(NULL);
return NULL;
}
static int
connect_client(struct per_vhost_data__minimal *vhd)
{
vhd->i.context = vhd->context;
vhd->i.port = 7681;
vhd->i.address = "localhost";
vhd->i.path = "/publisher";
vhd->i.host = vhd->i.address;
vhd->i.origin = vhd->i.address;
vhd->i.ssl_connection = 0;
vhd->i.protocol = "lws-minimal-broker";
vhd->i.pwsi = &vhd->client_wsi;
return !lws_client_connect_via_info(&vhd->i);
}
static int
callback_minimal_broker(struct lws *wsi, enum lws_callback_reasons reason,
void *user, void *in, size_t len)
{
struct per_vhost_data__minimal *vhd =
(struct per_vhost_data__minimal *)
lws_protocol_vh_priv_get(lws_get_vhost(wsi),
lws_get_protocol(wsi));
const struct msg *pmsg;
void *retval;
int n, m, r = 0;
switch (reason) {
/* --- protocol lifecycle callbacks --- */
case LWS_CALLBACK_PROTOCOL_INIT:
vhd = lws_protocol_vh_priv_zalloc(lws_get_vhost(wsi),
lws_get_protocol(wsi),
sizeof(struct per_vhost_data__minimal));
vhd->context = lws_get_context(wsi);
vhd->protocol = lws_get_protocol(wsi);
vhd->vhost = lws_get_vhost(wsi);
vhd->ring = lws_ring_create(sizeof(struct msg), 8,
__minimal_destroy_message);
if (!vhd->ring)
return 1;
pthread_mutex_init(&vhd->lock_ring, NULL);
/* start the content-creating threads */
for (n = 0; n < (int)LWS_ARRAY_SIZE(vhd->pthread_spam); n++)
if (pthread_create(&vhd->pthread_spam[n], NULL,
thread_spam, vhd)) {
lwsl_err("thread creation failed\n");
r = 1;
goto init_fail;
}
if (connect_client(vhd))
lws_timed_callback_vh_protocol(vhd->vhost,
vhd->protocol, LWS_CALLBACK_USER, 1);
break;
case LWS_CALLBACK_PROTOCOL_DESTROY:
init_fail:
vhd->finished = 1;
for (n = 0; n < (int)LWS_ARRAY_SIZE(vhd->pthread_spam); n++)
if (vhd->pthread_spam[n])
pthread_join(vhd->pthread_spam[n], &retval);
if (vhd->ring)
lws_ring_destroy(vhd->ring);
pthread_mutex_destroy(&vhd->lock_ring);
return r;
case LWS_CALLBACK_CLIENT_CONNECTION_ERROR:
lwsl_err("CLIENT_CONNECTION_ERROR: %s\n",
in ? (char *)in : "(null)");
vhd->client_wsi = NULL;
lws_timed_callback_vh_protocol(vhd->vhost,
vhd->protocol, LWS_CALLBACK_USER, 1);
break;
/* --- client callbacks --- */
case LWS_CALLBACK_CLIENT_ESTABLISHED:
lwsl_user("%s: established\n", __func__);
vhd->established = 1;
break;
case LWS_CALLBACK_CLIENT_WRITEABLE:
pthread_mutex_lock(&vhd->lock_ring); /* --------- ring lock { */
pmsg = lws_ring_get_element(vhd->ring, &vhd->tail);
if (!pmsg)
goto skip;
/* notice we allowed for LWS_PRE in the payload already */
m = lws_write(wsi, ((unsigned char *)pmsg->payload) + LWS_PRE,
pmsg->len, LWS_WRITE_TEXT);
if (m < (int)pmsg->len) {
pthread_mutex_unlock(&vhd->lock_ring); /* } ring lock */
lwsl_err("ERROR %d writing to ws socket\n", m);
return -1;
}
lws_ring_consume_single_tail(vhd->ring, &vhd->tail, 1);
/* more to do for us? */
if (lws_ring_get_element(vhd->ring, &vhd->tail))
/* come back as soon as we can write more */
lws_callback_on_writable(wsi);
skip:
pthread_mutex_unlock(&vhd->lock_ring); /* } ring lock ------- */
break;
case LWS_CALLBACK_CLIENT_CLOSED:
vhd->client_wsi = NULL;
vhd->established = 0;
lws_timed_callback_vh_protocol(vhd->vhost, vhd->protocol,
LWS_CALLBACK_USER, 1);
break;
case LWS_CALLBACK_EVENT_WAIT_CANCELLED:
/*
* When the "spam" threads add a message to the ringbuffer,
* they create this event in the lws service thread context
* using lws_cancel_service().
*
* We respond by scheduling a writable callback for the
* connected client, if any.
*/
if (vhd && vhd->client_wsi && vhd->established)
lws_callback_on_writable(vhd->client_wsi);
break;
/* rate-limited client connect retries */
case LWS_CALLBACK_USER:
printf("用户回调...\n");
lwsl_notice("%s: LWS_CALLBACK_USER\n", __func__);
if (connect_client(vhd))
lws_timed_callback_vh_protocol(vhd->vhost,
vhd->protocol,
LWS_CALLBACK_USER, 1);
break;
default:
break;
}
return lws_callback_http_dummy(wsi, reason, user, in, len);
}
static const struct lws_protocols protocols[] = {
{
"lws-minimal-broker",
callback_minimal_broker,
0,
0,
},
{ NULL, NULL, 0, 0 }
};
static void
sigint_handler(int sig)
{
interrupted = 1;
}
int main(int argc, const char **argv)
{
struct lws_context_creation_info info;
struct lws_context *context;
const char *p;
int n = 0, logs = LLL_USER | LLL_ERR | LLL_WARN | LLL_NOTICE
/* for LLL_ verbosity above NOTICE to be built into lws,
* lws must have been configured and built with
* -DCMAKE_BUILD_TYPE=DEBUG instead of =RELEASE */
/* | LLL_INFO */ /* | LLL_PARSER */ /* | LLL_HEADER */
/* | LLL_EXT */ /* | LLL_CLIENT */ /* | LLL_LATENCY */
/* | LLL_DEBUG */;
signal(SIGINT, sigint_handler);
if ((p = lws_cmdline_option(argc, argv, "-d")))
logs = atoi(p);
lws_set_log_level(logs, NULL);
lwsl_user("LWS minimal ws client tx\n");
lwsl_user(" Run minimal-ws-broker and browse to that\n");
memset(&info, 0, sizeof info); /* otherwise uninitialized garbage */
info.port = CONTEXT_PORT_NO_LISTEN; /* we do not run any server */
info.protocols = protocols;
/*
* since we know this lws context is only ever going to be used with
* one client wsis / fds / sockets at a time, let lws know it doesn't
* have to use the default allocations for fd tables up to ulimit -n.
* It will just allocate for 1 internal and 1 (+ 1 http2 nwsi) that we
* will use.
*/
info.fd_limit_per_thread = 1 + 1 + 1;
context = lws_create_context(&info);
if (!context) {
lwsl_err("lws init failed\n");
return 1;
}
while (n >= 0 && !interrupted)
n = lws_service(context, 0);
lws_context_destroy(context);
lwsl_user("Completed\n");
return 0;
}
服务端:
/*
* lws-minimal-ws-server
*
* Written in 2010-2019 by Andy Green
*
* This file is made available under the Creative Commons CC0 1.0
* Universal Public Domain Dedication.
*
* This demonstrates the most minimal http server you can make with lws,
* with an added websocket chat server.
*
* To keep it simple, it serves stuff in the subdirectory "./mount-origin" of
* the directory it was started in.
* You can change that by changing mount.origin.
*/
#include
#include
#include
#define LWS_PLUGIN_STATIC
#include "protocol_lws_minimal.c"
static struct lws_protocols protocols[] = {
{ "http", lws_callback_http_dummy, 0, 0 },
LWS_PLUGIN_PROTOCOL_MINIMAL,
{ NULL, NULL, 0, 0 } /* terminator */
};
static const lws_retry_bo_t retry = {
.secs_since_valid_ping = 3,
.secs_since_valid_hangup = 10,
};
static int interrupted;
static const struct lws_http_mount mount = {
/* .mount_next */ NULL, /* linked-list "next" */
/* .mountpoint */ "/", /* mountpoint URL */
/* .origin */ "./mount-origin", /* serve from dir */
/* .def */ "index.html", /* default filename */
/* .protocol */ NULL,
/* .cgienv */ NULL,
/* .extra_mimetypes */ NULL,
/* .interpret */ NULL,
/* .cgi_timeout */ 0,
/* .cache_max_age */ 0,
/* .auth_mask */ 0,
/* .cache_reusable */ 0,
/* .cache_revalidate */ 0,
/* .cache_intermediaries */ 0,
/* .origin_protocol */ LWSMPRO_FILE, /* files in a dir */
/* .mountpoint_len */ 1, /* char count */
/* .basic_auth_login_file */ NULL,
};
void sigint_handler(int sig)
{
interrupted = 1;
}
int main(int argc, const char **argv)
{
struct lws_context_creation_info info;
struct lws_context *context;
const char *p;
int n = 0, logs = LLL_USER | LLL_ERR | LLL_WARN | LLL_NOTICE
/* for LLL_ verbosity above NOTICE to be built into lws,
* lws must have been configured and built with
* -DCMAKE_BUILD_TYPE=DEBUG instead of =RELEASE */
/* | LLL_INFO */ /* | LLL_PARSER */ /* | LLL_HEADER */
/* | LLL_EXT */ /* | LLL_CLIENT */ /* | LLL_LATENCY */
/* | LLL_DEBUG */;
signal(SIGINT, sigint_handler);
if ((p = lws_cmdline_option(argc, argv, "-d")))
logs = atoi(p);
lws_set_log_level(logs, NULL);
lwsl_user("LWS minimal ws server | visit http://localhost:7681 (-s = use TLS / https)\n");
memset(&info, 0, sizeof info); /* otherwise uninitialized garbage */
info.port = 7681;
info.mounts = &mount;
info.protocols = protocols;
info.vhost_name = "localhost";
info.ws_ping_pong_interval = 10;
info.options =
LWS_SERVER_OPTION_HTTP_HEADERS_SECURITY_BEST_PRACTICES_ENFORCE;
if (lws_cmdline_option(argc, argv, "-s")) {
lwsl_user("Server using TLS\n");
info.options |= LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT;
info.ssl_cert_filepath = "localhost-100y.cert";
info.ssl_private_key_filepath = "localhost-100y.key";
}
if (lws_cmdline_option(argc, argv, "-h"))
info.options |= LWS_SERVER_OPTION_VHOST_UPG_STRICT_HOST_CHECK;
if (lws_cmdline_option(argc, argv, "-v"))
info.retry_and_idle_policy = &retry;
context = lws_create_context(&info);
if (!context) {
lwsl_err("lws init failed\n");
return 1;
}
while (n >= 0 && !interrupted)
n = lws_service(context, 0);
lws_context_destroy(context);
return 0;
}
由于服务端和客户端支持的回调协议名不一样,所以服务器这里一直打印,不影响使用:
下面有很多人问lws_getaddrinfo46 failed的问题,我怀疑是dns解析的问题,建议先使用ip和端口的形式试下,域名的问题我再看看源码,暂时没分析出原因,客户端似乎没办法自动根据域名解析出对应的ip和端口,目前从源码看是“ipv6 lws_getaddrinfo46 failed”。
还有客户端一般可以用官网给的不加密客户端,需要加密的话则需要证书,对于自签名的证书options配置参数需要改一下,是支持自签名证书的。