前言
本文主要介绍libwebsockets在linux下的交叉编译,及demo例程的简单使用。
一、libwebsockets是什么?
官方回答:Libweb套接字 (LWS)是一个灵活、轻量级的纯C库,可使用非阻塞事件循环以极小的内存占用轻松实现现代网络协议。自2010年以来,它一直在不断开发,并被全球数千万设备和数千名开发人员使用。
二、使用到的资源
- cmake源码:https://download.csdn.net/download/m0_46592311/87558003
- libwebsockets源码:https://download.csdn.net/download/m0_46592311/87557999
- websocket测试工具:https://download.csdn.net/download/m0_46592311/87558013
三、编译
1. cmake
需修改cmake/CMakeLists.txt,在其中添加set(CMAKE_USE_OPENSSL OFF),可以直接添加到文件首行, 这里具体位置应该没有要求。用于解决没有openssl的问题。
步骤:
./configure
sudo make
sudo make install
2. libwebsockets
由于用不到SSL功能,所以将配置项libwebsockets-main/CMakeLists.txt 中的LWS_WITH_SSL注释掉。
步骤:
cd libwebsockets-main
mkdir build
sudo cmake ..
sudo make
sudo make install
sudo ldconfig
四、 使用示例
做客户端
static int interrupted;
/* one of these created for each message */
struct msg
{
char payload[40960 * 2]; /* is malloc'd */
size_t len;
};
#define MAX_PAYLOAD_SIZE 1024
struct lws_rx
{
unsigned char databuf[MAX_PAYLOAD_SIZE];
int len;
char rxflag; // 接收标志
char txflag; // 待发送标志
char txedflag; // 已发送标志
int idcount;
};
struct per_vhost_data__minimal
{
struct lws_context *context;
struct lws_vhost *vhost;
const struct lws_protocols *protocol;
pthread_t pthread_spam[2];
lws_sorted_usec_list_t sul;
struct lws_client_connect_info i;
struct lws *client_wsi;
int counter;
char finished;
char established;
};
struct per_vhost_data__minimal data_minimal;
struct msg amsg; // 消息发送结构体
struct lws_rx lwsrx_buf; // 消息接收结构体
int jc_respond = 0; //
// 等待websocket响应
int lws_response(void)
{
if ((lwsrx_buf.txedflag == 0) && (lwsrx_buf.txflag == 0))
return 1;
int timeout = 10 * 1000 / 200; // 超时等待时间为10秒
while (timeout--)
{
if (lwsrx_buf.rxflag == 1) // 收到了数据应答
{
// lws_rxcontrol(lwsrx_buf); //解析RX接收内容
lwsrx_buf.txflag = 0;
lwsrx_buf.txedflag = 0;
lwsrx_buf.rxflag = 0;
return 1;
}
usleep(200 * 1000); // 200ms间抽一次接收数据
}
printf("ws响应超时!-------------------------------------.\n");
return 0;
}
static void *thread_spam(void *d)
{
struct per_vhost_data__minimal *vhd =
(struct per_vhost_data__minimal *)d;
int len = 40960 * 2, index = 1, n, whoami = 0;
int TimeCnt = 0; // 计数
std::string str_DevMsg;
do
{
// /* don't generate output if client not connected */
if (!vhd->established)
{
lwsl_user("websocket链接还未建立!\n");
goto wait;
}
lwsl_user("ws填充发送数据内容.\n");
n = lws_snprintf((char *)amsg.payload + LWS_PRE, (unsigned int)len,
"发送第%d条数据\n", TimeCnt);
amsg.len = (unsigned int)n;
lwsl_user("ws数据长度 %d.\n", amsg.len);
lwsrx_buf.txflag = 1;
/*
* This will cause a LWS_CALLBACK_EVENT_WAIT_CANCELLED
* in the lws service thread context.
*/
lws_cancel_service(vhd->context);
lws_response(); // 等待响应及数据解析
wait:
sleep(10);
TimeCnt++;
} while (!vhd->finished);
lwsl_notice("thread_spam %d exiting\n", whoami);
pthread_exit(NULL);
return NULL;
}
/*
* The retry and backoff policy we want to use for our client connections
*/
static const uint32_t backoff_ms[] = {1000, 2000, 3000, 4000, 5000};
static const lws_retry_bo_t retry = {
.retry_ms_table = backoff_ms,
.retry_ms_table_count = LWS_ARRAY_SIZE(backoff_ms),
.conceal_count = LWS_ARRAY_SIZE(backoff_ms) + 10,
.secs_since_valid_ping = 30, /* force PINGs after secs idle */
.secs_since_valid_hangup = 30 + 30, //;//35, /* hangup after secs idle */
.jitter_percent = 20,
};
static void
sul_connect_attempt(struct lws_sorted_usec_list *sul)
{
struct per_vhost_data__minimal *vhd =
lws_container_of(sul, struct per_vhost_data__minimal, sul);
vhd->i.context = vhd->context;
vhd->i.port = 5437;
vhd->i.address = "192.168.1.100";
string path = "/ws";
vhd->i.path = path.c_str();
vhd->i.host = vhd->i.address;
vhd->i.origin = vhd->i.address;
vhd->i.ssl_connection = 0;
vhd->i.iface = NULL;
vhd->i.protocol = "lws-minimal-broker";
vhd->i.pwsi = &vhd->client_wsi;
vhd->i.retry_and_idle_policy = &retry;
lwsl_user("%s: 连接Ws服务器\n", __func__);
if (!lws_client_connect_via_info(&vhd->i))
lws_sul_schedule(vhd->context, 0, &vhd->sul,
sul_connect_attempt, 10 * LWS_US_PER_SEC);
}
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 = &amsg;
void *retval;
int n, m, r = 0;
switch (reason)
{
/* --- protocol lifecycle callbacks --- */
case LWS_CALLBACK_PROTOCOL_INIT:
lwsl_user("%s: LWS_CALLBACK_PROTOCOL_INIT\n", __func__);
vhd = (struct per_vhost_data__minimal *)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);
/* start the content-creating threads */
lwsl_user("%s: start the content-creating threads\n", __func__);
if (pthread_create(&vhd->pthread_spam[0], NULL, thread_spam, vhd))
{
lwsl_err("thread creation failed\n");
r = 1;
goto init_fail;
}
sul_connect_attempt(&vhd->sul);
break;
case LWS_CALLBACK_PROTOCOL_DESTROY:
lwsl_err("CLIENT_CONNECTION_ERROR\n");
init_fail:
vhd->finished = 1;
pthread_join(vhd->pthread_spam[0], &retval);
lws_sul_cancel(&vhd->sul);
return r;
case LWS_CALLBACK_CLIENT_CONNECTION_ERROR:
lwsl_err("CLIENT_CONNECTION_ERROR: %s\n",
in ? (char *)in : "(null)");
vhd->client_wsi = NULL;
lws_sul_schedule(vhd->context, 0, &vhd->sul,
sul_connect_attempt, LWS_US_PER_SEC);
break;
/* --- client callbacks --- */
case LWS_CALLBACK_CLIENT_ESTABLISHED:
lwsl_user("%s: established\n", __func__);
vhd->established = 1;
break;
case LWS_CALLBACK_CLIENT_RECEIVE:
lwsl_user("RX: %s\n", (const char *)in);
if (len < MAX_PAYLOAD_SIZE)
{
memcpy(lwsrx_buf.databuf, in, len); // 拷贝数据
lwsrx_buf.rxflag = 1; // 收到数据标志
lwsrx_buf.len = len;
}
break;
case LWS_CALLBACK_CLIENT_WRITEABLE:
lwsl_user("%s: LWS_CALLBACK_CLIENT_WRITEABLE\n", __func__);
if (lwsrx_buf.txflag == 0) // 无数据待发送
{
lwsl_user("%s: 无数据需要发送.\n", __func__);
break;
}
lwsrx_buf.txedflag = 1; // 已发送标志置位
lwsrx_buf.txflag = 0; // 防止重复发送
printf("lws_write数据长度:%d\n", pmsg->len);
printf("%s\n", (unsigned char *)pmsg->payload);
/* 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)
{
lwsl_err("ERROR %d writing to ws socket\n", m);
return -1;
}
break;
case LWS_CALLBACK_CLIENT_CLOSED:
lwsl_user("%s: LWS_CALLBACK_CLIENT_CLOSED\n", __func__);
vhd->client_wsi = NULL;
vhd->established = 0;
lws_sul_schedule(vhd->context, 0, &vhd->sul,
sul_connect_attempt, LWS_US_PER_SEC);
break;
case LWS_CALLBACK_EVENT_WAIT_CANCELLED:
lwsl_user("%s: LWS_CALLBACK_EVENT_WAIT_CANCELLED\n", __func__);
/*
* 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;
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, 0, NULL, 0},
LWS_PROTOCOL_LIST_TERM};
// 启动websocket,断线重连
void *t_WebSocketOpenRun(void *para)
{
printf("websocket初始化.\n");
sleep(10); // 开机等10秒,等待4G拨号先,当然该程序不存在先后
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 */;
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;
}
while (n >= 0 && !interrupted)
{
n = lws_service(context, 0);
}
lws_context_destroy(context);
lwsl_user("Completed\n");
return;
}
测试
用网线连接电脑和树莓派