主頁 > .NET開發 > C# .NET Socket SocketHelper 高性能 5000客戶端 異步接收資料

C# .NET Socket SocketHelper 高性能 5000客戶端 異步接收資料

2020-09-14 09:16:14 .NET開發

    網上有很多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 =https://www.cnblogs.com/s0611163/p/ _CallbackTimeout; }
        }

        public int _WaitResultTimeout = 20;
        /// <summary>
        /// 等待回傳結果超時時間(單位:秒)
        /// </summary>
        public int WaitResultTimeout
        {
            get { return _WaitResultTimeout; }
            set { value =https://www.cnblogs.com/s0611163/p/ _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 = https://www.cnblogs.com/s0611163/p/null;
                        if (bl) bArrData =https://www.cnblogs.com/s0611163/p/ 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

    }

}
View Code

    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 = https://www.cnblogs.com/s0611163/p/null;
                        if (bl) bArrData =https://www.cnblogs.com/s0611163/p/ 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 = https://www.cnblogs.com/s0611163/p/null;
                        if (bl) bArrData =https://www.cnblogs.com/s0611163/p/ 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

    }
}
View Code

    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

    }

}
View Code

    代碼中接收訊息是異步接收,提高性能,發送訊息是同步發送,主要是為了和Android端對接方便,Android端按我這種方式發就可以了,

    下面是模擬500個客戶端的程式代碼下載鏈接:

    SocketHelper批量客戶端代碼

     由于網路、客戶端可能不在線等原因,訊息不一定能送達,所以為了保證訊息送達,需要使用資料庫,將發送失敗的訊息存入資料庫,定時重發,發送成功或者超時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

    }
}
View Code

    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 =https://www.cnblogs.com/s0611163/p/= 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 =https://www.cnblogs.com/s0611163/p/= 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 =https://www.cnblogs.com/s0611163/p/= 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 =https://www.cnblogs.com/s0611163/p/= 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

}
View Code

     

    C# Socket,沒有人比我的代碼更簡單明了了

 

轉載請註明出處,本文鏈接:https://www.uj5u.com/net/33194.html

標籤:C#

上一篇:Build 2020上公布的C# 9.0 新特性

下一篇:C#中的異步多執行緒1 同步和異步對比

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • WebAPI簡介

    Web體系結構: 有三個核心:資源(resource),URL(統一資源識別符號)和表示 他們的關系是這樣的:一個資源由一個URL進行標識,HTTP客戶端使用URL定位資源,表示是從資源回傳資料,媒體型別是資源回傳的資料格式。 接下來我們說下HTTP. HTTP協議的系統是一種無狀態的方式,使用請求/ ......

    uj5u.com 2020-09-09 22:07:47 more
  • asp.net core 3.1 入口:Program.cs中的Main函式

    本文分析Program.cs 中Main()函式中代碼的運行順序分析asp.net core程式的啟動,重點不是剖析原始碼,而是理清程式開始時執行的順序。到呼叫了哪些實體,哪些法方。asp.net core 3.1 的程式入口在專案Program.cs檔案里,如下。ususing System; us ......

    uj5u.com 2020-09-09 22:07:49 more
  • asp.net網站作為websocket服務端的應用該如何寫

    最近被websocket的一個問題困擾了很久,有一個需求是在web網站中搭建websocket服務。客戶端通過網頁與服務器建立連接,然后服務器根據ip給客戶端網頁發送資訊。 其實,這個需求并不難,只是剛開始對websocket的內容不太了解。上網搜索了一下,有通過asp.net core 實作的、有 ......

    uj5u.com 2020-09-09 22:08:02 more
  • ASP.NET 開源匯入匯出庫Magicodes.IE Docker中使用

    Magicodes.IE在Docker中使用 更新歷史 2019.02.13 【Nuget】版本更新到2.0.2 【匯入】修復單列匯入的Bug,單元測驗“OneColumnImporter_Test”。問題見(https://github.com/dotnetcore/Magicodes.IE/is ......

    uj5u.com 2020-09-09 22:08:05 more
  • 在webform中使用ajax

    如果你用過Asp.net webform, 說明你也算是.NET 開發的老兵了。WEBform應該是2011 2013左右,當時還用visual studio 2005、 visual studio 2008。后來基本都用的是MVC。 如果是新開發的專案,估計沒人會用webform技術。但是有些舊版 ......

    uj5u.com 2020-09-09 22:08:50 more
  • iis添加asp.net網站,訪問提示:由于擴展配置問題而無法提供您請求的

    今天在iis服務器配置asp.net網站,遇到一個問題,記錄一下: 問題:由于擴展配置問題而無法提供您請求的頁面。如果該頁面是腳本,請添加處理程式。如果應下載檔案,請添加 MIME 映射。 WindowServer2012服務器,添加角色安裝完.netframework和iis之后,運行aspx頁面 ......

    uj5u.com 2020-09-09 22:10:00 more
  • WebAPI-處理架構

    帶著問題去思考,大家好! 問題1:HTTP請求和回傳相應的HTTP回應資訊之間發生了什么? 1:首先是最底層,托管層,位于WebAPI和底層HTTP堆疊之間 2:其次是 訊息處理程式管道層,這里比如日志和快取。OWIN的參考是將訊息處理程式管道的一些功能下移到堆疊下端的OWIN中間件了。 3:控制器處理 ......

    uj5u.com 2020-09-09 22:11:13 more
  • 微信門戶開發框架-使用指導說明書

    微信門戶應用管理系統,采用基于 MVC + Bootstrap + Ajax + Enterprise Library的技術路線,界面層采用Boostrap + Metronic組合的前端框架,資料訪問層支持Oracle、SQLServer、MySQL、PostgreSQL等資料庫。框架以MVC5,... ......

    uj5u.com 2020-09-09 22:15:18 more
  • WebAPI-HTTP編程模型

    帶著問題去思考,大家好!它是什么?它包含什么?它能干什么? 訊息 HTTP編程模型的核心就是訊息抽象,表示為:HttPRequestMessage,HttpResponseMessage.用于客戶端和服務端之間交換請求和回應訊息。 HttpMethod類包含了一組靜態屬性: private stat ......

    uj5u.com 2020-09-09 22:15:23 more
  • 部署WebApi隨筆

    一、跨域 NuGet參考Microsoft.AspNet.WebApi.Cors WebApiConfig.cs中配置: // Web API 配置和服務 config.EnableCors(new EnableCorsAttribute("*", "*", "*")); 二、清除默認回傳XML格式 ......

    uj5u.com 2020-09-09 22:15:48 more
最新发布
  • C#多執行緒學習(二) 如何操縱一個執行緒

    <a href="https://www.cnblogs.com/x-zhi/" target="_blank"><img width="48" height="48" class="pfs" src="https://pic.cnblogs.com/face/2943582/20220801082530.png" alt="" /></...

    uj5u.com 2023-04-19 09:17:20 more
  • C#多執行緒學習(二) 如何操縱一個執行緒

    C#多執行緒學習(二) 如何操縱一個執行緒 執行緒學習第一篇:C#多執行緒學習(一) 多執行緒的相關概念 下面我們就動手來創建一個執行緒,使用Thread類創建執行緒時,只需提供執行緒入口即可。(執行緒入口使程式知道該讓這個執行緒干什么事) 在C#中,執行緒入口是通過ThreadStart代理(delegate)來提供的 ......

    uj5u.com 2023-04-19 09:16:49 more
  • 記一次 .NET某醫療器械清洗系統 卡死分析

    <a href="https://www.cnblogs.com/huangxincheng/" target="_blank"><img width="48" height="48" class="pfs" src="https://pic.cnblogs.com/face/214741/20200614104537.png" alt="" /&g...

    uj5u.com 2023-04-18 08:39:04 more
  • 記一次 .NET某醫療器械清洗系統 卡死分析

    一:背景 1. 講故事 前段時間協助訓練營里的一位朋友分析了一個程式卡死的問題,回過頭來看這個案例比較經典,這篇稍微整理一下供后來者少踩坑吧。 二:WinDbg 分析 1. 為什么會卡死 因為是表單程式,理所當然就是看主執行緒此時正在做什么? 可以用 ~0s ; k 看一下便知。 0:000> k # ......

    uj5u.com 2023-04-18 08:33:10 more
  • SignalR, No Connection with that ID,IIS

    <a href="https://www.cnblogs.com/smartstar/" target="_blank"><img width="48" height="48" class="pfs" src="https://pic.cnblogs.com/face/u36196.jpg" alt="" /></a>...

    uj5u.com 2023-03-30 17:21:52 more
  • 一次對pool的誤用導致的.net頻繁gc的診斷分析

    <a href="https://www.cnblogs.com/dotnet-diagnostic/" target="_blank"><img width="48" height="48" class="pfs" src="https://pic.cnblogs.com/face/3115652/20230225090434.png" alt=""...

    uj5u.com 2023-03-28 10:15:33 more
  • 一次對pool的誤用導致的.net頻繁gc的診斷分析

    <a href="https://www.cnblogs.com/dotnet-diagnostic/" target="_blank"><img width="48" height="48" class="pfs" src="https://pic.cnblogs.com/face/3115652/20230225090434.png" alt=""...

    uj5u.com 2023-03-28 10:13:31 more
  • C#遍歷指定檔案夾中所有檔案的3種方法

    <a href="https://www.cnblogs.com/xbhp/" target="_blank"><img width="48" height="48" class="pfs" src="https://pic.cnblogs.com/face/957602/20230310105611.png" alt="" /></a&...

    uj5u.com 2023-03-27 14:46:55 more
  • C#/VB.NET:如何將PDF轉為PDF/A

    <a href="https://www.cnblogs.com/Carina-baby/" target="_blank"><img width="48" height="48" class="pfs" src="https://pic.cnblogs.com/face/2859233/20220427162558.png" alt="" />...

    uj5u.com 2023-03-27 14:46:35 more
  • 武裝你的WEBAPI-OData聚合查詢

    <a href="https://www.cnblogs.com/podolski/" target="_blank"><img width="48" height="48" class="pfs" src="https://pic.cnblogs.com/face/616093/20140323000327.png" alt="" /><...

    uj5u.com 2023-03-27 14:46:16 more