zoukankan      html  css  js  c++  java
  • GJM : 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

  • 相关阅读:
    笔记44 Hibernate快速入门(一)
    tomcat 启用https协议
    笔记43 Spring Security简介
    笔记43 Spring Web Flow——订购披萨应用详解
    笔记42 Spring Web Flow——Demo(2)
    笔记41 Spring Web Flow——Demo
    Perfect Squares
    Factorial Trailing Zeroes
    Excel Sheet Column Title
    Excel Sheet Column Number
  • 原文地址:https://www.cnblogs.com/TDou/p/6478805.html
Copyright © 2011-2022 走看看