就这上篇文章的代码,我们会继续实现以下websocket的协议。

为什么要使用websocket呢?

它的最大特点就是,服务器可以主动向客户端推送信息,客户端也可以主动向服务器发送信息,是真正的双向平等对话,属于服务器推送技术的一种。

其他特点包括:

(1)建立在TCP协议之上,服务器端的实现比较容易。

(2)与HTTP协议有着良好的兼容性。默认端口也是80和443,并且握手阶段采用HTTP协议,因此握手时不容易屏蔽,能通过各种HTTP代理服务器。

(3)数据格式比较轻量,性能开销小,通信高效。

(4)可以发送文本,也可以发送二进制数据。

(5)没有同源限制,客户端可以与任意服务器通信。

(6)协议标识符是ws(如果加密,则为wss),服务器网址就是URL。

websocket的握手流程:

  1. 判断是否为WEBSOCKET的请求
  2. 获取KEY
  3. 连接KEY和GUID
  4. 进行SHA1的处理
  5. 保存SHA1后的结果,发送回客户端。

websocket 的握手的实现

int readline(char* allbuf,int idx,char* linebuf) {
int len = strlen(allbuf); for (;idx < len; ++idx) {
if(allbuf[idx]=='\r' && allbuf[idx+1]=='\n')
return idx+2;
else
*(linebuf++) = allbuf[idx];
} return -1;
} int base64_encode(char *in_str, int in_len, char *out_str) {
BIO *b64, *bio;
BUF_MEM *bptr = NULL;
size_t size = 0; if (in_str == NULL || out_str == NULL)
return -1; b64 = BIO_new(BIO_f_base64());
bio = BIO_new(BIO_s_mem());
bio = BIO_push(b64, bio); BIO_write(bio, in_str, in_len);
BIO_flush(bio); BIO_get_mem_ptr(bio, &bptr);
memcpy(out_str, bptr->data, bptr->length);
out_str[bptr->length-1] = '\0';
size = bptr->length; BIO_free_all(bio);
return size;
} int my_ws_handshark(struct ntyevent*ev){
int idx=0;
char sec_data[128]={0};
char sec_accept[128] = {0}; do
{
/* code */
char linebuf[1024]={0};
idx=readline(ev->buffer,idx,linebuf);
//获取到WEBSOCKET的KEY
if (strstr(linebuf,"Sec-WebSocket-Key"))
{
/* code */
strcat(linebuf,GUID);
SHA1(linebuf+19, strlen(linebuf+19), sec_data);
base64_encode(sec_data, strlen(sec_data), sec_accept); printf("idx: %d, line: %ld\n",idx, sizeof("Sec-WebSocket-Key: "));
memcpy(ev->sec_accept, sec_accept, ACCEPT_KEY_LENGTH); } } while ((ev->buffer[idx]!='\r'||ev->buffer[idx]!='\n')&& idx != -1); }

上面的代码就是我们服务端和客户端建立通信的时候,我们服务端如何解析客户端的连接请求。

下面就是我们需要返回给客户端的数据。

int my_ws_response(struct ntyevent *ev){
ev->wlength=sprintf(ev->wbuffer, "HTTP/1.1 101 Switching Protocols\r\n"
"Upgrade: websocket\r\n"
"Connection: Upgrade\r\n"
"Sec-WebSocket-Accept: %s\r\n\r\n", ev->sec_accept);
printf("response: %s\n", ev->wbuffer);
return ev->wlength;
}

现在我们的客户端应该就可以连接上我们的websocket服务器了,我们测试一下

下面就是服务器接收发送消息的处理了

首先我们先看 一下它的帧。如何进行解析

帧结构在说明中。

定义我们的帧结构,

struct ws_ophdr {

	unsigned char opcode:4,
rsv3:1,
rsv2:1,
rsv1:1,
fin:1; unsigned char pl_len:7,
mask:1; };

由于客户端发送的数据都要设置MASK的值为1,并进行解密。



写一下如果MASK值为1的时候,进行解密的代码。

void umask(char *payload, int length, char *mask_key) {

	int i = 0;

	for (i = 0;i < length;i ++) {
payload[i] ^= mask_key[i%4];
} }

之后就是我们进行消息解密的代码,我们要根据Payload的长度进行不同的处理。

如果 x值在0-125,则是payload的真实长度。

如果 x值是126,则后面2个字节形成的16位无符号整型数的值是payload的真实长度。

如果 x值是127,则后面8个字节形成的64位无符号整型数的值是payload的真实长度。

完整代码如下,这样我们就完成了我们简单的WEBsocket服务器



#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/epoll.h>
#include <arpa/inet.h> #include <fcntl.h>
#include <unistd.h>
#include <errno.h> #include <openssl/sha.h>
#include <openssl/pem.h>
#include <openssl/bio.h>
#include <openssl/evp.h> #define BUFFER_LENGTH 1024
#define MAX_EPOLL_EVENTS 1024
#define SERVER_PORT 8888
#define PORT_COUNT 100
#define ACCEPT_KEY_LENGTH 64 #define GUID "258EAFA5-E914-47DA-95CA-C5AB0DC85B11" enum {
WS_HANDSHARK = 0,
WS_TRANMISSION = 1,
WS_END = 2,
WS_COUNT
}; struct ws_ophdr { unsigned char opcode:4,
rsv3:1,
rsv2:1,
rsv1:1,
fin:1; unsigned char pl_len:7,
mask:1; }; typedef int NCALLBACK(int ,int, void*); struct ntyevent {
int fd;
int events;
void *arg;
int (*callback)(int fd, int events, void *arg); int status;
char buffer[BUFFER_LENGTH];
int length;
//long last_active; char wbuffer[BUFFER_LENGTH]; //response
int wlength; char sec_accept[ACCEPT_KEY_LENGTH]; int wsstatus; //0, 1, 2, 3 }; struct eventblock { struct eventblock *next;
struct ntyevent *events;
}; struct ntyreactor {
int epfd;
int blkcnt; struct eventblock *evblks;
}; int recv_cb(int fd, int events, void *arg);
int send_cb(int fd, int events, void *arg);
struct ntyevent *ntyreactor_idx(struct ntyreactor *reactor, int sockfd); void nty_event_set(struct ntyevent *ev, int fd, NCALLBACK callback, void *arg) { ev->fd = fd;
ev->callback = callback;
ev->events = 0;
ev->arg = arg;
//ev->last_active = time(NULL); return ; } int nty_event_add(int epfd, int events, struct ntyevent *ev) { struct epoll_event ep_ev = {0, {0}};
ep_ev.data.ptr = ev;
ep_ev.events = ev->events = events; int op;
if (ev->status == 1) {
op = EPOLL_CTL_MOD;
} else {
op = EPOLL_CTL_ADD;
ev->status = 1;
} if (epoll_ctl(epfd, op, ev->fd, &ep_ev) < 0) {
printf("event add failed [fd=%d], events[%d]\n", ev->fd, events);
return -1;
} return 0;
} int nty_event_del(int epfd, struct ntyevent *ev) { struct epoll_event ep_ev = {0, {0}}; if (ev->status != 1) {
return -1;
} ep_ev.data.ptr = ev;
ev->status = 0;
epoll_ctl(epfd, EPOLL_CTL_DEL, ev->fd, &ep_ev); return 0;
} int readline(char* allbuf,int idx,char* linebuf) {
int len = strlen(allbuf); for (;idx < len; ++idx) {
if(allbuf[idx]=='\r' && allbuf[idx+1]=='\n')
return idx+2;
else
*(linebuf++) = allbuf[idx];
} return -1;
} int base64_encode(char *in_str, int in_len, char *out_str) {
BIO *b64, *bio;
BUF_MEM *bptr = NULL;
size_t size = 0; if (in_str == NULL || out_str == NULL)
return -1; b64 = BIO_new(BIO_f_base64());
bio = BIO_new(BIO_s_mem());
bio = BIO_push(b64, bio); BIO_write(bio, in_str, in_len);
BIO_flush(bio); BIO_get_mem_ptr(bio, &bptr);
memcpy(out_str, bptr->data, bptr->length);
out_str[bptr->length-1] = '\0';
size = bptr->length; BIO_free_all(bio);
return size;
} int my_ws_handshark(struct ntyevent*ev){
int idx=0;
char sec_data[128]={0};
char sec_accept[128] = {0}; do
{
/* code */
char linebuf[1024]={0};
idx=readline(ev->buffer,idx,linebuf);
//获取到WEBSOCKET的KEY
if (strstr(linebuf,"Sec-WebSocket-Key"))
{
/* code */
strcat(linebuf,GUID);
SHA1(linebuf+19, strlen(linebuf+19), sec_data);
base64_encode(sec_data, strlen(sec_data), sec_accept); printf("idx: %d, line: %ld\n",idx, sizeof("Sec-WebSocket-Key: "));
memcpy(ev->sec_accept, sec_accept, ACCEPT_KEY_LENGTH); } } while ((ev->buffer[idx]!='\r'||ev->buffer[idx]!='\n')&& idx != -1); } void umask(char *payload, int length, char *mask_key) { int i = 0; for (i = 0;i < length;i ++) {
payload[i] ^= mask_key[i%4];
} } int ws_tranmission(struct ntyevent *ev) { struct ws_ophdr *hdr = (struct ws_ophdr *)ev->buffer; if (hdr->pl_len < 126) { unsigned char *payload = NULL;
if (hdr->mask) {
payload = ev->buffer + 6; umask(payload, hdr->pl_len, ev->buffer + 2);
} else {
payload = ev->buffer + 2;
} printf("payload: %s\n", payload); } else if (hdr->pl_len == 126) { } else if (hdr->pl_len == 127) { } else {
//assert(0);
} } int my_ws_response(struct ntyevent *ev){
ev->wlength=sprintf(ev->wbuffer, "HTTP/1.1 101 Switching Protocols\r\n"
"Upgrade: websocket\r\n"
"Connection: Upgrade\r\n"
"Sec-WebSocket-Accept: %s\r\n\r\n", ev->sec_accept);
printf("response: %s\n", ev->wbuffer);
return ev->wlength;
} int my_ws_request(struct ntyevent *ev) { if (ev->wsstatus == WS_HANDSHARK) {
my_ws_handshark(ev);
ev->wsstatus = WS_TRANMISSION;
} else if (ev->wsstatus == WS_TRANMISSION) {
ws_tranmission(ev);
} } int recv_cb(int fd, int events, void *arg) { struct ntyreactor *reactor = (struct ntyreactor*)arg;
struct ntyevent *ev = ntyreactor_idx(reactor, fd); if (ev == NULL) return -1; int len = recv(fd, ev->buffer, BUFFER_LENGTH, 0);
nty_event_del(reactor->epfd, ev); if (len > 0) { ev->length = len;
ev->buffer[len] = '\0';
1(ev);
//printf("recv [%d]:%s\n", fd, ev->buffer); nty_event_set(ev, fd, send_cb, reactor);
nty_event_add(reactor->epfd, EPOLLOUT, ev); } else if (len == 0) { nty_event_del(reactor->epfd, ev);
printf("recv_cb --> disconnect\n");
close(ev->fd); } else { if (errno == EAGAIN && errno == EWOULDBLOCK) { // } else if (errno == ECONNRESET){
nty_event_del(reactor->epfd, ev);
close(ev->fd);
}
printf("recv[fd=%d] error[%d]:%s\n", fd, errno, strerror(errno)); } return len;
} int send_cb(int fd, int events, void *arg) { struct ntyreactor *reactor = (struct ntyreactor*)arg;
struct ntyevent *ev = ntyreactor_idx(reactor, fd); if (ev == NULL) return -1;
my_ws_response(ev); int len = send(fd, ev->wbuffer, ev->wlength, 0);
if (len > 0) {
printf("send[fd=%d], [%d]%s\n", fd, len, ev->wbuffer); nty_event_del(reactor->epfd, ev);
nty_event_set(ev, fd, recv_cb, reactor);
nty_event_add(reactor->epfd, EPOLLIN, ev); } else { nty_event_del(reactor->epfd, ev);
close(ev->fd); printf("send[fd=%d] error %s\n", fd, strerror(errno)); } return len;
} int accept_cb(int fd, int events, void *arg) { struct ntyreactor *reactor = (struct ntyreactor*)arg;
if (reactor == NULL) return -1; struct sockaddr_in client_addr;
socklen_t len = sizeof(client_addr); int clientfd; if ((clientfd = accept(fd, (struct sockaddr*)&client_addr, &len)) == -1) {
if (errno != EAGAIN && errno != EINTR) { }
printf("accept: %s\n", strerror(errno));
return -1;
} int flag = 0;
if ((flag = fcntl(clientfd, F_SETFL, O_NONBLOCK)) < 0) {
printf("%s: fcntl nonblocking failed, %d\n", __func__, MAX_EPOLL_EVENTS);
return -1;
} struct ntyevent *event = ntyreactor_idx(reactor, clientfd); if (event == NULL) return -1; nty_event_set(event, clientfd, recv_cb, reactor);
nty_event_add(reactor->epfd, EPOLLIN, event); printf("new connect [%s:%d], pos[%d]\n",
inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port), clientfd); return 0; } int init_sock(short port) { int fd = socket(AF_INET, SOCK_STREAM, 0);
fcntl(fd, F_SETFL, O_NONBLOCK); struct sockaddr_in server_addr;
memset(&server_addr, 0, sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
server_addr.sin_port = htons(port); bind(fd, (struct sockaddr*)&server_addr, sizeof(server_addr)); if (listen(fd, 20) < 0) {
printf("listen failed : %s\n", strerror(errno));
return -1;
} printf("listen server port : %d\n", port);
return fd;
} int ntyreactor_alloc(struct ntyreactor *reactor) { if (reactor == NULL) return -1;
if (reactor->evblks == NULL) return -1; struct eventblock *blk = reactor->evblks; while (blk->next != NULL) {
blk = blk->next;
} struct ntyevent* evs = (struct ntyevent*)malloc((MAX_EPOLL_EVENTS) * sizeof(struct ntyevent));
if (evs == NULL) {
printf("ntyreactor_alloc ntyevent failed\n");
return -2;
}
memset(evs, 0, (MAX_EPOLL_EVENTS) * sizeof(struct ntyevent)); struct eventblock *block = malloc(sizeof(struct eventblock));
if (block == NULL) {
printf("ntyreactor_alloc eventblock failed\n");
return -3;
}
block->events = evs;
block->next = NULL; blk->next = block;
reactor->blkcnt ++; return 0;
} struct ntyevent *ntyreactor_idx(struct ntyreactor *reactor, int sockfd) { if (reactor == NULL) return NULL;
if (reactor->evblks == NULL) return NULL; int blkidx = sockfd / MAX_EPOLL_EVENTS;
while (blkidx >= reactor->blkcnt) {
ntyreactor_alloc(reactor);
} int i = 0;
struct eventblock *blk = reactor->evblks;
while (i++ != blkidx && blk != NULL) {
blk = blk->next;
} return &blk->events[sockfd % MAX_EPOLL_EVENTS];
} int ntyreactor_init(struct ntyreactor *reactor) { if (reactor == NULL) return -1;
memset(reactor, 0, sizeof(struct ntyreactor)); reactor->epfd = epoll_create(1);
if (reactor->epfd <= 0) {
printf("create epfd in %s err %s\n", __func__, strerror(errno));
return -2;
} struct ntyevent* evs = (struct ntyevent*)malloc((MAX_EPOLL_EVENTS) * sizeof(struct ntyevent));
if (evs == NULL) {
printf("create epfd in %s err %s\n", __func__, strerror(errno));
close(reactor->epfd);
return -3;
}
memset(evs, 0, (MAX_EPOLL_EVENTS) * sizeof(struct ntyevent)); struct eventblock *block = malloc(sizeof(struct eventblock));
if (block == NULL) {
free(evs);
close(reactor->epfd);
return -3;
}
block->events = evs;
block->next = NULL; reactor->evblks = block;
reactor->blkcnt = 1; return 0;
} int ntyreactor_destory(struct ntyreactor *reactor) { close(reactor->epfd); struct eventblock *blk = reactor->evblks;
struct eventblock *blk_next;
while (blk != NULL) {
blk_next = blk->next; free(blk->events);
free(blk); blk = blk_next;
} return 0;
} int ntyreactor_addlistener(struct ntyreactor *reactor, int sockfd, NCALLBACK *acceptor) { if (reactor == NULL) return -1;
if (reactor->evblks == NULL) return -1; struct ntyevent *event = ntyreactor_idx(reactor, sockfd);
if (event == NULL) return -1; nty_event_set(event, sockfd, acceptor, reactor);
nty_event_add(reactor->epfd, EPOLLIN, event); return 0;
} int ntyreactor_run(struct ntyreactor *reactor) {
if (reactor == NULL) return -1;
if (reactor->epfd < 0) return -1;
if (reactor->evblks == NULL) return -1; struct epoll_event events[MAX_EPOLL_EVENTS+1]; int checkpos = 0, i; while (1) { int nready = epoll_wait(reactor->epfd, events, MAX_EPOLL_EVENTS, 1000);
if (nready < 0) {
printf("epoll_wait error, exit\n");
continue;
} for (i = 0;i < nready;i ++) { struct ntyevent *ev = (struct ntyevent*)events[i].data.ptr; if ((events[i].events & EPOLLIN) && (ev->events & EPOLLIN)) {
ev->callback(ev->fd, events[i].events, ev->arg);
}
if ((events[i].events & EPOLLOUT) && (ev->events & EPOLLOUT)) {
ev->callback(ev->fd, events[i].events, ev->arg);
} } }
} int main(int argc, char *argv[]) { struct ntyreactor *reactor = (struct ntyreactor*)malloc(sizeof(struct ntyreactor));
ntyreactor_init(reactor); unsigned short port = SERVER_PORT;
if (argc == 2) {
port = atoi(argv[1]);
} int i = 0;
int sockfds[PORT_COUNT] = {0}; for (i = 0;i < PORT_COUNT;i ++) {
sockfds[i] = init_sock(port+i);
ntyreactor_addlistener(reactor, sockfds[i], accept_cb);
} ntyreactor_run(reactor); ntyreactor_destory(reactor); for (i = 0;i < PORT_COUNT;i ++) {
close(sockfds[i]);
}
free(reactor); return 0;
}

这样我们就完成简单的websocket服务器。

推荐一个零声学院免费教程,个人觉得老师讲得不错,

分享给大家:[Linux,Nginx,ZeroMQ,MySQL,Redis,

fastdfs,MongoDB,ZK,流媒体,CDN,P2P,K8S,Docker,

TCP/IP,协程,DPDK等技术内容,点击立即学习:

服务器

音视频

dpdk

Linux内核

websocket服务器的创建的更多相关文章

  1. Swoole学习(五)Swoole之简单WebSocket服务器的创建

    环境:Centos6.4,PHP环境:PHP7 服务端代码 <?php //创建websocket服务器 $host = '0.0.0.0'; $port = ; $ws = new swool ...

  2. swoole创建websocket服务器

    目录 1 安装准备 1.1 安装swoole前必须保证系统已经安装了下列软件 1.2 下载并解压 1.3 编译安装成功后,修改php.ini 2 构建Swoole基本实例 2.1 tcp服务器实例 2 ...

  3. HTML5学习总结-08 WebSocket 服务器推送

    一 WebSocket 随着互联网的发展,传统的HTTP协议已经很难满足Web应用日益复杂的需求了.近年来,随着HTML5的诞生,WebSocket协议被提出,它实现了浏览器与服务器的全双工通信,扩展 ...

  4. 根据Unix哲学来编写你的HTML5 Websocket服务器来实现全双工通信

    websocketd代表WebSocket的守护进程 websocketd处理的是浏览器和服务器之间的WebSocket连接,它会启动你所指定的服务器端应用来对WebSockets进行处理,然后在浏览 ...

  5. Erlang cowboy websocket 服务器

    Erlang cowboy websocket 服务器 原文见于: http://marcelog.github.io/articles/erlang_websocket_server_cowboy_ ...

  6. 【Netty】(7)---搭建websocket服务器

    [Netty](7)---搭建websocket服务器 说明:本篇博客是基于学习某网有关视频教学. 目的:创建一个websocket服务器,获取客户端传来的数据,同时向客户端发送数据 一.服务端 1. ...

  7. 如何用Baas快速在腾讯云上开发小程序-系列1:搭建API & WEB WebSocket 服务器

    版权声明:本文由贺嘉 原创文章,转载请注明出处: 文章原文链接:https://www.qcloud.com/community/article/221059001487422606 来源:腾云阁 h ...

  8. Netty---入门程序,搭建Websocket 服务器

    Netty 常用的场景: 1.充当HTTP 服务器,但Netty 并没有遵循servlet 的标准,反而实现了自己的一套标准进行Http 服务: 2,RPC 远程调用,在分布式系统中常用的框架 3.S ...

  9. 04.swoole学习笔记--webSocket服务器

    <?php //创建webSocket服务器 $serv=); //获取请求 //on //open 建立连接 $serv:服务器 $request:客户端信息 $serv->on('op ...

  10. HTTPS请求HTTP接口被浏览器阻塞,python实现websocket客户端,websocket服务器,跨域问题,dwebsocket,https,拦截,服务端

    HTTPS请求HTTP接口被浏览器阻塞,python实现websocket客户端,websocket服务器,跨域问题,dwebsocket,https,拦截,服务端 发表时间:2020-03-05 1 ...

随机推荐

  1. Docker从认识到实践再到底层原理(三)|Docker在Centos7环境下的安装和配置

    前言 那么这里博主先安利一些干货满满的专栏了! 首先是博主的高质量博客的汇总,这个专栏里面的博客,都是博主最最用心写的一部分,干货满满,希望对大家有帮助. 高质量博客汇总 然后就是博主最近最花时间的一 ...

  2. 小知识:后台执行Oracle创建索引免受会话中断影响

    因为客户环境的堡垒机经常会莫名的断开连接,也不是简单的超时,因为有时候即使你一直在操作,也可能会断. 这样对于操作一些耗时长且中途中断可能会导致异常的操作就很危险,而最简单的避免方法就是将其写到脚本中 ...

  3. Linux-编译源码时所需提前安装的常用依赖包列表

    编译源码时所需提前安装的常用依赖包列表: yum -y install gcc gcc-c++ autoconf libjpeg libjpeg-devel libpng libpng-devel f ...

  4. Linux-nmon系统监控工具

    一.Nmon介绍 Nmon得名于 Nigel 的监控器,是IBM的员工 Nigel Griffiths 为 AIX 和 Linux 系统开发的,使用 Nmon 可以很轻松的监控系统的 CPU.内存.网 ...

  5. Pandas分析泰坦尼克号生还比例

    提出问题 影响乘客生还的因素很多,这里只对乘客的性别.年龄.乘客等级.这三个因素感兴趣, 看看这四个因素是否会影响乘客的生还率. 1.性别是否会影响生还率 2.年龄是否会影响生还率 3.乘客等级会否会 ...

  6. Thinkpad笔记本指点杆(小红点)自动漂移的问题

    Dell, HP, Thinkpad在高端商务机上会配备指点杆(小红点), 有很多人是指点杆的忠实用户, 因为工作时可以双手不离键盘, 非常方便. 在指点杆的使用过程中, 有时候会遇到指点杆自己漂移的 ...

  7. 《深入理解Java虚拟机》(五) JVM调优案例

    目录 问题 排查问题经过了如下的过程: 排除是否数据库卡顿造成 任务管理器 与客户沟通 至此开始通过JVM排查问题: JVM参数介绍 第一次Full GC 第二次Full GC截图 第三次Full G ...

  8. 实操开源版全栈测试工具RunnerGo安装(三)MacOS安装

    以Sonoma 14.1.2系统为例 视频教程:https://www.bilibili.com/video/BV1fG411e7h2/?spm_id_from=333.999.0.0 1.下载并安装 ...

  9. win32 - PE Executable and section inject

    #include <iostream> #include <Windows.h> #include <ShlObj.h> #include <Shlwapi. ...

  10. LayUI框架应用常见问题

    https://layui.itze.cn/index.html LayUI框架文档主页 获取URL参数 诸如表格中的"编辑","详情"工具按钮,需要在弹出层页 ...