|
#include <stdint.h> |
|
#include <cstring> |
|
#include <cstdio> |
|
#include <list> |
|
|
|
#include <string> |
|
|
|
#include <uv.h> |
|
#include <libwebsockets.h> |
|
|
|
typedef struct { |
|
std::string buffer_block; |
|
bool is_first_fragment; |
|
bool is_final_fragment; |
|
bool is_binary_message; |
|
size_t buffer_index; |
|
} buffer_block_t; |
|
|
|
static void append_message(std::list<buffer_block_t>& pending_list, const std::string& buffer, bool is_binary) { |
|
pending_list.push_back(buffer_block_t()); |
|
buffer_block_t& last_msg = pending_list.back(); |
|
last_msg.is_first_fragment = true; |
|
last_msg.is_final_fragment = true; |
|
last_msg.is_binary_message = is_binary; |
|
last_msg.buffer_index = LWS_PRE; // lws_write 传入的buffer地址需要前面留LWS_PRE的长度 |
|
|
|
last_msg.buffer_block.resize(buffer.size() + LWS_PRE); |
|
memcpy(&last_msg.buffer_block[LWS_PRE], buffer.c_str(), buffer.size()); |
|
} |
|
|
|
#define MAX_ECHO_PAYLOAD 1024 |
|
int websocket_binding_callback_echo(struct lws *wsi, enum lws_callback_reasons reason, void *user, void *in, size_t len); |
|
struct websocket_binding_callback_session_data_echo { |
|
std::list<buffer_block_t> recv_list; |
|
std::list<buffer_block_t> send_list; |
|
}; |
|
/* list of supported protocols and callbacks */ |
|
|
|
static struct lws_protocols websocket_binding_protocols[] = { |
|
/* first protocol must always be HTTP handler */ |
|
{ |
|
"echo", /* name */ |
|
websocket_binding_callback_echo, /* callback */ |
|
sizeof(websocket_binding_callback_session_data_echo), /* per_session_data_size */ |
|
MAX_ECHO_PAYLOAD, /* max frame size / rx buffer */ |
|
}, |
|
{ NULL, NULL, 0, 0 } /* terminator */ |
|
}; |
|
|
|
static const struct lws_extension websocket_binding_exts[] = { |
|
{ NULL, NULL, NULL /* terminator */ } |
|
}; |
|
|
|
|
|
int websocket_binding_callback_echo(struct lws *wsi, enum lws_callback_reasons reason, void *user, void *in, size_t len) { |
|
struct websocket_binding_callback_session_data_echo* session = reinterpret_cast<websocket_binding_callback_session_data_echo*>(user); |
|
//lws_callback_on_writable(wsi); |
|
int n; |
|
switch(reason) { |
|
case LWS_CALLBACK_ESTABLISHED: { |
|
lwsl_info("LWS_CALLBACK_ESTABLISHED %p", user); |
|
memset(user, 0, sizeof(websocket_binding_callback_session_data_echo)); |
|
new (user)websocket_binding_callback_session_data_echo(); |
|
break; |
|
} |
|
case LWS_CALLBACK_CLOSED: { |
|
lwsl_info("LWS_CALLBACK_CLOSED %p", user); |
|
session->~websocket_binding_callback_session_data_echo(); |
|
break; |
|
} |
|
case LWS_CALLBACK_HTTP: { |
|
lwsl_info("LWS_CALLBACK_HTTP %p, %s", user, (char*)in); |
|
break; |
|
} |
|
case LWS_CALLBACK_SERVER_WRITEABLE: { |
|
lwsl_info("LWS_CALLBACK_SERVER_WRITEABLE %p", user); |
|
while (!session->send_list.empty()) { |
|
buffer_block_t& first_msg = session->send_list.front(); |
|
if (first_msg.buffer_index >= first_msg.buffer_block.size()) { |
|
session->send_list.pop_front(); |
|
continue; |
|
} |
|
|
|
n = 0; |
|
|
|
if (!first_msg.is_first_fragment) { |
|
n = LWS_WRITE_CONTINUATION; |
|
} else if (first_msg.is_binary_message) { |
|
n = LWS_WRITE_BINARY; |
|
} else { |
|
n = LWS_WRITE_TEXT; |
|
} |
|
|
|
if (!first_msg.is_final_fragment) { |
|
n |= LWS_WRITE_NO_FIN; |
|
} |
|
|
|
lwsl_info("+++ test-echo: writing %s", first_msg.buffer_block.c_str() + first_msg.buffer_index); |
|
n = lws_write(wsi, |
|
reinterpret_cast<unsigned char*>(&first_msg.buffer_block[first_msg.buffer_index]), |
|
first_msg.buffer_block.size() - first_msg.buffer_index, |
|
static_cast<lws_write_protocol>(n)); |
|
if (n < 0) { |
|
lwsl_err("ERROR %d writing to socket, hanging up", n); |
|
return 1; |
|
} |
|
|
|
first_msg.buffer_index += static_cast<size_t>(n); |
|
if (first_msg.buffer_index >= first_msg.buffer_block.size()) { |
|
session->send_list.pop_front(); |
|
} |
|
} |
|
|
|
lws_rx_flow_control(wsi, 1); |
|
break; |
|
} |
|
|
|
case LWS_CALLBACK_RECEIVE: { |
|
lwsl_info("LWS_CALLBACK_RECEIVE %p", user); |
|
session->recv_list.push_back(buffer_block_t()); |
|
buffer_block_t& this_fragment = session->recv_list.back(); |
|
bool is_binary_message = !!lws_frame_is_binary(wsi); |
|
this_fragment.is_first_fragment = !!lws_is_first_fragment(wsi); |
|
this_fragment.is_final_fragment = !!lws_is_final_fragment(wsi); |
|
this_fragment.is_binary_message = is_binary_message; |
|
this_fragment.buffer_index = len; |
|
this_fragment.buffer_block.assign(reinterpret_cast<char*>(in), len); |
|
lwsl_info("+++ recv: %s", this_fragment.buffer_block.c_str()); |
|
|
|
if (this_fragment.is_final_fragment) { |
|
// merge req msg |
|
size_t msg_sz = 0; |
|
size_t msg_idx = 0; |
|
|
|
std::string msg_data; |
|
for (std::list<buffer_block_t>::iterator it = session->recv_list.begin(); it != session->recv_list.end(); ++ it) { |
|
msg_sz += (*it).buffer_block.size(); |
|
} |
|
msg_data.resize(msg_sz); |
|
|
|
for (std::list<buffer_block_t>::iterator it = session->recv_list.begin(); it != session->recv_list.end(); ++ it) { |
|
memcpy(&msg_data[msg_idx], (*it).buffer_block.c_str(), (*it).buffer_block.size()); |
|
msg_idx += (*it).buffer_block.size(); |
|
} |
|
session->recv_list.clear(); |
|
|
|
// dispatch req msg |
|
lwsl_info("Dispatch msg %p, length=%llu", user, static_cast<unsigned long long>(msg_data.size())); |
|
append_message(session->send_list, msg_data, is_binary_message); |
|
} |
|
|
|
lws_rx_flow_control(wsi, 0); |
|
lws_callback_on_writable(wsi); |
|
break; |
|
} |
|
|
|
// just for client |
|
// |
|
// case LWS_CALLBACK_CLIENT_ESTABLISHED: |
|
// case LWS_CALLBACK_CLIENT_RECEIVE: |
|
// case LWS_CALLBACK_CLIENT_WRITEABLE: |
|
default: |
|
lwsl_info("LWS_CALLBACK_* %d %p", reason, user); |
|
break; |
|
} |
|
|
|
return 0; |
|
} |
|
|
|
#define LOG_DEBUG 15 |
|
void websocket_binding_log_fn(int level, const char *line) { |
|
printf("[LWS LOG][Level: %d]: %s\n", level, line); |
|
} |
|
|
|
int main () { |
|
// 配置和初始化 |
|
struct lws_context_creation_info info; |
|
memset(&info, 0, sizeof info); |
|
info.port = 16789; |
|
info.protocols = websocket_binding_protocols; |
|
info.extensions = websocket_binding_exts; |
|
|
|
// info.ssl_cert_filepath = "test.pem"; |
|
// info.ssl_private_key_filepath = "test.key.pem"; |
|
// info.ssl_ca_filepath = NULL; |
|
// info.ssl_cipher_list = NULL; |
|
// info.ecdh_curve = "prime256v1"; |
|
|
|
info.gid = -1; |
|
info.uid = -1; |
|
info.max_http_header_pool = 16; |
|
info.timeout_secs = 5; |
|
int opt = LWS_SERVER_OPTION_LIBUV; |
|
// opt |= LWS_SERVER_OPTION_ALLOW_NON_SSL_ON_SSL_PORT | LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT; |
|
info.options = opt; |
|
|
|
// 全局配置 |
|
lws_set_log_level(LOG_DEBUG, websocket_binding_log_fn); |
|
lwsl_info("Log setuped"); |
|
|
|
// 创建上下文 |
|
struct lws_context *context = lws_create_context(&info); |
|
if (context == NULL) { |
|
printf("libwebsocket init failed\n"); |
|
return -1; |
|
} |
|
|
|
lws_uv_initloop(context, uv_default_loop(), 0); |
|
|
|
uv_run(uv_default_loop(), UV_RUN_DEFAULT); |
|
|
|
lws_context_destroy(context); |
|
lws_context_destroy2(context); |
|
|
|
lwsl_notice("libwebsockets-server exited cleanly"); |
|
|
|
return 0; |
|
} |