使用SuperSocket实现自定义协议C/S设计

一、简介:

  21世纪是出于互联网+的时代,许多传统行业和硬件挂钩的产业也逐步转向了系统集成智能化,简单来说就是需要软硬件的结合。这时,软硬件通讯便是这里面最主要的技术点,我们需要做到的是让硬件能够听懂我们系统的指令,自定义协议便应运而生。

二、设计思路:

  1)引入SuperSocket所需要的各种项目文件

  2)新建两个WinForm添加具体功能

  3)启动项同样设置两个

  4)启动服务器监听

  5) 客户登陆

  6)服务器广播

三、代码实现

  1)引入项目文件,包含在项目中,这里包含是 右键解决方案>添加>现有项目

  2)新建两个WinForm,因为WinForm是一个有Program类的类库

    2.1)在项目中会有控件类型说明

    2.2)修改Text也就是显示给客户的名字

    2.3)修改 设计>Name 这个是为了在代码中设计功能用的名称

  3)现在我们去写服务器的启动功能

    服务器启动需要一个AppServer在体,我们定义这个载体为SocketRequestInfo,让他继承AppServer,从而调用SuperSocket的内部方法实现功能。然而定义SocketRequestInfo构造时可以传入参数来实现自定义,两个参数分别是:自定义的消息格式,以及需要一个session连接。而他的构造函数只需要将 数据,操作传给工厂(DefaultReceiveFilterFactory)来代做。

    3.1)MainForm : Form

/// <summary>
        /// 启动、停止
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void btnStartStop_Click(object sender, EventArgs e)
        {
            if (btnStartStop.Text.Equals("启动"))
            {

                SocketServer = new SuperSocketServer();
                SocketServer.Setup(new RootConfig(), config);
                SocketServer.NewRequestReceived += SocketServer_NewRequestReceived;
                SocketServer.NewSessionConnected += SocketServer_NewSessionConnected;
                SocketServer.SessionClosed += SocketServer_SessionClosed;

                SocketServer.Start();
                btnStartStop.Text = "停止";

            }
            else
            {
                SocketServer.Stop();
                SocketServer.Dispose();
                btnStartStop.Text = "启动";
            }
            lblCurrentState.Text = SocketServer.State.ToString();

        }

  类SuperSocketServer : AppServer<SocketProtocolSession, SocketRequestInfo>

    public class SuperSocketServer : AppServer<SocketProtocolSession, SocketRequestInfo>
    {
        public SuperSocketServer()
            : base(new DefaultReceiveFilterFactory<SocketRequestFilter, SocketRequestInfo>())//使用默认的接受过滤器工厂 
        {

        }
    }

  需要类SocketProtocolSession

    public class SocketProtocolSession : AppSession<SocketProtocolSession, SocketRequestInfo>
    {
        /// <summary>
        /// 当前用户名
        /// </summary>
        public string UserName { get; set; }

    }

  以及类SocketRequestInfo

/// <summary>
    /// 请求消息
    /// </summary>
    public class SocketRequestInfo : IRequestInfo
    {

        /// <summary>
        /// Key[继承自接口]
        /// </summary>
        public string Key { get; set; }

        /// <summary>
        /// 命令标识
        /// </summary>
        public byte Flag { get; set; }

        /// <summary>
        /// 命令字
        /// </summary>
        public byte Command { get; set; }


        /// <summary>
        /// 消息长度
        /// </summary>
        public int Length { get; set; }


        /// <summary>
        /// 消息内容
        /// </summary>
        public byte[] Data { get; set; }



    }

  和类SocketRequestFilter : FixedHeaderReceiveFilter<SocketRequestInfo>

public class SocketRequestFilter : FixedHeaderReceiveFilter<SocketRequestInfo>
    {
        /// <summary>
        /// 消息头长度
        /// </summary>
        protected const int headerSize = 4;

        /// <summary>
        /// 构造函数
        /// </summary>
        public SocketRequestFilter()
            : base(headerSize)
        {

        }

        /// <summary>
        /// 获取消息头
        /// </summary>
        /// <param name="header">头部</param>
        /// <param name="offset">偏移量</param>
        /// <param name="length">消息头长度</param>
        /// <returns></returns>
        protected override int GetBodyLengthFromHeader(byte[] header, int offset, int length)
        {
            return ((int)header[offset + 2] + (int)header[offset + 3] * 256);
        }

        /// <summary>
        /// 解析消息
        /// </summary>
        /// <param name="header">消息头</param>
        /// <param name="bodyBuffer">消息体</param>
        /// <param name="offset">偏移量</param>
        /// <param name="length">消息体长度</param>
        /// <returns></returns>
        protected override SocketRequestInfo ResolveRequestInfo(ArraySegment<byte> header, byte[] bodyBuffer, int offset, int length)
        {
            SocketRequestInfo request = new SocketRequestInfo();
            request.Flag = bodyBuffer[offset - headerSize + 0];
            request.Key = bodyBuffer.CloneRange(offset - headerSize, 1).ToHexString();
            request.Command = bodyBuffer[offset - headerSize + 1];
            request.Length = ((int)bodyBuffer[offset - headerSize + 2] + 256 * (int)bodyBuffer[offset - headerSize + 3]);
            request.Data = bodyBuffer.CloneRange(offset, length);
            return request;
        }
    }

    来实现这个实例的构造。

    3.2)填充配置信息,来启动服务

 //初始化服务配置
        IServerConfig config = new ServerConfig
        {
            Name = "SuperSocketServer",
            Ip = "Any",
            Port = 3000,
            MaxConnectionNumber = 1024,
            MaxRequestLength = 4096,
            ClearIdleSession = true,
            ClearIdleSessionInterval = 120,
            IdleSessionTimeOut = 120,
            KeepAliveInterval = 60
        };

  

    3.3)定义服务器的连接功功能

/// <summary>
        /// 新建连接
        /// </summary>
        /// <param name="session"></param>
        private void SocketServer_NewSessionConnected(SocketProtocolSession session)
        {
            txtMessage.Text += string.Format("{0}新建连接{1}", session.RemoteEndPoint.Address.ToString(), Environment.NewLine);
        }

  

    3.4)定义服务器的关闭功能

/// <summary>
        /// 断开连接
        /// </summary>
        /// <param name="session"></param>
        /// <param name="value"></param>
        private void SocketServer_SessionClosed(SocketProtocolSession session, SuperSocket.SocketBase.CloseReason value)
        {
            txtMessage.Text += string.Format("{0}断开连接{1}", session.RemoteEndPoint.Address.ToString(), Environment.NewLine);
        }

  

    3.5)定义服务器的接收信息功能

 /// <summary>
        /// 收到数据
        /// </summary>
        /// <param name="session"></param>
        /// <param name="requestInfo"></param>
        private void SocketServer_NewRequestReceived(SocketProtocolSession session, SocketRequestInfo requestInfo)
        {

            if (requestInfo.Flag == 0xAC)//0xAC类消息
            {
                if (requestInfo.Command == 0x01)//用户登录
                {
                    string userName = Encoding.UTF8.GetString(requestInfo.Data);
                    session.UserName = userName;
                    txtMessage.Text += string.Format("{0}登录{1}", userName, Environment.NewLine);
                    session.Send(ack, 0, ack.Length);//回复
                }
                if (!string.IsNullOrEmpty(session.UserName))//登录后才可以收消息
                {
                    if (requestInfo.Command == 0x02)//普通消息
                    {
                        string message = Encoding.UTF8.GetString(requestInfo.Data);
                        txtMessage.Text += string.Format("{0}消息:{1} {2}", session.UserName, message, Environment.NewLine);
                        session.Send(ack, 0, ack.Length);//回复
                    }
                    else if (requestInfo.Command == 0x03)
                    {

                    }
                }
            }
        }

  

    3.6)启动服务

  4)客户端连接

    4.1)同样的想要玩转客户端也需要一个这样的实例,而这个实例比较简单,只需要继承类 EasyClient就好了。

    4.2)在初始化这个实例的时候需要做的连接,关闭,错误返回,接收信息的操作如下代码

private void client_Error(object sender, ErrorEventArgs e)
        {
            txtMessage.Text += string.Format("连接错误{0}", Environment.NewLine);
        }

        private void client_Closed(object sender, EventArgs e)
        {
            txtMessage.Text += string.Format("连接断开{0}", Environment.NewLine);
            btnConnect.Text = "连接";
        }


        /// <summary>
        /// 发送注册包
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void client_Connected(object sender, EventArgs e)
        {
            txtMessage.Text += string.Format("连接成功{0}", Environment.NewLine);
            btnConnect.Text = "断开";
        }

        /// <summary>
        /// 收到消息
        /// </summary>
        private void OnMessageReceive(PackageInfo package)
        {
            if (package.Flag == 0xCA)
            {
                if (package.Command == 0xFE)//服务端默认应答
                {
                    txtMessage.Text += string.Format("服务端消息接受成功{0}", Environment.NewLine);
                }
                else if (package.Command == 0xFA)//服务器时间
                {
                    long ticks = BitConverter.ToInt64(package.Data, 0);
                    DateTime datetime = new DateTime(ticks);
                    txtMessage.Text += string.Format("服务器时间:{0:yyyy-MM-dd HH:mm:ss}{1}", datetime, Environment.NewLine);
                }
            }
        }

  以上部分实现了客户端数据向Server发送时的不带数据服务器接收,注入服务器所需要的业务逻辑,然后服务端发送数据给客户端则是以lambda表达式来接收。

  5)客户端登陆:

    5.1)登陆需要做的只是实例一个上边的继承了EasyClient的客户端实例

    5.2)使用该实例send到客户端,send的数据为字符数组

    5.3)哭护短接收信息以有可以直接对数据进行操作

        private void btnLogin_Click_1(object sender, EventArgs e)
        {
            if (client.IsConnected)
            {
                List<byte> buffer = new List<byte>();
                byte[] data = Encoding.UTF8.GetBytes(txtUserName.Text);

                buffer.Add(0xAC);
                buffer.Add(0x01);
                buffer.AddRange(BitConverter.GetBytes(data.Length).Take(2));
                buffer.AddRange(data);

                //数组小抽屉,也就是把数组放在抽屉里
                ArraySegment<byte> segment = new ArraySegment<byte>(buffer.ToArray());
                client.Send(segment);
            }
            else
            {
                MessageBox.Show("请先连接服务器", this.Text, MessageBoxButtons.OK, MessageBoxIcon.Error);
            }
        }

  

  6)客户端发送消息

    发送消息如上登陆

/// <summary>
        /// 发送消息
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        
        private void btnSend_Click_1(object sender, EventArgs e)
        {
            if (client.IsConnected)
            {
                List<byte> buffer = new List<byte>();
                byte[] data = Encoding.UTF8.GetBytes(txtMsgContent.Text);

                buffer.Add(0xAC);
                buffer.Add(0x02);
                buffer.AddRange(BitConverter.GetBytes(data.Length).Take(2));
                buffer.AddRange(data);

                ArraySegment<byte> segment = new ArraySegment<byte>(buffer.ToArray());
                client.Send(segment);
            }
            else
            {
                MessageBox.Show("请先连接服务器", this.Text, MessageBoxButtons.OK, MessageBoxIcon.Error);
            }
        }

  

  7)服务端广播信息

    广播消息如上登陆一样发送数组

/// <summary>
        /// 广播[将当前系统时间发送到所有在线客户端]
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void btnBroadcast_Click(object sender, EventArgs e)
        {
            var sessions = SocketServer.GetAllSessions();

            foreach (SocketProtocolSession session in sessions)
            {
                byte[] data = BitConverter.GetBytes(DateTime.Now.Ticks);

                List<byte> buffer = new List<byte>();
                buffer.Add(0xCA);
                buffer.Add(0xFA);
                buffer.AddRange(BitConverter.GetBytes(data.Length).Take(2));
                buffer.AddRange(data);
                session.Send(buffer.ToArray(), 0, buffer.Count);
            }
        }

  

  

三、总结

  1)使用superSocket就需要一个YourAppServer实例,这个实例必须继承字人家SuperSocket的AppServer才能调用。想要继承这个App Server就得能拿得到会话连接和会话数据并对数据进行一系列的操作。

   2)在客户端和服务端中都有连接的代码块,在服务端这么做侧重业务的实现,而客户端只需要一个谅解功能就可以了。

   3)在发送时上诉实例使用了两种方法,一种直接发送,第二种使用数组抽屉盒子,后者更优化是第二种可以解决一个问题就是出现断网时候可以自己缓存消息,接通以后继续传输而前者不行。

  5)增加项目时候要注意引入以后需要编译

   4)源码分享:http://pan.baidu.com/s/1kVqWdNp

  

原文地址:https://www.cnblogs.com/wenlong512/p/7469932.html