关于VB版之前已经写了,有需要可以进传送门《VB封装的WebSocket模块,拿来即用》两个使用都差不多,这里简单概述一下:

连接完成后,没有握手就用Handshake()先完成握手
之后接收数据,先用AnalyzeHeader()得到数据帧结构(DataFrame)
然后再用PickDataV()PickData()得到源数据,对于掩码数据是在这里反掩码
关于发送数据,则是:
服务端发送无需掩码用PackData()将数据组装一下就可以发送
而模拟客户端向服务器的发送需要加掩码,用PackMaskData()

相关资料下载:《WebSocket协议中文版.pdf》
*等有时间我再做个demo供下载吧,这个类使用还算简单

WebSocketProtocol10.cs

 using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Globalization;
using System.Runtime.InteropServices;
using System.Security.Cryptography;
using System.Text;
/*
* 详见<5.2 基本帧协议>和<11.8.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 ... |
+---------------------------------------------------------------+
*/
//2017-06-17
//By: 悠悠然
//QQ: 2860898817
//E-mail: ur1986@foxmail.com
namespace WebSocketProtocol10
{
/// <summary>
/// Opcode操作码是一个在 0 到 15(包括)之间的整数数字。
/// </summary>
public enum OpcodeType:byte
{
Contin = 0x0, //表示连续消息片断
Text = 0x1, //表示文本消息片断
Binary = 0x2, //表未二进制消息片断
// 0x3 - 0x7 非控制帧保留
Close = 0x8, //表示连接关闭
Ping = 0x9, //表示心跳检查的ping
Pong = 0xA, //表示心跳检查的pong
// 0xB - 0xF 控制帧保留
Unkown
};
/// <summary>
/// 数据帧头,就是包头结构
/// </summary>
public struct DataFrame
{
/// <summary>0表示不是当前消息的最后一帧,后面还有消息,1表示这是当前消息的最后一帧;</summary>
public bool FIN;
/// <summary>1位,若没有自定义协议,必须为0,否则必须断开.</summary>
public bool RSV1;
/// <summary>1位,若没有自定义协议,必须为0,否则必须断开.</summary>
public bool RSV2;
/// <summary>1位,若没有自定义协议,必须为0,否则必须断开.</summary>
public bool RSV3;
/// <summary>4位操作码,定义有效负载数据,如果收到了一个未知的操作码,连接必须断开.</summary>
public OpcodeType Opcode;
/// <summary>1位,定义传输的数据是否有加掩码,如果有掩码则存放在MaskingKey</summary>
public bool MASK;
/// <summary>0或4个字节,客户端发送给服务端的数据,都是通过内嵌的一个32位值作为掩码的;掩码键只有在掩码位设置为1的时候存在。</summary>
[MarshalAs(UnmanagedType.ByValArray, SizeConst = )]
public byte[] MaskingKey;
/// <summary>传输数据的长度</summary>
public int Payloadlen;
/// <summary>数据起始位</summary>
public int DataOffset;
} public class WSProtocol
{
#region 握手
/// <summary>
/// 获取连接请求附带的参数
/// </summary>
public static NameValueCollection QueryString(byte[] recv)
{
//前端js如: ws = new WebSocket("ws://127.0.0.1:8899/ws?id=1&session=a1b2c3")
//该函数相当于ASP.NET中的Request.QueryString,就是取得参数 id 和 session 的
NameValueCollection NV = new NameValueCollection();
string n = string.Empty;
string v = string.Empty;
bool tf1 = false;
bool tf2 = false;
foreach (byte b in recv)
{
if (tf1)
{
if (b == )
break;
else if (b == && tf2 == false)//=
tf2 = true;
else if (b == )//&
{
tf2 = false;
if (!string.IsNullOrEmpty(n) && !string.IsNullOrEmpty(v))
NV.Add(n, v);
n = string.Empty;
v = string.Empty;
}
else
{
if (tf2)
v += (char)b;
else
n += (char)b;
}
}
else if (b == )//?
tf1 = true;
else if (b == || b == ) break;
}
if (!string.IsNullOrEmpty(n) && !string.IsNullOrEmpty(v))
NV.Add(n, v);
return NV;
}
public static byte[] Handshake(string request)
{
string webSocketKey = getCilentWSKey(request, "Sec-WebSocket-Key:");
string acceptKey = produceAcceptKey(webSocketKey);
StringBuilder response = new StringBuilder(); //响应串
response.Append("HTTP/1.1 101 Web Socket Protocol Handshake\r\n");
response.Append("Upgrade: WebSocket\r\n");
response.Append("Connection: Upgrade\r\n");
response.AppendFormat("Sec-WebSocket-Accept: {0}\r\n", acceptKey);
response.AppendFormat("WebSocket-Origin: {0}\r\n", getCilentWSKey(request, "Sec-WebSocket-Origin"));
response.AppendFormat("WebSocket-Location: {0}\r\n", getCilentWSKey(request, "Host"));
response.Append("\r\n");
return Encoding.UTF8.GetBytes(response.ToString());
}
private static string getCilentWSKey(string request, string kname)
{
int i = CultureInfo.InvariantCulture.CompareInfo.IndexOf(request, kname, CompareOptions.IgnoreCase);
if (i > )
{
i += kname.Length;
int j = request.IndexOf("\r\n", i);
if (j > )
return request.Substring(i, j - i).Trim();
}
return string.Empty;
}
// 根据Sec-WebSocket-Key和MagicKey生成AcceptKey
private static string produceAcceptKey(string webSocketKey)
{
string MagicKey = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
Byte[] acceptKey = SHA1.Create().ComputeHash(Encoding.ASCII.GetBytes(webSocketKey + MagicKey));
return Convert.ToBase64String(acceptKey);
}
#endregion
#region 数据帧解析
/// <summary>
/// 数据帧头的解析
/// </summary>
public static DataFrame AnalyzeHeader(byte[] data)
{
DataFrame df;
df.FIN = (data[] & 0x80) == 0x80 ? true : false;
df.RSV1 = (data[] & 0x40) == 0x40 ? true : false;
df.RSV2 = (data[] & 0x40) == 0x20 ? true : false;
df.RSV3 = (data[] & 0x40) == 0x10 ? true : false;
byte[] b = { data[] };
BitArray bit = new BitArray(b);
bit.Set(, false);
bit.Set(, false);
bit.Set(, false);
bit.Set(, false);
bit.CopyTo(b, );
df.Opcode = (OpcodeType)b[]; df.MASK = (data[] & 0x80) == 0x80 ? true : false;
df.MaskingKey = new Byte[];
int len = data[] & 0x7F;
/// 0-125 表示传输数据的长度;
/// 126 表示随后的两个字节是一个16进制无符号数,用来表示传输数据的长度;
/// 127 表示随后的是8个字节是一个64位无符合数,这个数用来表示传输数据的长度。
/// 多字节长度的数量是以网络字节的顺序表示。负载数据的长度为扩展数据及应用数据之和,扩展数据的长度可能为0,因而此时负载数据的长度就为应用数据的长度。
switch (len)
{
case :
df.Payloadlen = (UInt16)(data[] << | data[]);
if(df.MASK)
{
Buffer.BlockCopy(data, , df.MaskingKey, , );
df.DataOffset = ;
}else
df.DataOffset = ;
break;
case :
Byte[] byteLen = new Byte[];
Buffer.BlockCopy(data, , byteLen, , );
df.Payloadlen = (int)BitConverter.ToUInt64(byteLen, );
if (df.MASK)
{
Buffer.BlockCopy(data, , df.MaskingKey, , );
df.DataOffset = ;
}
else
df.DataOffset = ;
break;
default:
if (len < )
{
df.Payloadlen = len;
if (df.MASK)
{
Buffer.BlockCopy(data, , df.MaskingKey, , );
df.DataOffset = ;
}
else
df.DataOffset = ;
}
else
{
df.Payloadlen = ;
df.DataOffset = ;
}
break;
}
return df;
}
#endregion
#region 处理数据--接收
/*
* PickDataV 方法是出于性能的考虑,用于有时数据只是为了接收,做一些逻辑判断,并不需要对数据块进行单独提炼
* PickData 有两个重载就不赘述了...
*/
/// <summary>
/// 如果数据存在掩码就反掩码,具体的使用数据就
/// </summary>
public static void PickDataV(byte[] data, DataFrame dtype)
{
if (dtype.MASK)
{
int j = ;
for (int i = dtype.DataOffset; i < dtype.DataOffset + dtype.Payloadlen; i++)
{
data[i] ^= dtype.MaskingKey[j++];
if (j == )
j = ;
}
}
}
public static byte[] PickData(byte[] data, DataFrame dtype)
{
byte[] byt = new byte[dtype.Payloadlen];
PickDataV(data, dtype);
Buffer.BlockCopy(data, dtype.DataOffset, byt, , dtpye.Payloadlen);
return byt;
}
public static string PickData(byte[] data,DataFrame dtype,Encoding encode)
{
PickDataV(data, dtype);
return encode.GetString(data, dtype.DataOffset, dtype.Payloadlen);
}
#endregion
#region 处理数据--发送
/*
* PackData 两个重载,用于组装无掩码数据
* PackMaskData 两个重载,用于将数据掩码后组装
*/
/// <summary>
/// 组装无掩码数据,一般用于服务端向客户端发送
/// </summary>
public static byte[] PackData(string data, Encoding encode, OpcodeType dwOpcode = OpcodeType.Text)
{
//字符默认用UTF8编码处理,如果有别的编码需求,自行处理后用下面的重载函数
byte[] buff = encode.GetBytes(data);
return PackData(buff, dwOpcode);
}
public static byte[] PackData(byte[] buff, OpcodeType dwOpcode = OpcodeType.Text)
{
List<byte> byt = new List<byte>();
byt.Add((byte)(0x80 | (byte)dwOpcode));
if (buff.Length < )
byt.Add((byte)buff.Length);
else if (buff.Length <= ushort.MaxValue)
{
ushort l = (ushort)buff.Length;
byte[] bl = BitConverter.GetBytes(l);
byt.Add(0x7e);
byt.Add(bl[]);
byt.Add(bl[]);
}
else
{
//由于用不到,未做测试
ulong l = (ulong)buff.Length;
byt.Add(0x7f);
byt.AddRange(BitConverter.GetBytes(l));
}
byt.AddRange(buff);
return byt.ToArray();
} /// <summary>
/// 将数据掩码后组装,一般是客户端向服务端发送
/// </summary>
public static byte[] PackMaskData(string str, Encoding encode, OpcodeType dwOpcode = OpcodeType.Text)
{
byte[] byt = encode.GetBytes(str);
return PackMaskData(byt, dwOpcode);
}
public static byte[] PackMaskData(byte[] byt, OpcodeType dwOpcode = OpcodeType.Text)
{
List<byte> data = new List<byte>();
//掩码我用的是固定值,有需要也可以自己做成随机的
byte[] maskey ={ , , , };
int j = ;
for (int i = ; i < byt.Length; i++)
{
data[i] ^= maskey[j++];
if (j > ) j = ;
}
data.Add((byte)(0x80 | (byte)OpcodeType.Text));//第一字节,FIN+RSV1+RSV2+RSV3+opcode
if (byt.Length < )
{
data.Add((Byte)(0x80 | (Byte)byt.Length));
}
else if (byt.Length <= ushort.MaxValue)//
{
data.Add();//固定 掩码位+126
byte[] b=BitConverter.GetBytes((ushort)byt.Length);
data.Add(b[]);//反转
data.Add(b[]);
}
else
{
//这部分没有经过实际测试,依靠协议文档推写出来的
//我的需求只是聊天通信,若有传送文件等需求,请自行测试
data.Add();//固定 掩码位+127
byte[] b = BitConverter.GetBytes((ulong)byt.Length);
Array.Reverse(b);//反转
data.AddRange(b);
}
data.AddRange(byt);
data.AddRange(maskey);
return data.ToArray();
}
#endregion
#region 常用控制帧
/*
下面的 Ping、Pong、Close 是非掩码信号,用于服务端向客户端发送,如果客户端想服务端发送就需要掩码
使用的时候直接 socket.Send(WSProtocol.PingFrame, 0, WSProtocol.PingFrame.Length);
我用的0长度,其实是可以包含数据的,但是附带数据客户端处理又麻烦了 * 如果有附带信息的需求,也可以用上面[发送]里的函数,可选参数指定OpcodeType
* 特别注意:收到ping的时候,回应pong.而收到pong的时候,回应还是pong
* 在协议里,ping是最为要求应答信号的,而pong是作为单向心跳检测的
*/
private static byte[] dwPing = { 0x89, 0x0 };
private static byte[] dwPong = { 0x8a, 0x0 };
private static byte[] dwClose = { 0x88, 0x0 };
public static byte[] PingFrame { get { return dwPing; } }
public static byte[] PongFrame { get { return dwPong; } }
public static byte[] CloseFrame { get { return dwClose; } }
#endregion
}
}

C#封装的websocket协议类的更多相关文章

  1. Python 基于urllib.request封装http协议类

    基于urllib.request封装http协议类 by:授客QQ:1033553122 测试环境: Python版本:Python 3.3   代码实践 #!/usr/bin/env python ...

  2. Websocket协议之php实现

    前面学习了HTML5中websocket的握手协议.打开和关闭连接等基础内容,最近用php实现了与浏览器websocket的双向通信.在学习概念的时候觉得看懂了的内容,真正在实践过程中还是会遇到各种问 ...

  3. 基于Socket通讯(C#)和WebSocket协议(net)编写的两种聊天功能(文末附源码下载地址)

    今天我们来盘一盘Socket通讯和WebSocket协议在即时通讯的小应用——聊天. 理论大家估计都知道得差不多了,小编也通过查阅各种资料对理论知识进行了充电,发现好多demo似懂非懂,拷贝回来又运行 ...

  4. PHP swoole websocket协议上机指南

    这一上机实验的开发环境用的是jetbrain公司的phpstorm 上级步骤如下: 创建websocket服务端 <?php $server = ); function onopen($serv ...

  5. Cocos2d-X网络编程(3) Cocos2d中的网络通信协议——WebSocket协议

    WebSocket protocol 是HTML5一种新的协议.它实现了浏览器与服务器全双工通信.实现浏览器与服务器的即时通讯.即服务器也能主动向客户端发消息. WebSocket代理类和方法: co ...

  6. WebSocket协议详解及应用

    WebSocket协议详解及应用(七)-WebSocket协议关闭帧 本篇介绍WebSocket协议的关闭帧,包括客户端及服务器如何发送并处理关闭帧.关闭帧错误码及错误处理方法.本篇内容主要翻译自RF ...

  7. WebSocket协议开发

    一直以来,网络在很大程度上都是围绕着HTTP的请求/响应模式而构建的.客户端加载一个网页,然后直到用户点击下一页之前,什么都不会发生.在2005年左右,Ajax开始让网络变得更加动态了.但所有的HTT ...

  8. Websocket协议数据帧传输和关闭连接

    之前总结了关于Websocket协议的握手连接方式等其他细节,现在对socket连接建立后的数据帧传输和关闭细节总结. 一.数据帧格式 数据传输使用的是一系列数据帧,出于安全考虑和避免网络截获,客户端 ...

  9. WebSocket协议

    websocket 简介 (2013-04-09 15:39:28) 转载▼   分类: websocket 一 WebSocket是html5新增加的一种通信协议,目前流行的浏览器都支持这个协议,例 ...

随机推荐

  1. WordPress Plugin Contact Form Builder [CSRF → LFI]

    # Exploit Title: Contact Form Builder [CSRF → LFI]# Date: 2019-03-17# Exploit Author: Panagiotis Vag ...

  2. vue keep-alive内置缓存组件

    1.当组件在keep-alive被切换时将会执行activeted和deactiveted两个生命周期 2.inlude 正则表达式或字符串 ,只有符合条件的组件会被缓存 exclude正则表达式或字 ...

  3. 动态渲染页面爬取-Selenium & Splash

    模拟浏览器的动机 JS动态渲染的页面不止Ajax一种 很多网页的Ajax接口含有加密参数,分析其规律的成本过高 通过对浏览器运行方式的模拟,我们将做到:可见即可爬 Python中常用的模拟浏览器运行的 ...

  4. OnePlus5刷机后一直检查更新

    大概是由于爱折腾,上一个手机是Nexus5,现在又是Oneplus5,闲来无事就爱刷机. 昨天看OnePlus官网的氧OS更新到Android9.0,于是又开启了刷机旅程. 显然这次没有之前那么顺利, ...

  5. Mac环境下Scrapy的安装

    直接命令安装: $ easy_install scrapy 从 GitHub 安装: $ git clonehttps://github.com/scrapy/scrapy.git $ cd scra ...

  6. Maven - pom中的<repository> <pluginRepositories>

    总结: <repository> 允许我们可以在POM中配置其它的远程仓库.这样做的原因有很多,比如你有一个局域网的远程仓库,使用该仓库能大大提高下载速度,继而提高构建速度,也有可能你依赖 ...

  7. 【转】一文掌握 Linux 性能分析之 I/O 篇

    [转]一文掌握 Linux 性能分析之 I/O 篇 这是 Linux 性能分析系列的第三篇,前两篇分别讲了 CPU 和 内存,本篇来看 IO. IO 和 存储密切相关,存储可以概括为磁盘,内存,缓存, ...

  8. Linux Input子系统浅析(二)-- 模拟tp上报键值【转】

    转自:https://blog.csdn.net/xiaopangzi313/article/details/52383226 版权声明:本文为博主原创文章,未经博主允许不得转载. https://b ...

  9. nginx,作为前端的你会多少?

    --现在阅读的你,如果是个FE,相信你不是个纯切图仔.反之,如果是,该进阶了,老铁! 前端的我们,已经不仅仅是做页面,写样式了,我们还需要会做相关的服务器部署.废话不多说,下面就从前端的角度来讲以下n ...

  10. .net core 接口返回图片并且进行压缩

    背景:  .net core 中默认已经取消可以直接访问图片,因为这样不安全. 导致我们上传的图片无法直接通过url访问. 解决方案:  一: 通过修改项目配置,使可以直接通过url访问.(方法略,可 ...