網上有很多Socket框架,但是我想,C#既然有Socket類,難道不是給人用的嗎?
寫了一個SocketServerHelper和SocketClientHelper,分別只有5、6百行代碼,比不上大神寫的,和業務代碼耦合也比較重,但對新手非常友好,容易看懂。
支持返回值或回調,支持不定長度的數據包。客戶端和服務端均支持斷線重連。
自己本機測試,5000個客戶端並發發送消息正常,CPU壓力有點大。由於局域網機子性能差,局域網只測試了500個客戶端並發發送消息正常。
短短1000多行代碼,花了好多天心血,改了無數BUG,越寫代碼,越覺得自己資質平平,邏輯思維不夠用。寫Socket代碼不像寫一般的代碼,實在不行加個try catch完事,這個東西既要穩定,又要性能,真的是每一個邏輯分支,每一個異常分支,都要想清楚,都要處理好,代碼里我還是Exception用習慣了,沒細分。
有時候為了解決一個BUG,找了一整天,也找不出BUG在哪,現在終於測試難過了,達到了自己的預想。
通過這幾天的踩坑,測試,得出結論:
1、Socket TCP 不會丟包,TCP是可靠的。(本機測試、局域網測試,可能沒有遇到更惡劣的網絡環境)
2、Socket TCP 能夠保證順序,接收到的順序和發送的順序一致
3、代碼里有數據校驗,但是錯誤的分支永遠都不會走,校驗是一定能通過的,不存在數據校驗不通過,把錯誤的數據包簡單丟棄的情況,否則說明代碼寫的還是有BUG
以下是主要代碼:
SocketServerHelper代碼:

using Models; using Newtonsoft.Json; using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Configuration; using System.Linq; using System.Net; using System.Net.Sockets; using System.Runtime.InteropServices; using System.Text; using System.Threading; using System.Threading.Tasks; namespace Utils { /// <summary> /// Socket服務端幫助類 /// </summary> public class SocketServerHelper { #region 變量 private int _serverPort; private Socket serverSocket; private ConcurrentDictionary<ClientSocket, string> clientSocketList = new ConcurrentDictionary<ClientSocket, string>(); private ConcurrentDictionary<string, ClientSocket> _dictRoomNoClientSocket = new ConcurrentDictionary<string, ClientSocket>(); private ConcurrentDictionary<string, ClientSocket> _dictDevNoClientSocket = new ConcurrentDictionary<string, ClientSocket>(); public int _CallbackTimeout = 20; /// <summary> /// 等待回調超時時間(單位:秒) /// </summary> public int CallbackTimeout { get { return _CallbackTimeout; } set { value = _CallbackTimeout; } } public int _WaitResultTimeout = 20; /// <summary> /// 等待返回結果超時時間(單位:秒) /// </summary> public int WaitResultTimeout { get { return _WaitResultTimeout; } set { value = _WaitResultTimeout; } } private object _lockSend = new object(); public event EventHandler<ReceivedSocketResultEventArgs> ReceivedSocketResultEvent; private System.Timers.Timer _checkClientTimer; #endregion #region SocketServerHelper 構造函數 public SocketServerHelper(int serverPort) { _serverPort = serverPort; } #endregion #region 啟動服務 /// <summary> /// 啟動服務 /// </summary> public bool StartServer() { try { IPEndPoint ipEndPoint = new IPEndPoint(IPAddress.Any, _serverPort); serverSocket = new Socket(ipEndPoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp); serverSocket.Bind(ipEndPoint); serverSocket.Listen(5000); Thread thread = new Thread(new ThreadStart(delegate () { while (true) { Socket client = null; ClientSocket clientSocket = null; try { client = serverSocket.Accept(); client.SendTimeout = 20000; client.ReceiveTimeout = 20000; client.SendBufferSize = 10240; client.ReceiveBufferSize = 10240; clientSocket = new ClientSocket(client); clientSocketList.TryAdd(clientSocket, null); LogUtil.Log("監聽到新的客戶端,當前客戶端數:" + clientSocketList.Count); } catch (Exception ex) { LogUtil.Error(ex); Thread.Sleep(1); continue; } if (client == null) continue; try { byte[] buffer = new byte[10240]; SocketAsyncEventArgs args = new SocketAsyncEventArgs(); clientSocket.SocketAsyncArgs = args; clientSocket.SocketAsyncCompleted = (s, e) => { ReceiveData(clientSocket, e); }; args.SetBuffer(buffer, 0, buffer.Length); args.Completed += clientSocket.SocketAsyncCompleted; client.ReceiveAsync(args); } catch (Exception ex) { LogUtil.Error(ex); } } })); thread.IsBackground = true; thread.Start(); //檢測客戶端 _checkClientTimer = new System.Timers.Timer(); _checkClientTimer.AutoReset = false; _checkClientTimer.Interval = 1000; _checkClientTimer.Elapsed += CheckClient; _checkClientTimer.Start(); LogUtil.Log("服務已啟動"); return true; } catch (Exception ex) { LogUtil.Error(ex, "啟動服務出錯"); return false; } } #endregion #region 檢測客戶端 /// <summary> /// 檢測客戶端 /// </summary> private void CheckClient(object sender, System.Timers.ElapsedEventArgs e) { try { foreach (ClientSocket clientSkt in clientSocketList.Keys.ToArray()) { Socket skt = clientSkt.Socket; ClientSocket temp; string strTemp; DateTime now = DateTime.Now; if (now.Subtract(clientSkt.LastHeartbeat).TotalSeconds > 60) { clientSocketList.TryRemove(clientSkt, out strTemp); LogUtil.Log("客戶端已失去連接,當前客戶端數:" + clientSocketList.Count); ActionUtil.TryDoAction(() => { if (skt.Connected) skt.Disconnect(false); }); ActionUtil.TryDoAction(() => { skt.Close(); skt.Dispose(); if (clientSkt.SocketAsyncArgs != null) { if (clientSkt.SocketAsyncCompleted != null) { clientSkt.SocketAsyncArgs.Completed -= clientSkt.SocketAsyncCompleted; } clientSkt.SocketAsyncArgs.Dispose(); } clientSkt.SocketAsyncCompleted = null; clientSkt.SocketAsyncArgs = null; }); } } } catch (Exception ex) { LogUtil.Error(ex, "檢測客戶端出錯"); } finally { _checkClientTimer.Start(); } } #endregion #region 接收數據 /// <summary> /// 處理接收的數據包 /// </summary> private void ReceiveData(ClientSocket clientSkt, SocketAsyncEventArgs e) { if (clientSkt == null) return; Socket skt = clientSkt.Socket; try { CopyTo(e.Buffer, clientSkt.Buffer, 0, e.BytesTransferred); #region 校驗數據 if (clientSkt.Buffer.Count < 4) { if (skt.Connected) { if (!skt.ReceiveAsync(e)) ReceiveData(clientSkt, e); } return; } else { byte[] bArrHeader = new byte[4]; CopyTo(clientSkt.Buffer, bArrHeader, 0, 0, bArrHeader.Length); string strHeader = Encoding.ASCII.GetString(bArrHeader); if (strHeader.ToUpper() == "0XFF") { if (clientSkt.Buffer.Count < 5) { if (skt.Connected) { if (!skt.ReceiveAsync(e)) ReceiveData(clientSkt, e); } return; } else { byte[] bArrType = new byte[1]; CopyTo(clientSkt.Buffer, bArrType, 4, 0, bArrType.Length); if (bArrType[0] == 0) { } //心跳包 else if (bArrType[0] == 2 || bArrType[0] == 4) //注冊包、返回值包 { if (clientSkt.Buffer.Count < 9) { if (skt.Connected) { if (!skt.ReceiveAsync(e)) ReceiveData(clientSkt, e); } return; } else { byte[] bArrLength = new byte[4]; CopyTo(clientSkt.Buffer, bArrLength, 5, 0, bArrLength.Length); int dataLength = BitConverter.ToInt32(bArrLength, 0); if (dataLength == 0 || clientSkt.Buffer.Count < dataLength + 9) { if (skt.Connected) { if (!skt.ReceiveAsync(e)) ReceiveData(clientSkt, e); } return; } } } else { LogUtil.Error(string.Format("type錯誤,丟掉錯誤數據,重新接收,roomNo={0},devNo={1}", clientSkt.RoomNo, clientSkt.DevNo)); clientSkt.Buffer.Clear(); //把錯誤的數據丟掉 if (skt.Connected) { if (!skt.ReceiveAsync(e)) ReceiveData(clientSkt, e); } return; } } } else { LogUtil.Error(string.Format("不是0XFF,丟掉錯誤數據,重新接收,roomNo={0},devNo={1}", clientSkt.RoomNo, clientSkt.DevNo)); LogUtil.Error(ByteArrToString(clientSkt.Buffer)); clientSkt.Buffer.Clear(); //把錯誤的數據丟掉 if (skt.Connected) { if (!skt.ReceiveAsync(e)) ReceiveData(clientSkt, e); } return; } } #endregion SocketData data = null; do { data = ProcessSocketData(clientSkt); } while (data != null); if (skt.Connected) { if (!skt.ReceiveAsync(e)) ReceiveData(clientSkt, e); } } catch (Exception ex) { LogUtil.Error(ex, "處理接收的數據包 異常"); } } /// <summary> /// 字節數組轉字符串 /// </summary> private string ByteArrToString(List<byte> byteList) { List<string> list = new List<string>(); foreach (byte b in byteList) { list.Add(b.ToString("X2")); } return string.Join(" ", list); } #endregion #region 處理接收的數據包 /// <summary> /// 處理接收的數據包 /// </summary> private SocketData ProcessSocketData(ClientSocket clientSkt) { int readLength = 0; SocketData data = ResolveBuffer(clientSkt.Buffer, out readLength); if (data != null) { if (readLength > 0) clientSkt.RemoveBufferData(readLength); if (data.Type == 0) //收到心跳包 { clientSkt.LastHeartbeat = DateTime.Now; //心跳應答 if (clientSkt.RoomNo != null || clientSkt.DevNo != null) { lock (clientSkt.LockSend) { byte[] bArrHeader = Encoding.ASCII.GetBytes("0XFF"); SocketHelper.Send(clientSkt.Socket, bArrHeader); SocketHelper.Send(clientSkt.Socket, new byte[] { 0x01 }); } } else { LogUtil.Log("沒有注冊信息"); } //LogUtil.Log("收到心跳包,客戶端連接正常,roomNo=" + clientSkt.RoomNo + ",devNo=" + clientSkt.DevNo); } if (data.Type == 2) //收到注冊包 { if (data.SocketRegisterData != null && clientSkt != null) { ClientSocket temp; if (data.SocketRegisterData.RoomNo != null) _dictRoomNoClientSocket.TryRemove(data.SocketRegisterData.RoomNo, out temp); if (data.SocketRegisterData.DevNo != null) _dictDevNoClientSocket.TryRemove(data.SocketRegisterData.DevNo, out temp); clientSkt.RoomNo = data.SocketRegisterData.RoomNo; clientSkt.DevNo = data.SocketRegisterData.DevNo; if (data.SocketRegisterData.RoomNo != null) _dictRoomNoClientSocket.TryAdd(data.SocketRegisterData.RoomNo, clientSkt); if (data.SocketRegisterData.DevNo != null) _dictDevNoClientSocket.TryAdd(data.SocketRegisterData.DevNo, clientSkt); LogUtil.Log("收到注冊包,roomNo=" + clientSkt.RoomNo + ",devNo=" + clientSkt.DevNo); //注冊反饋 lock (clientSkt.LockSend) { byte[] bArrHeader = Encoding.ASCII.GetBytes("0XFF"); SocketHelper.Send(clientSkt.Socket, bArrHeader); SocketHelper.Send(clientSkt.Socket, new byte[] { 0x05 }); } } } if (data.Type == 4) //收到返回值包 { ThreadHelper.Run(() => { if (data.SocketResult != null) clientSkt.CallbackDict.TryAdd(data.SocketResult.callbackId, data.SocketResult); if (ReceivedSocketResultEvent != null) { ReceivedSocketResultEvent(null, new Models.ReceivedSocketResultEventArgs(data.SocketResult)); } }); //LogUtil.Log("收到返回值包,roomNo=" + clientSkt.RoomNo + ",devNo=" + clientSkt.DevNo); } } return data; } #endregion #region ResolveBuffer /// <summary> /// 解析字節數組 /// </summary> private SocketData ResolveBuffer(List<byte> buffer, out int readLength) { SocketData socketData = null; readLength = 0; try { if (buffer.Count < 4) return null; byte[] bArrHeader = new byte[4]; CopyTo(buffer, bArrHeader, 0, 0, bArrHeader.Length); readLength += bArrHeader.Length; string strHeader = Encoding.ASCII.GetString(bArrHeader); if (strHeader.ToUpper() == "0XFF") { if (buffer.Count < 5) return null; byte[] bArrType = new byte[1]; CopyTo(buffer, bArrType, 4, 0, bArrType.Length); readLength += bArrType.Length; byte bType = bArrType[0]; socketData = new SocketData(); socketData.Type = bType; if (socketData.Type == 2) { if (buffer.Count < 9) return null; byte[] bArrLength = new byte[4]; CopyTo(buffer, bArrLength, 5, 0, bArrLength.Length); readLength += bArrLength.Length; int dataLength = BitConverter.ToInt32(bArrLength, 0); if (dataLength == 0 || buffer.Count < dataLength + 9) return null; byte[] dataBody = new byte[dataLength]; CopyTo(buffer, dataBody, 9, 0, dataBody.Length); readLength += dataBody.Length; string jsonString = Encoding.UTF8.GetString(dataBody); socketData.SocketRegisterData = JsonConvert.DeserializeObject<SocketRegisterData>(jsonString); } if (socketData.Type == 4) { if (buffer.Count < 9) return null; byte[] bArrLength = new byte[4]; CopyTo(buffer, bArrLength, 5, 0, bArrLength.Length); readLength += bArrLength.Length; int dataLength = BitConverter.ToInt32(bArrLength, 0); if (dataLength == 0 || buffer.Count < dataLength + 9) return null; byte[] dataBody = new byte[dataLength]; CopyTo(buffer, dataBody, 9, 0, dataBody.Length); readLength += dataBody.Length; string jsonString = Encoding.UTF8.GetString(dataBody); socketData.SocketResult = JsonConvert.DeserializeObject<SocketResult>(jsonString); } } else { LogUtil.Error("不是0XFF"); return null; } } catch (Exception ex) { LogUtil.Error(ex, "解析字節數組 出錯"); return null; } return socketData; } #endregion #region CopyTo /// <summary> /// 數組復制 /// </summary> private void CopyTo(byte[] bArrSource, List<byte> listTarget, int sourceIndex, int length) { for (int i = 0; i < length; i++) { if (sourceIndex + i < bArrSource.Length) { listTarget.Add(bArrSource[sourceIndex + i]); } } } /// <summary> /// 數組復制 /// </summary> private void CopyTo(List<byte> listSource, byte[] bArrTarget, int sourceIndex, int targetIndex, int length) { for (int i = 0; i < length; i++) { if (targetIndex + i < bArrTarget.Length && sourceIndex + i < listSource.Count) { bArrTarget[targetIndex + i] = listSource[sourceIndex + i]; } } } #endregion #region 停止服務 /// <summary> /// 停止服務 /// </summary> public void StopServer() { try { foreach (ClientSocket clientSocket in clientSocketList.Keys.ToArray()) { Socket socket = clientSocket.Socket; ActionUtil.TryDoAction(() => { if (socket.Connected) socket.Disconnect(false); }); ActionUtil.TryDoAction(() => { socket.Close(); socket.Dispose(); }); } clientSocketList.Clear(); _dictDevNoClientSocket.Clear(); _dictRoomNoClientSocket.Clear(); if (serverSocket != null) { ActionUtil.TryDoAction(() => { if (serverSocket.Connected) serverSocket.Disconnect(false); }); ActionUtil.TryDoAction(() => { serverSocket.Close(); serverSocket.Dispose(); }); } LogUtil.Log("服務已停止"); } catch (Exception ex) { LogUtil.Error(ex, "停止服務出錯"); } } #endregion #region 釋放資源 /// <summary> /// 釋放資源 /// </summary> public void Dispose() { if (_checkClientTimer != null) { _checkClientTimer.Stop(); _checkClientTimer.Close(); } } #endregion #region Send /// <summary> /// Send 單個發送 並等待結果 /// </summary> /// <returns>false:發送失敗 true:發送成功,但接收端是否處理成功要等待返回結果</returns> public SocketResult Send(WebApiMsgContent msgContent, string roomNo, string devNo) { SocketData data = new SocketData(); data.Type = 3; data.MsgContent = msgContent; ClientSocket clientSocket = null; if (roomNo != null) _dictRoomNoClientSocket.TryGetValue(roomNo, out clientSocket); if (devNo != null) _dictDevNoClientSocket.TryGetValue(devNo, out clientSocket); if (clientSocket != null) { if (string.IsNullOrWhiteSpace(msgContent.callbackId)) { msgContent.callbackId = Guid.NewGuid().ToString("N"); } Send(clientSocket, data); return WaitSocketResult(clientSocket, msgContent.callbackId); } else { SocketResult socketResult = new SocketResult(); socketResult.success = false; socketResult.errorMsg = "客戶端不存在"; return socketResult; } } /// <summary> /// Send 單個發送 /// </summary> /// <returns>false:發送失敗 true:發送成功,但接收端是否處理成功要等待返回結果</returns> public void Send(WebApiMsgContent msgContent, string roomNo, string devNo, Action<SocketResult> callback = null) { SocketData data = new SocketData(); data.Type = 3; data.MsgContent = msgContent; ClientSocket clientSocket = null; if (roomNo != null) _dictRoomNoClientSocket.TryGetValue(roomNo, out clientSocket); if (devNo != null) _dictDevNoClientSocket.TryGetValue(devNo, out clientSocket); if (clientSocket != null) { if (string.IsNullOrWhiteSpace(msgContent.callbackId)) { msgContent.callbackId = Guid.NewGuid().ToString("N"); } if (callback != null) { WaitCallback(clientSocket, msgContent.callbackId, callback); } Send(clientSocket, data); } else { SocketResult socketResult = new SocketResult(); socketResult.success = false; socketResult.errorMsg = "客戶端不存在"; if (callback != null) callback(socketResult); } } /// <summary> /// 等待回調 /// </summary> private void WaitCallback(ClientSocket clientSocket, string callbackId, Action<SocketResult> callback = null) { DateTime dt = DateTime.Now.AddSeconds(_CallbackTimeout); System.Timers.Timer timer = new System.Timers.Timer(); timer.AutoReset = false; timer.Interval = 100; timer.Elapsed += (s, e) => { try { SocketResult socketResult; if (!clientSocket.CallbackDict.TryGetValue(callbackId, out socketResult) && DateTime.Now < dt) { timer.Start(); return; } SocketResult sktResult; clientSocket.CallbackDict.TryRemove(callbackId, out sktResult); if (socketResult == null) { socketResult = new SocketResult(); socketResult.success = false; socketResult.errorMsg = "超時"; } if (callback != null) callback(socketResult); timer.Close(); } catch (Exception ex) { LogUtil.Error("WaitCallback error" + ex); } }; timer.Start(); } /// <summary> /// 等待SocketResult /// </summary> private SocketResult WaitSocketResult(ClientSocket clientSocket, string callbackId) { SocketResult socketResult; DateTime dt = DateTime.Now.AddSeconds(_WaitResultTimeout); while (!clientSocket.CallbackDict.TryGetValue(callbackId, out socketResult) && DateTime.Now < dt) { Thread.Sleep(10); } SocketResult sktResult; clientSocket.CallbackDict.TryRemove(callbackId, out sktResult); if (socketResult == null) { socketResult = new SocketResult(); socketResult.success = false; socketResult.errorMsg = "超時"; } return socketResult; } /// <summary> /// Send /// </summary> /// <returns>false:發送失敗 true:發送成功,但不表示對方已收到</returns> private void Send(ClientSocket clientSocket, SocketData data) { bool bl = false; Socket socket = clientSocket.Socket; lock (clientSocket.LockSend) { byte[] bArrHeader = Encoding.ASCII.GetBytes("0XFF"); //發送header bl = SocketHelper.Send(socket, bArrHeader); if (data.Type == 1) { if (bl) bl = SocketHelper.Send(socket, new byte[] { 0x01 }); //發送type } if (data.Type == 3) { if (bl) bl = SocketHelper.Send(socket, new byte[] { 0x03 }); //發送type if (data.MsgContent != null) { byte[] bArrData = null; if (bl) bArrData = Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(data.MsgContent)); if (bl) bl = SocketHelper.Send(socket, BitConverter.GetBytes(bArrData.Length)); //發送length if (bl) bl = SocketHelper.Send(socket, bArrData); //發送body } } } } #endregion } }
SocketClientHelper代碼:

using Models; using Newtonsoft.Json; using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Configuration; using System.Linq; using System.Net; using System.Net.Sockets; using System.Text; using System.Threading; using System.Threading.Tasks; namespace Utils { /// <summary> /// Socket客戶端幫助類 /// </summary> public class SocketClientHelper { #region 變量 private string _serverIP; private int _serverPort; private object _lockSend = new object(); private Socket clientSocket; private SocketAsyncEventArgs _socketAsyncArgs; public EventHandler<SocketAsyncEventArgs> _socketAsyncCompleted { get; set; } private System.Timers.Timer heartbeatTimer; public event EventHandler<SocketReceivedEventArgs> SocketReceivedEvent; private System.Timers.Timer _checkServerTimer; private DateTime _lastHeartbeat; private List<byte> _buffer = new List<byte>(); private string _roomNo; private string _devNo; private bool _registerSuccess = false; public string RoomNo { get { return _roomNo; } } public string DevNo { get { return _devNo; } } /// <summary> /// 刪除接收到的一個包 /// </summary> private void RemoveBufferData(int count) { for (int i = 0; i < count; i++) { if (_buffer.Count > 0) { _buffer.RemoveAt(0); } } } #endregion #region SocketClientHelper 構造函數 public SocketClientHelper(string serverIP, int serverPort) { _serverIP = serverIP; _serverPort = serverPort; } #endregion #region 連接服務器 /// <summary> /// 連接服務器 /// </summary> public bool ConnectServer() { try { if (clientSocket == null || !clientSocket.Connected) { if (clientSocket != null) { clientSocket.Close(); clientSocket.Dispose(); } string ip = ConfigurationManager.AppSettings["ServerIP"]; string hostName = ConfigurationManager.AppSettings["HostName"]; int port = Convert.ToInt32(ConfigurationManager.AppSettings["ServerPort"]); IPEndPoint ipep = null; if (hostName != null) { IPHostEntry host = Dns.GetHostEntry(hostName); IPAddress ipAddr = host.AddressList[0]; ipep = new IPEndPoint(ipAddr, port); } else { ipep = new IPEndPoint(IPAddress.Parse(ip), port); } clientSocket = new Socket(ipep.AddressFamily, SocketType.Stream, ProtocolType.Tcp); clientSocket.SendTimeout = 20000; clientSocket.ReceiveTimeout = 20000; clientSocket.SendBufferSize = 10240; clientSocket.ReceiveBufferSize = 10240; try { clientSocket.Connect(ipep); } catch (Exception ex) { LogUtil.Error(ex); return false; } if (clientSocket == null || !clientSocket.Connected) return false; _lastHeartbeat = DateTime.Now; try { byte[] buffer = new byte[10240]; _socketAsyncArgs = new SocketAsyncEventArgs(); _socketAsyncArgs.SetBuffer(buffer, 0, buffer.Length); _socketAsyncCompleted = (s, e) => { ReceiveData(clientSocket, e); }; _socketAsyncArgs.Completed += _socketAsyncCompleted; clientSocket.ReceiveAsync(_socketAsyncArgs); } catch (Exception ex) { LogUtil.Error(ex); } //檢測服務端 _checkServerTimer = new System.Timers.Timer(); _checkServerTimer.AutoReset = false; _checkServerTimer.Interval = 1000; _checkServerTimer.Elapsed += CheckServer; _checkServerTimer.Start(); LogUtil.Log("已連接服務器"); return true; } return true; } catch (Exception ex) { LogUtil.Error(ex, "連接服務器失敗"); return false; } } #endregion #region 檢測服務端 /// <summary> /// 檢測服務端 /// </summary> private void CheckServer(object sender, System.Timers.ElapsedEventArgs e) { try { DateTime now = DateTime.Now; if (now.Subtract(_lastHeartbeat).TotalSeconds > 60) { LogUtil.Log("服務端已失去連接"); try { if (clientSocket.Connected) clientSocket.Disconnect(false); clientSocket.Close(); clientSocket.Dispose(); _socketAsyncArgs.Completed -= _socketAsyncCompleted; _socketAsyncCompleted = null; _socketAsyncArgs.Dispose(); _socketAsyncArgs = null; } catch (Exception ex) { LogUtil.Error(ex); } Thread.Sleep(3000); int tryCount = 0; while (!ConnectServer() && tryCount++ < 10000) //重連 { Thread.Sleep(3000); } RegisterToServer(_roomNo, _devNo); //重新注冊 } } catch (Exception ex) { LogUtil.Error(ex, "檢測服務端出錯"); } finally { _checkServerTimer.Start(); } } #endregion #region 斷開服務器 /// <summary> /// 斷開服務器 /// </summary> public void DisconnectServer() { try { if (clientSocket != null) { if (clientSocket.Connected) clientSocket.Disconnect(false); clientSocket.Close(); clientSocket.Dispose(); } LogUtil.Log("已斷開服務器"); } catch (Exception ex) { LogUtil.Error(ex, "斷開服務器失敗"); } } #endregion #region 釋放資源 /// <summary> /// 釋放資源 /// </summary> public void Dispose() { if (heartbeatTimer != null) { heartbeatTimer.Stop(); heartbeatTimer.Close(); } if (_checkServerTimer != null) { _checkServerTimer.Stop(); _checkServerTimer.Close(); } } #endregion #region 心跳 public void StartHeartbeat() { heartbeatTimer = new System.Timers.Timer(); heartbeatTimer.AutoReset = false; heartbeatTimer.Interval = 10000; heartbeatTimer.Elapsed += new System.Timers.ElapsedEventHandler((obj, eea) => { lock (_lockSend) { try { byte[] bArrHeader = Encoding.ASCII.GetBytes("0XFF"); SocketHelper.Send(clientSocket, bArrHeader); SocketHelper.Send(clientSocket, new byte[] { 0x00 }); } catch (Exception ex) { LogUtil.Error("向服務器發送心跳包出錯:" + ex.Message); } finally { heartbeatTimer.Start(); } } }); heartbeatTimer.Start(); } #endregion #region 停止心跳 public void StopHeartbeat() { heartbeatTimer.Stop(); } #endregion #region 注冊 /// <summary> /// 注冊 /// </summary> public bool RegisterToServer(string roomNo, string devNo) { _registerSuccess = false; SocketData data = new SocketData(); data.Type = 2; data.SocketRegisterData = new SocketRegisterData(); data.SocketRegisterData.RoomNo = roomNo; data.SocketRegisterData.DevNo = devNo; _roomNo = roomNo; _devNo = devNo; Send(data); DateTime dt = DateTime.Now; while (!_registerSuccess && DateTime.Now.Subtract(dt).TotalMilliseconds < 5000) { Thread.Sleep(100); } return _registerSuccess; } #endregion #region 接收數據 /// <summary> /// 處理接收的數據包 /// </summary> private void ReceiveData(Socket socket, SocketAsyncEventArgs e) { try { CopyTo(e.Buffer, _buffer, 0, e.BytesTransferred); #region 校驗數據 if (_buffer.Count < 4) { if (socket.Connected) { if (!socket.ReceiveAsync(e)) ReceiveData(socket, e); } return; } else { byte[] bArrHeader = new byte[4]; CopyTo(_buffer, bArrHeader, 0, 0, bArrHeader.Length); string strHeader = Encoding.ASCII.GetString(bArrHeader); if (strHeader.ToUpper() == "0XFF") { if (_buffer.Count < 5) { if (socket.Connected) { if (!socket.ReceiveAsync(e)) ReceiveData(socket, e); } return; } else { byte[] bArrType = new byte[1]; CopyTo(_buffer, bArrType, 4, 0, bArrType.Length); if (bArrType[0] == 1 || bArrType[0] == 5) { } //心跳應答包、注冊反饋包 else if (bArrType[0] == 3) //消息包 { if (_buffer.Count < 9) { if (socket.Connected) { if (!socket.ReceiveAsync(e)) ReceiveData(socket, e); } return; } else { byte[] bArrLength = new byte[4]; CopyTo(_buffer, bArrLength, 5, 0, bArrLength.Length); int dataLength = BitConverter.ToInt32(bArrLength, 0); if (dataLength == 0 || _buffer.Count < dataLength + 9) { if (socket.Connected) { if (!socket.ReceiveAsync(e)) ReceiveData(socket, e); } return; } } } else { LogUtil.Error("type錯誤,丟掉錯誤數據,重新接收"); _buffer.Clear(); //把錯誤的數據丟掉 if (socket.Connected) { if (!socket.ReceiveAsync(e)) ReceiveData(socket, e); } return; } } } else { LogUtil.Error("不是0XFF,丟掉錯誤數據,重新接收"); _buffer.Clear(); //把錯誤的數據丟掉 if (socket.Connected) { if (!socket.ReceiveAsync(e)) ReceiveData(socket, e); } return; } } #endregion SocketData data = null; do { data = ProcessSocketData(socket); } while (data != null); if (socket.Connected) { if (!socket.ReceiveAsync(e)) ReceiveData(socket, e); } } catch (Exception ex) { LogUtil.Error(ex, "處理接收的數據包 異常"); } } #endregion #region 處理接收的數據包 /// <summary> /// 處理接收的數據包 /// </summary> private SocketData ProcessSocketData(Socket socket) { int readLength = 0; SocketData data = ResolveBuffer(_buffer, out readLength); if (data != null) { if (readLength > 0) RemoveBufferData(readLength); if (data.Type == 1) //心跳應答 { _lastHeartbeat = DateTime.Now; //LogUtil.Log("收到心跳應答包,服務端正常"); } if (data.Type == 3) //消息數據 { if (SocketReceivedEvent != null) { SocketReceivedEventArgs args = new SocketReceivedEventArgs(data.MsgContent); args.Callback = new CallbackSocket(socket); ThreadHelper.Run((obj) => { try { SocketReceivedEvent(this, obj as SocketReceivedEventArgs); } catch (Exception ex) { LogUtil.Error(ex); } }, args); } } if (data.Type == 5) //注冊反饋 { _registerSuccess = true; LogUtil.Log("收到注冊反饋包,注冊成功"); } } return data; } #endregion #region ResolveBuffer /// <summary> /// 解析字節數組 /// </summary> private SocketData ResolveBuffer(List<byte> buffer, out int readLength) { SocketData socketData = null; readLength = 0; try { if (buffer.Count < 4) return null; byte[] bArrHeader = new byte[4]; CopyTo(buffer, bArrHeader, 0, 0, bArrHeader.Length); readLength += bArrHeader.Length; string strHeader = Encoding.ASCII.GetString(bArrHeader); if (strHeader.ToUpper() == "0XFF") { if (buffer.Count < 5) return null; byte[] bArrType = new byte[1]; CopyTo(buffer, bArrType, 4, 0, bArrType.Length); readLength += bArrType.Length; byte bType = bArrType[0]; socketData = new SocketData(); socketData.Type = bType; if (socketData.Type == 3) { if (buffer.Count < 9) return null; byte[] bArrLength = new byte[4]; CopyTo(buffer, bArrLength, 5, 0, bArrLength.Length); readLength += bArrLength.Length; int dataLength = BitConverter.ToInt32(bArrLength, 0); if (dataLength == 0 || buffer.Count < dataLength + 9) return null; byte[] dataBody = new byte[dataLength]; CopyTo(buffer, dataBody, 9, 0, dataBody.Length); readLength += dataBody.Length; string jsonString = Encoding.UTF8.GetString(dataBody); socketData.MsgContent = JsonConvert.DeserializeObject<WebApiMsgContent>(jsonString); } } else { LogUtil.Error("不是0XFF"); return null; } } catch (Exception ex) { LogUtil.Error(ex, "解析字節數組 出錯"); return null; } return socketData; } #endregion #region CopyTo /// <summary> /// 數組復制 /// </summary> private void CopyTo(byte[] bArrSource, List<byte> listTarget, int sourceIndex, int length) { for (int i = 0; i < length; i++) { if (sourceIndex + i < bArrSource.Length) { listTarget.Add(bArrSource[sourceIndex + i]); } } } /// <summary> /// 數組復制 /// </summary> private void CopyTo(List<byte> listSource, byte[] bArrTarget, int sourceIndex, int targetIndex, int length) { for (int i = 0; i < length; i++) { if (targetIndex + i < bArrTarget.Length && sourceIndex + i < listSource.Count) { bArrTarget[targetIndex + i] = listSource[sourceIndex + i]; } } } #endregion #region Send /// <summary> /// Send /// </summary> public void Send(SocketData data) { Send(clientSocket, data); } /// <summary> /// Send /// </summary> public void Send(Socket socket, SocketData data) { lock (_lockSend) { byte[] bArrHeader = Encoding.ASCII.GetBytes("0XFF"); //發送header bool bl = SocketHelper.Send(socket, bArrHeader); if (data.Type == 0) { if (bl) bl = SocketHelper.Send(socket, new byte[] { 0x00 }); //發送type } else if (data.Type == 2) { if (bl) bl = SocketHelper.Send(socket, new byte[] { 0x02 }); //發送type if (data.SocketRegisterData != null) { byte[] bArrData = null; if (bl) bArrData = Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(data.SocketRegisterData)); if (bl) bl = SocketHelper.Send(socket, BitConverter.GetBytes(bArrData.Length)); //發送length if (bl) bl = SocketHelper.Send(socket, bArrData); //發送body } } if (data.Type == 4) { if (bl) bl = SocketHelper.Send(socket, new byte[] { 0x04 }); //發送type if (data.SocketResult != null) { byte[] bArrData = null; if (bl) bArrData = Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(data.SocketResult)); if (bl) bl = SocketHelper.Send(socket, BitConverter.GetBytes(bArrData.Length)); //發送length if (bl) bl = SocketHelper.Send(socket, bArrData); //發送body } } } } #endregion } }
SocketHelper代碼(里面同步接收的方法Receive和ReceiveByte沒有用到):

using Models; using Newtonsoft.Json; using System; using System.Collections.Generic; using System.Configuration; using System.Linq; using System.Net; using System.Net.Sockets; using System.Text; using System.Threading; using System.Threading.Tasks; namespace Utils { /// <summary> /// Socket封裝 /// </summary> public static class SocketHelper { #region 變量 #endregion #region Send /// <summary> /// Send /// </summary> public static bool Send(Socket socket, byte[] data) { try { if (socket == null || !socket.Connected) return false; int sendTotal = 0; while (sendTotal < data.Length) { int sendLength = data.Length - sendTotal; if (sendLength > 1024) sendLength = 1024; int sendOnce = socket.Send(data, sendTotal, sendLength, SocketFlags.None); sendTotal += sendOnce; } return true; } catch (Exception ex) { LogUtil.Error(ex); return false; } } #endregion #region Receive /// <summary> /// Receive /// </summary> public static byte[] Receive(Socket socket, int length) { try { byte[] buffer = new byte[length]; int receiveCount = 0; while ((receiveCount = socket.Receive(buffer, 0, length, SocketFlags.None)) == 0) { Thread.Sleep(1); } while (receiveCount < length) { int revCount = socket.Receive(buffer, receiveCount, buffer.Length - receiveCount, SocketFlags.None); receiveCount += revCount; } return buffer; } catch (Exception ex) { return null; } } /// <summary> /// Receive /// </summary> public static byte? ReceiveByte(Socket socket) { try { byte[] buffer = new byte[1]; int receiveCount = 0; while ((receiveCount = socket.Receive(buffer, 0, 1, SocketFlags.None)) == 0) { Thread.Sleep(1); } return buffer[0]; } catch (Exception ex) { return null; } } #endregion #region IsZero /// <summary> /// IsZero /// </summary> public static bool IsZero(byte[] data) { bool bl = true; foreach (byte b in data) { if (b != 0) { return false; } } LogUtil.Error("接收的字節數組內容全是0"); return bl; } #endregion } }
代碼中接收消息是異步接收,提高性能,發送消息是同步發送,主要是為了和Android端對接方便,Android端按我這種方式發就可以了。
下面是模擬500個客戶端的程序代碼下載鏈接:
由於網絡、客戶端可能不在線等原因,消息不一定能送達,所以為了保證消息送達,需要使用數據庫,將發送失敗的消息存入數據庫,定時重發,發送成功或者超時2天則刪除失敗記錄,下面是自己畫的時序圖,可能畫的不太專業:
業務相關代碼:
MsgUtil代碼:

using Models; using Newtonsoft.Json; using PrisonWebApi.Controllers.Common; using PrisonWebApi.DAL; using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.Configuration; using System.Linq; using System.Threading; using System.Timers; using System.Web; namespace Utils { /// <summary> /// Web API 消息工具類 /// </summary> public static class MsgUtil { #region 變量 private static WebApiMsgDal m_WebApiMsgDal = null; private static System.Timers.Timer _timer; private static SocketServerHelper _socketServerHelper; #endregion #region Init 初始化 /// <summary> /// 初始化 /// </summary> public static void Init() { ThreadHelper.Run(() => { m_WebApiMsgDal = ServiceHelper.Get<WebApiMsgDal>(); int port = int.Parse(ConfigurationManager.AppSettings["SocketServerPort"]); _socketServerHelper = new SocketServerHelper(port); _socketServerHelper.ReceivedSocketResultEvent += _socketServerHelper_ReceivedSocketResultEvent; _socketServerHelper.StartServer(); _timer = new System.Timers.Timer(); _timer.AutoReset = false; _timer.Interval = 40000; //注意,這個參數必須比Socket等待回調超時時間CallbackTimeout大 _timer.Elapsed += MsgTask; _timer.Start(); LogUtil.Log("Web API 消息工具類 初始化成功"); }, (ex) => { LogUtil.Error("Web API 消息工具類 初始化失敗"); }); } #endregion #region 定時任務 /// <summary> /// 定時任務 /// </summary> private static void MsgTask(object sender, ElapsedEventArgs e) { ThreadHelper.Run(() => { try { m_WebApiMsgDal.DeleteTimeoutMsg(); //刪除超時的消息 List<WEBAPI_MSG> list = m_WebApiMsgDal.GetMsgList(); foreach (WEBAPI_MSG msg in list) { WebApiMsgContent msgContent = JsonConvert.DeserializeObject<WebApiMsgContent>(msg.MSGCONTENT); msgContent.callbackId = msg.ID; Send(msgContent, msg.RECEIVER, msg.RECEIVER, null); } if (list.Count > 0) { LogUtil.Log("已重發" + list.Count.ToString() + "條消息"); } } catch (Exception ex) { LogUtil.Error(ex); } finally { _timer.Start(); } }); } #endregion #region 接收數據 /// <summary> /// 接收數據 /// </summary> private static void _socketServerHelper_ReceivedSocketResultEvent(object sender, ReceivedSocketResultEventArgs e) { Func<string, bool> func = (callbackId) => { try { if (m_WebApiMsgDal.Exists((string)callbackId)) { m_WebApiMsgDal.DeleteById((string)callbackId); } } catch (Exception ex) { LogUtil.Error(ex, "刪除消息出錯"); return false; } return true; }; int tryCount = 0; if (e.SocketResult != null) { while (!func(e.SocketResult.callbackId) && tryCount++ < 10) { Thread.Sleep(1000); } } } #endregion #region Send 發送消息 /// <summary> /// Send 發送消息 /// </summary> public static void Send(WebApiMsgContent msgContent, string roomNo, string devNo, Action<SocketResult> callback = null) { _socketServerHelper.Send(msgContent, roomNo, devNo, callback); } /// <summary> /// Send 發送消息 /// </summary> public static SocketResult Send(WebApiMsgContent msgContent, string roomNo, string devNo) { try { return _socketServerHelper.Send(msgContent, roomNo, devNo); } catch (Exception ex) { LogUtil.Error(ex, "發送消息失敗"); return null; } } #endregion #region 釋放資源 /// <summary> /// 釋放資源 /// </summary> public static void Dispose() { ThreadHelper.Run(() => { _timer.Stop(); _timer.Elapsed -= MsgTask; _timer.Close(); _timer.Dispose(); _timer = null; _socketServerHelper.StopServer(); _socketServerHelper.ReceivedSocketResultEvent -= _socketServerHelper_ReceivedSocketResultEvent; LogUtil.Log("Web API 消息工具類 釋放資源成功"); }, (ex) => { LogUtil.Error("Web API 消息工具類 釋放資源失敗"); }); } #endregion } }
Web API 接口 MsgController 代碼:

using DBUtil; using Models; using Newtonsoft.Json; using PrisonWebApi.DAL; using Swashbuckle.Swagger.Annotations; using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.Globalization; using System.Linq; using System.Net; using System.Net.Http; using System.Web; using System.Web.Http; using Utils; namespace PrisonWebApi.Controllers.Common { /// <summary> /// Web API 消息 /// </summary> [RoutePrefix("api/msg")] public class MsgController : ApiController { #region 變量屬性 private WebApiMsgDal m_WebApiMsgDal = ServiceHelper.Get<WebApiMsgDal>(); private TwoCJsDal m_TwoCJsDal = ServiceHelper.Get<TwoCJsDal>(); private BackstageAppInstallDal m_BackstageAppInstallDal = ServiceHelper.Get<BackstageAppInstallDal>(); private RollCallDal m_RollCallDal = ServiceHelper.Get<RollCallDal>(); private RollCallConfirmDal m_RollCallConfirmDal = ServiceHelper.Get<RollCallConfirmDal>(); #endregion #region 發送消息 /// <summary> /// 發送消息 /// </summary> /// <param name="data">POST數據</param> [HttpPost] [Route("SendMsg")] [SwaggerResponse(HttpStatusCode.OK, "返回JSON", typeof(JsonResult<SendMsgData>))] public HttpResponseMessage SendMsg([FromBody] SendMsgData data) { JsonResult jsonResult = null; if (data == null || data.msgContent == null) { jsonResult = new JsonResult("請檢查參數格式", ResultCode.參數不正確); return ApiHelper.ToJson(jsonResult); } if (data.roomNo != null && data.devNos != null) { jsonResult = new JsonResult("監室號和設備編碼(指倉內屏或倉外屏的設備編碼)不能都有值,應填寫其中一個,或者都不填寫", ResultCode.參數不正確); return ApiHelper.ToJson(jsonResult); } if (string.IsNullOrWhiteSpace(data.msgContent.msgTime)) data.msgContent.msgTime = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"); if (!string.IsNullOrWhiteSpace(data.devNos)) { try { foreach (string devNo in data.devNos.Split(',')) { data.msgContent.callbackId = Guid.NewGuid().ToString("N"); MsgUtil.Send(data.msgContent, null, devNo, (socketResult) => { if (socketResult == null || !socketResult.success) { WEBAPI_MSG info = new WEBAPI_MSG(); info.ID = Guid.NewGuid().ToString("N"); info.MSGTIME = DateTime.ParseExact(data.msgContent.msgTime, "yyyy-MM-dd HH:mm:ss", CultureInfo.InvariantCulture); info.RECEIVER = devNo; info.MSGCONTENT = JsonConvert.SerializeObject(data.msgContent); m_WebApiMsgDal.Insert(info); } }); } } catch (Exception ex) { LogUtil.Error(ex, "消息發送失敗"); jsonResult = new JsonResult("消息發送失敗", ResultCode.操作失敗); return ApiHelper.ToJson(jsonResult); } } else { if (!string.IsNullOrWhiteSpace(data.roomNo)) { try { data.msgContent.callbackId = Guid.NewGuid().ToString("N"); MsgUtil.Send(data.msgContent, data.roomNo, null, (socketResult) => { if (socketResult == null || !socketResult.success) { WEBAPI_MSG info = new WEBAPI_MSG(); info.ID = Guid.NewGuid().ToString("N"); info.MSGTIME = DateTime.ParseExact(data.msgContent.msgTime, "yyyy-MM-dd HH:mm:ss", CultureInfo.InvariantCulture); info.RECEIVER = data.roomNo; info.MSGCONTENT = JsonConvert.SerializeObject(data.msgContent); m_WebApiMsgDal.Insert(info); } }); } catch (Exception ex) { LogUtil.Error(ex, "消息發送失敗"); jsonResult = new JsonResult("消息發送失敗", ResultCode.操作失敗); return ApiHelper.ToJson(jsonResult); } } else { try { List<string> roomNoList = m_TwoCJsDal.GetRoomNoListAll(); foreach (string roomNo in roomNoList) { data.msgContent.callbackId = Guid.NewGuid().ToString("N"); MsgUtil.Send(data.msgContent, roomNo, null, (socketResult) => { if (socketResult == null || !socketResult.success) { WEBAPI_MSG info = new WEBAPI_MSG(); info.ID = Guid.NewGuid().ToString("N"); info.MSGTIME = DateTime.ParseExact(data.msgContent.msgTime, "yyyy-MM-dd HH:mm:ss", CultureInfo.InvariantCulture); info.RECEIVER = roomNo; info.MSGCONTENT = JsonConvert.SerializeObject(data.msgContent); m_WebApiMsgDal.Insert(info); } }); } } catch (Exception ex) { LogUtil.Error(ex, "消息發送失敗"); jsonResult = new JsonResult("消息發送失敗", ResultCode.操作失敗); return ApiHelper.ToJson(jsonResult); } } } jsonResult = new JsonResult<CommonSubmitResult>(new CommonSubmitResult() { msg = "消息發送成功" }); return ApiHelper.ToJson(jsonResult); } #endregion #region APP安裝消息反饋 /// <summary> /// APP安裝消息反饋 /// </summary> /// <param name="data">POST數據</param> [HttpPost] [Route("InstallMsgFeedback")] [SwaggerResponse(HttpStatusCode.OK, "返回JSON", typeof(JsonResult<CommonSubmitResult>))] public HttpResponseMessage InstallMsgFeedback([FromBody] InstallMsgFeedbackData data) { JsonResult jsonResult = null; if (data == null) { jsonResult = new JsonResult("請檢查參數格式", ResultCode.參數不正確); return ApiHelper.ToJson(jsonResult); } BACKSTAGE_APP_INSTALL info = m_BackstageAppInstallDal.Get(data.id); if (info != null) { if (data.success) { info.STATUS = "1"; m_BackstageAppInstallDal.Update(info); } jsonResult = new JsonResult<CommonSubmitResult>(new CommonSubmitResult() { msg = "反饋成功", id = info.ID }); } else { jsonResult = new JsonResult("反饋失敗:安裝記錄不存在", ResultCode.操作失敗); return ApiHelper.ToJson(jsonResult); } return ApiHelper.ToJson(jsonResult); } #endregion #region 發起點名成功反饋 /// <summary> /// 發起點名成功反饋 /// </summary> /// <param name="data">POST數據</param> [HttpPost] [Route("RollCallMsgFeedback")] [SwaggerResponse(HttpStatusCode.OK, "返回JSON", typeof(JsonResult<CommonSubmitResult>))] public HttpResponseMessage RollCallMsgFeedback([FromBody] RollCallMsgFeedbackData data) { JsonResult jsonResult = null; if (data == null) { jsonResult = new JsonResult("請檢查參數格式", ResultCode.參數不正確); return ApiHelper.ToJson(jsonResult); } ROLL_CALL info = m_RollCallDal.Get(data.id); if (info != null) { if (data.success) { info.STATUS = "2"; info.UPDATE_TIME = DateTime.Now; m_RollCallDal.Update(info); } else { info.STATUS = "3"; info.ERROR_MSG = data.errorMsg; info.UPDATE_TIME = DateTime.Now; m_RollCallDal.Update(info); } jsonResult = new JsonResult<CommonSubmitResult>(new CommonSubmitResult() { msg = "反饋成功", id = info.ID }); } else { jsonResult = new JsonResult("反饋失敗:點名記錄不存在", ResultCode.操作失敗); return ApiHelper.ToJson(jsonResult); } return ApiHelper.ToJson(jsonResult); } #endregion #region 點名確認 /// <summary> /// 點名確認 /// </summary> /// <param name="data">POST數據</param> [HttpPost] [Route("RollCallConfirm")] [SwaggerResponse(HttpStatusCode.OK, "返回JSON", typeof(JsonResult<CommonSubmitResult>))] public HttpResponseMessage RollCallConfirm([FromBody] RollCallConfirmData data) { JsonResult jsonResult = null; if (data == null) { jsonResult = new JsonResult("請檢查參數格式", ResultCode.參數不正確); return ApiHelper.ToJson(jsonResult); } ROLL_CALL_CONFIRM info = m_RollCallConfirmDal.Get(data.rollCallId, data.prisonerId); if (info == null) info = new ROLL_CALL_CONFIRM(); info.ROLL_CALL_ID = data.rollCallId; info.PRISONERID = data.prisonerId; info.CONFIRM_TIME = DateTime.Now; info.UPDATE_TIME = DateTime.Now; info.STATUS = "1"; m_RollCallConfirmDal.InsertOrUpdate(info); jsonResult = new JsonResult<CommonSubmitResult>(new CommonSubmitResult() { msg = "點名確認成功", id = info.ID }); return ApiHelper.ToJson(jsonResult); } #endregion } #region SendMsgData 發送消息數據 /// <summary> /// 發送消息數據 /// </summary> [MyValidate] public class SendMsgData { /// <summary> /// 消息內容 /// </summary> [Required] public WebApiMsgContent msgContent { get; set; } /// <summary> /// 監室號(如果為空,並且devNos也為空,則發送到所有監室;如果為空,並且devNos不為空,則按devNos發送) /// </summary> public string roomNo { get; set; } /// <summary> /// 設備編碼(逗號隔開)(倉內屏或倉外屏的設備編碼) /// </summary> public string devNos { get; set; } } /// <summary> /// APP安裝消息反饋 /// </summary> [MyValidate] public class InstallMsgFeedbackData { /// <summary> /// 安裝記錄ID /// </summary> [Required] public string id { get; set; } /// <summary> /// 安裝是否成功 /// </summary> [Required] public bool success { get; set; } /// <summary> /// 安裝失敗原因 /// </summary> public string errorMsg { get; set; } } /// <summary> /// 發起點名成功反饋 /// </summary> [MyValidate] public class RollCallMsgFeedbackData { /// <summary> /// 點名ID /// </summary> [Required] public string id { get; set; } /// <summary> /// 發起點名是否成功 /// </summary> [Required] public bool success { get; set; } /// <summary> /// 發起點名失敗原因 /// </summary> public string errorMsg { get; set; } } /// <summary> /// 點名確認數據 /// </summary> [MyValidate] public class RollCallConfirmData { /// <summary> /// 點名ID /// </summary> [Required] public string rollCallId { get; set; } /// <summary> /// 在押人員ID /// </summary> [Required] public string prisonerId { get; set; } } #endregion }
有個嚴重BUG說明一下,博客就不改了:
客戶端或服務端離線的時候,服務端或客戶端接收到長度為0的數據,造成死循環,需要加個判斷(完整代碼參見 https://gitee.com/s0611163/SocketHelper ):

if (e.BytesTransferred == 0) { ReleaseClientSocket(clientSkt, skt); return; }