利用libwebsockets写ws、wss服务端和客户端
文章目录
- 利用libwebsockets写ws、wss服务端和客户端
-
服务端:
server.c
#include "libwebsockets.h"
#include <signal.h>
#include <string.h>
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 );
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;
}
return 0;
}
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;
struct lws_context *context = lws_create_context(&ctx_info);
while ( !exit_sig ) {
lws_service(context, 1000);
}
lws_context_destroy(context);
return 0;
}
编译脚本,compile.sh
libdir=libwebsockets
g++ -g -o server server.c -I$libdir/include -L$libdir/lib -lwebsockets
客户端
client.c
#include "libwebsockets.h"
#include <signal.h>
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;
};
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:
lwsl_notice( "Rx: %s\n", (char *) in );
break;
case LWS_CALLBACK_CLIENT_WRITEABLE:
if ( data->msg_count < 3 ) {
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 );
lws_write( wsi, &data->buf[ LWS_PRE ], data->len, LWS_WRITE_TEXT );
}
break;
}
return 0;
}
struct lws_protocols protocols[] = {
{
"ws", callback, sizeof( struct session_data ), MAX_PAYLOAD_SIZE,
},
{
NULL, NULL, 0
}
};
int main() {
signal( SIGTERM, sighdl );
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;
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;
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;
struct lws *wsi = lws_client_connect_via_info( &conn_info );
while ( !exit_sig ) {
lws_service( context, 1000 );
lws_callback_on_writable( wsi );
}
lws_context_destroy( context );
return 0;
}
编译脚本:
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客户端分别代表发送和接收,都可以看一下):
客户端:
#include <libwebsockets.h>
#include <string.h>
#include <signal.h>
#include <pthread.h>
static int interrupted;
struct msg {
void *payload;
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;
struct lws_ring *ring;
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 {
if (!vhd->established)
goto wait;
pthread_mutex_lock(&vhd->lock_ring);
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
lws_cancel_service(vhd->context);
wait_unlock:
pthread_mutex_unlock(&vhd->lock_ring);
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) {
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);
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;
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);
pmsg = lws_ring_get_element(vhd->ring, &vhd->tail);
if (!pmsg)
goto skip;
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);
lwsl_err("ERROR %d writing to ws socket\n", m);
return -1;
}
lws_ring_consume_single_tail(vhd->ring, &vhd->tail, 1);
if (lws_ring_get_element(vhd->ring, &vhd->tail))
lws_callback_on_writable(wsi);
skip:
pthread_mutex_unlock(&vhd->lock_ring);
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:
if (vhd && vhd->client_wsi && vhd->established)
lws_callback_on_writable(vhd->client_wsi);
break;
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
;
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);
info.port = CONTEXT_PORT_NO_LISTEN;
info.protocols = protocols;
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;
}
服务端:
#include <libwebsockets.h>
#include <string.h>
#include <signal.h>
#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 }
};
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 = {
NULL,
"/",
"./mount-origin",
"index.html",
NULL,
NULL,
NULL,
NULL,
0,
0,
0,
0,
0,
0,
LWSMPRO_FILE,
1,
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
;
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);
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配置参数需要改一下,是支持自签名证书的。
坑点
有个大家一直踩的坑点就是连接域名的时候一直报错,最后发现使用域名时是不用加wss://前缀的,大家注意一下,比如使用minimal-ws-client测试wss服务连接:
./lws-minimal-ws-client -s openhw.work.weixin.qq.com
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)