zoukankan      html  css  js  c++  java
  • 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($"
    
    异常信息:{ 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)
        {
            
        } 
    }
  • 相关阅读:
    Java基础教程:Java内存区域
    Java基础教程:多线程基础——线程池
    微服务实践:服务治理
    微服务实践:服务设计
    微服务实践:什么是微服务
    SpringBoot学习笔记:读取配置文件
    Java进阶教程:使用Lombok提升开发效率
    Sagas模式
    执行力:Just Do It
    执行力:Just Do It
  • 原文地址:https://www.cnblogs.com/netlock/p/13564446.html
Copyright © 2011-2022 走看看