就这上篇文章的代码,我们会继续实现以下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. Java并发(七)----线程sleep、yield、线程优先级

    1.sleep 与 yield sleep 调用 sleep 会让当前线程从 Running 进入 Timed Waiting 状态(阻塞) 其它线程可以使用 interrupt 方法打断正在睡眠的线 ...

  2. HBase-通过外部表将Hive数据写入到HBase

    a) 准备测试数据 这里准备的csv文件data_test.csv,内容没用''包裹,逗号作为列分隔符 171301,燕青,男,27,发展部 171207,武松,男,39,开发部 171307,李逵, ...

  3. NC24263 USACO 2018 Feb G]Directory Traversal

    题目链接 题目 题目描述 奶牛Bessie令人惊讶地精通计算机.她在牛棚的电脑里用一组文件夹储存了她所有珍贵的文件,比如: bessie/ folder1/ file1 folder2/ file2 ...

  4. SSD 简介—— NAND 芯片介绍

    制作 存储芯片的制作和其他芯片制作大致相同,从沙子中提取单晶硅制作晶圆再封装芯片. 闪存芯片从架构上分为NOR和NAND NOR Flash的source line把每个cell都并联起来,而NAND ...

  5. Linux中查看dmesg中 ata1对应的盘符,以及SATA/NVME SSD的rescan/delete操作方法

    1.查看dmesg 中ata1 对应的盘符: Step1;  lsscsi -s 查看盘符(bdf) 对应的host Id: Step2: ll /sys/class/scsi_host  可以找到不 ...

  6. 老王电子的拆机 ESP32-SOLO-1 填坑报告

    ESP32-SOLO-1 拆装 都是带板的, 长这个样子 需要用热风枪从背面吹, 因为中间有焊点, esp32朝下, 用280度大概2到3分钟, 四周需要均匀着风, 用镊子试探天线部分是否松动, 将外 ...

  7. Js中Symbol对象

    Js中Symbol对象 ES6引入了一种新的基本数据类型Symbol,表示独一无二的值,最大的用法是用来定义对象的唯一属性名,Symbol()函数会返回symbol类型的值,该类型具有静态属性和静态方 ...

  8. PostgreSQL中查看版本的几种方式

    PostgreSQL中查看版本的几种方式 1.SQL方式 1 2 3 4 5 6 7 8 9 10 postgres=# show server_version;  server_version -- ...

  9. MongoDB的安装及启动

    下载地址 https://www.mongodb.com/try/download/community 安装步骤 自定义安装目录 配置环境 下面是你安装后的mongodb的目录 在电脑的环境变量Pat ...

  10. itsdangerous模块的使用

    简介 生成临时身份令牌(通过邮件让用户注册激活的时候地址当中带有用户的信息.但是信息一般都是敏感信息,而且还想让它具有时效性,所以就可以选择itsdangerous模块 官网:https://itsd ...