-
WebSocket 协议及服务端实现
2016-05-24 18:10:30WebSocket 笔记 协议理解及服务器端实现 Bottle, May 24 2016 bottle@fridayws.com 前言: HTML 从1993年的HTML第一版发展到现在的 2014年的 HTML 5; 从最早为科学家们共享信息的文档, 到现在的包罗万象, 日常...WebSocket 笔记
协议理解及服务器端实现
Bottle, May 24 2016
bottle@fridayws.com
前言: HTML 从1993年的HTML第一版发展到现在的 2014年的 HTML 5; 从最早为科学家们共享信息的文档, 到现在的包罗万象, 日常生活中的无处不在(信息, 游戏, 商城等等). 现在传统的Web协议(HTTP, FTP等) 已经不够满足需要. 而HTML 5的来临开启了一个全新的时代.
在 HTML5 来临的同时, 它带来了一个强大的功能, 使我们的 B/S 应用和传统的 C/S应用 更近了一步, 它就是 WebSocket. 早期浏览器中通过 HTTP协议 仅能实现单向的通信, Comet 可以一定程度上模拟双向通信,但效率较低,并需要服务器有较好的支持; Flash 中的 Socket 和 XMLSocket 可以实现真正的双向通信, 通过 Flex Ajax Bridge, 可以在 Javascript 中使用这两项功能, 但是它需要flash的支持, 另外在性能上也会有更多的开销. WebSocket 的出现, 必然会替代上面两项技术, 得到广泛的使用. 面对这种状况, HTML5 定义了WebSocket协议, 能更好的节省服务器资源和带宽并达到实时通信.
目前网络止介绍WebSocket协议的文章有很多, 本文描述在Linux下, 用C去实现WebSocket的一个例子, 为了这个文档的完整性, 我就直接把WebSocket的协议规范拷贝过来了.
注: 我这里主要是介绍WebSocket 13版本. 早期版本因为在浏览器中基本没有在用了. 所以就不说了. 有兴趣的朋友可以在Google上考一下古. - -
WebSocket 13版本中, 通讯的实现主要是有两个阶段. 下面简单介绍一下.
一. 握手阶段:
握手的一个目的是为了兼容基于HTTP的服务器端程序, 这样一个端口可以同时处理HTTP客户端和WebSocket客户端,因此WebSocket客户端握手是一个HTTP Upgrade请求; 第二个目的是在客户端和服务器之前互相验证对方的有效性.
在WebSocket 13版本中, 握手过程可以见下图:
客户端发送一个HTTP Upgrade的请求到服务器端, 服务端接收请求后主要是成针对客户端发送的Sec-WebSocket-Key, 生成Sec-WebSocket-Accept. 生成方式比较简单就是将Sec-WebSocket-Key拼接上一个UUID (258EAFA5-E914-47DA-95CA-C5AB0DC85B11),然后对拼接后的字符串做sha1哈希, 然后将hash的结果使用base64编码成明文放入Sec-WebSocket-Accept返回给客户端. 至此握手完成.
下面附上握手相关代码.
#include <openssl/sha.h> #include <openssl/buffer.h> #include <openssl/bio.h> #include <openssl/evp.h> /** +------------------------------------------------------------------------------ * @desc : 握手数据解析, 并返回校验数据 +------------------------------------------------------------------------------ * @access : public * @author : bottle<bottle@fridayws.com> * @since : 16-05-11 * @param : const char* data 需要校验的数据 * @param : char* request 可发送回客户端的已处理数据, 需要预先分配内存 * @return : 0 +------------------------------------------------------------------------------ **/ int shakeHands(const char* data, char* request) { char* key = "Sec-WebSocket-Key: "; char* begin = NULL; char* val = NULL; int needle = 0; begin = strstr(data, key); if (!begin) return -1; val = (char*)malloc(sizeof(char) * 256); // 这里可以选择使用一个栈变量存 memset(val, 0, 256); begin += strlen(key); unsigned int blen = strlen(begin); int i = 0; for (i = 0; i < blen; ++i) { if (*(begin + i) == '\r' && *(begin + i + 1) == '\n') break; *(val + i) = *(begin + i); } strcat(val, "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"); char mt[SHA_DIGEST_LENGTH] = {0}; char accept[256] = {0}; SHA1(val, strlen(val), mt); memset(accept, 0, 256); base64_encode(mt, strlen(mt), accept, 256); memset(request, 0, 1024); sprintf(request, "HTTP/1.1 101 Switching Protocols\r\n" "Upgrade: websocket\r\n" "Connection: Upgrade\r\n" "Sec-WebSocket-Accept: %*s\r\n" "Sec-webSocket-Version: 13\r\n" "Server: Bottle-websocket-server\r\n\r\n" , strlen(accept), accept); free(val); val = NULL; return 0; }
注: SHA1和base64的代码, 网上会有很多, 我这边是使用的OpenSSL的库实现. 在最后, 我会给出全部代码. 使用OpenSSL的库需要包含它的一系列头文件. 并在编译时指定-lssl
二. 数据传输阶段.
WebSocket传输数据是通过数据帧的方式传输的. 它的头部结构见下图.
- 前两个字节
- 数据长度计算方式
从上表中我们可以看到基本的描述. 数据长度字段存储方式是不定的. 这边可以使用更为清楚的方式来说明一下. 长度拓展字段最大可以有8个字节, 一个64bit unsigned int的长度. 选择哪种方式去存放长度. 可以通过第二个字节的后七位来判断. 而长度后面可能会有4个字节的掩码信息(如果前面的mask位为1的话); 下面附上代码.
// Type define typedef unsigned char BYTE; // 定义一个BYTE类型 typedef unsigned short UINT16; // 定义一个UINT16类型 typedef unsigned long UINT64; // 定义一个UINT64类型 typedef struct _WebSocketMark { BYTE fin:1; BYTE rsv1:1; BYTE rsv2:1; BYTE rsv3:1; BYTE opcode:4; BYTE mask:1; BYTE payloadlen:7; } WSMark; typedef struct _WebSocketHeader { WSMark mark; UINT64 reallength; unsigned char mask[4]; unsigned short headlength; } WSHeader;
因为前两个字节是非整字节数据, 我这边使用位域结构去表示.
而下面的UINT64 reallength; 表示 数据的实际长度;
unsigned char mask[4]; 存放掩码信息(如果有的话);
unsigned short headlength; 存放整个个头部的长度, 如果后面需要的话, 方便计算.
下面是解析头部的程序代码.
/** +------------------------------------------------------------------------------ * @desc : 解析接收到的数据包 +------------------------------------------------------------------------------ * @access : public * @author : bottle<bottle@fridayws.com> * @since : 16-05-11 * @param : unsigned char* buf 接收到的数据内容 * @param : size_t length 接收的数据长度 * @param : WSHeader* 头部存放结构体 * @return : int 成功返回0 +------------------------------------------------------------------------------ **/ int parsePack(unsigned char* buf, size_t length, WSHeader* header) { header->mark.fin = buf[0] >> 7; // 或使用 buf[0] & 0x80 header->mark.rsv1 = buf[0] & 0x40; header->mark.rsv2 = buf[0] & 0x20; header->mark.rsv3 = buf[0] & 0x10; header->mark.opcode = buf[0] & 0xF; header->mark.mask = buf[1] >> 7; header->mark.payloadlen = buf[1] & 0x7F; header->headlength = 2; header->reallength = header->mark.payloadlen; if (header->mark.payloadlen == 126) // 如果payload length 值为 0x7E的话 { UINT16 tmp16 = 0; // 我们使用后面的 2 个字节存放实际数据长度 memcpy(&tmp16, buf + 2, 2); header->reallength = ntohs(tmp16); // 网络字节序转本地字节序 header->headlength += 2; } else if (header->mark.payloadlen == 127) // 如果payload length 值为 0x7F的话 { UINT64 tmp64 = 0; // 我们使用后续的 8 个字节存放实际数据长度 memcpy(&tmp64, buf + 2, 8); header->reallength = ntohl(tmp64); // 网络字节序转本地字节序 header->headlength += 8; } memset(header->mask, 0, 4); if (header->mark.mask) { memcpy(header->mask, buf + header->headlength, 4); header->headlength += 4; } return 0; }
备注 1: OpenSSL 库实现 base64_encode
/** +------------------------------------------------------------------------------ * @desc : 对数据做base64处理 +------------------------------------------------------------------------------ * @access : public * @author : bottle<bottle@fridayws.com> * @since : 16-05-11 * @param : char* str 需要做base64的字符串 * @param : int len 数据长度 * @param : char* encode 处理好的数据存放位置 * @param : int 数据的实际长度 * @return : int 长度 +------------------------------------------------------------------------------ **/ int base64_encode(char* str, int len, char* encode, int elen) { BIO* bmem, * b64; BUF_MEM* bptr = NULL; b64 = BIO_new(BIO_f_base64()); bmem = BIO_new (BIO_s_mem()); b64 = BIO_push(b64, bmem); BIO_write(b64, str, len); BIO_flush(b64); BIO_get_mem_ptr(b64, &bptr); elen = bptr->length; memcpy(encode, bptr->data, bptr->length); if (encode[elen - 1] == '\n' || encode[elen - 1] == '\r') encode[elen - 1] = '\0'; BIO_free_all(b64); return elen; } /** +------------------------------------------------------------------------------ * @desc : 反处理, 解析base64 +------------------------------------------------------------------------------ * @access : public * @author : bottle<bottle@fridayws.com> * @since : 16-05-11 * @param : char* encode 已编码过的数据 * @param : int elen 编码过的数据长度 * @param : char* decode 存放解码后的数据 * @param : int dlen 存放解码后的数据长度 * @return : void +------------------------------------------------------------------------------ **/ int base64_decode(char* encode, int elen, char* decode, int dlen) { int len = 0; BIO* b64, * bmem; b64 = BIO_new(BIO_f_base64()); bmem = BIO_new_mem_buf(encode, elen); bmem = BIO_push(b64, bmem); len = BIO_read(bmem, decode, elen); decode[len] = 0; BIO_free_all(bmem); return 0; }
备注 2: 接收完整数据内容代码
/** +------------------------------------------------------------------------------ * @desc : 获取消息体 +------------------------------------------------------------------------------ * @access : public * @author : bottle<bottle@fridayws.com> * @since : 16-05-17 * @param : const int cfd client fd * @param : const unsigned char* buf 消息buf * @param : size_t bufsize 消息长度 * @param : unsigned char* container 获取到的消息体存放地址 * @param : const WSHeader* pHeader 头结构体地址 * @return : int +------------------------------------------------------------------------------ **/ int getPackPayloadData(const int cfd, const unsigned char* buf, size_t bufsize, unsigned char* container, const WSHeader* pHeader) { memset(container, 0, pHeader->reallength + 1); int readlength = 0; int recvlength = 0; int count = 0; char *_buf = (char*)calloc(bufsize, sizeof(char)); // 动态分配足够大空间 if (pHeader->mark.mask) // 如果有掩码 { readlength = bufsize - pHeader->headlength; int x = 0; memcpy(container, buf + pHeader->headlength, pHeader->reallength > readlength ? readlength : pHeader->reallength); while(pHeader->reallength > readlength) { memset(_buf, 0, bufsize); count = recv(cfd, _buf, bufsize, MSG_DONTWAIT); recvlength = (pHeader->reallength - readlength) > bufsize ? bufsize : (pHeader->reallength - readlength); memcpy(container + readlength, _buf, recvlength); readlength += recvlength; } for (x = 0; x < pHeader->reallength; ++x) *(container + x) ^= pHeader->mask[x % 4]; } else { readlength = bufsize - pHeader->headlength; memcpy(container, buf + pHeader->headlength, pHeader->reallength > readlength ? readlength : pHeader->reallength); while(pHeader->reallength > readlength) { memset(_buf, 0, bufsize); count = recv(cfd, _buf, bufsize, MSG_DONTWAIT); recvlength = pHeader->reallength - readlength > bufsize ? bufsize : pHeader->reallength - readlength; memcpy(container + readlength, _buf, recvlength); readlength += recvlength; } } free(_buf); _buf = NULL; return 0; }
备注 3: 打包需要发送的数据(这边超大数据<数据长度超过0xFFFF>处理可能会有问题, 只是写一个实例没有做测试和处理), 我这个写的不是很好, 后面正式使用会做一些优化.
/** +------------------------------------------------------------------------------ * @desc : 对发送数据打包 +------------------------------------------------------------------------------ * @author : Bottle<bottle.friday@gmail.com> * @since : 2016-05-11 * @param : const unsigned char* message 需要发送的消息体 * @param : size_t len 发送数据长度 * @param : BYTE fin 是否是结束消息 (1 bit) * @param : BYTE opcode 消息类型(4 bit) 共15种类型 * @param : BYTE mask (是否需要做掩码运算 1 bit) * @param : unsigned char** send 输出参数, 存放处理好的数据包 * @param : size_t* slen 输出参数, 记录数据包的长度 * @return : int 成功返回0 +------------------------------------------------------------------------------ **/ int packData(const unsigned char* message, size_t len, BYTE fin, BYTE opcode, BYTE mask, unsigned char** send, size_t* slen) { int headLength = 0; // 基本一个包可以发送完所有数据 *slen = len; if (len < 126) // 如果不需要扩展长度位, 两个字节存放 fin(1bit) + rsv[3](1bit) + opcode(4bit); mask(1bit) + payloadLength(7bit); *slen += 2; else if (len < 0xFFFF) // 如果数据长度超过126 并且小于两个字节, 我们再用后面的两个字节(16bit) 表示 UINT16 *slen += 4; else // 如果数据更长的话, 我们使用后面的8个字节(64bit)表示 UINT64 *slen += 8; // 判断是否有掩码 if (mask & 0x1) // 判断是不是1 *slen += 4; // 4byte 掩码位 // 长度已确定, 现在可以重新分配内存 *send = (unsigned char*)realloc((void*)*send, *slen); // 做数据设置 memset(*send, 0, *slen); **send = fin << 7; **send = **send | (0xF & opcode); //处理opcode *(*send + 1) = mask << 7; if (len < 126) { *(*send + 1) = *(*send + 1) | len; //start += 2; headLength += 2; } else if (len < 0xFFFF) { *(*send + 1) = *(*send + 1) | 0x7E; // 设置第二个字节后7bit为126 UINT16 tmp = htons((UINT16)len); //UINT16 tmp = len; memcpy(*send + 2, &tmp, sizeof(UINT16)); headLength += 4; } else { *(*send + 1) = *(*send + 1) | 0x7F; // 设置第二个字节后为7bit 127 UINT64 tmp = htonl((UINT64)len); //UINT64 tmp = len; memcpy(*send + 2, &tmp, sizeof(UINT64)); headLength += 10; } // 处理掩码 if (mask & 0x1) { // 因协议规定, 从服务器向客户端发送的数据, 一定不能使用掩码处理. 所以这边省略 headLength += 4; } memcpy((*send) + headLength, message, len); *(*send + (*slen - 1)) = '\0'; return 0; }
完整程序代码在GitHub上, 地址是:https://github.com/BottleHe/c-demo/blob/master/websocket/websocket.c . 有兴趣的朋友可以看看, 别编译的时候可以直接使用下面的指令, 系统环境是CentOS 6.4. gcc websocket.c -lssl
-
服务端和客户端WebSocket开发(服务端:nodejs+ws,客户端:CocosCreator)
2020-06-12 17:09:52nodejs游戏服务器开发系列文章以nodejs+Typescript+CocosCreator+WebSocket为例,搭建服务器和客户端。 WebSocket介绍 官网:http://websocket.org/index.html 详细介绍参考阮一峰的教程:...转载自:https://blog.csdn.net/iningwei/article/details/100106243
前言
nodejs游戏服务器开发系列文章以nodejs+Typescript+CocosCreator+WebSocket为例,搭建服务器和客户端。WebSocket介绍
官网:http://websocket.org/index.html
详细介绍参考阮一峰的教程:http://www.ruanyifeng.com/blog/2017/05/websocket.html
还有这两篇文章,写的非常好:WebSocket介绍一,WebSocket介绍二
官方API地址:https://developer.mozilla.org/zh-CN/docs/Web/API/WebSocket需要注意:
RFC规范指出,WebSocket是一个message-based的协议,它可以自动将数据分片,并且自动将分片的数据组装。
也就是说,WebSocket的RFC标准是不会产生粘包(sticky)、半包(half a pack)问题的。无需应用层开发人员关心缓存以及手工组装message。WebSocket API中提供的message响应就是一个完整的消息。
但是需要注意的是,由于各个WebSocket库对RFC规范实现的良莠不齐,有些库还是会有一些莫名问题,需要自己甄别。特别是当你的数据message特别大的时候(到底是多大是特别大,由具体实现决定)。转载自:https://blog.csdn.net/iningwei/article/details/100106243
nodejs集成WebSocket
nodejs中最常用的WebSocket库ws托管在git上https://github.com/websockets/ws
api文档为:https://github.com/websockets/ws/blob/master/doc/ws.md安装方法:
服务器项目目录Server下执行 npm install ws
会在node_modules目录下添加ws和async-limiter文件夹。
继续执行 npm install @types/ws,会在node_modules/@types目录下添加ws文件夹,其内为ws模块的声明文件。WebSocket数据类型
WebSocket数据传输类型只支持文本类型和二进制类型。二进制支持Blob和ArrayBuffer两种类型。二进制默认是Blob类型。// 设置二进制数据类型为blob(默认类型)
ws.binaryType = "blob";
// Event handler for receiving Blob messages
ws.onmessage = function(e) {
if(e.data instanceof Blob){
console.log("Blob message received", e.data);
var blob = new Blob(e.data);
}
};
//ArrayBuffer
ws.binaryType = "arraybuffer";
ws.onmessage = function(e) {
if(e.data instanceof ArrayBuffer){
console.log("ArrayBuffer Message Received", + e.data);
// e.data即ArrayBuffer类型
var a = new Uint8Array(e.data);
}
};
写一个简单的WebSocket服务器
打开index.ts,复制以下内容:import * as WebSocket from "ws"
const server = new WebSocket.Server({ port: 8083 });
server.on("listening", () => {
console.log("服务器启动完毕!开始侦听");
});server.on("connection", function connection(ws) {
ws.on("message", function incoming(message) {
console.log("received:%s", message);
});
ws.send("hhhello");
});
客户端代码
typescript已经封装有WebSocket模块,使用自带的即可。private ws: WebSocket;
start() {
console.log("go!");
this.ws = new WebSocket("ws://192.168.2.31:8083");this.ws.onopen = this.onOpen.bind(this);
this.ws.onmessage = function (event) {
console.log("client rcv:" + event.data);
}.bind(this);
this.ws.onclose = function (event) {
console.log("服务器已关");
}.bind(this);
this.ws.onerror = function (event) {
}.bind(this);
}private onOpen(event: MessageEvent) {
console.log("连接建立啦");
this.sendData(JSON.stringify({
ctype: "login",
data: {
name: "jack",
age: 22
}
}));
}
private sendData(data) {
this.ws.send(data);
}
总结
上述代码演示了,客户端把JSON格式的数据字符串化,传给服务器。 -
2,服务端和客户端WebSocket开发(服务端:nodejs+ws,客户端:CocosCreator)
2019-08-27 18:40:08前言 ...nodejs游戏服务器开发系列文章以nodejs+Typescript+CocosCreator+WebSocket为例,搭建服务器和客户端。 WebSocket介绍 官网:https://developer.mozilla.org/zh-CN/docs/Web/API/WebSocket ...前言
nodejs游戏服务器开发系列文章以nodejs+Typescript+CocosCreator+WebSocket为例,搭建服务器和客户端。
WebSocket介绍
官网:http://websocket.org/index.html
详细介绍参考阮一峰的教程:http://www.ruanyifeng.com/blog/2017/05/websocket.html
还有这两篇文章,写的非常好:WebSocket介绍一,WebSocket介绍二
官方API地址:https://developer.mozilla.org/zh-CN/docs/Web/API/WebSocket需要注意:
RFC规范指出,WebSocket是一个message-based的协议,它可以自动将数据分片,并且自动将分片的数据组装。
也就是说,WebSocket的RFC标准是不会产生粘包(sticky)、半包(half a pack)问题的。无需应用层开发人员关心缓存以及手工组装message。WebSocket API中提供的message响应就是一个完整的消息。
但是需要注意的是,由于各个WebSocket库对RFC规范实现的良莠不齐,有些库还是会有一些莫名问题,需要自己甄别。特别是当你的数据message特别大的时候(到底是多大是特别大,由具体实现决定)。nodejs集成WebSocket
nodejs中最常用的WebSocket库ws托管在git上https://github.com/websockets/ws
api文档为:https://github.com/websockets/ws/blob/master/doc/ws.md安装方法:
服务器项目目录Server下执行npm install ws
会在node_modules目录下添加ws和async-limiter文件夹。
继续执行npm install @types/ws
,会在node_modules/@types目录下添加ws文件夹,其内为ws模块的声明文件。WebSocket数据类型
WebSocket数据传输类型只支持文本类型和二进制类型。二进制支持Blob和ArrayBuffer两种类型。二进制默认是Blob类型。
// 设置二进制数据类型为blob(默认类型) ws.binaryType = "blob"; // Event handler for receiving Blob messages ws.onmessage = function(e) { if(e.data instanceof Blob){ console.log("Blob message received", e.data); var blob = new Blob(e.data); } };
//ArrayBuffer ws.binaryType = "arraybuffer"; ws.onmessage = function(e) { if(e.data instanceof ArrayBuffer){ console.log("ArrayBuffer Message Received", + e.data); // e.data即ArrayBuffer类型 var a = new Uint8Array(e.data); } };
写一个简单的WebSocket服务器
打开index.ts,复制以下内容:
import * as WebSocket from "ws" const server = new WebSocket.Server({ port: 8083 }); server.on("listening", () => { console.log("服务器启动完毕!开始侦听"); }); server.on("connection", function connection(ws) { ws.on("message", function incoming(message) { console.log("received:%s", message); }); ws.send("hhhello"); });
客户端代码
typescript已经封装有WebSocket模块,使用自带的即可。
private ws: WebSocket; start() { console.log("go!"); this.ws = new WebSocket("ws://192.168.2.31:8083"); this.ws.onopen = this.onOpen.bind(this); this.ws.onmessage = function (event) { console.log("client rcv:" + event.data); }.bind(this); this.ws.onclose = function (event) { console.log("服务器已关"); }.bind(this); this.ws.onerror = function (event) { }.bind(this); } private onOpen(event: MessageEvent) { console.log("连接建立啦"); this.sendData(JSON.stringify({ ctype: "login", data: { name: "jack", age: 22 } })); } private sendData(data) { this.ws.send(data); }
总结
上述代码演示了,客户端把JSON格式的数据字符串化,传给服务器。
-
SpringBoot实践(websocket通信实现)-服务端实现
2020-04-23 11:13:57新的开始,新的知识。 在学习安卓的过程中,有一个是关于做石头剪刀布游戏的左右,我设法想实现人对人对战,正好想尝试一下websocket编程,本篇写的是websocket服务端。传送门: ... 实验现象: ...新的开始,新的知识。
在学习安卓的过程中,有一个是关于做石头剪刀布游戏的左右,我设法想实现人对人对战,正好想尝试一下websocket编程,本篇写的是websocket服务端。传送门:
https://blog.csdn.net/Abit_Go/article/details/105701388
实验现象:
配置pom.xml文件
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.1.3.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent> <groupId>com.example5</groupId> <artifactId>demo</artifactId> <version>0.0.1-SNAPSHOT</version> <name>demo</name> <description>Demo project for Spring Boot</description> <properties> <java.version>1.8</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-websocket</artifactId> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>1.2.54</version> </dependency> </dependencies> <build> <resources> <resource> <directory>src/main/java</directory> <includes> <include>**/*.xml</include> </includes> </resource> <resource> <directory>src/main/resources</directory> </resource> </resources> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-surefire-plugin</artifactId> <configuration> <skip>true</skip> </configuration> </plugin> </plugins> </build> </project>
在配置文件中加入下面的插件配置是因为打包得时候加入websocket包会报错,但是可以在idea运行。
<plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-surefire-plugin</artifactId> <configuration> <skip>true</skip> </configuration> </plugin>
首先创建启动项
package com; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.context.annotation.Bean; import org.springframework.scheduling.annotation.EnableScheduling; import org.springframework.web.servlet.config.annotation.CorsRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter; @EnableScheduling @SpringBootApplication public class DemoApplication { //解决前端跨域问题 @Bean public WebMvcConfigurer webMvcConfigurer() { return new WebMvcConfigurerAdapter() { @Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping("/**").allowedOrigins("*"); } }; } public static void main(String[] args) throws Exception { SpringApplication.run(DemoApplication.class, args); } }
其次websocket的配置
package com.config; import org.springframework.context.annotation.Bean; import org.springframework.stereotype.Component; import org.springframework.web.socket.server.standard.ServerEndpointExporter; @Component public class WebSocketConfig { @Bean public ServerEndpointExporter serverEndpointExporter(){ return new ServerEndpointExporter(); } }
然后编写websoket的逻辑代码:
package com.config; import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONObject; import org.springframework.stereotype.Component; import javax.websocket.OnClose; import javax.websocket.OnMessage; import javax.websocket.OnOpen; import javax.websocket.Session; import javax.websocket.server.PathParam; import javax.websocket.server.ServerEndpoint; import java.io.IOException; import java.util.concurrent.ConcurrentHashMap; @Component @ServerEndpoint("/websocket/{name}") public class WebSocket { private Session session; private String name; private static ConcurrentHashMap<String, WebSocket> webSocketSet = new ConcurrentHashMap<>(); @OnOpen public void OnOpen(Session session, @PathParam(value = "name") String name) { this.session = session; this.name = name; webSocketSet.put(name, this); System.out.println("[webSocketSet]当前用户接入:" + webSocketSet.size()); } @OnClose public void OnClose() { webSocketSet.remove(this.name); System.out.println("[webSocketSet]当前用户断开:" + this.name); } @OnMessage public void OnMessage(String message) { System.out.println("[webSocketSet]当前收到消息:" + message); JSONObject jsonObject = JSONObject.parseObject(message); String name = jsonObject.getString("name"); //不是心跳包 if(!message.equals("heartbeat")){ //直接全局发送 GroupSending(name,message); } } public void GroupSending(String nosend,String message){ for(String name:webSocketSet.keySet()){ try { if(!nosend.equals(name)) webSocketSet.get(name).session.getBasicRemote().sendText(message); } catch (IOException e) { webSocketSet.remove(this.name); System.out.println("广播失败"); } } } public void AppointSending(String name,String message){ try { webSocketSet.get(name).session.getBasicRemote().sendText(message); } catch (IOException e) { webSocketSet.remove(this.name); System.out.println("单播失败"); } } }
最后为yml配置文件(部分可选):
server: port: 8989 tomcat: max-connections: 1000 accesslog: enabled: true # directory: c:/logs uri-encoding: utf-8 max-threads: 200 default-autowire: "byName" default-lazy-init: "true"
-
java websocket ssl_如何让服务端同时支持WebSocket和SSL加密的WebSocket(即同时支持ws和wss)?...
2021-02-28 07:32:00自从HTML5出来以后,使用WebSocket通信就变得火热起来,基于WebSocket开发的手机APP和手机游戏也越来越多。我的一些开发APP的朋友,开始使用WebSocket通信,后来觉得通信不够安全,想要对通信进行加密,于是自然而然... -
如何让服务端同时支持WebSocket和SSL加密的WebSocket(即同时支持ws和wss)
2018-08-08 18:50:00自从HTML5出来以后,使用WebSocket通信就变得火热起来,基于WebSocket开发的手机APP和手机游戏也越来越多。我的一些开发APP的朋友,开始使用WebSocket通信,后来觉得通信不够安全,想要对通信进行加密,于是自然而然... -
如何让服务端同时支持WebSocket和SSL加密的WebSocket(即同时支持ws和wss)?
2017-11-17 10:40:00自从HTML5出来以后,使用WebSocket通信就变得火热起来,基于WebSocket开发的手机APP和手机游戏也越来越多。我的一些开发APP的朋友,开始使用WebSocket通信,后来觉得通信不够安全,想要对通信进行加密,于是自然而然... -
回归前端学习第20天——实现俄罗斯方块小游戏1(搭建websocket)
2020-08-11 00:20:37结合WebSocket 来实现一个俄罗斯方块小游戏吧~WebSocket 介绍WebSocket 特点服务端实时通信的方法:AJAX轮询和Long Polling长轮询开始码代码吧,小游戏走起!搭建自己的WebSocket 效果图 WebSocket 介绍 webSocket... -
websocket 在线用户列表_[WebSocket]使用WebSocket实现实时多人答题对战游戏
2020-12-04 07:09:40第一章:手把手搭建WebSocket多人在线聊天室(SpringBoot+WebSocket)[WebSocket]第二章:WebSocket集群分布式改造——实现多人在线聊天室在本文中,我将介绍如何使用WebSocket向实时多人答题对战游戏提供服务端,... -
基于Spring 4.0 的 Web Socket 聊天室/游戏服务端简单架构
2018-12-21 11:04:26跟webservice来相比,Web Socket可以做到保持长连接,或者说强连接,一直握手存在两端可以互相发送消息互相收到消息,而webservice是一次性的,...一、WebSocket是HTML5出的,是一种协议,也就是说原版的HTTP协议没... -
WebSocket 广播技术的应用 游戏公告
2020-11-08 13:28:19由于公司项目中使用到websocket, 所以本人利用空余时间学习一番,如果本文中有说错的地方,还望指出! 首先要知道websocket 是什么 ? WebSocket使得客户端和服务器之间的数据交换...广播技术应用 简单websocket游戏公 -
websocket
2017-07-05 10:58:53websocket是Html5新增加特性之一,目的是浏览器与服务端建立全双工的通信方式,解决http请求-响应带来过多的资源消耗,同时对特殊场景应用提供了全新的实现方式,比如聊天、股票交易、游戏等对对实时性要求较高的... -
.net 实时通信_[WebSocket]使用WebSocket实现实时多人答题对战游戏
2021-01-16 13:07:19第一章:手把手搭建WebSocket多人在线聊天室(SpringBoot+WebSocket)[WebSocket]第二章:WebSocket集群分布式改造——实现多人在线聊天室在本文中,我将介绍如何使用WebSocket向实时多人答题对战游戏提供服务端,... -
websocket 在线用户列表_【WebSocket】实时多人答题对战游戏
2020-12-12 11:44:30系列教程回顾:手把手搭建WebSocket多人在线聊天室【多人聊天室】WebSocket集群/分布式改造在本文中,我将介绍如何使用WebSocket向实时多人答题对战游戏提供服务端,并详细介绍通接口的设计。 这是我在最近作业竞赛... -
ycsocket:基于swoole的套接字框架,支持协程版MySQL,Redis连接池,已用于大型RPG游戏服务端-源码
2021-02-03 10:52:51大型RPG游戏,纯php解决方案,php + swoole + swoole_orm + zephir,这个游戏的战斗部分完全用zephir来实现,转化为php扩展,能做到同时兼顾性能和开发效率,(zephir代码有机会我再开源出来,目前时机不成熟,游戏... -
jforgame:jforgame是一个一站式游戏服务端开发框架,包含游戏服,跨服,匹配服,后台管理系统等模块。...
2021-02-02 19:33:21支持socket / webSocket接入,兼容手游/页游服务端架构 通信协议支持protobuf或java反射,为客户端提供多种选择 框架提供多种组件,可以直接二次开发业务逻辑 提供热更机制以及jmx接口,方便对生产项目进行监控与... -
rudesocket如何使用_[WebSocket]使用WebSocket实现实时多人答题对战游戏
2020-12-20 19:45:51系列教程回顾:在本文中,我将介绍如何使用WebSocket向实时多人答题对战游戏提供服务端,并详细介绍通接口的设计。这是我在最近作业竞赛中设计的小项目,和小伙伴们一起设计了整个游戏流程和后端代码,前端页面暂时... -
使用SpringBoot及Construct2的WebSocket制作联机游戏(二)
2019-01-25 15:45:49使用SpringBoot及Construct2的WebSocket制作联机游戏(一) 一、介绍: 1、SpringBoot服务端添加登录及注册接口,并进行Postman测试 2、SpringBoot服务端WebSocket对接收数据和返回数据进行数据封装,添加Json工具... -
Java游戏服务器开发之二十四--WebSocket中添加ssl,支持wss协议
2018-08-20 10:40:09Java游戏服务器开发之二十四–WebSocket中添加ssl,支持wss协议 Java游戏服务器开发之二十四–WebSocket中添加ssl,支持wss协议 概述 服务端 客户端测试及问题 网页 netty客户端访问wss 改变的内容 新增 修改... -
WebSocket协议
2019-09-18 14:26:27WebSocket让我们可以在客户端和web服务端之间实现实时通信,不需要客户端发起请求,服务端就可以直接向客户端发送数据,目前大多数浏览器都支持WebSocket协议。由于这一特性,它能够应用到很多场景下,比如游戏,... -
开源Html5+Websocket+Mqtt实时聊天室
2019-03-07 09:00:00本应用示例使用Coolpy7作为Mqtt服务器并启用Websocket代理完美支持高并发大流量即时通过能力,本示以即时通信聊天为为例。还可以应用到其他软件...WebSocket代理服务端 (Coolpy7_ws) Html5聊天室前端 安装并运... -
使用SpringBoot及Construct2的WebSocket制作联机游戏(一)
2019-01-23 10:33:02服务端:SpringBoot框架下的WebSocket实现 客户端:Construct2使用官方插件WebSocket实现 业务:连接、发送信息、接收信息 二、服务端实现 1、导入相关依赖 <!-- ... -
的websocket_Websocket技术选型参考
2021-01-15 10:46:28WebSocket 使得客户端和服务器之间的数据交换变得更加简单,允许服务端主动向客户端推送数据,当然也支持客户端发送数据到服务端。通常用来社交聊天、弹幕、多玩家游戏、协同编辑、股票基金实时报价、资讯自动更新等... -
WebSocket集成XMPP网页即时通讯1:Java Web Project服务端/客户端Jetty9开发初探
2016-09-18 17:39:34Web 应用的信息交互过程通常是客户端通过...比如说在线游戏、在线证券、设备监控、新闻在线播报、RSS 订阅推送等等,当客户端浏览器准备呈现这些信息的时候,这些信息在服务器端可能已经过时了。所以保持客户端和服 -
.net 实时通信_【WebSocket】实时多人答题对战游戏
2021-01-16 13:07:19系列教程回顾:手把手搭建WebSocket多人在线聊天室【多人聊天室】WebSocket集群/分布式改造在本文中,我将介绍如何使用WebSocket向实时多人答题对战游戏提供服务端,并详细介绍通接口的设计。 这是我在最近作业竞赛... -
mqtt 域名连接_开源Html5+Websocket+Mqtt实时聊天室
2021-01-03 01:40:50还可以应用到其他软件应用如:网页客服系统、网站信息通知、网页即时通信系统、网页游戏等等技术应用架构简介系统架构包括:MQTT服务端程序(Coolpy7)WebSocket代理服务端 (Coolpy7_ws)Html5聊天室前端安装并运行运行...
-
宪法学--期末复习知识点总结.pdf
-
PHP随机不重复的数(自定义,以随机大乐透举例)
-
中国计量学院《工程图学》历年期末考试试卷(含答案).pdf
-
ElementUI 的 el-select 设置值后显示value而不是label
-
西南科技大学电子线路分析与实践试卷和答案.pdf
-
浙江科技学院《土木工程材料》16套复习测试题(含答案).pdf
-
详解Go 中方法与函数的区别
-
Unity RUST 逆向安全开发
-
java并发编程--Phaser的使用
-
浙江科技大学《材料力学》期末复习题.pdf
-
浙江科技学院土木工程材料试题.pdf
-
vue3从0到1-超详细
-
MySQL 性能优化(思路拓展及实操)
-
朱老师鸿蒙系列课程第1期-3.鸿蒙系统Harmonyos源码配置和管理
-
用微服务spring cloud架构打造物联网云平台
-
PHP中路由和rewrite的使用
-
Spring Cloud 学习笔记(2 / 3)
-
SecureCRT无法使用非root用户登录解决办法
-
MMM 集群部署实现 MySQL 高可用和读写分离
-
零基础极简以太坊智能合约开发环境搭建并开发部署