简要: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 #endifl另外需要三个base64.hpp SHA1.cpp SHA1.h文件,百度都可以下载到,所以不添加