C# 實現(xiàn)WebSocket服務(wù)端教程
.net4.5中實現(xiàn)了對websocket的支持
在這里我使用的是.net4.0。因此需要對原本的socket發(fā)送的數(shù)據(jù)根據(jù)websocket的協(xié)議進行解析和打包。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net.Sockets;
using System.Threading;
using System.Net;
namespace WebSocketServer
{
class Program
{
static void Main(string[] args)
{
WebSocket socket = new WebSocket();
socket.start(8064);
}
}
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net.Sockets;
using System.Net;
namespace WebSocketServer
{
public class Session
{
private Socket _sockeclient;
private byte[] _buffer;
private string _ip;
private bool _isweb = false;
public Socket SockeClient
{
set { _sockeclient = value; }
get { return _sockeclient; }
}
public byte[] buffer
{
set { _buffer = value; }
get { return _buffer; }
}
public string IP
{
set { _ip = value; }
get { return _ip; }
}
public bool isWeb
{
set { _isweb = value; }
get { return _isweb; }
}
}
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Net.Sockets;
using System.Net;
using System.Text.RegularExpressions;
using System.Security.Cryptography;
namespace WebSocketServer
{
public class WebSocket
{
private Dictionary<string, Session> SessionPool = new Dictionary<string, Session>();
private Dictionary<string, string> MsgPool = new Dictionary<string, string>();
#region 啟動WebSocket服務(wù)
/// <summary>
/// 啟動WebSocket服務(wù)
/// </summary>
public void start(int port)
{
Socket SockeServer = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
SockeServer.Bind(new IPEndPoint(IPAddress.Any, port));
SockeServer.Listen(20);
SockeServer.BeginAccept(new AsyncCallback(Accept), SockeServer);
Console.WriteLine("服務(wù)已啟動");
Console.WriteLine("按任意鍵關(guān)閉服務(wù)");
Console.ReadLine();
}
#endregion
#region 處理客戶端連接請求
/// <summary>
/// 處理客戶端連接請求
/// </summary>
/// <param name="result"></param>
private void Accept(IAsyncResult socket)
{
// 還原傳入的原始套接字
Socket SockeServer = (Socket)socket.AsyncState;
// 在原始套接字上調(diào)用EndAccept方法,返回新的套接字
Socket SockeClient = SockeServer.EndAccept(socket);
byte[] buffer = new byte[4096];
try
{
//接收客戶端的數(shù)據(jù)
SockeClient.BeginReceive(buffer, 0, buffer.Length, SocketFlags.None, new AsyncCallback(Recieve), SockeClient);
//保存登錄的客戶端
Session session = new Session();
session.SockeClient = SockeClient;
session.IP = SockeClient.RemoteEndPoint.ToString();
session.buffer = buffer;
lock (SessionPool)
{
if (SessionPool.ContainsKey(session.IP))
{
this.SessionPool.Remove(session.IP);
}
this.SessionPool.Add(session.IP, session);
}
//準備接受下一個客戶端
SockeServer.BeginAccept(new AsyncCallback(Accept), SockeServer);
Console.WriteLine(string.Format("Client {0} connected", SockeClient.RemoteEndPoint));
}
catch (Exception ex)
{
Console.WriteLine("Error : " + ex.ToString());
}
}
#endregion
#region 處理接收的數(shù)據(jù)
/// <summary>
/// 處理接受的數(shù)據(jù)
/// </summary>
/// <param name="socket"></param>
private void Recieve(IAsyncResult socket)
{
Socket SockeClient = (Socket)socket.AsyncState;
string IP = SockeClient.RemoteEndPoint.ToString();
if (SockeClient == null || !SessionPool.ContainsKey(IP))
{
return;
}
try
{
int length = SockeClient.EndReceive(socket);
byte[] buffer = SessionPool[IP].buffer;
SockeClient.BeginReceive(buffer, 0, buffer.Length, SocketFlags.None, new AsyncCallback(Recieve), SockeClient);
string msg = Encoding.UTF8.GetString(buffer, 0, length);
// websocket建立連接的時候,除了TCP連接的三次握手,websocket協(xié)議中客戶端與服務(wù)器想建立連接需要一次額外的握手動作
if (msg.Contains("Sec-WebSocket-Key"))
{
SockeClient.Send(PackageHandShakeData(buffer, length));
SessionPool[IP].isWeb = true;
return;
}
if (SessionPool[IP].isWeb)
{
msg = AnalyzeClientData(buffer, length);
}
byte[] msgBuffer = PackageServerData(msg);
foreach (Session se in SessionPool.Values)
{
se.SockeClient.Send(msgBuffer, msgBuffer.Length, SocketFlags.None);
}
}
catch
{
SockeClient.Disconnect(true);
Console.WriteLine("客戶端 {0} 斷開連接", IP);
SessionPool.Remove(IP);
}
}
#endregion
#region 客戶端和服務(wù)端的響應(yīng)
/*
* 客戶端向服務(wù)器發(fā)送請求
*
* GET / HTTP/1.1
* Origin: http://localhost:1416
* Sec-WebSocket-Key: vDyPp55hT1PphRU5OAe2Wg==
* Connection: Upgrade
* Upgrade: Websocket
*Sec-WebSocket-Version: 13
* User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64; Trident/7.0; rv:11.0) like Gecko
* Host: localhost:8064
* DNT: 1
* Cache-Control: no-cache
* Cookie: DTRememberName=admin
*
* 服務(wù)器給出響應(yīng)
*
* HTTP/1.1 101 Switching Protocols
* Upgrade: websocket
* Connection: Upgrade
* Sec-WebSocket-Accept: xsOSgr30aKL2GNZKNHKmeT1qYjA=
*
* 在請求中的“Sec-WebSocket-Key”是隨機的,服務(wù)器端會用這些數(shù)據(jù)來構(gòu)造出一個SHA-1的信息摘要。把“Sec-WebSocket-Key”加上一個魔幻字符串
* “258EAFA5-E914-47DA-95CA-C5AB0DC85B11”。使用 SHA-1 加密,之后進行 BASE-64編碼,將結(jié)果做為 “Sec-WebSocket-Accept” 頭的值,返回給客戶端
*/
#endregion
#region 打包請求連接數(shù)據(jù)
/// <summary>
/// 打包請求連接數(shù)據(jù)
/// </summary>
/// <param name="handShakeBytes"></param>
/// <param name="length"></param>
/// <returns></returns>
private byte[] PackageHandShakeData(byte[] handShakeBytes, int length)
{
string handShakeText = Encoding.UTF8.GetString(handShakeBytes, 0, length);
string key = string.Empty;
Regex reg = new Regex(@"Sec\-WebSocket\-Key:(.*?)\r\n");
Match m = reg.Match(handShakeText);
if (m.Value != "")
{
key = Regex.Replace(m.Value, @"Sec\-WebSocket\-Key:(.*?)\r\n", "$1").Trim();
}
byte[] secKeyBytes = SHA1.Create().ComputeHash(Encoding.ASCII.GetBytes(key + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"));
string secKey = Convert.ToBase64String(secKeyBytes);
var responseBuilder = new StringBuilder();
responseBuilder.Append("HTTP/1.1 101 Switching Protocols" + "\r\n");
responseBuilder.Append("Upgrade: websocket" + "\r\n");
responseBuilder.Append("Connection: Upgrade" + "\r\n");
responseBuilder.Append("Sec-WebSocket-Accept: " + secKey + "\r\n\r\n");
return Encoding.UTF8.GetBytes(responseBuilder.ToString());
}
#endregion
#region 處理接收的數(shù)據(jù)
/// <summary>
/// 處理接收的數(shù)據(jù)
/// </summary>
/// <param name="recBytes"></param>
/// <param name="length"></param>
/// <returns></returns>
private string AnalyzeClientData(byte[] recBytes, int length)
{
int start = 0;
// 如果有數(shù)據(jù)則至少包括3位
if (length < 2) return "";
// 判斷是否為結(jié)束針
bool IsEof = (recBytes[start] >> 7) > 0;
// 暫不處理超過一幀的數(shù)據(jù)
if (!IsEof) return "";
start++;
// 是否包含掩碼
bool hasMask = (recBytes[start] >> 7) > 0;
// 不包含掩碼的暫不處理
if (!hasMask) return "";
// 獲取數(shù)據(jù)長度
UInt64 mPackageLength = (UInt64)recBytes[start] & 0x7F;
start++;
// 存儲4位掩碼值
byte[] Masking_key = new byte[4];
// 存儲數(shù)據(jù)
byte[] mDataPackage;
if (mPackageLength == 126)
{
// 等于126 隨后的兩個字節(jié)16位表示數(shù)據(jù)長度
mPackageLength = (UInt64)(recBytes[start] << 8 | recBytes[start + 1]);
start += 2;
}
if (mPackageLength == 127)
{
// 等于127 隨后的八個字節(jié)64位表示數(shù)據(jù)長度
mPackageLength = (UInt64)(recBytes[start] << (8 * 7) | recBytes[start] << (8 * 6) | recBytes[start] << (8 * 5) | recBytes[start] << (8 * 4) | recBytes[start] << (8 * 3) | recBytes[start] << (8 * 2) | recBytes[start] << 8 | recBytes[start + 1]);
start += 8;
}
mDataPackage = new byte[mPackageLength];
for (UInt64 i = 0; i < mPackageLength; i++)
{
mDataPackage[i] = recBytes[i + (UInt64)start + 4];
}
Buffer.BlockCopy(recBytes, start, Masking_key, 0, 4);
for (UInt64 i = 0; i < mPackageLength; i++)
{
mDataPackage[i] = (byte)(mDataPackage[i] ^ Masking_key[i % 4]);
}
return Encoding.UTF8.GetString(mDataPackage);
}
#endregion
#region 發(fā)送數(shù)據(jù)
/// <summary>
/// 把發(fā)送給客戶端消息打包處理(拼接上誰什么時候發(fā)的什么消息)
/// </summary>
/// <returns>The data.</returns>
/// <param name="message">Message.</param>
private byte[] PackageServerData(string msg)
{
byte[] content = null;
byte[] temp = Encoding.UTF8.GetBytes(msg);
if (temp.Length < 126)
{
content = new byte[temp.Length + 2];
content[0] = 0x81;
content[1] = (byte)temp.Length;
Buffer.BlockCopy(temp, 0, content, 2, temp.Length);
}
else if (temp.Length < 0xFFFF)
{
content = new byte[temp.Length + 4];
content[0] = 0x81;
content[1] = 126;
content[2] = (byte)(temp.Length & 0xFF);
content[3] = (byte)(temp.Length >> 8 & 0xFF);
Buffer.BlockCopy(temp, 0, content, 4, temp.Length);
}
return content;
}
#endregion
}
}
補充知識:【TCP/IP】使用C#實現(xiàn)websocket服務(wù)端與客戶端通信
一、websocket簡介
websocket是一種在單個TCP連接上進行全雙工通信的協(xié)議。
websocket使得客戶端和服務(wù)器之間的數(shù)據(jù)交換變得更加簡單,允許服務(wù)端主動向客戶端推送數(shù)據(jù)。在WebSocket API中,瀏覽器和服務(wù)器只需要完成一次握手,兩者之間就直接可以創(chuàng)建持久性的連接,并進行雙向數(shù)據(jù)傳輸。
二、背景
很多網(wǎng)站為了實現(xiàn)推送技術(shù),所用的技術(shù)都是輪詢。
輪詢是在特定的時間間隔,由瀏覽器對客戶端發(fā)出HTTP請求,然后由服務(wù)器返回最新的數(shù)據(jù)給客戶端的瀏覽器。這種傳統(tǒng)的模式帶來很明顯的缺點,即瀏覽器需要不斷的向服務(wù)器發(fā)出請求,然后HTTP請求可能包含較長的頭部,其中真正有效的數(shù)據(jù)可能只是很小的一部分,顯然這樣會浪費很多的寬帶等資源。
在這種情況下,HTML5定義了websocket協(xié)議,能更好的節(jié)省服務(wù)器資源和寬帶,而且能夠更實時地進行通訊。
三、優(yōu)點
1、控制開銷
創(chuàng)建連接后,服務(wù)器和客戶端之間交換數(shù)據(jù)時,用于協(xié)議控制的數(shù)據(jù)包頭部相對較小。
2、實時性更強
由于協(xié)議是全雙工的,所以服務(wù)器可以隨時主動給客戶端下發(fā)數(shù)據(jù)。相對于HTTP請求需要等待客戶端發(fā)起請求服務(wù)端才能響應(yīng),延遲明顯更少。
3、保持連接狀態(tài)
與HTTP不同的是,Websocket需要先創(chuàng)建連接,這就使得其成為一種有狀態(tài)的協(xié)議,之后通信時可以省略部分狀態(tài)信息。而HTTP請求可能需要在每個請求都攜帶狀態(tài)信息(如身份認證等)。
4、更好的二進制支持
5、支持擴展和更好的實現(xiàn)壓縮效果
四、原理
websocket同HTTP一樣也是應(yīng)用層的協(xié)議,但是它是一種雙向通信協(xié)議,建立在TCP之上的。
連接過程(握手過程)
1、客戶端、服務(wù)器建立TCP連接,三次握手。
這是通信的基礎(chǔ),傳輸控制層,若失敗后續(xù)都不執(zhí)行。
2、TCP連接成功后,客戶端通過HTTP協(xié)議向服務(wù)器傳送websocket支持的版本號信息。(開始前的HTTP握手)
3、服務(wù)器收到客戶端的握手請求后,同樣采用HTTP協(xié)議回饋數(shù)據(jù)。
4、當收到了連接成功的消息后,通過TCP通道進行傳輸通信。
五、websocket和socket的關(guān)系
socket其實并不是一個協(xié)議,而是為了方便使用TCP和UDP而抽象出來的一層,是位于應(yīng)用層和傳輸控制層之間的一組接口。
socket是應(yīng)用層與TCP/IP協(xié)議通信的中間軟件抽象層,它是一組接口。在設(shè)計模式中,socket其實就是一個門面模式,它把復(fù)雜的TCP/IP協(xié)議隱藏在socket接口后面,對用戶來說,一組簡單的接口就是全部,讓socket去組織數(shù)據(jù),以符合指定的協(xié)議。
兩臺主機通信,必須通過socket連接,socket則利用TCP/IP協(xié)議建立TCP連接。TCP連接則更依靠于底層的IP協(xié)議,IP協(xié)議的連接則依賴于鏈路層等更低層次。
websocket則是一個典型的應(yīng)用層協(xié)議。

六、使用C#實現(xiàn)websocket服務(wù)端與客戶端通信
(一) SuperWebSocket實現(xiàn)服務(wù)端
1、創(chuàng)建窗口程序,WindowsFormsWebsocketServer
2、添加程序包
工具 -->Nuget包管理 -->管理解決方案的Nuget程序包 -->搜索 SuperWebSocket ,選擇SuperWebSocketNETServer,點擊右側(cè) 安裝,等待安裝完成,安裝完成以后,項目會多出很多引用庫,如下

3、代碼實例
using SuperWebSocket;
using System;
using System.Windows.Forms;
namespace WindowsFormsWebsocketServer
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
websocketServer();
}
private void websocketServer()
{
Log("我是服務(wù)端");
WebSocketServer webSocketServer = new WebSocketServer();
webSocketServer.NewSessionConnected += WebSocketServer_NewSessionConnected;
webSocketServer.NewMessageReceived += WebSocketServer_NewMessageReceived;
webSocketServer.SessionClosed += WebSocketServer_SessionClosed;
if (!webSocketServer.Setup("127.0.0.1", 1234))
{
Log("設(shè)置服務(wù)監(jiān)聽失?。?);
}
if (!webSocketServer.Start())
{
Log("啟動服務(wù)監(jiān)聽失??!");
}
Log("啟動服務(wù)監(jiān)聽成功!");
//webSocketServer.Dispose();
}
private void WebSocketServer_NewSessionConnected(WebSocketSession session)
{
Log("歡迎客戶端: 加入");
//SendToAll(session, msg);
}
private void WebSocketServer_NewMessageReceived(WebSocketSession session, string value)
{
Log("服務(wù)端收到客戶端的數(shù)據(jù) ==》"+value);
//SendToAll(session, value);
}
private void WebSocketServer_SessionClosed(WebSocketSession session, SuperSocket.SocketBase.CloseReason value)
{
Log("客戶端:關(guān)閉,原因:");
//SendToAll(session, msg);
}
/// <summary>
/// 廣播,同步推送消息給所有的客戶端
/// </summary>
/// <param name="webSocketSession"></param>
/// <param name="msg"></param>
public static void SendToAll(WebSocketSession webSocketSession, string msg)
{
foreach (var item in webSocketSession.AppServer.GetAllSessions())
{
item.Send(msg);
}
}
private delegate void DoLog(string msg);
public void Log(string msg)
{
if (this.logReveal.InvokeRequired)
{
DoLog doLog = new DoLog(Log);
this.logReveal.Invoke(doLog, new object[] { msg });
}
else
{
if (this.logReveal.Items.Count > 20)
{
this.logReveal.Items.RemoveAt(0);
}
msg = DateTime.Now.ToLocalTime().ToString() + " " + msg;
this.logReveal.Items.Add(msg);
}
}
}
}
(二)WebSocket4Net實現(xiàn)客戶端
1、創(chuàng)建窗口程序,WindowsFormsWebsocketClient
2、添加程序包
工具 -->Nuget包管理 -->管理解決方案的Nuget程序包 -->搜索 WebSocket4Net ,選擇WebSocket4Net,點擊右側(cè) 安裝,等待安裝完成,安裝完成以后,項目會多出很多引用庫,如下

3、代碼實例
using System;
using WebSocket4Net;
using System.Threading;
using System.Windows.Forms;
namespace WindowsFormsWebsocketClient
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
websocketServerTest();
}
public static WebSocket webSocket4Net = null;
public void websocketServerTest()
{
FileUtil.getInstance().Log("我是客戶端");
webSocket4Net = new WebSocket("ws://127.0.0.1:1234");
webSocket4Net.Opened += WebSocket4Net_Opened;
webSocket4Net.Error += websocket_Error;
webSocket4Net.Closed += new EventHandler(websocket_Closed);
webSocket4Net.MessageReceived += WebSocket4Net_MessageReceived;
webSocket4Net.Open();
Thread thread = new Thread(ClientSendMsgToServer);
thread.IsBackground = true;
thread.Start();
//webSocket4Net.Dispose();
}
private void saveBtn_Click(object sender, EventArgs e)
{
websocketServerTest();
}
public void ClientSendMsgToServer()
{
int i = 1;
while (true)
{
webSocket4Net.Send("love girl" + i++);
Thread.Sleep(TimeSpan.FromSeconds(5));
}
}
private void WebSocket4Net_MessageReceived(object sender, MessageReceivedEventArgs e)
{
FileUtil.getInstance().Log("服務(wù)端回復(fù)的數(shù)據(jù):" + e.Message);
}
private void WebSocket4Net_Opened(object sender, EventArgs e)
{
FileUtil.getInstance().Log("客戶端連接成功!發(fā)送數(shù)據(jù)中...");
webSocket4Net.Send("來自客戶端,準備發(fā)送數(shù)據(jù)!");
}
private void websocket_Error(object sender, EventArgs e)
{
FileUtil.getInstance().Log("WebSocket錯誤");
Thread.Sleep(5000);
if (webSocket4Net.State!= WebSocketState.Open&&webSocket4Net.State!=WebSocketState.Connecting)
{
websocketServerTest();
}
}
private void websocket_Closed(object sender, EventArgs e)
{
FileUtil.getInstance().Log("WebSocket已關(guān)閉");
Thread.Sleep(5000);
if (webSocket4Net.State != WebSocketState.Open && webSocket4Net.State != WebSocketState.Connecting)
{
websocketServerTest();
}
}
}
}
(三)客戶端向服務(wù)端發(fā)送消息
客戶端:

服務(wù)端:

以上這篇C# 實現(xiàn)WebSocket服務(wù)端教程就是小編分享給大家的全部內(nèi)容了,希望能給大家一個參考,也希望大家多多支持腳本之家。
相關(guān)文章
C#過濾DataTable中空數(shù)據(jù)和重復(fù)數(shù)據(jù)的示例代碼
這篇文章主要給大家介紹了關(guān)于C#過濾DataTable中空數(shù)據(jù)和重復(fù)數(shù)據(jù)的相關(guān)資料,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2021-01-01
Unity shader實現(xiàn)移動端模擬深度水效果
這篇文章主要為大家詳細介紹了Unity shader實現(xiàn)移動端模擬深度水效果,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下2020-05-05
C#執(zhí)行表達式樹(Expression Tree)的具體使用
本文將深入探討表達式樹的基本概念、創(chuàng)建方法、修改和刪除節(jié)點、查詢和遍歷技巧以及在C#中的應(yīng)用示例,具有一定的參考價值,感興趣的可以了解一下2024-03-03

