C# 实现包装tcp/ip 为websocket 服务器传输图片

C# 实现包装tcp/ip 为websocket 服务器传输图片

介绍

要求:web客户端,c#服务器
实现服务器单向给客户端传输图片。
网上找了一下,可以用websocketSharp实现,但是引入第三方库需要架构组审批之类的,总之不太好。
我想想试试用tcp/ip 包装成websocket 进行传输图片,结果还是踩了不少坑的。

包装

websocket 是建立在tcp/ip上的,
那么,只要我知道这两者之间有哪些区别,就好办了!
here we go~

  1. step 1 升级协议

客户端先打开一个TCP连接,随后再发起升级协商,升级为websocket。
客户端发送的升级协议为:

GET /socket HTTP/1.1 // 请求的方法必须是GET,HTTP版本必须至少是1.1
 Host: thirdparty.com
 Origin: Example Domain
 Connection: Upgrade 
 Upgrade: websocket // 请求升级到WebSocket 协议
 Sec-WebSocket-Version: 13 // 客户端使用的WebSocket 协议版本
 Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ== // 自动生成的键,以验证服务器对协议的支持,其值必须是nonce组成的随机选择的16字节的被base64编码后的值
 Sec-WebSocket-Protocol: appProtocol, appProtocol-v2 // 可选的应用指定的子协议列表
 Sec-WebSocket-Extensions: x-webkit-deflate-message, x-custom-extension // 可选的客户端支持的协议扩展列表,指示了客户端希望使用的协议级别的扩展

其中 Sec-WebSocket-Key: 客户端用来验证服务器支持请求的键,服务器必须加密该值后返回。
服务器回应客户端的升级请求:

HTTP/1.1 101 Switching Protocols // 101 响应码确认升级到WebSocket 协议
 Upgrade: websocket
 Connection: Upgrade
 Access-Control-Allow-Origin: Example Domain // CORS 首部表示选择同意跨源连接
 Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo= // 签名的键值验证协议支持
 Sec-WebSocket-Protocol: appProtocol-v2 // 服务器选择的应用子协议
 Sec-WebSocket-Extensions: x-custom-extension // 服务器选择的WebSocket 扩展

其中Sec-WebSocket-Accept 是将 客户端中Sec-WebSocket-Key加密哈希的新值。
加密方法如下:

  1. 将Sec-WebSocket-Key 拼接 ”258EAFA5-E914-47DA-95CA-C5AB0DC85B11” 为新字符串
  2. 对新字符串进行哈希
  3. 再进行base64-encode

ok
我的思路:
当tcp服务器监听到客户端升级websocket请求后,发送一个包装成websocket应答格式的包给客户端,建立连接!

这一步,服务器包装应答的C#代码为:

private static byte[] PackHandShakeData(string secKeyAccept)
  {
   var responseBuilder = new StringBuilder();
   responseBuilder.Append("HTTP/1.1 101 Switching Protocols" + Environment.NewLine);
   responseBuilder.Append("Upgrade: websocket" + Environment.NewLine);
   responseBuilder.Append("Connection: Upgrade" + Environment.NewLine);
   responseBuilder.Append("Sec-WebSocket-Accept: " + secKeyAccept + Environment.NewLine + Environment.NewLine);  
   return Encoding.UTF8.GetBytes(responseBuilder.ToString());
  }

加密key:

private static string GetSecKeyAccetp(byte[] handShakeBytes, int bytesLength)
 {
  string handShakeText = Encoding.UTF8.GetString(handShakeBytes, 0, bytesLength);
  string key = string.Empty;
  Regex r = new Regex(@"Sec\-WebSocket\-Key:(.*?)\r\n");
  Match m = r.Match(handShakeText);
  if (m.Groups.Count != 0)
  {
   key = Regex.Replace(m.Value, @"Sec\-WebSocket\-Key:(.*?)\r\n", "$1").Trim();
  }
  byte[] encryptionString = SHA1.Create().ComputeHash(Encoding.ASCII.GetBytes(key + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"));
  return Convert.ToBase64String(encryptionString);
 }

2.数据包装
建立连接后就开始传输数据了!
websocket数据格式:
在这里插入图片描述

WebSocket 数据二进制格式:

  • FIN: 1 bit 。表示此帧是否是消息的最后帧,第一帧也可能是最后帧。
  • RSV1,RSV2,RSV3: 各1 bit 。必须是0,除非协商了扩展定义了非0的意义。
  • opcode:4 bit。表示被传输帧的类型:x0 表示一个后续帧;x1 表示一个文本帧;x2 表示一个二进制帧;x3-7 为以后的非控制帧保留;x8 表示一个连接关闭;x9 表示一个ping;xA 表示一个pong;xB-F 为以后的控制帧保留。
  • Mask: 1 bit。表示净荷是否有掩码(只适用于客户端发送给服务器的消息)。
  • Payload length: 7 bit, 7 + 16 bit, 7 + 64 bit。 净荷长度由可变长度字段表示: 如果是 0~125,就是净荷长度;如果是 126,则接下来 2 字节表示的 16 位无符号整数才是这一帧的长度; 如果是 127,则接下来 8 字节表示的 64 位无符号整数才是这一帧的长度。

关键的来了!

opcode!

要实现的是传输图片 ,也就是二进制帧,所以是x2!

(之前传图片一直不成功就是在这里)

聪明的你应该知道了 我们数据包(假设叫它)byte data[]
第一行应该是:

date[0] = 0x82

0x82二进制为1000 0010
1表示最后帧
10 为 x2 表示二进制

对应的C#代码为:

private static byte[] PackDataBytes(byte[] bytes)
  {
   byte[] contentBytes = null;
   byte[] temp = bytes;
   //byte[] temp =message;

   if (temp.Length < 126)
   {
    contentBytes = new byte[temp.Length + 2];
    contentBytes[0] = 0x82;
    contentBytes[1] = (byte)temp.Length;
    Array.Copy(temp, 0, contentBytes, 2, temp.Length);
   }		  
   else if (temp.Length < 0xFFFF)
   {
    contentBytes = new byte[temp.Length + 4];
    contentBytes[0] = 0x82;
    contentBytes[1] = 126;
    contentBytes[2] = (byte)(temp.Length & 0xFF);
    contentBytes[3] = (byte)(temp.Length >> 8 & 0xFF);
    Array.Copy(temp, 0, contentBytes, 4, temp.Length);
   }
   else
   {
    contentBytes = new byte[temp.Length + 10];
    contentBytes[0] = 0x82;
    contentBytes[1] = 127;
    contentBytes[2] = 0;
    contentBytes[3] = 0;
    contentBytes[4] = 0;
    contentBytes[5] = 0;
    contentBytes[6] = (byte)(temp.Length >> 24);
    contentBytes[7] = (byte)(temp.Length >> 16);
    contentBytes[8] = (byte)(temp.Length >> 8);
    contentBytes[9] = (byte)(temp.Length & 0xFF);
    Array.Copy(temp, 0, contentBytes, 10, temp.Length);
   } 
   return contentBytes;

想要传字符串的话0x81就行

ok,搞定!

实现C#服务器代码


class WebsocketImageWrapper
 {
  private static readonly Logger Logger = LogManager.GetCurrentClassLogger();
  Thread threadWatch = null;
  Socket socketWatch = null;
  int countClient = 0;
  Dictionary<int, Socket> ClientConnectionItems = new Dictionary<int , Socket> { };
  /// <summary>
  /// start a tcp/ip server 
  /// </summary>
  public WebsocketImageWrapper(IPEndPoint endpoint)
  {
//define a socket using tcp/ip
   socketWatch = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
//IPAddress ipaddress = IPAddress.Parse("127.0.0.1");
   //IPEndPoint endpoint = new IPEndPoint(IPAddress.Any, 1234);
   try
   {
//define the ip address as localhost and the endpoint as 1234
    socketWatch.Bind(endpoint);
    //limit the length of the socket's listening queue to 20
    socketWatch.Listen(20);
   }catch (Exception ex)
   {
    Logger.Error(ex, "websoket start failed");
   }
   threadWatch = new Thread(WatchConnecting);
   threadWatch.IsBackground = true;
   threadWatch.Start();
   Logger.Debug("websocket start ok");
 }
/// <summary>
  /// watch the websocket connection and then do the handshake  
  /// </summary>
  private void WatchConnecting()
  {
   //Socket socConnection = null;
   while (true)  
   {
    Socket socConnection = socketWatch.Accept();
    countClient++;
    ClientConnectionItems.Add(countClient, socConnection);
    byte[] buffer = new byte[1024];
    int length = socConnection.Receive(buffer);
    //to connection with the websocket client, server must do the handshake 
    socConnection.Send(PackHandShakeData(GetSecKeyAccetp(buffer, length)));
    //create a parameterized thread
    var paramerStart = new ParameterizedThreadStart(RecMsg);
    Thread thr = new Thread(paramerStart);
    thr.IsBackground = true;
    thr.Start(countClient);
   }
  }
  
  /// <summary>
  /// send blob image to client
  /// </summary>
  /// <param name="socConnection"></param>
  private void SendImage(Socket socConnection)
  {}
  
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章