Socket 與 Websocket通信交互 Linux/C++/epoll網絡模型
簡要:Websocket是基於http協議實現的,而Socket是基於TCP/IP協議實現的。所以要想使Socket與Websocket進行數據交互,就必須在網絡層手動解析http協議,大致分爲兩個步驟:握手連接 拆分協議幀。本實例使用Linux網絡庫,C++開發語言,epoll網絡模型(不熟悉的童鞋可以百度,兩者網絡數據通信和epoll網絡模型沒有聯繫)
示例代碼:
1.main測試cpp
#include <stdio.h>
#include <sys/epoll.h>
#include <stdlib.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <sys/resource.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <string.h>
#include <netdb.h>
#include <map>
#include <sstream>
#include "define.h"
#include "SHA1.h"
#include "base64.hpp"
#define MAXBUF 1024
#define MAXEPOLLSIZE 10000
static int ConnType[MAXBUF] = {unknow};
// 解析http數據頭信息
int fetch_http_info(std::map<std::string, std::string>& header_map, const std::string& src)
{
std::string end_char = "\r\n\r\n";
std::string::size_type pos = src.find(end_char);
if (std::string::npos == pos)
{
return 0;
}
std::istringstream s(src);
std::string line;
while (std::getline(s, line))
{
if (!line.empty() &&
line[line.size() - 1] == '\r')
{
line.erase(line.end() - 1);
}
std::string::size_type end = line.find(": ", 0);
if (end != std::string::npos)
{
std::string key = line.substr(0, end);
std::string value = line.substr(end + 2);
header_map[key] = value;
}
}
return int(pos + end_char.size());
};
//解析數據
unsigned char * analyData(unsigned char * buf, unsigned long * bufLen)
{
char fin, maskFlag,masks[4];
unsigned char * payloadData;
char temp[8];
unsigned long n, payloadLen=0;
unsigned int i=0;
if (*bufLen < 2)
{
return NULL;
}
fin = (buf[0] & 0x80) == 0x80; // 1bit,1表示最後一幀
if (!fin)
{
return NULL;// 超過一幀暫不處理
}
maskFlag = (buf[1] & 0x80) == 0x80; // 是否包含掩碼
if (!maskFlag)
{
return NULL;// 不包含掩碼的暫不處理
}
payloadLen = buf[1] & 0x7F; // 數據長度
if (payloadLen == 126)
{
memcpy(masks,buf+4, 4);
payloadLen =(buf[2]&0xFF) << 8 | (buf[3]&0xFF);
payloadData=(unsigned char *)malloc(payloadLen);
memset(payloadData,0,payloadLen);
memcpy(payloadData,buf+8,payloadLen);
}
else if (payloadLen == 127)
{
memcpy(masks,buf+10,4);
for ( i = 0; i < 8; i++)
{
temp[i] = buf[9 - i];
}
memcpy(&n,temp,8);
payloadData=(unsigned char *)malloc(n);
memset(payloadData,0,n);
memcpy(payloadData,buf+14,n);//toggle error(core dumped) if data is too long.
payloadLen=n;
}
else
{
memcpy(masks,buf+2,4);
payloadData=(unsigned char *)malloc(payloadLen);
memset(payloadData,0,payloadLen);
memcpy(payloadData,buf+6,payloadLen);
}
for (i = 0; i < payloadLen; i++)
{
payloadData[i] = (char)(payloadData[i] ^ masks[i % 4]);
}
printf("data(%ld):%s\n",payloadLen,payloadData);
*bufLen = payloadLen;
return payloadData;
}
//包裝數據
unsigned char * packData(const unsigned char * message,unsigned long * len)
{
unsigned char * data=NULL;
unsigned long n;
n = strlen((const char *)message);
if (n < 126)
{
data=(unsigned char *)malloc(n+2);
memset(data,0,n+2);
data[0] = 0x81;
data[1] = n;
memcpy(data+2,message,n);
*len=n+2;
}
else if (n < 0xFFFF)
{
data=(unsigned char *)malloc(n+4);
memset(data,0,n+4);
data[0] = 0x81;
data[1] = 126;
data[2] = (n>>8 & 0xFF);
data[3] = (n & 0xFF);
memcpy(data+4,message,n);
*len=n+4;
}
else
{
// 暫不處理超長內容
*len=0;
}
return data;
}
extern int do_use_fd(int connfd);
int main(int argc, char *argv[])
{
//設置端口
if(argc != 2)
{
printf("請設置端口號!\n");
}
int port = atoi(argv[1]);
int listener, conn_sock, kdpfd, nfds, n, ret, curfds;
socklen_t len;
struct sockaddr_in server_addr, client_addr;
struct epoll_event ev;
struct epoll_event pevent[MAXEPOLLSIZE];
struct rlimit rt;
rt.rlim_max = rt.rlim_cur = MAXEPOLLSIZE;
//設置系統資源,打開最大文件數
if (setrlimit(RLIMIT_NOFILE, &rt) == -1)
{
perror("setrlimit");
exit(EXIT_FAILURE);
}
else
{
printf("設置系統資源參數成功!\n");
}
//創建socket
if( (listener = socket(AF_INET, SOCK_STREAM, 0)) == -1)
{
perror("socket");
exit(EXIT_FAILURE);
}
else
{
printf("socket 創建成功!\n");
}
//設置非堵塞
if (fcntl(listener, F_SETFL, fcntl(listener, F_GETFL, 0) | O_NONBLOCK) == -1)
{
perror("fcntl");
exit(EXIT_FAILURE);
}
bzero(&server_addr, sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(port);
server_addr.sin_addr.s_addr = INADDR_ANY; //0.0.0.0所有地址
//綁定
if ( bind( listener, (struct sockaddr*)&server_addr, sizeof(struct sockaddr)) == -1 )
{
perror("bind");
exit(EXIT_FAILURE);
}
else
{
printf("IP 地址和端口綁定成功\n");
}
if (listen(listener, 10) == -1)
{
perror("listen");
exit(EXIT_FAILURE);
}
else
{
printf("開啓服務成功!\n");
}
//創建epoll爲ET模式
kdpfd = epoll_create(MAXEPOLLSIZE);
len = sizeof(struct sockaddr_in);
ev.events = EPOLLIN | EPOLLET;
ev.data.fd = listener;
//socket加入epoll
if( epoll_ctl(kdpfd, EPOLL_CTL_ADD, listener, &ev) < 0 )
{
fprintf( stderr, "epoll set insertion error: fd=%d\n", listener );
exit(EXIT_FAILURE);
}
else
{
printf("監聽 socket 加入 epoll 成功!\n");
}
//設置延遲和事件個數,事件由累加完成
curfds = 1;
//int timeout = 10*1000;
while(1)
{
//等待有事件發生
//nfds = epoll_wait(kdpfd, pevent, curfds, timeout);
nfds = epoll_wait(kdpfd, pevent, curfds, -1);
if( nfds == -1 )
{
perror("epoll_wait");
break;
}
else if (nfds == 0)
{
printf("waiting for connecting...\n");
continue;
}
for (n = 0; n < nfds; ++n)
{
if ((pevent[n].events & EPOLLERR) || (pevent[n].events & EPOLLHUP) || (!(pevent[n].events & EPOLLIN)))
{
//此FD上發生錯誤,或者套接字未準備好讀取(那麼爲什麼通知我們?)
fprintf (stderr, "epoll error\n");
close(pevent[n].data.fd);
continue;
}
else if (pevent[n].data.fd == listener)
{
//我們在監聽套接字上有一個通知,這意味着一個或多個傳入連接
while (1)
{
conn_sock = accept(listener, (struct sockaddr*)&client_addr, &len);
if( conn_sock == -1 )
{
if ((errno == EAGAIN) || (errno == EWOULDBLOCK))
{
//我們已經處理了所有傳入的連接
break;
}
else
{
perror ("accept error");
break;
}
}
//else
// printf("有連接來自於: %s:%d, 分配的 socket 爲:%d\n", inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port), conn_sock);
char hbuf[1024], sbuf[1024];
if ( 0 == getnameinfo((struct sockaddr*)&client_addr, len, hbuf, sizeof(hbuf), sbuf, sizeof(sbuf), NI_NUMERICHOST | NI_NUMERICSERV))
printf("Accepted connection on descriptor %d (host=%s, port=%s)\n", conn_sock, hbuf, sbuf);
if (fcntl(conn_sock, F_SETFL, fcntl(conn_sock, F_GETFL, 0) | O_NONBLOCK) == -1)
{
perror("fcntl");
break;
}
ev.events = EPOLLIN | EPOLLET;
ev.data.fd = conn_sock;
if( -1 == epoll_ctl( kdpfd, EPOLL_CTL_ADD, conn_sock, &ev))
{
fprintf(stderr, "把 socket '%d' 加入 epoll 失敗!%s\n", conn_sock, strerror(errno));
exit(EXIT_FAILURE);
}
curfds ++;
}
continue;
}
else
{
if (do_use_fd(pevent[n].data.fd) < 0)
{
printf ("關閉 %d\n", pevent[n].data.fd);
epoll_ctl(kdpfd, EPOLL_CTL_DEL, pevent[n].data.fd,&ev);
close(pevent[n].data.fd);
curfds--;
}
}
}
}
close(listener);
close(kdpfd);
return 0;
}
int do_use_fd(int connfd)
{
int done = 0;
while(1)
{
char buf[MAXBUF + 1];
bzero(buf, MAXBUF + 1);
int nread;
//讀取客戶端socket流
nread = recv(connfd, buf, MAXBUF, 0);
if (nread == -1)
{
if (errno != EAGAIN)
{
perror ("recv");
done = -1;
}
break;
}
else if (nread == 0)
{
done = -1;
break;
}
printf("%d接收消息成功:'%s',共%d個字節的數據\n", connfd, buf, nread);
//臨時測試寫法 實際用這樣會越界
if(ConnType[connfd] == unknow)
{
// 握手
std::map<std::string, std::string> header_map;
int offset = fetch_http_info(header_map, std::string(buf, buf+nread));
ConnType[connfd] = header_map.find("Sec-WebSocket-Key") != header_map.end() ? websocket : common;
if (ConnType[connfd] == websocket)
{
nread -= offset;
if (nread > 0) memcpy(buf, buf + offset ,nread);
// 握手
const std::string& client_key = header_map["Sec-WebSocket-Key"];
const std::string& mask = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
CSHA1 sha1;
sha1.Update((unsigned char*)client_key.data(), client_key.size());
sha1.Update((unsigned char*)mask.data(), mask.size());
sha1.Final();
unsigned char key_sh[20] = "";
sha1.GetHash(key_sh);
std::string server_key = websocketpp::base64_encode(key_sh, 20);
std::string response;
response += "HTTP/1.1 101 Switching Protocols\r\n";
response += "Upgrade: websocket\r\n";
response += "Connection: Upgrade\r\n";
response += "Sec-WebSocket-Accept: ";
response += server_key;
response += "\r\n";
response += "\r\n";
send(connfd, (unsigned char*)response.data(), response.size(), 0);
// if connect type is websocket, do pack msg by websocket protocl
//nPackLen = webPackLen;
// analyData(pPacket, (unsigned long *)&webPackLen);
}
}
/*if(ConnType[connfd] == websocket)
{
std::string bufStr;
int len = 0;
len = websocket_struct(bufStr, (unsigned char*)buf, nread);
//消息不完整
if(0 == len)
{
continue;
}
}*/
if(ConnType[connfd] == websocket)
{
const char *callBackData = "Hello, I'm epoll Server....\n";
data_frame des;
des.fin = 1;
des.opcode = 2;
des.mask = 0;
des.payload_length = strlen(callBackData);
des.payload_data = new unsigned char[des.payload_length];
memcpy(des.payload_data, callBackData, strlen(callBackData));
std::string bufStr = des.make_data();
//響應客戶端
if ( -1 == send(connfd, (unsigned char*)bufStr.data(), bufStr.size(), 0))
perror ("write");
}
}
return done;
}
2.示例用到的頭文件
define.h
#ifndef __DEFINE_H__
#define __DEFINE_H__
//套接字連接類型
/*enum ServiceType
{
unknow = 0, // 未知
common, // 二進制
websocket,
};*/
typedef struct ConnectStruct
{
int connfd;
int conntype;
}ConnStruct;
#define unknow 0 // 未知
#define common 1 // 二進制
#define websocket 2
#endif
l另外需要三個base64.hpp SHA1.cpp SHA1.h文件,百度都可以下載到,所以不添加