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)
  {}
  
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章