精华内容
下载资源
问答
  • WebSocket 协议及服务端实现

    万次阅读 2016-05-24 18:10:30
    WebSocket 笔记 协议理解及服务器端实现 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传输数据是通过数据帧的方式传输的. 它的头部结构见下图.


    1. 前两个字节

    1. 数据长度计算方式

    从上表中我们可以看到基本的描述. 数据长度字段存储方式是不定的. 这边可以使用更为清楚的方式来说明一下.  长度拓展字段最大可以有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  






    展开全文
  • nodejs游戏服务器开发系列文章以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格式的数据字符串化,传给服务器。

    展开全文
  • 前言 ...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格式的数据字符串化,传给服务器。

    展开全文
  • 新的开始,新的知识。 在学习安卓的过程中,有一个是关于做石头剪刀布游戏的左右,我设法想实现人对人对战,正好想尝试一下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"
    

     

    展开全文
  • 自从HTML5出来以后,使用WebSocket通信就变得火热起来,基于WebSocket开发的手机APP和手机游戏也越来越多。我的一些开发APP的朋友,开始使用WebSocket通信,后来觉得通信不够安全,想要对通信进行加密,于是自然而然...
  • 自从HTML5出来以后,使用WebSocket通信就变得火热起来,基于WebSocket开发的手机APP和手机游戏也越来越多。我的一些开发APP的朋友,开始使用WebSocket通信,后来觉得通信不够安全,想要对通信进行加密,于是自然而然...
  • 自从HTML5出来以后,使用WebSocket通信就变得火热起来,基于WebSocket开发的手机APP和手机游戏也越来越多。我的一些开发APP的朋友,开始使用WebSocket通信,后来觉得通信不够安全,想要对通信进行加密,于是自然而然...
  • 结合WebSocket 来实现一个俄罗斯方块小游戏吧~WebSocket 介绍WebSocket 特点服务端实时通信的方法:AJAX轮询和Long Polling长轮询开始码代码吧,小游戏走起!搭建自己的WebSocket 效果图 WebSocket 介绍 webSocket...
  • 第一章:手把手搭建WebSocket多人在线聊天室(SpringBoot+WebSocket)[WebSocket]第二章:WebSocket集群分布式改造——实现多人在线聊天室在本文中,我将介绍如何使用WebSocket向实时多人答题对战游戏提供服务端,...
  • 跟webservice来相比,Web Socket可以做到保持长连接,或者说强连接,一直握手存在两端可以互相发送消息互相收到消息,而webservice是一次性的,...一、WebSocket是HTML5出的,是一种协议,也就是说原版的HTTP协议没...
  • 由于公司项目中使用到websocket, 所以本人利用空余时间学习一番,如果本文中有说错的地方,还望指出! 首先要知道websocket 是什么 ? WebSocket使得客户端和服务器之间的数据交换...广播技术应用 简单websocket游戏
  • websocket

    2017-07-05 10:58:53
    websocket是Html5新增加特性之一,目的是浏览器与服务端建立全双工的通信方式,解决http请求-响应带来过多的资源消耗,同时对特殊场景应用提供了全新的实现方式,比如聊天、股票交易、游戏等对对实时性要求较高的...
  • 第一章:手把手搭建WebSocket多人在线聊天室(SpringBoot+WebSocket)[WebSocket]第二章:WebSocket集群分布式改造——实现多人在线聊天室在本文中,我将介绍如何使用WebSocket向实时多人答题对战游戏提供服务端,...
  • 系列教程回顾:手把手搭建WebSocket多人在线聊天室【多人聊天室】WebSocket集群/分布式改造在本文中,我将介绍如何使用WebSocket向实时多人答题对战游戏提供服务端,并详细介绍通接口的设计。 这是我在最近作业竞赛...
  • 大型RPG游戏,纯php解决方案,php + swoole + swoole_orm + zephir,这个游戏的战斗部分完全用zephir来实现,转化为php扩展,能做到同时兼顾性能和开发效率,(zephir代码有机会我再开源出来,目前时机不成熟,游戏...
  • 支持socket / webSocket接入,兼容手游/页游服务端架构 通信协议支持protobuf或java反射,为客户端提供多种选择 框架提供多种组件,可以直接二次开发业务逻辑 提供热更机制以及jmx接口,方便对生产项目进行监控与...
  • 系列教程回顾:在本文中,我将介绍如何使用WebSocket向实时多人答题对战游戏提供服务端,并详细介绍通接口的设计。这是我在最近作业竞赛中设计的小项目,和小伙伴们一起设计了整个游戏流程和后端代码,前端页面暂时...
  • 使用SpringBoot及Construct2的WebSocket制作联机游戏(一) 一、介绍: 1、SpringBoot服务端添加登录及注册接口,并进行Postman测试 2、SpringBoot服务端WebSocket对接收数据和返回数据进行数据封装,添加Json工具...
  • Java游戏服务器开发之二十四–WebSocket中添加ssl,支持wss协议 Java游戏服务器开发之二十四–WebSocket中添加ssl,支持wss协议 概述 服务端 客户端测试及问题 网页 netty客户端访问wss 改变的内容 新增 修改...
  • WebSocket协议

    2019-09-18 14:26:27
    WebSocket让我们可以在客户端和web服务端之间实现实时通信,不需要客户端发起请求,服务端就可以直接向客户端发送数据,目前大多数浏览器都支持WebSocket协议。由于这一特性,它能够应用到很多场景下,比如游戏,...
  • 本应用示例使用Coolpy7作为Mqtt服务器并启用Websocket代理完美支持高并发大流量即时通过能力,本示以即时通信聊天为为例。还可以应用到其他软件...WebSocket代理服务端 (Coolpy7_ws) Html5聊天室前端 安装并运...
  • 服务端:SpringBoot框架下的WebSocket实现 客户端:Construct2使用官方插件WebSocket实现 业务:连接、发送信息、接收信息 二、服务端实现 1、导入相关依赖 &lt;!-- ...
  • WebSocket 使得客户端和服务器之间的数据交换变得更加简单,允许服务端主动向客户端推送数据,当然也支持客户端发送数据到服务端。通常用来社交聊天、弹幕、多玩家游戏、协同编辑、股票基金实时报价、资讯自动更新等...
  • Web 应用的信息交互过程通常是客户端通过...比如说在线游戏、在线证券、设备监控、新闻在线播报、RSS 订阅推送等等,当客户端浏览器准备呈现这些信息的时候,这些信息在服务器端可能已经过时了。所以保持客户端和服
  • 系列教程回顾:手把手搭建WebSocket多人在线聊天室【多人聊天室】WebSocket集群/分布式改造在本文中,我将介绍如何使用WebSocket向实时多人答题对战游戏提供服务端,并详细介绍通接口的设计。 这是我在最近作业竞赛...
  • 还可以应用到其他软件应用如:网页客服系统、网站信息通知、网页即时通信系统、网页游戏等等技术应用架构简介系统架构包括:MQTT服务端程序(Coolpy7)WebSocket代理服务端 (Coolpy7_ws)Html5聊天室前端安装并运行运行...

空空如也

空空如也

1 2 3 4 5 ... 9
收藏数 174
精华内容 69
关键字:

websocket游戏服务端