向服务器发送 WebSockets 消息
Send WebSockets message to server
我正在尝试使用一台设备的 API,但它使用的是 WS 接口和强制 Origin header,这给我带来了麻烦。
在 Chrome 中,我可以在加载具有正确来源的页面时打开控制台,轻松创建 WS 连接和 send/receive 消息:
请注意,已发送的消息(绿色)始终由服务器确认。
作为参考,如果我在不同的页面上创建连接会发生这种情况,这会导致 Origin header 不匹配,报告为 404:
为了回避这个问题,我转向了 C,因为反正我的程序的其余部分都是用 C 编写的。这是我现在拥有的代码,主要基于 this answer:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <signal.h>
#include <libwebsockets.h>
#define KGRN "3[0;32;32m"
#define KCYN "3[0;36m"
#define KRED "3[0;32;31m"
#define KYEL "3[1;33m"
#define KBLU "3[0;32;34m"
#define KCYN_L "3[1;36m"
#define KBRN "3[0;33m"
#define RESET "3[0m"
static int destroy_flag = 0;
static int connection_flag = 0;
static int writeable_flag = 0;
static void INT_HANDLER(int signo) {
destroy_flag = 1;
}
struct session_data {
int fd;
};
struct pthread_routine_tool {
struct lws_context *context;
struct lws *wsi;
};
static int websocket_write_back(struct lws *wsi_in, char *str, int str_size_in)
{
if (str == NULL || wsi_in == NULL)
return -1;
int n;
int len;
char *out = NULL;
if (str_size_in < 1)
len = strlen(str);
else
len = str_size_in;
out = (char *)malloc(sizeof(char)*(LWS_SEND_BUFFER_PRE_PADDING + len + LWS_SEND_BUFFER_POST_PADDING));
//* setup the buffer*/
memcpy (out + LWS_SEND_BUFFER_PRE_PADDING, str, len );
//* write out*/
n = lws_write(wsi_in, out + LWS_SEND_BUFFER_PRE_PADDING, len, LWS_WRITE_TEXT);
printf(KBLU"[websocket_write_back] %s\n"RESET, str);
//* free the buffer*/
free(out);
return n;
}
static int ws_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;
connection_flag = 0;
break;
case LWS_CALLBACK_CLOSED:
printf(KYEL"[Main Service] LWS_CALLBACK_CLOSED\n"RESET);
destroy_flag = 1;
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, "{\"command\":\"subscribe\",\"identifier\":\"{\\"channel\\":\\"DevicesChannel\\",\\"share_token\\":\\"D0E91\\"}\"}", -1);
websocket_write_back(wsi, "{\"command\":\"message\",\"identifier\":\"{\\"channel\\":\\"DevicesChannel\\",\\"share_token\\":\\"D0E91\\"}\",\"data\":\"{\\"value\\":100,\\"action\\":\\"set_buzz\\"}\"}", -1);
writeable_flag = 1;
break;
default:
break;
}
return 0;
}
static void *pthread_routine(void *tool_in)
{
struct pthread_routine_tool *tool = tool_in;
printf(KBRN"[pthread_routine] Good day. This is pthread_routine.\n"RESET);
//* waiting for connection with server done.*/
while(!connection_flag)
usleep(1000*20);
//*Send greeting to server*/
lws_callback_on_writable(tool->wsi);
}
int main(void)
{
//* register the signal SIGINT handler */
struct sigaction act;
act.sa_handler = INT_HANDLER;
act.sa_flags = 0;
sigemptyset(&act.sa_mask);
sigaction( SIGINT, &act, 0);
struct lws_context *context = NULL;
struct lws_context_creation_info info;
struct lws *wsi = NULL;
struct lws_protocols protocol;
memset(&info, 0, sizeof info);
info.port = CONTEXT_PORT_NO_LISTEN;
info.iface = NULL;
info.protocols = &protocol;
info.ssl_cert_filepath = NULL;
info.ssl_private_key_filepath = NULL;
info.extensions = lws_get_internal_extensions();
info.gid = -1;
info.uid = -1;
info.options = 0;
protocol.name = "websockets";
protocol.callback = &ws_service_callback;
protocol.per_session_data_size = sizeof(struct session_data);
protocol.rx_buffer_size = 0;
protocol.id = 0;
protocol.user = NULL;
context = lws_create_context(&info);
printf(KRED"[Main] context created.\n"RESET);
if (context == NULL) {
printf(KRED"[Main] context is NULL.\n"RESET);
return -1;
}
wsi = lws_client_connect(context, "mobu1.herokuapp.com", 443, 1,
"/cable", "mobu1.herokuapp.com", "link.motorbunny.com",
if (wsi == NULL) {
printf(KRED"[Main] wsi create error.\n"RESET);
return -1;
}
printf(KGRN"[Main] wsi create success.\n"RESET);
struct pthread_routine_tool tool;
tool.wsi = wsi;
tool.context = context;
pthread_t pid;
pthread_create(&pid, NULL, pthread_routine, &tool);
pthread_detach(pid);
while(!destroy_flag)
{
lws_service(context, 50);
}
lws_context_destroy(context);
return 0;
}
运行 上述程序的结果是这样的:
如您所见,从服务器到我的客户端的周期性 ping 正在被接收,但 lws_callback_on_writable(wsi);
似乎没有效果,因为 LWS_CALLBACK_CLIENT_WRITEABLE
回调从未被调用过。此外,如果我在其他任何地方直接调用 websocket_write_back()
,它似乎不会向服务器发送任何内容,也不会出现任何确认。
我做错了什么吗?
编辑 1:
我发现这个简洁的 wscat
,我可以在其中复制 Chrome 的结果:
现在的问题是,我怎样才能将它与我的 C 程序连接起来,使其可以等待来自服务器的欢迎消息,然后发送两条消息?
更好的是,如何保持连接,以便我的程序可以在不同的时间点发送多个命令,而不必一直进行握手?
我发现了一个让我的 C 程序通过 wsta 程序向服务器发送 WebSocket 消息的丑陋技巧。
它需要一个文本文件,每当我的程序要向服务器发送消息时,它就会将其附加到该文件中。然后 tail -f
在后台拾取新行,并通过管道传输到维护连接的 wsta
。输出可以重定向到 /dev/null
,这样 wsta
输出就不会污染我的程序的输出,或者如果需要解析来自服务器的响应,则可以将输出发送到文件。
使这项工作的整个脚本看起来像这样(或者你可以使用带 cat
的 FIFO 管道而不是带 tail
的文件):
#!/bin/bash
touch commands.txt
tail commands.txt -f -n 0 | wsta --header "Origin: https://link.motorbunny.com" "wss://mobu1.herokuapp.com/cable" &> /dev/null &
./program
在C程序中,我只需要写入commands.txt
文件:
FILE* cmd;
char sync_str[6];
void mb_connect()
{
fprintf (cmd, "{\"command\":\"subscribe\",\"identifier\":\"{\\"channel\\":\\"DevicesChannel\\",\\"share_token\\":\\"%s\\"}\"}\n",sync_str);
fflush(cmd);
}
void mb_send(int power, char* type)
{
fprintf (cmd, "{\"command\":\"message\",\"identifier\":\"{\\"channel\\":\\"DevicesChannel\\",\\"share_token\\":\\"%s\\"}\",\"data\":\"{\\"value\\":%d,\\"action\\":\\"set_%s\\"}\"}\n",sync_str,power,type);
fflush(cmd);
}
int main()
{
cmd = fopen ("commands.txt","w");
...
mb_connect();
...
mb_send(200,"buzz");
...
mb_send(0,"buzz");
}
LWS_CALLBACK_CLIENT_WRITEABLE
回调从未被调用的原因是因为这个特定的服务器使用 non-standard 握手。所以,为了绕过这个,我 forked 一个 libwsclient
的分支,并修改了握手检查功能,使其不会因不匹配而失败。我还添加了一个可选的 Origin
header.
现在,我原来的程序只需要
wsclient *client;
char sync_str[6];
void mb_send(int power, char* type)
{
char cmd[2048];
sprintf (cmd, "{\"command\":\"message\",\"identifier\":\"{\\"channel\\":\\"DevicesChannel\\",\\"share_token\\":\\"%s\\"}\",\"data\":\"{\\"value\\":%d,\\"action\\":\\"set_%s\\"}\"}",sync_str,power,type);
libwsclient_send(client,cmd);
}
void mb_connect()
{
char cmd[2048];
sprintf (cmd, "{\"command\":\"subscribe\",\"identifier\":\"{\\"channel\\":\\"DevicesChannel\\",\\"share_token\\":\\"%s\\"}\"}",sync_str);
libwsclient_send(client,cmd);
mb_send(0,"buzz");
}
int nop()
{
return 0;
}
int main()
{
client = libwsclient_new_extra("wss://mobu1.herokuapp.com/cable","https://link.motorbunny.com");
if(!client) {
fprintf(stderr, "Unable to initialize new WS client.\n");
exit(1);
}
libwsclient_onopen(client, &nop);
libwsclient_onmessage(client, &nop);
libwsclient_onerror(client, &nop);
libwsclient_onclose(client, &nop);
libwsclient_run(client);
...
mb_connect();
...
mb_send(200,"buzz");
mb_send(40,"twirl");
...
mb_send(0,"buzz");
mb_send(0,"twirl");
}
我正在尝试使用一台设备的 API,但它使用的是 WS 接口和强制 Origin header,这给我带来了麻烦。
在 Chrome 中,我可以在加载具有正确来源的页面时打开控制台,轻松创建 WS 连接和 send/receive 消息:
作为参考,如果我在不同的页面上创建连接会发生这种情况,这会导致 Origin header 不匹配,报告为 404:
为了回避这个问题,我转向了 C,因为反正我的程序的其余部分都是用 C 编写的。这是我现在拥有的代码,主要基于 this answer:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <signal.h>
#include <libwebsockets.h>
#define KGRN "3[0;32;32m"
#define KCYN "3[0;36m"
#define KRED "3[0;32;31m"
#define KYEL "3[1;33m"
#define KBLU "3[0;32;34m"
#define KCYN_L "3[1;36m"
#define KBRN "3[0;33m"
#define RESET "3[0m"
static int destroy_flag = 0;
static int connection_flag = 0;
static int writeable_flag = 0;
static void INT_HANDLER(int signo) {
destroy_flag = 1;
}
struct session_data {
int fd;
};
struct pthread_routine_tool {
struct lws_context *context;
struct lws *wsi;
};
static int websocket_write_back(struct lws *wsi_in, char *str, int str_size_in)
{
if (str == NULL || wsi_in == NULL)
return -1;
int n;
int len;
char *out = NULL;
if (str_size_in < 1)
len = strlen(str);
else
len = str_size_in;
out = (char *)malloc(sizeof(char)*(LWS_SEND_BUFFER_PRE_PADDING + len + LWS_SEND_BUFFER_POST_PADDING));
//* setup the buffer*/
memcpy (out + LWS_SEND_BUFFER_PRE_PADDING, str, len );
//* write out*/
n = lws_write(wsi_in, out + LWS_SEND_BUFFER_PRE_PADDING, len, LWS_WRITE_TEXT);
printf(KBLU"[websocket_write_back] %s\n"RESET, str);
//* free the buffer*/
free(out);
return n;
}
static int ws_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;
connection_flag = 0;
break;
case LWS_CALLBACK_CLOSED:
printf(KYEL"[Main Service] LWS_CALLBACK_CLOSED\n"RESET);
destroy_flag = 1;
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, "{\"command\":\"subscribe\",\"identifier\":\"{\\"channel\\":\\"DevicesChannel\\",\\"share_token\\":\\"D0E91\\"}\"}", -1);
websocket_write_back(wsi, "{\"command\":\"message\",\"identifier\":\"{\\"channel\\":\\"DevicesChannel\\",\\"share_token\\":\\"D0E91\\"}\",\"data\":\"{\\"value\\":100,\\"action\\":\\"set_buzz\\"}\"}", -1);
writeable_flag = 1;
break;
default:
break;
}
return 0;
}
static void *pthread_routine(void *tool_in)
{
struct pthread_routine_tool *tool = tool_in;
printf(KBRN"[pthread_routine] Good day. This is pthread_routine.\n"RESET);
//* waiting for connection with server done.*/
while(!connection_flag)
usleep(1000*20);
//*Send greeting to server*/
lws_callback_on_writable(tool->wsi);
}
int main(void)
{
//* register the signal SIGINT handler */
struct sigaction act;
act.sa_handler = INT_HANDLER;
act.sa_flags = 0;
sigemptyset(&act.sa_mask);
sigaction( SIGINT, &act, 0);
struct lws_context *context = NULL;
struct lws_context_creation_info info;
struct lws *wsi = NULL;
struct lws_protocols protocol;
memset(&info, 0, sizeof info);
info.port = CONTEXT_PORT_NO_LISTEN;
info.iface = NULL;
info.protocols = &protocol;
info.ssl_cert_filepath = NULL;
info.ssl_private_key_filepath = NULL;
info.extensions = lws_get_internal_extensions();
info.gid = -1;
info.uid = -1;
info.options = 0;
protocol.name = "websockets";
protocol.callback = &ws_service_callback;
protocol.per_session_data_size = sizeof(struct session_data);
protocol.rx_buffer_size = 0;
protocol.id = 0;
protocol.user = NULL;
context = lws_create_context(&info);
printf(KRED"[Main] context created.\n"RESET);
if (context == NULL) {
printf(KRED"[Main] context is NULL.\n"RESET);
return -1;
}
wsi = lws_client_connect(context, "mobu1.herokuapp.com", 443, 1,
"/cable", "mobu1.herokuapp.com", "link.motorbunny.com",
if (wsi == NULL) {
printf(KRED"[Main] wsi create error.\n"RESET);
return -1;
}
printf(KGRN"[Main] wsi create success.\n"RESET);
struct pthread_routine_tool tool;
tool.wsi = wsi;
tool.context = context;
pthread_t pid;
pthread_create(&pid, NULL, pthread_routine, &tool);
pthread_detach(pid);
while(!destroy_flag)
{
lws_service(context, 50);
}
lws_context_destroy(context);
return 0;
}
运行 上述程序的结果是这样的:
如您所见,从服务器到我的客户端的周期性 ping 正在被接收,但 lws_callback_on_writable(wsi);
似乎没有效果,因为 LWS_CALLBACK_CLIENT_WRITEABLE
回调从未被调用过。此外,如果我在其他任何地方直接调用 websocket_write_back()
,它似乎不会向服务器发送任何内容,也不会出现任何确认。
我做错了什么吗?
编辑 1:
我发现这个简洁的 wscat
,我可以在其中复制 Chrome 的结果:
我发现了一个让我的 C 程序通过 wsta 程序向服务器发送 WebSocket 消息的丑陋技巧。
它需要一个文本文件,每当我的程序要向服务器发送消息时,它就会将其附加到该文件中。然后 tail -f
在后台拾取新行,并通过管道传输到维护连接的 wsta
。输出可以重定向到 /dev/null
,这样 wsta
输出就不会污染我的程序的输出,或者如果需要解析来自服务器的响应,则可以将输出发送到文件。
使这项工作的整个脚本看起来像这样(或者你可以使用带 cat
的 FIFO 管道而不是带 tail
的文件):
#!/bin/bash
touch commands.txt
tail commands.txt -f -n 0 | wsta --header "Origin: https://link.motorbunny.com" "wss://mobu1.herokuapp.com/cable" &> /dev/null &
./program
在C程序中,我只需要写入commands.txt
文件:
FILE* cmd;
char sync_str[6];
void mb_connect()
{
fprintf (cmd, "{\"command\":\"subscribe\",\"identifier\":\"{\\"channel\\":\\"DevicesChannel\\",\\"share_token\\":\\"%s\\"}\"}\n",sync_str);
fflush(cmd);
}
void mb_send(int power, char* type)
{
fprintf (cmd, "{\"command\":\"message\",\"identifier\":\"{\\"channel\\":\\"DevicesChannel\\",\\"share_token\\":\\"%s\\"}\",\"data\":\"{\\"value\\":%d,\\"action\\":\\"set_%s\\"}\"}\n",sync_str,power,type);
fflush(cmd);
}
int main()
{
cmd = fopen ("commands.txt","w");
...
mb_connect();
...
mb_send(200,"buzz");
...
mb_send(0,"buzz");
}
LWS_CALLBACK_CLIENT_WRITEABLE
回调从未被调用的原因是因为这个特定的服务器使用 non-standard 握手。所以,为了绕过这个,我 forked 一个 libwsclient
的分支,并修改了握手检查功能,使其不会因不匹配而失败。我还添加了一个可选的 Origin
header.
现在,我原来的程序只需要
wsclient *client;
char sync_str[6];
void mb_send(int power, char* type)
{
char cmd[2048];
sprintf (cmd, "{\"command\":\"message\",\"identifier\":\"{\\"channel\\":\\"DevicesChannel\\",\\"share_token\\":\\"%s\\"}\",\"data\":\"{\\"value\\":%d,\\"action\\":\\"set_%s\\"}\"}",sync_str,power,type);
libwsclient_send(client,cmd);
}
void mb_connect()
{
char cmd[2048];
sprintf (cmd, "{\"command\":\"subscribe\",\"identifier\":\"{\\"channel\\":\\"DevicesChannel\\",\\"share_token\\":\\"%s\\"}\"}",sync_str);
libwsclient_send(client,cmd);
mb_send(0,"buzz");
}
int nop()
{
return 0;
}
int main()
{
client = libwsclient_new_extra("wss://mobu1.herokuapp.com/cable","https://link.motorbunny.com");
if(!client) {
fprintf(stderr, "Unable to initialize new WS client.\n");
exit(1);
}
libwsclient_onopen(client, &nop);
libwsclient_onmessage(client, &nop);
libwsclient_onerror(client, &nop);
libwsclient_onclose(client, &nop);
libwsclient_run(client);
...
mb_connect();
...
mb_send(200,"buzz");
mb_send(40,"twirl");
...
mb_send(0,"buzz");
mb_send(0,"twirl");
}