C# Socket使用以及DotNetty和Supersocket 框架


1.Socket服務端與客戶端通話

1服務端

using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading.Tasks;

namespace tSocket
{
    class Program
    {
        byte[] bytes = new byte[1024];
        Socket cSocket;
        static void Main(string[] args)
        {
            Program p = new Program();
            //打開鏈接
            p.open();
            //向服務端發送消息
            Console.WriteLine("請輸入你要對服務端發送的消息:");
            string mes = Console.ReadLine();
            string con = p.messge(mes);
            Console.WriteLine("接受到服務端的消息:" + con);


        }
        byte[] data = new byte[1024];
        string messge(string mes)
        {
            //將發送的消息轉成字節數組
            bytes = Encoding.UTF8.GetBytes(mes);
            //發送
            cSocket.Send(bytes);
            while (true)
            {
                //接受服務端發送的消息,放入字節數組
                int len = cSocket.Receive(data);
                //將字節數組轉成可讀明文
                string con = Encoding.UTF8.GetString(data, 0, len);
                ////返回
                return con;
            }
          
        }
        /// <summary>
        /// 打開鏈接
        /// </summary>
        void open()
        {
            //創建Socket對象 指定連接方式
            cSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
            //創建IP,端口
            IPAddress ip = IPAddress.Parse("10.116.253.10");
            int port = 7526;
            //封裝IP和端口
            IPEndPoint Ipoint = new IPEndPoint(ip, port);
            //打開鏈接
            cSocket.Connect(Ipoint);
        }
    }
}

2.客戶端

using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading.Tasks;

namespace ServerSocket
{
    class Program
    {
        
        static void Main(string[] args)
        {
            //創建Socket對象,指定他的鏈接方式
            Socket serverSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
            //建立IP
            string ip = "10.116.253.10";
            //創建端口
            int prot = 7526;//1~9999
            IPAddress IPAdd = IPAddress.Parse(ip);
            //封裝IP和端口
            IPEndPoint point = new IPEndPoint(IPAdd, prot);
            //綁定IP和端口
            serverSocket.Bind(point);
            //開始監聽
            serverSocket.Listen(100);
            Console.WriteLine("開始監聽!");

            int i = 0;
            while (true)
            {
                i++;
                //接受客戶鏈接
                
               Socket cSocket = serverSocket.Accept();
               Console.WriteLine("接受第"+i+"個客戶的連接!");
               Client c = new Client(cSocket);
            }

        }
    }
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace ServerSocket
{
    class Client
    {
        Socket sSocket;
        byte[] data = new byte[1024];
        Thread t;
        public Client(Socket cSocket)
        {
            //接受客戶的連接
            sSocket = cSocket;
            //創建線程
            t = new Thread(Mess);
            //開始線程
            t.Start();
        }

        void Mess()
        {
            try
            {
                while (true)
                {
                    //將用戶發送的數據以一個字節數組裝起
                    int length = sSocket.Receive(data);
                    Console.WriteLine("接受客戶端發的消息!");
                    string mess = Encoding.UTF8.GetString(data, 0, length);

                    if (mess == "con")
                    {
                        string con = "DataSource =.";
                        byte[] bytes = Encoding.UTF8.GetBytes(con);
                        sSocket.Send(bytes);
                    }
                    Console.WriteLine("接到用戶的消息:" + mess);
                }
            }
            catch (Exception)
            {
                sSocket.Close();
            }



        }
    }
}

2.DotNetty

DotNetty是微軟的Azure團隊,使用C#實現的Netty的版本發布。不但使用了C#和.Net平台的技術特點,並且保留了Netty原來絕大部分的編程接口。讓我們在使用時,完全可以依照Netty官方的教程來學習和使用DotNetty應用程序。

Netty 是一個異步事件驅動的網絡應用程序框架,用於快速開發可維護的高性能協議服務器和客戶端。

優點

  1. 關注點分離——業務和網絡邏輯解耦;
  2. 模塊化和可復用性;
  3. 可測試性作為首要的要求

歷史

  1. 阻塞Socket通信特點:
    1. 建立連接要阻塞線程,讀取數據要阻塞線程
    2. 如果要管理多個客戶端,就需要為每個客戶端建立不同的線程
    3. 會有大量的線程在休眠狀態,等待接收數據,資源浪費
    4. 每個線程都要占用系統資源
    5. 線程的切換很耗費系統資源
  2. 非阻塞Socket(NIO)特點:
    1. 如圖,每個Socket如果需要讀寫操作,都通過事件通知的方式通知選擇器,這樣就實現了一個線程管理多個Socket的目的。
    2. 選擇器甚至可以在所有的Socket空閑的時候允許線程先去干別的事情
    3. 減少了線程數量導致的資源占用,減少了線程切換導致的資源消耗

Netty設計的關鍵點

異步和事件驅動是Netty設計的關鍵

核心組件

  • Channel:一個連接就是一個Channel
  • 回調:通知的基礎

官方也提供了一些例子。地址如下

https://github.com/Azure/DotNetty

3.Supersocket 

開源地址https://github.com/kerryjiang/SuperSocket

SuperSocket是重量輕的可擴展套接字應用程序框架。您可以使用它輕松構建始終連接的套接字應用程序,而無需考慮如何使用套接字,如何維護套接字連接以及套接字如何工作。這是一個純C#項目,旨在進行擴展,因此只要以.NET語言開發它們,就可以輕松地將它們集成到您的現有系統中。

首先安裝:SuperSocket.Engine

SuperSoket的三大對象:

Session: 每一個用戶連接就是一個Session
AppServer: Socket服務器實例
Commands: 客戶端向服務器發送消息的命令集合

首先在配置文件加入如下配置

<configSections>
  <section name="superSocket" type="SuperSocket.SocketEngine.Configuration.SocketServiceConfig, SuperSocket.SocketEngine"/>
</configSections>
<superSocket>
  <servers>
    <server name="ChatSocket" textEncoding="gb2312"
            serverType="XT.SocketService.AppServer.ChatServer, XT.SocketService"
            ip="Any" port="2020"
            maxConnectionNumber="1000">
    </server>
    <!-- 可以配置多個Server-->
  </servers>
</superSocket>

AppServer代碼如下

[AuthorisizeFilter]
public class ChatServer:AppServer<ChatSession>
{
    protected override bool Setup(IRootConfig rootConfig, IServerConfig config)
    {
        Console.WriteLine("准備讀取配置文件。。。。");
        return base.Setup(rootConfig, config);
    }

    protected override void OnStarted()
    {
        Console.WriteLine("Chat服務啟動。。。");
        base.OnStarted();
    }

    protected override void OnStopped()
    {
        Console.WriteLine("Chat服務停止。。。");
        base.OnStopped();
    }

    /// <summary>
    /// 新的連接
    /// </summary>
    /// <param name="session"></param>
    protected override void OnNewSessionConnected(ChatSession session)
    {
        Console.WriteLine($"Chat服務新加入的連接:{session.LocalEndPoint.Address.ToString()}");
        base.OnNewSessionConnected(session);
    }
}

Session代碼如下

/// <summary>
/// 表示用戶連接
/// </summary>
//[AuthorisizeFilter]
public class ChatSession : AppSession<ChatSession>
{
    public string Id { get; set; }

    public string PassWord { get; set; }

    public bool IsLogin { get; set; }

    public DateTime LoginTime { get; set; }

    public DateTime LastHbTime { get; set; }

    public bool IsOnline
    { 
        get
        {
            return this.LastHbTime.AddSeconds(10) > DateTime.Now;
        }
    }

    /// <summary>
    /// 消息發送
    /// </summary>
    /// <param name="message"></param>
    public override void Send(string message)
    {
        Console.WriteLine($"准備發送給{this.Id}:{message}");
        base.Send(message.Format());
    }

    protected override void OnSessionStarted()
    {
        this.Send("Welcome to SuperSocket Chat Server");
    }

    protected override void OnInit()
    {
        this.Charset = Encoding.GetEncoding("gb2312");
        base.OnInit();
    }

    protected override void HandleUnknownRequest(StringRequestInfo requestInfo)
    {
        Console.WriteLine("收到命令:" + requestInfo.Key.ToString());
        this.Send("不知道如何處理 " + requestInfo.Key.ToString() + " 命令");
    }

    /// <summary>
    /// 異常捕捉
    /// </summary>
    /// <param name="e"></param>
    protected override void HandleException(Exception e)
    {
        this.Send($"\n\r異常信息:{ e.Message}");
        //base.HandleException(e);
    }

    /// <summary>
    /// 連接關閉
    /// </summary>
    /// <param name="reason"></param>
    protected override void OnSessionClosed(CloseReason reason)
    {
        Console.WriteLine("鏈接已關閉。。。");
        base.OnSessionClosed(reason);
    }
}

Commands代碼如下 : 客戶端發送消息命令 Check 1 123456
Check 代表類名 ,1代表session.id(會話ID),1代表session.PassWord (會話密碼)

public class Check : CommandBase<ChatSession, StringRequestInfo>
{
    public override void ExecuteCommand(ChatSession session, StringRequestInfo requestInfo)
    {
        if (requestInfo.Parameters != null && requestInfo.Parameters.Length == 2)
        {
            ChatSession oldSession = session.AppServer.GetAllSessions().FirstOrDefault(a => requestInfo.Parameters[0].Equals(a.Id));
            if (oldSession != null) // 說過之前有用戶用這個Id 登錄過
            {
                oldSession.Send("您的賬號已經在他處登錄,您已經被踢下線了");
                oldSession.Close();
            }

            #region 這里就可以連接數據庫進行數據驗證做登錄
            ///---------------------
            #endregion
            session.Id = requestInfo.Parameters[0];
            session.PassWord = requestInfo.Parameters[1];
            session.IsLogin = true;
            session.LoginTime = DateTime.Now;

            session.Send("登錄成功");

            { // 獲取當前登錄用戶的離線消息 
                ChatDataManager.SendLogin(session.Id, c =>
                { 
                    session.Send($"{c.FromId} 給你發送消息:{c.Message} {c.Id}");
                });

            }
        }
        else
        {
            session.Send("參數錯誤");
        }
    }
}

離線消息存儲的相關類

public class ChatDataManager
{
    /// <summary>
    /// key是用戶id
    /// List 這個用戶的全部消息
    /// </summary>
    private static Dictionary<string, List<ChatModel>> Dictionary = new Dictionary<string, List<ChatModel>>();

    public static void Add(string userId, ChatModel model)
    {
        if (Dictionary.ContainsKey(userId))
        {
            Dictionary[userId].Add(model);
        }
        else
        {
            Dictionary[userId] = new List<ChatModel>() { model };
        }
    }
    public static void Remove(string userId, string modelId)
    {
        if (Dictionary.ContainsKey(userId))
        {
            Dictionary[userId] = Dictionary[userId].Where(m => m.Id != modelId).ToList();
        }
    }

    public static void SendLogin(string userId, Action<ChatModel> action)
    {
        if (Dictionary.ContainsKey(userId))
        {
            foreach (var item in Dictionary[userId])
            {
                action.Invoke(item);
                item.State = 1;
            }
        }
    }
}
/// <summary>
/// 一條消息的記錄
/// </summary>
public class ChatModel
{
    /// <summary>
    /// 每條分配個唯一Id
    /// </summary>
    public string Id { get; set; }
    /// <summary>
    /// 來源編號
    /// </summary>
    public string FromId { get; set; }
    /// <summary>
    /// 目標編號
    /// </summary>
    public string ToId { get; set; }
    /// <summary>
    /// 消息內容
    /// </summary>
    public string Message { get; set; }
    /// <summary>
    /// 消息時間
    /// </summary>
    public DateTime CreateTime { get; set; }
    /// <summary>
    /// 消息狀態  0未發送 1已發送待確認  2確認收到
    /// </summary>
    public int State { get; set; }
}

基本使用獲取離線消息

public class Chat : CommandBase<ChatSession, StringRequestInfo>
{
   public override void ExecuteCommand(ChatSession session, StringRequestInfo requestInfo)
   {
       // 還是傳遞兩個參數  1、 要發給誰 ToId    2、消息內容
       if (requestInfo.Parameters != null && requestInfo.Parameters.Length == 2)
       {
           string toId = requestInfo.Parameters[0];
           string message = requestInfo.Parameters[1];
           ChatSession toSession = session.AppServer.GetAllSessions().FirstOrDefault(a => toId.Equals(a.Id));
            
           string modelId = Guid.NewGuid().ToString();
           if (toSession != null) // 說過之前有用戶用這個Id 登錄過
           {
               toSession.Send($"{session.Id} 給你發消息:{message} {modelId}");
               ChatDataManager.Add(toId, new ChatModel()
               {
                   FromId = session.Id,
                   ToId = toId,
                   Message = message,
                   Id = modelId,
                   State = 1,// 待確認
                   CreateTime = DateTime.Now
               });
           }
           else
           {
               ChatDataManager.Add(toId, new ChatModel()
               {
                   FromId = session.Id,
                   ToId = toId,
                   Message = message,
                   Id = modelId,
                   State = 0,// 未發送
                   CreateTime = DateTime.Now
               }); 
               session.Send("消息未發送成功");
           }
       }
       else
       {
           session.Send("參數錯誤");
       }
   }
}
public class Confirm : CommandBase<ChatSession, StringRequestInfo>
{
    public override void ExecuteCommand(ChatSession session, StringRequestInfo requestInfo)
    { 
        if (requestInfo.Parameters != null && requestInfo.Parameters.Length == 1)
        {
            string modelId = requestInfo.Parameters[0]; 
            Console.WriteLine($"用戶{session.Id} 已確認,收到消息{modelId}");
            ChatDataManager.Remove(session.Id, modelId);
        }
        else
        {
            session.Send("參數錯誤");
        }
    }
}

心跳檢測:主要就是定時發送消息,沒接到消息就發起重連

public class HB : CommandBase<ChatSession, StringRequestInfo>
{
    public override void ExecuteCommand(ChatSession session, StringRequestInfo requestInfo)
    {
        if (requestInfo.Parameters != null && requestInfo.Parameters.Length == 1)
        {
            if ("R".Equals(requestInfo.Parameters[0]))
            {
                session.LastHbTime = DateTime.Now;
                session.Send("R");
            }
            else
            {
                session.Send("參數錯誤");
            }
        }
        else
        {
            session.Send("參數錯誤");
        }
    }
}

SuperSocket的AOP的使用

class AuthorisizeFilterAttribute : CommandFilterAttribute
{
     
    public override void OnCommandExecuting(CommandExecutingContext commandContext)
    { 
        ChatSession session = (ChatSession)commandContext.Session;
        string command = commandContext.CurrentCommand.Name; 
        if (!session.IsLogin)
        {
            if (!command.Equals("Check"))
            {
                session.Send($"請先登錄,再操作");
                commandContext.Cancel = true;
            }
            else
            {

            }
        }
        else if (!session.IsOnline)
        {
            session.LastHbTime = DateTime.Now;
        }

    }

    public override void OnCommandExecuted(CommandExecutingContext commandContext)
    {
        
    } 
}

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM