1 websocket 輕量客戶端
因以前發(fā)過這個代碼,但是一直沒有整理,這次整理了一下,持續(xù)修改,主要是要使用在arm的linux上,發(fā)送接收的數(shù)據(jù)壓縮成圖片發(fā)送出去。
要達到輕量websocket 使用,必須要達到幾個方面才能足夠簡單,
1、不用加入其他的庫
2、只需要使用頭文件包含就可以
3、跨平臺
如果正常應(yīng)用,可以使用websocketpp等庫,問題就是比較麻煩,要使用boost或者asio庫,當然asio也是足夠簡單,頭文件包含,編譯通過要設(shè)置參數(shù),問題不大,不過不夠簡單
2 應(yīng)用場景
1 windows 使用
2 linux使用
3 linux arm 板子上使用
在arm上編譯的時候,就不用編譯那么多的庫文件了
3 原理
使用select模型 和原始操作系統(tǒng)的socket來直接編寫代碼,select模型比較簡單,非長時間阻塞模式,以下是webscoket協(xié)議字節(jié)示意圖
本文函數(shù)根據(jù)上圖實現(xiàn)了websocket協(xié)議。定義的主要數(shù)據(jù)結(jié)構(gòu)如下所示,websocket協(xié)議里面包含兩種數(shù)據(jù),一種是二進制,一種是文本,是可以指定的
struct wsheader_type {
unsigned header_size;
bool fin;
bool mask;
enum opcode_type {
CONTINUATION = 0x0,
TEXT_FRAME = 0x1,
BINARY_FRAME = 0x2,
CLOSE = 8,
PING = 9,
PONG = 0xa,
} opcode;
int N0;
uint64_t N;
uint8_t masking_key[4];
};
websocket鏈接
websocket鏈接使用的是http協(xié)議,所不同的是必須做upgrade
static const char* desthttp = "GET /%s HTTP/1.1\r\n"
"Host: %s:%d\r\n"
"Upgrade: websocket\r\n"
"Connection: Upgrade\r\n"
"Origin: %s\r\n"
"Sec-WebSocket-Key: x3JJHMbDL1EzLkh9GBhXDw==\r\n"
"Sec-WebSocket-Version: 13\r\n\r\n";
上面把http協(xié)議升級為websocket協(xié)議的內(nèi)容寫出,把客戶端的內(nèi)容填充過去發(fā)送到服務(wù)端就行,從下面代碼可以看出我們需要哪些內(nèi)容
char line[256];
int status;
int i;
sprintf(line, desthttp, path, host, port, origin.c_str());
::send(sockfd, line, (int)strlen(line), 0);
發(fā)送和接收
使用select來做異步的模式,發(fā)送的時候指定參數(shù)
很簡單,就如下所示
fd_set wfds;
timeval tv = { timeout / 1000, (timeout % 1000) * 1000 };
FD_ZERO(&wfds);
if (txbuf.size()) { FD_SET(sockfd, &wfds); }
select((int)(sockfd + 1), NULL, &wfds, 0, timeout > 0 ? &tv : 0);
當然,如果要將socket置為非阻塞,開始的時候還是要設(shè)置的
#ifdef _WIN32
u_long on = 1;
ioctlsocket(sockfd, FIONBIO, &on);
#else
fcntl(sockfd, F_SETFL, O_NONBLOCK);
#endif
下面是發(fā)送的函數(shù),參數(shù)為毫秒
//參數(shù)是毫秒
void pollSend(int timeout)
{
if (v_state == CLOSED) {
if (timeout > 0) {
timeval tv = { timeout / 1000, (timeout % 1000) * 1000 };
select(0, NULL, NULL, NULL, &tv);
}
return;
}
if (timeout != 0) {
fd_set wfds;
timeval tv = { timeout / 1000, (timeout % 1000) * 1000 };
FD_ZERO(&wfds);
if (txbuf.size()) { FD_SET(sockfd, &wfds); }
select((int)(sockfd + 1), NULL, &wfds, 0, timeout > 0 ? &tv : 0);
}
while (txbuf.size()) {
int ret = ::send(sockfd, (char*)&txbuf[0], (int)txbuf.size(), 0);
if (ret < 0 && (socketerrno == SOCKET_EWOULDBLOCK || socketerrno == SOCKET_EAGAIN_EINPROGRESS)) {
break;
}
else if (ret <= 0) {
closesocket(sockfd);
v_state = CLOSED;
fputs(ret < 0 ? "Connection error!\n" : "Connection closed!\n", stderr);
break;
}
else {
txbuf.erase(txbuf.begin(), txbuf.begin() + ret);
}
}
if (!txbuf.size() && v_state == CLOSING) {
closesocket(sockfd);
v_state = CLOSED;
}
}
下面是接收函數(shù)
void pollRecv(int timeout)
{
if (v_state == CLOSED) {
if (timeout > 0) {
timeval tv = { timeout / 1000, (timeout % 1000) * 1000 };
select(0, NULL, NULL, NULL, &tv);
}
return;
}
if (timeout != 0) {
fd_set rfds;
//fd_set wfds;
timeval tv = { timeout / 1000, (timeout % 1000) * 1000 };
FD_ZERO(&rfds);
FD_SET(sockfd, &rfds);
select((int)(sockfd + 1), &rfds, NULL, 0, timeout > 0 ? &tv : 0);
if (!FD_ISSET(sockfd, &rfds))
{
printf("out of here ,no data\n");
return;
}
}
while (true) {
// FD_ISSET(0, &rfds) will be true
int N = (int)rxbuf.size();
ssize_t ret;
//錢波 64K 一個IP包長
rxbuf.resize(N + 64000);
ret = recv(sockfd, (char*)&rxbuf[0] + N, 64000, 0);
if (ret < 0 && (socketerrno == SOCKET_EWOULDBLOCK || socketerrno == SOCKET_EAGAIN_EINPROGRESS)) {
rxbuf.resize(N);
break;
}
else if (ret <= 0) {
rxbuf.resize(N);
closesocket(sockfd);
v_state = CLOSED;
fputs(ret < 0 ? "Connection error!\n" : "Connection closed!\n", stderr);
break;
}
else {//接收到的數(shù)據(jù)
rxbuf.resize(N + ret);
}
}
}
可以看出,我們使用select 僅僅是不阻塞,簡單使用FD_ISSET宏去判決是否有數(shù)據(jù)達到,如果我們沒有收到數(shù)據(jù),我們就直接返回。
為了簡單使用程序,我們封裝一個class來使用接收和發(fā)送
class c_ws_class //:public TThreadRunable
{
thread v_thread;
std::mutex v_mutex;
std::condition_variable v_cond;
WebSocket v_ws;
int v_stop = 1;
string v_url;
callback_message_recv v_recv = NULL;
//已經(jīng)
//bool v_is_working = false;
public:
static int InitSock()
{
#ifdef _WIN32
INT rc;
WSADATA wsaData;
rc = WSAStartup(MAKEWORD(2, 2), &wsaData);
if (rc) {
printf("WSAStartup Failed.\n");
return -1;
}
#endif
return 0;
}
static void UnInitSock()
{
WSACleanup();
}
c_ws_class()
{}
~c_ws_class()
{
v_ws.close();
}
public:
void set_url(const char * url)
{
v_url = url;
}
int connect()
{
if (v_url.empty())
return -1;
return v_ws.connect(v_url);
}
void Start(callback_message_recv recv)
{
//because we will connect all over the time, so v_stop is zero
v_stop = 0;
v_ws.initSize(0, 0);
v_recv = recv;
v_thread = std::thread(std::bind(&c_ws_class::Run, this));
}
bool send(const char * str)
{
if (str != NULL)
{
if (v_ws.getReadyState() != CLOSED)
{
v_ws.send(str);
v_ws.pollSend(10);
return true;
}
return false;
}
return false;
}
void sendBinary(uint8_t *data, int len)
{
if (v_ws.getReadyState() != CLOSED)
{
v_ws.sendBinary(data, len);
v_ws.pollSend(5);
}
}
void Stop()
{
v_stop = 1;
}
int isStop()
{
return v_stop;
}
void Join()
{
if (v_thread.joinable())
v_thread.join();
}
void Run()
{
while (v_stop == 0) {
//WebSocket::pointer wsp = &*ws; // <-- because a unique_ptr cannot be copied into a lambda
if (v_stop == 1)
break;
if (v_ws.getReadyState() == CLOSED)
{
//斷線重連
if (connect() != 0)
{
for (int i = 0; i < 20; i++)
{
std::this_thread::sleep_for(std::chrono::milliseconds(100));
if (v_stop == 1)
break;
}
}
}
else
{
v_ws.pollRecv(10);
v_ws.dispatch(v_recv);
}
}
v_ws.close();
//std::cout << "server exit" << endl;
v_stop = 1;
}
void WaitForSignal()
{
std::unique_lock<std::mutex> ul(v_mutex);
v_cond.wait(ul);
}
void Notify()
{
v_cond.notify_one();
}
};
以上為封裝的外層線程代碼,里面同時也封裝了斷線重連。
我們常常使用python或者使用nodejs來做測試,這里使用nodejs寫一個簡單的服務(wù)器程序,接收到數(shù)據(jù)以后發(fā)回。
const WebSocket = require('ws');
const wss = new WebSocket.Server({ port: 8000 });
wss.on('connection', function connection(ws) {
ws.on('message', function incoming(message) {
console.log('received: %s', message);
ws.send('recv:'+message);
});//當收到消息時,在控制臺打印出來,并回復(fù)一條信息
});
測試結(jié)果
服務(wù)端nodejs顯示
客戶端鏈接后顯示文章來源:http://www.zghlxwxcb.cn/news/detail-524103.html
整個的頭文件代碼和測試代碼在gitee上面
gitee地址文章來源地址http://www.zghlxwxcb.cn/news/detail-524103.html
到了這里,關(guān)于實現(xiàn)c++輕量級別websocket協(xié)議客戶端的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!