C# 实现WebSocket服务端教程
作者:左手121
.net4.5中实现了对websocket的支持
在这里我使用的是.net4.0。因此需要对原本的socket发送的数据根据websocket的协议进行解析和打包。
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服务 /// <summary> /// 启动WebSocket服务 /// </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("服务已启动"); Console.WriteLine("按任意键关闭服务"); Console.ReadLine(); } #endregion #region 处理客户端连接请求 /// <summary> /// 处理客户端连接请求 /// </summary> /// <param name="result"></param> private void Accept(IAsyncResult socket) { // 还原传入的原始套接字 Socket SockeServer = (Socket)socket.AsyncState; // 在原始套接字上调用EndAccept方法,返回新的套接字 Socket SockeClient = SockeServer.EndAccept(socket); byte[] buffer = new byte[4096]; try { //接收客户端的数据 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 处理接收的数据 /// <summary> /// 处理接受的数据 /// </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协议中客户端与服务器想建立连接需要一次额外的握手动作 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 客户端和服务端的响应 /* * 客户端向服务器发送请求 * * 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 * * 服务器给出响应 * * HTTP/1.1 101 Switching Protocols * Upgrade: websocket * Connection: Upgrade * Sec-WebSocket-Accept: xsOSgr30aKL2GNZKNHKmeT1qYjA= * * 在请求中的“Sec-WebSocket-Key”是随机的,服务器端会用这些数据来构造出一个SHA-1的信息摘要。把“Sec-WebSocket-Key”加上一个魔幻字符串 * “258EAFA5-E914-47DA-95CA-C5AB0DC85B11”。使用 SHA-1 加密,之后进行 BASE-64编码,将结果做为 “Sec-WebSocket-Accept” 头的值,返回给客户端 */ #endregion #region 打包请求连接数据 /// <summary> /// 打包请求连接数据 /// </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 处理接收的数据 /// <summary> /// 处理接收的数据 /// </summary> /// <param name="recBytes"></param> /// <param name="length"></param> /// <returns></returns> private string AnalyzeClientData(byte[] recBytes, int length) { int start = 0; // 如果有数据则至少包括3位 if (length < 2) return ""; // 判断是否为结束针 bool IsEof = (recBytes[start] >> 7) > 0; // 暂不处理超过一帧的数据 if (!IsEof) return ""; start++; // 是否包含掩码 bool hasMask = (recBytes[start] >> 7) > 0; // 不包含掩码的暂不处理 if (!hasMask) return ""; // 获取数据长度 UInt64 mPackageLength = (UInt64)recBytes[start] & 0x7F; start++; // 存储4位掩码值 byte[] Masking_key = new byte[4]; // 存储数据 byte[] mDataPackage; if (mPackageLength == 126) { // 等于126 随后的两个字节16位表示数据长度 mPackageLength = (UInt64)(recBytes[start] << 8 | recBytes[start + 1]); start += 2; } if (mPackageLength == 127) { // 等于127 随后的八个字节64位表示数据长度 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 发送数据 /// <summary> /// 把发送给客户端消息打包处理(拼接上谁什么时候发的什么消息) /// </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#实现websocket服务端与客户端通信
一、websocket简介
websocket是一种在单个TCP连接上进行全双工通信的协议。
websocket使得客户端和服务器之间的数据交换变得更加简单,允许服务端主动向客户端推送数据。在WebSocket API中,浏览器和服务器只需要完成一次握手,两者之间就直接可以创建持久性的连接,并进行双向数据传输。
二、背景
很多网站为了实现推送技术,所用的技术都是轮询。
轮询是在特定的时间间隔,由浏览器对客户端发出HTTP请求,然后由服务器返回最新的数据给客户端的浏览器。这种传统的模式带来很明显的缺点,即浏览器需要不断的向服务器发出请求,然后HTTP请求可能包含较长的头部,其中真正有效的数据可能只是很小的一部分,显然这样会浪费很多的宽带等资源。
在这种情况下,HTML5定义了websocket协议,能更好的节省服务器资源和宽带,而且能够更实时地进行通讯。
三、优点
1、控制开销
创建连接后,服务器和客户端之间交换数据时,用于协议控制的数据包头部相对较小。
2、实时性更强
由于协议是全双工的,所以服务器可以随时主动给客户端下发数据。相对于HTTP请求需要等待客户端发起请求服务端才能响应,延迟明显更少。
3、保持连接状态
与HTTP不同的是,Websocket需要先创建连接,这就使得其成为一种有状态的协议,之后通信时可以省略部分状态信息。而HTTP请求可能需要在每个请求都携带状态信息(如身份认证等)。
4、更好的二进制支持
5、支持扩展和更好的实现压缩效果
四、原理
websocket同HTTP一样也是应用层的协议,但是它是一种双向通信协议,建立在TCP之上的。
连接过程(握手过程)
1、客户端、服务器建立TCP连接,三次握手。
这是通信的基础,传输控制层,若失败后续都不执行。
2、TCP连接成功后,客户端通过HTTP协议向服务器传送websocket支持的版本号信息。(开始前的HTTP握手)
3、服务器收到客户端的握手请求后,同样采用HTTP协议回馈数据。
4、当收到了连接成功的消息后,通过TCP通道进行传输通信。
五、websocket和socket的关系
socket其实并不是一个协议,而是为了方便使用TCP和UDP而抽象出来的一层,是位于应用层和传输控制层之间的一组接口。
socket是应用层与TCP/IP协议通信的中间软件抽象层,它是一组接口。在设计模式中,socket其实就是一个门面模式,它把复杂的TCP/IP协议隐藏在socket接口后面,对用户来说,一组简单的接口就是全部,让socket去组织数据,以符合指定的协议。
两台主机通信,必须通过socket连接,socket则利用TCP/IP协议建立TCP连接。TCP连接则更依靠于底层的IP协议,IP协议的连接则依赖于链路层等更低层次。
websocket则是一个典型的应用层协议。
六、使用C#实现websocket服务端与客户端通信
(一) SuperWebSocket实现服务端
1、创建窗口程序,WindowsFormsWebsocketServer
2、添加程序包
工具 -->Nuget包管理 -->管理解决方案的Nuget程序包 -->搜索 SuperWebSocket ,选择SuperWebSocketNETServer,点击右侧 安装,等待安装完成,安装完成以后,项目会多出很多引用库,如下
3、代码实例
using SuperWebSocket; using System; using System.Windows.Forms; namespace WindowsFormsWebsocketServer { public partial class Form1 : Form { public Form1() { InitializeComponent(); websocketServer(); } private void websocketServer() { Log("我是服务端"); 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("设置服务监听失败!"); } if (!webSocketServer.Start()) { Log("启动服务监听失败!"); } Log("启动服务监听成功!"); //webSocketServer.Dispose(); } private void WebSocketServer_NewSessionConnected(WebSocketSession session) { Log("欢迎客户端: 加入"); //SendToAll(session, msg); } private void WebSocketServer_NewMessageReceived(WebSocketSession session, string value) { Log("服务端收到客户端的数据 ==》"+value); //SendToAll(session, value); } private void WebSocketServer_SessionClosed(WebSocketSession session, SuperSocket.SocketBase.CloseReason value) { Log("客户端:关闭,原因:"); //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实现客户端
1、创建窗口程序,WindowsFormsWebsocketClient
2、添加程序包
工具 -->Nuget包管理 -->管理解决方案的Nuget程序包 -->搜索 WebSocket4Net ,选择WebSocket4Net,点击右侧 安装,等待安装完成,安装完成以后,项目会多出很多引用库,如下
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("服务端回复的数据:" + e.Message); } private void WebSocket4Net_Opened(object sender, EventArgs e) { FileUtil.getInstance().Log("客户端连接成功!发送数据中..."); webSocket4Net.Send("来自客户端,准备发送数据!"); } 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已关闭"); Thread.Sleep(5000); if (webSocket4Net.State != WebSocketState.Open && webSocket4Net.State != WebSocketState.Connecting) { websocketServerTest(); } } } }
(三)客户端向服务端发送消息
客户端:
服务端:
以上这篇C# 实现WebSocket服务端教程就是小编分享给大家的全部内容了,希望能给大家一个参考,也希望大家多多支持脚本之家。