手把手教你打造Socket的TCP通讯连接

转自:http://www.cnblogs.com/Kation/archive/2013/03/06/2946761.html

本文章将讲解基于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接口。

 

复制代码
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);
}
复制代码

 

做一个类SocketHandler继承ISocketHandler接口

 

复制代码
/// <summary>
/// Socket处理程序
/// </summary>
public class SocketHandler : ISocketHandler
{
    /// <summary>
    /// 开始接收
    /// </summary>
    /// <param name="stream">Socket网络流</param>
    /// <param name="callback">回调函数</param>
    /// <param name="state">自定义状态</param>
    /// <returns>异步结果</returns>
    public IAsyncResult BeginReceive(Stream stream, AsyncCallback callback, object state)
    {

    }

    /// <summary>
    /// 结束接收
    /// </summary>
    /// <param name="asyncResult">异步结果</param>
    /// <returns>接收到的数据</returns>
    public 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>
    public IAsyncResult BeginSend(byte[] data, int offset, int count, Stream stream, AsyncCallback callback, object state)
    {

    }

    /// <summary>
    /// 结束发送
    /// </summary>
    /// <param name="asyncResult">异步结果</param>
    /// <returns>发送是否成功</returns>
    public bool EndSend(IAsyncResult asyncResult)
    {

    }
}
复制代码

 

增加两个属性与构造函数。

 

复制代码
    //异步处理关系集合
    private Dictionary<IAsyncResult, SocketHandlerState> StateSet;
    //发送队列
    private List<SocketHandlerState> SendQueue;

    /// <summary>
    /// 实例化Socket处理程序
    /// </summary>
    public SocketHandler()
    {
        StateSet = new Dictionary<IAsyncResult, SocketHandlerState>();
        SendQueue = new List<SocketHandlerState>();
    }
复制代码

 

StateSet可以保存我们的异步调用结果等数据

 

SendQueue用来做一个发送队列

 

接下来我们从发送数据开始。

 

由于需要用到Stream的异步方法,我们需要定义一个State类。

 

复制代码
internal class SocketHandlerState
{
    /// <summary>
    /// 数据
    /// </summary>
    public byte[] Data { get; set; }
    /// <summary>
    /// 异步结果
    /// </summary>
    public IAsyncResult AsyncResult { get; set; }
    /// <summary>
    /// Socket网络流
    /// </summary>
    public Stream Stream { get; set; }
    /// <summary>
    /// 异步回调函数
    /// </summary>
    public AsyncCallback AsyncCallBack { get; set; }
    /// <summary>
    /// 是否完成
    /// </summary>
    public bool Completed { get; set; }
    /// <summary>
    /// 数据长度
    /// </summary>
    public int DataLength { get; set; }
}
复制代码

 

因为我们需要返回IAsyncResult,所以我们继承该接口做一个SocketHandlerState类。

 

复制代码
/// <summary>
/// Socket异步操作状态
/// </summary>
public class SocketAsyncResult : IAsyncResult
{
    /// <summary>
    /// 实例化Socket异步操作状态
    /// </summary>
    /// <param name="state"></param>
    public SocketAsyncResult(object state)
    {
        AsyncState = state;
        AsyncWaitHandle = new AutoResetEvent(false);
    }

    /// <summary>
    /// 获取用户定义的对象,它限定或包含关于异步操作的信息。
    /// </summary>
    public object AsyncState { get; private set; }

    /// <summary>
    /// 获取用于等待异步操作完成的 System.Threading.WaitHandle。
    /// </summary>
    public WaitHandle AsyncWaitHandle { get; private set; }

    /// <summary>
    /// 获取一个值,该值指示异步操作是否同步完成。
    /// </summary>
    public bool CompletedSynchronously { get { return false; } }

    /// <summary>
    /// 获取一个值,该值指示异步操作是否已完成。
    /// </summary>
    public bool IsCompleted { get; internal set; }
}
复制代码

 

然后开始编写发送数据相关函数。

 

这里我将发送数据的大小限制为最大65535。

 

只需发送长度为2的头信息即可把数据长度发送到对方。

 

复制代码
    /// <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>
    public IAsyncResult BeginSend(byte[] data, int offset, int count, Stream stream, AsyncCallback callback, object state)
    {
        //data不能为null
        if (data == null)
            throw new ArgumentNullException("data");
        //offset不能小于0和超过data长度
        if (offset > data.Length || offset < 0)
            throw new ArgumentOutOfRangeException("offset");
        //count不能大于65535
        if (count <= 0 || count > data.Length - offset || count > ushort.MaxValue)
            throw new ArgumentOutOfRangeException("count");
        //stream不能为null
        if (stream == null)
            throw new ArgumentNullException("stream");
        //回调函数不能为null
        if (callback == null)
            throw new ArgumentNullException("callback");
        //stream异常
        if (!stream.CanWrite)
            throw new ArgumentException("stream不支持写入。");

        SocketAsyncResult result = new SocketAsyncResult(state);

        //初始化SocketHandlerState
        SocketHandlerState shs = new SocketHandlerState();
        shs.Data = data;
        shs.AsyncResult = result;
        shs.Stream = stream;
        shs.AsyncCallBack = callback;
        shs.DataLength = 0;

        //锁定SendQueue
        //避免多线程同时发送数据
        lock (SendQueue)
        {
            //添加状态
            SendQueue.Add(shs);
            //如果SendQueue数量大于1,则表示有数据尚未发送完成
            if (SendQueue.Count > 1)
                return result;
        }

        //获取数据长度
        //ushort的最大值为65535
        //转换为byte[]长度为2
        var dataLength = BitConverter.GetBytes((ushort)data.Length);
        //向对方发送长度为2的头信息,表示接下来要发送的数据长度
        stream.Write(dataLength, 0, dataLength.Length);
        //开始异步发送数据
        stream.BeginWrite(shs.Data, 0, shs.Data.Length, EndWrite, shs).AsyncWaitHandle.WaitOne();

        return result;
    }

    //stream异步结束写入
    private void EndWrite(IAsyncResult ar)
    {
        SocketHandlerState state = (SocketHandlerState)ar.AsyncState;

        //锁定StateSet
        lock (StateSet)
            StateSet.Add(state.AsyncResult, state);

        try
        {
            state.Stream.EndWrite(ar);
        }
        catch
        {
            //出现Socket异常,发送失败
            state.Completed = false;
            //允许等待线程继续
            ((AutoResetEvent)state.AsyncResult.AsyncWaitHandle).Set();
            //执行异步回调函数
            state.AsyncCallBack(state.AsyncResult);
            return;
        }
        //发送成功
        state.Completed = true;
        //允许等待线程继续
        ((AutoResetEvent)state.AsyncResult.AsyncWaitHandle).Set();
        //执行异步回调函数
        state.AsyncCallBack(state.AsyncResult);

        //锁定SendQueue
        lock (SendQueue)
        {
            SocketHandlerState prepare = null;
            //移除当前发送完成的数据
            SendQueue.Remove(state);
            //如果SendQueue还有数据存在,则继续发送
            if (SendQueue.Count > 0)
            {
                prepare = SendQueue[0];
            }
            if (prepare != null)
            {
                //获取数据长度
                //ushort的最大值为65535
                //转换为byte[]长度为2
                var dataLength = BitConverter.GetBytes((ushort)prepare.Data.Length);
                //向对方发送长度为2的头信息,表示接下来要发送的数据长度
                prepare.Stream.Write(dataLength, 0, dataLength.Length);
                //开始异步发送数据
                prepare.Stream.BeginWrite(prepare.Data, 0, prepare.Data.Length, EndWrite, prepare).AsyncWaitHandle.WaitOne();
            }
        }
    }

    /// <summary>
    /// 结束发送
    /// </summary>
    /// <param name="asyncResult">异步结果</param>
    /// <returns>发送是否成功</returns>
    public bool EndSend(IAsyncResult asyncResult)
    {
        //判断异步操作状态是否属于当前处理程序
        if (!StateSet.ContainsKey(asyncResult))
            throw new ArgumentException("无法识别的asyncResult。");
        SocketHandlerState state = StateSet[asyncResult];
        lock (StateSet)
            StateSet.Remove(asyncResult);
        return state.Completed;
    }
复制代码

 

接下来是接收数据的相关方法。

 

复制代码
    /// <summary>
    /// 开始接收
    /// </summary>
    /// <param name="stream">Socket网络流</param>
    /// <param name="callback">回调函数</param>
    /// <param name="state">自定义状态</param>
    /// <returns>异步结果</returns>
    public IAsyncResult BeginReceive(Stream stream, AsyncCallback callback, object state)
    {
        //stream不能为null
        if (stream == null)
            throw new ArgumentNullException("stream");
        //回调函数不能为null
        if (callback == null)
            throw new ArgumentNullException("callback");
        //stream异常
        if (!stream.CanRead)
            throw new ArgumentException("stream不支持读取。");

        SocketAsyncResult result = new SocketAsyncResult(state);

        //初始化SocketHandlerState
        SocketHandlerState shs = new SocketHandlerState();
        shs.Data = new byte[2];
        shs.AsyncResult = result;
        shs.Stream = stream;
        shs.AsyncCallBack = callback;
        shs.Completed = true;
        //开始异步接收长度为2的头信息
        //该头信息包含要接收的主要数据长度
        stream.BeginRead(shs.Data, 0, 2, EndRead, shs);
        return result;
    }

    //stream异步结束读取
    private void EndRead(IAsyncResult ar)
    {
        SocketHandlerState state = (SocketHandlerState)ar.AsyncState;
        int dataLength;
        try
        {
            dataLength = state.Stream.EndRead(ar);
        }
        catch
        {
            dataLength = 0;
        }
        //dataLength为0则表示Socket断开连接
        if (dataLength == 0)
        {
            lock (StateSet)
                StateSet.Add(state.AsyncResult, state);
            //设定接收到的数据位空byte数组
            state.Data = new byte[0];
            //允许等待线程继续
            ((AutoResetEvent)state.AsyncResult.AsyncWaitHandle).Set();
            //执行异步回调函数
            state.AsyncCallBack(state.AsyncResult);
            return;
        }

        //如果是已完成状态,则表示state.Data的数据是头信息
        if (state.Completed)
        {
            //设定状态为未完成
            state.Completed = false;
            //已接收得数据长度为0
            state.DataLength = 0;
            //获取主要数据长度
            var length = BitConverter.ToUInt16(state.Data, 0);
            //初始化数据的byte数组
            state.Data = new byte[length];
            try
            {
                //开始异步接收主要数据
                state.Stream.BeginRead(state.Data, 0, length, EndRead, state);
            }
            catch
            {
                //出现Socket异常
                lock (StateSet)
                    StateSet.Add(state.AsyncResult, state);
                state.Data = new byte[0];
                ((AutoResetEvent)state.AsyncResult.AsyncWaitHandle).Set();
                state.AsyncCallBack(state.AsyncResult);
            }
            return;
        }
        //接收到主要数据
        else
        {
            //判断是否接收了完整的数据
            if (dataLength + state.DataLength != state.Data.Length)
            {
                //增加已接收数据长度
                state.DataLength += dataLength;
                try
                {
                    //继续接收数据
                    state.Stream.BeginRead(state.Data, state.DataLength, state.Data.Length - state.DataLength, EndRead, state);
                }
                catch
                {
                    //出现Socket异常
                    lock (StateSet)
                        StateSet.Add(state.AsyncResult, state);
                    state.Data = new byte[0];
                    ((AutoResetEvent)state.AsyncResult.AsyncWaitHandle).Set();
                    state.AsyncCallBack(state.AsyncResult);
                    return;
                }
                return;
            }
            //接收完成
            state.Completed = true;
            lock (StateSet)
                StateSet.Add(state.AsyncResult, state);
            ((AutoResetEvent)state.AsyncResult.AsyncWaitHandle).Set();
            state.AsyncCallBack(state.AsyncResult);
        }
    }

    /// <summary>
    /// 结束接收
    /// </summary>
    /// <param name="asyncResult">异步结果</param>
    /// <returns>接收到的数据</returns>
    public byte[] EndReceive(IAsyncResult asyncResult)
    {
        //判断异步操作状态是否属于当前处理程序
        if (!StateSet.ContainsKey(asyncResult))
            throw new ArgumentException("无法识别的asyncResult。");
        SocketHandlerState state = StateSet[asyncResult];
        lock (StateSet)
            StateSet.Remove(asyncResult);
        return state.Data;
    }
复制代码

 

至此,SocketHandler的功能已经实现。

现在我们只剩下服务器端了。

服务器端包含两个类,一个TCPListener,一个TCPListenerClient。

TCPListener只管Start与Stop还有Accept。

TCPListenerClient是连接到服务器的客户端,相当于TCPClient在TCPListener上的体现。

现在我们开始编写TCPListener。

复制代码
/// <summary>
/// TCP监听端
/// </summary>
public class TCPListener : IEnumerable<TCPListenerClient>
{
    private Socket socket;
    private HashSet<TCPListenerClient> clients;

    /// <summary>
    /// 实例化TCP监听者。
    /// </summary>
    public TCPListener()
    {
        clients = new HashSet<TCPListenerClient>();
        IsStarted = false;
        Handler = new SocketHandler();
    }

    public ISocketHandler Handler { get; set; }

    private int port;
    /// <summary>
    /// 监听端口。
    /// </summary>
    public int Port
    {
        get { return port; }
        set
        {
            if (value < 0 || value > 65535)
                throw new ArgumentOutOfRangeException(port + "不是有效端口。");
            port = value;
        }
    }

    /// <summary>
    /// 服务启动中
    /// </summary>
    public bool IsStarted { get; private set; }

    /// <summary>
    /// 开始服务。
    /// </summary>
    public void Start()
    {

    }

    /// <summary>
    /// 停止服务。
    /// </summary>
    public void Stop()
    {

    }

    /// <summary>
    /// 接收完成时引发事件。
    /// </summary>
    public event EventHandler<SocketEventArgs> ReceiveCompleted;
    /// <summary>
    /// 接受客户完成时引发事件。
    /// </summary>
    public event EventHandler<SocketEventArgs> AcceptCompleted;
    /// <summary>
    /// 客户断开完成时引发事件。
    /// </summary>
    public event EventHandler<SocketEventArgs> DisconnectCompleted;
    /// <summary>
    /// 发送完成时引发事件。
    /// </summary>
    public event EventHandler<SocketEventArgs> SendCompleted;

    /// <summary>
    /// 获取客户端泛型。
    /// </summary>
    /// <returns></returns>
    public IEnumerator<TCPListenerClient> GetEnumerator()
    {
        return clients.GetEnumerator();
    }

    /// <summary>
    /// 获取客户端泛型。
    /// </summary>
    /// <returns></returns>
    System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
    {
        return clients.GetEnumerator();
    }

    /// <summary>
    /// 释放资源。
    /// </summary>
    /// <returns></returns>
    public void Dispose()
    {

    }
}
复制代码

TCPListener继承IEnumerable<TCPListenerClient>与IDisposable

clients保存所有已连接的客户端。

编写Start方法。

复制代码
    /// <summary>
    /// 开始服务。
    /// </summary>
    public void Start()
    {
        lock (this)
        {
            if (IsStarted)
                throw new InvalidOperationException("已经开始服务。");
            socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
            //绑定端口
            //可以引发端口被占用异常
            socket.Bind(new IPEndPoint(IPAddress.Any, port));
            //监听队列
            socket.Listen(512);
            //如果端口是0,则是随机端口,把这个端口赋值给port
            port = ((IPEndPoint)socket.LocalEndPoint).Port;
            //服务启动中设置为true
            IsStarted = true;
            //开始异步监听
            socket.BeginAccept(EndAccept, null);
        }
    }

    //异步监听结束
    private void EndAccept(IAsyncResult result)
    {
        //获得客户端Socket
        Socket clientSocket = socket.EndAccept(result);
        //实例化客户端类
        TCPListenerClient client = new TCPListenerClient(this, clientSocket);
        //增加事件钩子
        client.SendCompleted += client_SendCompleted;
        client.ReceiveCompleted += client_ReceiveCompleted;
        client.DisconnectCompleted += client_DisconnectCompleted;
        socket.BeginAccept(EndAccept, null);

        //增加客户端
        lock (clients)
            clients.Add(client);

        //客户端连接事件
        if (AcceptCompleted != null)
            AcceptCompleted(this, new SocketEventArgs(client, SocketAsyncOperation.Accept));
    }

    //客户端断开连接
    private void client_DisconnectCompleted(object sender, SocketEventArgs e)
    {
        //移除客户端
        lock (clients)
            clients.Remove((TCPListenerClient)e.Socket);

        e.Socket.DisconnectCompleted -= client_DisconnectCompleted;
        e.Socket.ReceiveCompleted -= client_ReceiveCompleted;
        e.Socket.SendCompleted -= client_SendCompleted;
        if (DisconnectCompleted != null)
            DisconnectCompleted(this, e);
    }

    //收到客户端发送的数据
    private void client_ReceiveCompleted(object sender, SocketEventArgs e)
    {
        if (ReceiveCompleted != null)
            ReceiveCompleted(this, e);
    }

    //向客户端发送数据完成
    private void client_SendCompleted(object sender, SocketEventArgs e)
    {
        if (SendCompleted != null)
            SendCompleted(this, e);
    }
复制代码

编写Stop与Dispose方法。

复制代码
    /// <summary>
    /// 停止服务。
    /// </summary>
    public void Stop()
    {
        lock (this)
        {
            if (!IsStarted)
                throw new InvalidOperationException("没有开始服务。");
            foreach (TCPListenerClient client in clients)
            {
                client.Disconnect();
                client.DisconnectCompleted -= client_DisconnectCompleted;
                client.ReceiveCompleted -= client_ReceiveCompleted;
                client.SendCompleted -= client_SendCompleted;
            }
            socket.Close();
            socket = null;
            IsStarted = false;
        }
    }

    /// <summary>
    /// 释放资源
    /// </summary>
    public void Dispose()
    {
        if (socket == null)
            return;
        Stop();
    }
复制代码

轮到TCPListenerClient了,TCPListenerClient其实和TCPClient差不多,也是要继承ISocket和IDisposable。

既然重复代码做么多,要不要合并起来呢?答案是肯定的。

做一个SocketBase类,继承ISocket和IDisposable。

大部分代码直接从TCPClient复制过来。

View Code
复制代码
public class SocketBase : ISocket, IDisposable
{
    protected Socket Socket { get; private set; }
    protected Stream Stream { get; set; }

    /// <summary>
    /// 实例化TCP客户端。
    /// </summary>
    public SocketBase(Socket socket, ISocketHandler socketHandler)
    {
        if (socket == null)
            throw new ArgumentNullException("socket");
        if (socketHandler == null)
            throw new ArgumentNullException("socketHandler");
        Socket = socket;
        Handler = socketHandler;
    }
    
    /// <summary>
    /// Socket处理程序
    /// </summary>
    public ISocketHandler Handler { get; set; }

    /// <summary>
    /// 获取是否已连接。
    /// </summary>
    public bool IsConnected { get { return Socket.Connected; } }

    #region 断开连接

    /// <summary>
    /// 断开与服务器的连接。
    /// </summary>
    public void Disconnect()
    {
        //判断是否已连接
        if (!IsConnected)
            throw new SocketException(10057);
        lock (this)
        {
            //Socket异步断开并等待完成
            Socket.BeginDisconnect(true, EndDisconnect, true).AsyncWaitHandle.WaitOne();
        }
    }

    /// <summary>
    /// 异步断开与服务器的连接。
    /// </summary>
    public void DisconnectAsync()
    {
        //判断是否已连接
        if (!IsConnected)
            throw new SocketException(10057);
        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));
    }

    #endregion

    #region 发送数据

    /// <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 });
        }
    }

    #endregion

    #region 接收数据

    protected 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 });
    }

    #endregion

    #region 事件

    ///// <summary>
    ///// 断开完成时引发事件。
    ///// </summary>
    public event EventHandler<SocketEventArgs> DisconnectCompleted;
    ///// <summary>
    ///// 接收完成时引发事件。
    ///// </summary>
    public event EventHandler<SocketEventArgs> ReceiveCompleted;
    ///// <summary>
    ///// 发送完成时引发事件。
    ///// </summary>
    public event EventHandler<SocketEventArgs> SendCompleted;

    #endregion

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

然后我们再写TCPListenerClient,继承SocketBase。

复制代码
public class TCPListenerClient : SocketBase
{
    internal TCPListenerClient(TCPListener listener, Socket socket)
        :base(socket,listener.Handler)
    {
        this["RemoteEndPoint"] = socket.RemoteEndPoint;
        //创建Socket网络流
        Stream = new NetworkStream(socket);
//设置服务器
        Listener = listener;            
        data = new Dictionary<string, object>();

        //开始异步接收数据
        SocketAsyncState state = new SocketAsyncState();
        Handler.BeginReceive(Stream, EndReceive, state);
    }

    public TCPListener Listener { get; private set; }
}
复制代码

我们还可以给TCPListenerClient加上点东西,比如类似Session的东西。

复制代码
    private Dictionary<string, object> data;

    public object this[string key]
    {
        get
        {
            key = key.ToLower();
            if (data.ContainsKey(key))
                return data[key];
            return null;
        }
        set
        {

            key = key.ToLower();
            if (value == null)
            {
                if (data.ContainsKey(key))
                    data.Remove(key);
                return;
            }
            if (data.ContainsKey(key))
                data[key] = value;
            else
                data.Add(key, value);
        }
    }
复制代码

为构造函数添加以下代码。

        data = new Dictionary<string, object>();
        //保存IP地址到字典
        this["RemoteEndPoint"] = socket.RemoteEndPoint;

这样,我们的TCPListenerClient就完成了。

接下来我们再把TCPClient修改以下,继承SocketBase。

View Code
复制代码
/// <summary>
/// TCP客户端
/// </summary>
public class TCPClient : SocketBase
{
    /// <summary>
    /// 实例化TCP客户端。
    /// </summary>
    public TCPClient()
        : base(new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp), new SocketHandler())
    {
    }

    public bool IsUseAuthenticate { get; set; }

    /// <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);
        if (IsUseAuthenticate)
        {
            NegotiateStream negotiate = new NegotiateStream(Stream);
            negotiate.AuthenticateAsClient();
            while (!negotiate.IsMutuallyAuthenticated)
            {
                Thread.Sleep(10);
            }
        }
        //连接完成
        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;
}
复制代码

所有工作,全部完成。

这个Socket还有很多功能可以增加、改造。

比如你自己写一个Handler内置加密解密,或者压缩与解压缩。

还可以再改写一下Stream,可以弄成NegotiateStream验证等等。

服务器开启后,开始异步监听客户端连接。客户端连接时,实例化TCPListenerClient,并开始异步监听数据。接收到数据时,判断数据长度,0则为断开连接,不为0则引发接收数据完成事件。

可以通过TCPListenerClient发送数据或断开连接。

服务器关闭时要先断开所有客户端连接。

客户端连接服务器,开始异步接收服务器数据。接收到数据时,判断数据长度,0则为断开连接,不为0则引发接收数据完成事件。

客户端连接服务器后,可以发送数据与断开连接。

SocketHandler是专门处理接收发送的对象。

发送数据时,要判断发送队列是否有数据正在发送或等待发送。如果有数据,则把要发送的数据加入发送队列。

发送数据过程,先发送要发送数据的长度=>ushor类型=>byte[]类型。然后再发送主数据。

发送完成时,判断发送队列是否还有数据,有则继续发送。返回是否发送成功。遇到异常则不成功。

接收数据时,先接收byte[2]的主要数据长度头信息,转换为ushort类型。

然后接收这个长度的数据,EndRead里要判断是否接收完全,不完全则继续接收。

接收完成后直接返回接收到的数据。

 

下面发一个简易测试延迟程序的示例代码。

客户端。

View Code
复制代码
namespace PingTesterClient
{
    class Program
    {

        static TCPClient client;
        static void Main(string[] args)
        {
            client = new TCPClient();
            client.ReceiveCompleted += Receive;
            Console.WriteLine("请输入IP地址:");
            client.Connect(new System.Net.IPEndPoint(IPAddress.Parse(Console.ReadLine()), 5000));

            byte[] data = BitConverter.GetBytes(DateTime.Now.TimeOfDay.TotalMilliseconds);
            client.SendAsync(data);

            Console.ReadLine();
        }

        private static void Receive(object sender, SocketEventArgs e)
        {
            Console.WriteLine(DateTime.Now.TimeOfDay.TotalMilliseconds - BitConverter.ToDouble(e.Data, 0));

            System.Threading.Thread.Sleep(100);
            byte[] data = BitConverter.GetBytes(DateTime.Now.TimeOfDay.TotalMilliseconds);
            client.SendAsync(data);
        }
    }
}
复制代码

服务器。

View Code
复制代码
namespace PingTesterServer
{
    class Program
    {
        static void Main(string[] args)
        {
            var listener = new TCPListener();
            listener.Port = 5000;
            listener.ReceiveCompleted += listener_ReceiveCompleted;
            listener.Start();

            Console.ReadLine();
        }

        static void listener_ReceiveCompleted(object sender, SocketEventArgs e)
        {
            e.Socket.SendAsync(e.Data);
        }
    }
}
复制代码

客户端发送当前时间转byte[]数据

服务器接收到后又发送客户端发送的数据。

客户端收到数据后,用当前时间减去收到的时间,除以2,得到网络延迟。

 

posted @ 2013-04-11 18:46  ADTL  阅读(777)  评论(0)    收藏  举报