惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Microsoft Azure Blog
Microsoft Azure Blog
Google Online Security Blog
Google Online Security Blog
D
Darknet – Hacking Tools, Hacker News & Cyber Security
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
Simon Willison's Weblog
Simon Willison's Weblog
T
Threat Research - Cisco Blogs
C
CXSECURITY Database RSS Feed - CXSecurity.com
L
Lohrmann on Cybersecurity
www.infosecurity-magazine.com
www.infosecurity-magazine.com
Forbes - Security
Forbes - Security
P
Palo Alto Networks Blog
Schneier on Security
Schneier on Security
S
Schneier on Security
T
Tor Project blog
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
WordPress大学
WordPress大学
The Hacker News
The Hacker News
Hacker News - Newest:
Hacker News - Newest: "LLM"
罗磊的独立博客
Application and Cybersecurity Blog
Application and Cybersecurity Blog
F
Fortinet All Blogs
博客园 - 三生石上(FineUI控件)
小众软件
小众软件
C
Check Point Blog
Stack Overflow Blog
Stack Overflow Blog
Blog — PlanetScale
Blog — PlanetScale
雷峰网
雷峰网
S
Security @ Cisco Blogs
PCI Perspectives
PCI Perspectives
Spread Privacy
Spread Privacy
W
WeLiveSecurity
SecWiki News
SecWiki News
A
About on SuperTechFans
H
Help Net Security
博客园 - 司徒正美
Recent Commits to openclaw:main
Recent Commits to openclaw:main
爱范儿
爱范儿
S
Securelist
M
MIT News - Artificial intelligence
云风的 BLOG
云风的 BLOG
月光博客
月光博客
Jina AI
Jina AI
博客园 - 叶小钗
Vercel News
Vercel News
阮一峰的网络日志
阮一峰的网络日志
Recent Announcements
Recent Announcements
S
Secure Thoughts
The Cloudflare Blog
美团技术团队
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More

博客园 - Danny Chen

python asyncio 获取协程返回值和使用callback c#等待所有子线程执行完毕方法 Python virtualenv 查看当前正在运行的python进程 Mac 删除/卸载 自己安装的python python读取txt文件最后一行(文件大+文件小) pycharm 注册码/License server 2017年最新 Linux cat命令详解 在Mac平台上安装配置ELK时的一些总结 Mac上搭建ELK C#中用schema验证xml的合法性 C#中XML与对象之间的序列化、反序列化 C#位运算 SQL Server 权限管理 如何用C#动态编译、执行代码 C#动态调用WCF接口,两种方式任你选。 动态调用WCF服务 矩阵的坐标变换(转) 【.NET线程--进阶(一)】--线程方法详解
[C#]手把手教你打造Socket的TCP通讯连接(一)
Danny Chen · 2017-06-16 · via 博客园 - Danny Chen

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