• [C#]手把手教你打造Socket的TCP通讯连接(一)


    本文章将讲解基于TCP连接的Socket通讯,使用Socket异步功能,并且无粘包现象,通过事件驱动使用。

    在编写Socket代码之前,我们得要定义一下Socket的基本功能。

    作为一个TCP连接,不论是客户端还是服务器端,它都得有以下接口:

    public interface ISocket
    {
        /// <summary>
        /// 获取是否已连接。
        /// </summary>
        bool IsConnected { get; }
        /// <summary>
        /// 发送数据。
        /// </summary>
        /// <param name="data">要发送的数据。</param>
        void Send(byte[] data);
        /// <summary>
        /// 异步发送数据。
        /// </summary>
        /// <param name="data">要发送的数据。</param>
        void SendAsync(byte[] data);
        /// <summary>
        /// 断开连接。
        /// </summary>
        void Disconnect();
        /// <summary>
        /// 异步断开连接。
        /// </summary>
        void DisconnectAsync();        
        /// <summary>
        /// 断开完成时引发事件。
        /// </summary>
        event EventHandler<SocketEventArgs> DisconnectCompleted;
        /// <summary>
        /// 接收完成时引发事件。
        /// </summary>
        event EventHandler<SocketEventArgs> ReceiveCompleted;
        /// <summary>
        /// 发送完成时引发事件。
        /// </summary>
        event EventHandler<SocketEventArgs> SendCompleted;
    }

    用到的事件参数SocketEventArgs。

    /// <summary>
    /// Socket事件参数
    /// </summary>
    public class SocketEventArgs : EventArgs
    {
        /// <summary>
        /// 实例化Socket事件参数
        /// </summary>
        /// <param name="socket">相关Socket</param>
        /// <param name="operation">操作类型</param>
        public SocketEventArgs(ISocket socket, SocketAsyncOperation operation)
        {
            if (socket == null)
                throw new ArgumentNullException("socket");
            Socket = socket;
            Operation = operation;
        }
    
        /// <summary>
        /// 获取或设置事件相关数据。
        /// </summary>
        public byte[] Data { get; set; }
    
        /// <summary>
        /// 获取数据长度。
        /// </summary>
        public int DataLength { get { return Data == null ? 0 : Data.Length; } }
    
        /// <summary>
        /// 获取事件相关Socket
        /// </summary>
        public ISocket Socket { get; private set; }
    
        /// <summary>
        /// 获取事件操作类型。
        /// </summary>
        public SocketAsyncOperation Operation { get; private set; }
    }

    因为作为客户端只管收发,比较简单,所以这里从客户端开始做起。

    定义类TCPClient继承接口ISocket和IDisposable

    /// <summary>
    /// TCP客户端
    /// </summary>
    public class TCPClient : ISocket, IDisposable
    {
        /// <summary>
        /// 获取是否已连接。
        /// </summary>
        public bool IsConnected { get; }
        /// <summary>
        /// 发送数据。
        /// </summary>
        /// <param name="data">要发送的数据。</param>
        public void Send(byte[] data)
        {
    
        }
        /// <summary>
        /// 异步发送数据。
        /// </summary>
        /// <param name="data">要发送的数据。</param>
        public void SendAsync(byte[] data)
        {
    
        }
        /// <summary>
        /// 断开连接。
        /// </summary>
        public void Disconnect()
        {
    
        }
        /// <summary>
        /// 异步断开连接。
        /// </summary>
        public void DisconnectAsync()
        {
    
        }      
        /// <summary>
        /// 断开完成时引发事件。
        /// </summary>
        public event EventHandler<SocketEventArgs> DisconnectCompleted;
        /// <summary>
        /// 接收完成时引发事件。
        /// </summary>
        public event EventHandler<SocketEventArgs> ReceiveCompleted;
        /// <summary>
        /// 发送完成时引发事件。
        /// </summary>
        public event EventHandler<SocketEventArgs> SendCompleted;
    
        /// <summary>
        /// 释放资源。
        /// </summary>
        public void Dispose()
        {
    
        }
    }

    并在此之上,增加以下方法

        /// <summary>
        /// 连接至服务器。
        /// </summary>
        /// <param name="endpoint">服务器终结点。</param>
        public void Connect(IPEndPoint endpoint)
        {
    
        }
    
        /// <summary>
        /// 异步连接至服务器。
        /// </summary>
        /// <param name="endpoint"></param>
        public void ConnectAsync(IPEndPoint endpoint)
        {
    
        }
    

    下面我们开始编写构造函数,实例化一个Socket并保存到私有变量里。

    把IsConnected指向Socket.Connected。

        private Socket Socket;
        private Stream Stream;
        /// <summary>
        /// 实例化TCP客户端。
        /// </summary>
        public TCPClient()
        {
            Socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
        }
    
        /// <summary>
        /// 获取是否已连接。
        /// </summary>
        public bool IsConnected { get { return Socket.Connected; } }

    因为接下来我们开始编写Socket的异步功能,所以在此之前,我们要做一个状态类,用来保存异步状态。

    internal class SocketAsyncState
    {
        /// <summary>
        /// 是否完成。
        /// </summary>
        public bool Completed { get; set; }
    
        /// <summary>
        /// 数据
        /// </summary>
        public byte[] Data { get; set; }
    /// <summary>
        /// 是否异步
        /// </summary>
        public bool IsAsync { get; set; }
    }

    下面我们开始编写TCP连接功能。

        /// <summary>
        /// 连接至服务器。
        /// </summary>
        /// <param name="endpoint">服务器终结点。</param>
        public void Connect(IPEndPoint endpoint)
        {
            //判断是否已连接
            if (IsConnected)
                throw new InvalidOperationException("已连接至服务器。");
            if (endpoint == null)
                throw new ArgumentNullException("endpoint");
            //锁定自己,避免多线程同时操作
            lock (this)
            {
                SocketAsyncState state = new SocketAsyncState();
                //Socket异步连接
                Socket.BeginConnect(endpoint, EndConnect, state).AsyncWaitHandle.WaitOne();
                //等待异步全部处理完成
                while (!state.Completed) { }
            }
        }
    
        /// <summary>
        /// 异步连接至服务器。
        /// </summary>
        /// <param name="endpoint"></param>
        public void ConnectAsync(IPEndPoint endpoint)
        {
            //判断是否已连接
            if (IsConnected)
                throw new InvalidOperationException("已连接至服务器。");
            if (endpoint == null)
                throw new ArgumentNullException("endpoint");
            //锁定自己,避免多线程同时操作
            lock (this)
            {
                SocketAsyncState state = new SocketAsyncState();
                //设置状态为异步
                state.IsAsync = true;
                //Socket异步连接
                Socket.BeginConnect(endpoint, EndConnect, state);
            }
        }
    
        private void EndConnect(IAsyncResult result)
        {
            SocketAsyncState state = (SocketAsyncState)result.AsyncState;
        
            try
            {
                Socket.EndConnect(result);
            }
            catch
            {
                //出现异常,连接失败。
                state.Completed = true;
                //判断是否为异步,异步则引发事件
                if (state.IsAsync && ConnectCompleted != null)
                    ConnectCompleted(this, new SocketEventArgs(this, SocketAsyncOperation.Connect));
                return;
            }
        
            //连接成功。
            //创建Socket网络流
            Stream = new NetworkStream(Socket);
            //连接完成
            state.Completed = true;
            if (state.IsAsync && ConnectCompleted != null)
            {
                ConnectCompleted(this, new SocketEventArgs(this, SocketAsyncOperation.Connect));
            }
            
            //开始接收数据
            Handler.BeginReceive(Stream, EndReceive, state);
        }

        /// <summary>
        /// 连接完成时引发事件。
       
    /// </summary>
        public event EventHandler<SocketEventArgs> ConnectCompleted;

    以上为连接服务器的代码,EndConnect中最后的Handler为一个处理IO收发的类,这留到后面再说。

    接下来我们开始做断开服务器的方法。

        /// <summary>
        /// 断开与服务器的连接。
        /// </summary>
        public void Disconnect()
        {
            //判断是否已连接
            if (!IsConnected)
                throw new InvalidOperationException("未连接至服务器。");
            lock (this)
            {
                //Socket异步断开并等待完成
                Socket.BeginDisconnect(true, EndDisconnect, true).AsyncWaitHandle.WaitOne();
            }
        }
    
        /// <summary>
        /// 异步断开与服务器的连接。
        /// </summary>
        public void DisconnectAsync()
        {
            //判断是否已连接
            if (!IsConnected)
                throw new InvalidOperationException("未连接至服务器。");
            lock (this)
            {
                //Socket异步断开
                Socket.BeginDisconnect(true, EndDisconnect, false);
            }
        }
    
        private void EndDisconnect(IAsyncResult result)
        {
            try
            {
                Socket.EndDisconnect(result);
            }
            catch
            {
    
            }
            //是否同步
            bool sync = (bool)result.AsyncState;
            
            if (!sync && DisconnectCompleted!=null)
            {
                DisconnectCompleted(this, new SocketEventArgs(this, SocketAsyncOperation.Disconnect));
            }
        }
    
        //这是一个给收发异常准备的断开引发事件方法
        private void Disconnected(bool raiseEvent)
        {
            if (raiseEvent && DisconnectCompleted != null)
                DisconnectCompleted(this, new SocketEventArgs(this, SocketAsyncOperation.Disconnect));
        }

    至此,我们已经完成了客户端的连接于断开功能。

    现在我们开始写客户端的发送接收功能。

    对于Socket的发送与接收,在大量数据吞吐的时候,容易造成粘包问题,要解决这个问题,我们先定义一个ISocketHandler接口。

    该接口定义了Socket的发送与接收。

    public interface ISocketHandler
    {
        /// <summary>
        /// 开始接收
        /// </summary>
        /// <param name="stream">Socket网络流</param>
        /// <param name="callback">回调函数</param>
        /// <param name="state">自定义状态</param>
        /// <returns>异步结果</returns>
        IAsyncResult BeginReceive(Stream stream, AsyncCallback callback, object state);
        /// <summary>
        /// 结束接收
        /// </summary>
        /// <param name="asyncResult">异步结果</param>
        /// <returns>接收到的数据</returns>
        byte[] EndReceive(IAsyncResult asyncResult);
        /// <summary>
        /// 开始发送
        /// </summary>
        /// <param name="data">要发送的数据</param>
        /// <param name="offset">数据偏移</param>
        /// <param name="count">发送长度</param>
        /// <param name="stream">Socket网络流</param>
        /// <param name="callback">回调函数</param>
        /// <param name="state">自定义状态</param>
        /// <returns>异步结果</returns>
        IAsyncResult BeginSend(byte[] data, int offset, int count, Stream stream, AsyncCallback callback, object state);
        /// <summary>
        /// 结束发送
        /// </summary>
        /// <param name="asyncResult">异步结果</param>
        /// <returns>发送是否成功</returns>
        bool EndSend(IAsyncResult asyncResult);
    }

    在TCPClient中添加一个属性。

        /// <summary>
        /// Socket处理程序
        /// </summary>
        public ISocketHandler Handler { get; set; }

    这个ISocketHandler在上面的EndConnect里有使用到BeginReceive()。

    而使用BeginReceive的回调函数则是这个。

        private void EndReceive(IAsyncResult result)
        {
            SocketAsyncState state = (SocketAsyncState)result.AsyncState;
            //接收到的数据
            byte[] data = Handler.EndReceive(result);
            //如果数据长度为0,则断开Socket连接
            if (data.Length == 0)
            {
                Disconnected(true);
                return;
            }
    
            //再次开始接收数据
            Handler.BeginReceive(Stream, EndReceive, state);
    
            //引发接收完成事件
            if (ReceiveCompleted != null)
                ReceiveCompleted(this, new SocketEventArgs(this, SocketAsyncOperation.Receive) { Data = data });
        }

    有了这个回调函数,我们的客户端就能持续的接收数据。

    现在剩下发送数据的功能要完成。

        /// <summary>
        /// 发送数据。
        /// </summary>
        /// <param name="data">要发送的数据。</param>
        public void Send(byte[] data)
        {
            //是否已连接
            if (!IsConnected)
                throw new SocketException(10057);
            //发送的数据不能为null
            if (data == null)
                throw new ArgumentNullException("data");
            //发送的数据长度不能为0
            if (data.Length == 0)
                throw new ArgumentException("data的长度不能为0");
    
            //设置异步状态
            SocketAsyncState state = new SocketAsyncState();
            state.IsAsync = false;
            state.Data = data;
            try
            {
                //开始发送数据
                Handler.BeginSend(data, 0, data.Length, Stream, EndSend, state).AsyncWaitHandle.WaitOne();
            }
            catch
            {
                //出现异常则断开Socket连接
                Disconnected(true);
            }
        }
    
        /// <summary>
        /// 异步发送数据。
        /// </summary>
        /// <param name="data">要发送的数据。</param>
        public void SendAsync(byte[] data)
        {
            //是否已连接
            if (!IsConnected)
                throw new SocketException(10057);
            //发送的数据不能为null
            if (data == null)
                throw new ArgumentNullException("data");
            //发送的数据长度不能为0
            if (data.Length == 0)
                throw new ArgumentException("data的长度不能为0");
    
            //设置异步状态
            SocketAsyncState state = new SocketAsyncState();
            state.IsAsync = true;
            state.Data = data;
            try
            {
                //开始发送数据并等待完成
                Handler.BeginSend(data, 0, data.Length, Stream, EndSend, state);
            }
            catch
            {
                //出现异常则断开Socket连接
                Disconnected(true);
            }
        }
    
        private void EndSend(IAsyncResult result)
        {
            SocketAsyncState state = (SocketAsyncState)result.AsyncState;
            
            //是否完成
            state.Completed = Handler.EndSend(result);
            //没有完成则断开Socket连接
            if (!state.Completed)
                Disconnected(true);
            //引发发送结束事件
            if (state.IsAsync && SendCompleted != null)
            {
                SendCompleted(this, new SocketEventArgs(this, SocketAsyncOperation.Send) { Data = state.Data });
            }
        }

    至此,客户端的发送接收也完成了。

    我们再写一下释放资源的方法。

        /// <summary>
        /// 释放资源
        /// </summary>
        public void Dispose()
        {
            lock (this)
            {
                if (IsConnected)
                    Socket.Disconnect(false);
                Socket.Close();
            }
        }

    整个客户端编写完成。

    下一篇将讲解ISocketHandler的实现方法,用ISocketHandler来完成Socket的IO工作。

    你可以在ISocketHandler中定义你自己的Socket通讯协议。

    原文地址:http://www.cnblogs.com/Kation/archive/2013/03/06/2946761.html

  • 相关阅读:
    bzoj 3993: [SDOI2015]星际战争
    bzoj 4066: 简单题
    bzoj 3611: [Heoi2014]大工程
    bzoj 3530: [Sdoi2014]数数
    bzoj 3529: [Sdoi2014]数表
    bzoj 3504: [Cqoi2014]危桥
    bzoj 3489: A simple rmq problem
    bzoj 3211: 花神游历各国
    bzoj 3196: Tyvj 1730 二逼平衡树
    bzoj 3172: [Tjoi2013]单词
  • 原文地址:https://www.cnblogs.com/Kation/p/2946761.html
Copyright © 2020-2023  润新知