WebSocket C# 服务端发送大数据,分包发送大数据 方法

参见协议

WebSocket数据帧结构如下图所示:
      0                   1                   2                   3
      0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
     +-+-+-+-+-------+-+-------------+-------------------------------+
     |F|R|R|R| opcode|M| Payload len |    Extended payload length    |
     |I|S|S|S|  (4)  |A|     (7)     |             (16/64)           |
     |N|V|V|V|       |S|             |   (if payload len==126/127)   |
     | |1|2|3|       |K|             |                               |
     +-+-+-+-+-------+-+-------------+ - - - - - - - - - - - - - - - +
     |     Extended payload length continued, if payload len == 127  |
     + - - - - - - - - - - - - - - - +-------------------------------+
     |                               |Masking-key, if MASK set to 1  |
     +-------------------------------+-------------------------------+
     | Masking-key (continued)       |          Payload Data         |
     +-------------------------------- - - - - - - - - - - - - - - - +
     :                     Payload Data continued ...                :
     + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +
     |                     Payload Data continued ...                |
     +---------------------------------------------------------------+

FIN:1位

表示这是消息的最后一帧(结束帧),一个消息由一个或多个数据帧构成。若消息由一帧构成,起始帧即结束帧。

RSV1,RSV2,RSV3:各1位

这里我翻译不好,大致意思是如果未定义扩展,各位是0;如果定义了扩展,即为非0值。如果接收的帧此处非0,扩展中却没有该值的定义,那么关闭连接。

OPCODE:4位

解释PayloadData,如果接收到未知的opcode,接收端必须关闭连接。

0x0表示附加数据帧

0x1表示文本数据帧

0x2表示二进制数据帧

0x3-7暂时无定义,为以后的非控制帧保留

0x8表示连接关闭

0x9表示ping

0xA表示pong

0xB-F暂时无定义,为以后的控制帧保留

MASK:1位

用于标识PayloadData是否经过掩码处理。如果是1,Masking-key域的数据即是掩码密钥,用于解码PayloadData。客户端发出的数据帧需要进行掩码处理,所以此位是1。

Payload length:7位,7+16位,7+64位
PayloadData的长度(以字节为单位)。
如果其值在0-125,则是payload的真实长度。
如果值是126,则后面2个字节形成的16位无符号整型数的值是payload的真实长度。注意,网络字节序,需要转换。
如果值是127,则后面8个字节形成的64位无符号整型数的值是payload的真实长度。注意,网络字节序,需要转换。
长度表示遵循一个原则,用最少的字节表示长度(我理解是尽量减少不必要的传输)。举例说,payload真实长度是124,在0-125之间,必须用前7位表示;不允许长度1是126或127,然后长度2是124,这样违反原则。

以上说明很重要.下面的代码就是按此原则设计

Payload长度是ExtensionData长度与ApplicationData长度之和。ExtensionData长度可能是0,这种情况下,Payload长度即是ApplicationData长度。

WebSocket协议规定数据通过帧序列传输。

客户端必须对其发送到服务器的所有帧进行掩码处理。

服务器一旦收到无掩码帧,将关闭连接。服务器可能发送一个状态码是1002(表示协议错误)的Close帧。

而服务器发送客户端的数据帧不做掩码处理,一旦客户端发现经过掩码处理的帧,将关闭连接。客户端可能使用状态码1002。

消息分片

分片目的是发送长度未知的消息。如果不分片发送,即一帧,就需要缓存整个消息,计算其长度,构建frame并发送;使用分片的话,可使用一个大小合适的buffer,用消息内容填充buffer,填满即发送出去。

分片规则:

1.一个未分片的消息只有一帧(FIN为1,opcode非0)

2.一个分片的消息由起始帧(FIN为0,opcode非0),若干(0个或多个)帧(FIN为0,opcode为0),结束帧(FIN为1,opcode为0)。

3.控制帧可以出现在分片消息中间,但控制帧本身不允许分片。

4.分片消息必须按次序逐帧发送。

5.如果未协商扩展的情况下,两个分片消息的帧之间不允许交错。

6.能够处理存在于分片消息帧之间的控制帧

7.发送端为非控制消息构建长度任意的分片

8.client和server兼容接收分片消息与非分片消息

9.控制帧不允许分片,中间媒介不允许改变分片结构(即为控制帧分片)

10.如果使用保留位,中间媒介不知道其值表示的含义,那么中间媒介不允许改变消息的分片结构

11.如果协商扩展,中间媒介不知道,那么中间媒介不允许改变消息的分片结构,同样地,如果中间媒介不了解一个连接的握手信息,也不允许改变该连接的消息的分片结构

12.由于上述规则,一个消息的所有分片是同一数据类型(由第一个分片的opcode定义)的数据。因为控制帧不允许分片,所以一个消息的所有分片的数据类型是文本、二进制、opcode保留类型中的一种。

需要注意的是,如果控制帧不允许夹杂在一个消息的分片之间,延迟会较大,比如说当前正在传输一个较大的消息,此时的ping必须等待消息传输完成,才能发送出去,会导致较大的延迟。为了避免类似问题,需要允许控制帧夹杂在消息分片之间。

部分调试解析消息头代码如下:

#if RFC6455调试代码
                    #region 调试代码
                    if (receiveBuffer.Length > 2)
                    {

                        byte bt1 = receiveBuffer[0];
                        byte bt2 = receiveBuffer[1];
                        int Opcode = ((bt1 >> 4) & 0xF);

                        Console.WriteLine(string.Format("FIN:{0},RSV1:{1} RSV2:{2} RSV3:{3};Opcode:{4},mask:{5};len:{6}"
                            , bt1 >> 7 & 1
                            , (bt1 >> 6) & 1
                            , (bt1 >> 5) & 1
                            , (bt1 >> 4) & 1
                            , (bt1 & 0xF).ToString("X2")
                            ///////第二字节
                            , bt2 >> 7 & 1 //mask
                            , bt2 & 0x7f //Payload len   ==((byte)(bt2<<1)>>1)
                            ));
                        //FIN:1位,用来表明这是一个消息的最后的消息片断,当然第一个消息片断也可能是最后的一个消息片断
                        //RSV1, RSV2, RSV3: 分别都是1位,如果双方之间没有约定自定义协议,那么这几位的值都必须为0,否则必须断掉WebSocket连接
                        //Opcode:4位操作码,定义有效负载数据,如果收到了一个未知的操作码,连接也必须断掉,以下是定义的操作码:
                        //*  %x0 表示连续消息片断
                        //*  %x1 表示文本消息片断
                        //*  %x2 表未二进制消息片断
                        //*  %x3-7 为将来的非控制消息片断保留的操作码
                        //*  %x8 表示连接关闭
                        //*  %x9 表示心跳检查的ping
                        //*  %xA 表示心跳检查的pong
                        //*  %xB-F 为将来的控制消息片断的保留操作码
                    }
                    #endregion
#endif

发送方法代码如下:

        /// <summary>
        /// WebSocket Send 发送数据到客户端,打包服务器数据
        /// </summary>
        /// <param name="bytes">要发送的数据</param>
        /// <param name="sendMax">每次发送的最大数据包</param>

        public void Send(Byte[] bytes,int sendMax=65536)
        {
            bool canSend = true;
            //每次最大发送 64Kb的数据
            int SendMax = sendMax;

            int num = 0;
            //已经发送的字节数据
            int taked = 0;
            while (canSend)
            {
                //内容数据
                byte[] contentBytes = null;
                var sendArr = bytes.Skip(num * SendMax).Take(SendMax).ToArray();
                taked += sendArr.Length;
                if (sendArr.Length > 0)
                {
                    //是否可以继续发送
                    canSend = bytes.Length > taked;
                    if (sendArr.Length < 126)
                    {
                        #region 一次发送小于126的数据
                        contentBytes = new byte[sendArr.Length + 2];
                        contentBytes[0] = (byte)(num == 0 ? 0x81 : (!canSend ? 0x80 : 0));
                        contentBytes[1] = (byte)sendArr.Length;
                        Array.Copy(sendArr, 0, contentBytes, 2, sendArr.Length);
                        canSend = false;
                        #endregion

                    }
                    else if (sendArr.Length < 0xFFFF)
                    {
                        #region 发送小于65535的数据
                        contentBytes = new byte[sendArr.Length + 4];
                        //首次不分片发送,大于128字节的数据一次发完
                        if (!canSend && num == 0)
                        {
                            contentBytes[0] = 0x81;
                        }
                        else
                        {
                            //一个分片的消息由起始帧(FIN为0,opcode非0),若干(0个或多个)帧(FIN为0,opcode为0),结束帧(FIN为1,opcode为0)。
                            contentBytes[0] = (byte)(num == 0 ? 0x01 : (!canSend ? 0x80 : 0));
                        }
                        contentBytes[1] = 126;
                        byte[] ushortlen = BitConverter.GetBytes((short)sendArr.Length);
                        contentBytes[2] = ushortlen[1];
                        contentBytes[3] = ushortlen[0];
                        Array.Copy(sendArr, 0, contentBytes, 4, sendArr.Length);
                        #endregion
                    }
                    else if (sendArr.LongLength < long.MaxValue)
                    {
                        #region 一次发送所有数据
                        //long数据一次发完
                        contentBytes = new byte[sendArr.Length + 10];
                        //首次不分片发送,大于128字节的数据一次发完
                        if (!canSend && num == 0)
                        {
                            contentBytes[0] = 0x81;
                        }
                        else
                        {
                            //一个分片的消息由起始帧(FIN为0,opcode非0),若干(0个或多个)帧(FIN为0,opcode为0),结束帧(FIN为1,opcode为0)。
                            contentBytes[0] = (byte)(num == 0 ? 0x01 : (!canSend ? 0x80 : 0));
                        }
                        contentBytes[1] = 127;
                        byte[] ulonglen = BitConverter.GetBytes((long)sendArr.Length);
                        contentBytes[2] = ulonglen[7];
                        contentBytes[3] = ulonglen[6];
                        contentBytes[4] = ulonglen[5];
                        contentBytes[5] = ulonglen[4];
                        contentBytes[6] = ulonglen[3];
                        contentBytes[7] = ulonglen[2];
                        contentBytes[8] = ulonglen[1];
                        contentBytes[9] = ulonglen[0];

                        Array.Copy(sendArr, 0, contentBytes, 10, sendArr.Length);
                        #endregion
                    }
                }

                try
                {
                    if (contentBytes != null)
                    {
                        Socket.Send(contentBytes);
                    }

                }
                catch (NullReferenceException)
                {
                    break;
                }
                catch (System.Net.Sockets.SocketException)
                {

                    break;
                }
                catch (ObjectDisposedException)
                {
                    break;
                }
                finally
                {
                    num++;
                }

            }
        }
时间: 2024-10-13 20:20:58

WebSocket C# 服务端发送大数据,分包发送大数据 方法的相关文章

xcode中的udp发送文件函数,分包发送每包8000个字节

-(void) UDP_SendFile:(NSString *) filename; { NSString * filepath=[NSTemporaryDirectory() stringByAppendingString:filename]; NSData * sdata=[[NSData alloc] initWithContentsOfFile:filepath]; Byte *sarr; int sumpak,curpak; //分包发送 int total=sdata.length

WebSocket 实现服务端给客户端推送消息

目录 代码发布 应用场景 ajax 操作 队列 递归 如何实现服务端主动给客户端推送消息的效果 长轮询(兼容性好) websocker(主流浏览器都支持) 代码验证(了解) 代码发布 服务端主动给客户端推送消息 截至目前为止,我们所写的 web 项目基本都是基于 HTTP 协议的 HTTP 协议有四大特性:无链接 基于 HTTP 协议实现服务端主动给客户端推送消息好像有点麻烦--- 我们都经历过,浏览器打开一个网站不动,网站过一会儿自动弹出消息 再比如网页版本的微信和 qq,我们所有人创建一个群

C# Socket服务端与客户端通信(包含大文件的断点传输)

步骤: 一.服务端的建立 1.服务端的项目建立以及页面布局 2.各功能按键的事件代码 1)传输类型说明以及全局变量 2)Socket通信服务端具体步骤:   (1)建立一个Socket   (2)接收信息   (3)发送数据(这里分发送字符串.文件(包含大文件).震动) 二.客户端的建立 1.服务端的项目建立以及页面布局 2.各功能按键的事件代码 1)传输类型说明以及全局变量 2)Socket通信服务端具体步骤:   (1)建立一个Socket   (2)接收信息   (3)发送数据(这里分发送

springboot 项目==基于websocket的服务端推送消息。

1.创建springboot项目,首先我们还是先引入依赖 <!-- webSocket begin--><dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-websocket</artifactId></dependency><!-- webSocket end--> 2.创建配置类 

简析服务端通过GT导入SHP至PG的方法

文章版权由作者李晓晖和博客园共有,若转载请于明显处标明出处:http://www.cnblogs.com/naaoveGIS/ 1.背景 项目中需要在浏览器端直接上传SHP后服务端进行数据的自动入PG库以及发布至geoserver.本方法是以geotools为开发工具实现入库,以geoserver manager来实现服务的自动发布.这里着重描述geotools编写SHP入库的方法. 2.Geotools介绍 2.1总体介绍 Geotools是Java语言编写的开源GIS工具包,其功能涵盖了地理

websocket在服务端获取客户端IP

https://imxieyi.com/2016/09/18/tomcat-websocket%E6%9C%8D%E5%8A%A1%E5%99%A8%E8%8E%B7%E5%8F%96%E5%AE%A2%E6%88%B7%E7%AB%AFip%E5%9C%B0%E5%9D%80/ https://blog.csdn.net/qq_42894258/article/details/89950459?depth_1-utm_source=distribute.pc_relevant.none-tas

初识websocket及java服务端的简单实现

概念:WebSocket是一种在单个TCP连接上进行全双工通信的协议. WebSocket使得客户端和服务器之间的数据交换变得更加简单,允许服务端主动向客户端推送数据.在WebSocket API中,浏览器和服务器只需要完成一次握手,两者之间就直接可以创建持久性的连接,并进行双向数据传输 背景:很多网站为了实现推送技术,所用的技术都是轮询.轮询是在特定的的时间间隔(如每1秒),由浏览器对服务器发出HTTP请求,然后由服务器返回最新的数据给客户端的浏览器.这种传统的模式带来很明显的缺点,即浏览器需

springboot整合websocket实现客户端与服务端通信

定义 ?WebSocket是通过单个TCP连接提供全双工(双向通信)通信信道的计算机通信协议.此WebSocket API可在用户的浏览器和服务器之间进行双向通信.用户可以向服务器发送消息并接收事件驱动的响应,而无需轮询服务器. 它可以让多个用户连接到同一个实时服务器,并通过API进行通信并立即获得响应. 案例介绍 ? 后端在接收到用户新下的订单后,通知到后台系统 服务端代码 pom.xml <dependency> <groupId>org.springframework.boo

用nodejs快速实现websocket服务端(带SSL证书生成)

有不少公司将nodejs的socket.io作为websocket的解决方案,很遗憾的是socket.io是对websocket的封装,并不支持html5原始的websocket协议,微信小程序使用的websocket却是接近于html5原始websocket协议,socket.io居然没有用武之地了. 当然情况也没有惨到需要你自己一步一步去实现websocket服务端,我们的大node有很多websocket库,ws就是其中一个,号称最轻量级,最快.ws的用法比较简单,直接看github说明(