C#中套接字对APM模式的封装

4 篇文章 0 订阅
2 篇文章 0 订阅

C#中套接字对APM模式的封装

在.Net4.5中支持的三种异步模型:
基于事件的异步模式 (Event-based Asynchronous Pattern, EAP)
异步编程模型 (Asynchronous Programming Model, APM)
基于任务的异步模式 (Task-based Asynchronous Pattern, TAP)

EAP特性:
   - 有一个或多个 [方法名]Async() 方法
   - 可能含有 [方法名]Completed 的事件,用于在异步方法结束时作为回调
   - 可能含有 [方法名]AsyncCancel() 的方法,用于取消异步过程
举个例子:在Sysetm.Net.WebClient类中, 有以下成员

    string DownloadString(string Address) // 同步下载字符串
    void DownloadStringAsync(string Address) // 异步下载字符串
    DownloadStringCompleted // 在异步下载完成之后触发的事件
    // 通常的做法是调用Async方法, 并且在Completed事件触发后对UI做出响应
    // 但是缺点就是可能会为了响应完成事件在类中生成太多方法



APM特性:
   - 返回值IAsyncResult
   - 有一对方法, Begin[方法名] () 和 End[方法名] ();
   - 在调用 Begin[方法名] () 之后任务完成时需要调用 End[方法名] () 获取任务结果
举个例子:典型的System.Net.Sockets.Socket 下的 BeginConnect(), EndConnect() 等



TAP特性:
   - 用一个方法表示异步操作和它的结果



在.Net4.5中增加了async await两个关键字的支持
await用于阻塞当前线程,等待调用函数完成
方法内部使用await关键字的话在方法签名中需要加上async
当方法的返回值是Task或者Task<T>时,方法就是可等待的

T val = await Method();



System.Net.Sockets命名空间下的套接字提供了APM的异步模型
本文主要介绍如何将其封装成其它的异步模型

以下代码中均不包含异常处理, 多线程处理, 只做简单演示用
以下代码中均不包含异常处理, 多线程处理, 只做简单演示用
以下代码中均不包含异常处理, 多线程处理, 只做简单演示用
重要的事情说三遍

class MySocket
{
    Socket socket = null;
    // 构造函数以及socket的初始化在此省略
}



首先是Connect

    public Task ConnectAsync(string host, int port)
    {
            DnsEndPoint endpoint = new DnsEndPoint(host, port);
            return Task.Factory.StartNew(()=>
            {
                // 在新线程中调用套接字的连接
                // 可以是异步也可以是同步
                // 异步连接需要阻塞创建的新线程以让Task在套接字连接建立之后结束(而不是在调用BeginConnect或者ConnectAsync之后立即结束)
                // 异步连接可以使用ManualResetEvent对象阻塞线程
                socket.Connect(endpoint);

                // 这里开始接收消息
                BeginReceive();
                return;
            });
    }



接下来需要接收来自对方的消息
由于接收消息是被动的(需要对方发送消息),因此个人偏好于将消息的接收封装成事件(EAP

// 在这里为了简化操作委托有三个参数,并不符合事件的设计规范
public delegate void SocketMessageReceivedEventHandler(object sender, byte[] data, int len);
class MySocket
{
    public event SocketMessageReceivedEventHandler MessageReceived;
    private Task recvTask = null;
    private byte[] buffer = new byte[2048];

    ...

    // 在套接字连接建立成功之后调用BeginReceive以准备消息接收
    // 虽然是BeginReceive但是它不是APM模型
    private void BeginReceive()
    {
        // 创建一个新任务
        // 不能等待它,因为它是一个无限循环
        recvTask = Task.Factory.StartNew(()=>
        {
            while(true)
            {
                // 套接字的数据接收有两个陷阱:粘包和包分段,请注意

                // 这里演示同步接收
                // 如果是异步接收, 在回调函数中调用事件并继续接收数据即可
                int actualDataLength = socket.Receive(buffer, buffer.Length, SocketFlags.None);

                if(MessageReceived != null)
                    // 再次强调,在这里为了简化操作委托有三个参数,并不符合事件的设计规范
                    // 如果需要更多信息,请使用自定义类型
                    MessageReceived(this, buffer, actualDataLength);
            }
        });
    }
}



之后是处理消息的发送

    // 这个是同步版本
    public Task<int> SendSyncVersionAsync(byte[] data)
    {
        return Task.Factory.StartNew<int>(()=>
        {
            return socket.Send(data);
        });
    }
    // 这个是异步版本
    // 异步版本中演示了TaskCompletionSource的基本使用方法
    public Task<int> SendAsyncVersionAsync(byte[] data)
    {
        TaskCompletionSource<int> tcs = new TaskCompletionSource<int>();
        socket.BeginSend(data, 0, data.Length, SocketFlags.None, SendCallBack, tcs);
        return tcs.Task;
    }
    private void SendCallBack(IAsyncResult iar)
    {
        TaskCompletionSource<int> tcs = (TaskCompletionSource<int>) iar.AsyncState;
        int actuallen = socket.EndSend(iar);
        tcs.SetResult(actuallen); 
    }




测试代码

        static async void TrySocket()
        {
            MySocket socket = new MySocket();
            socket.MessageReceived += Socket_MessageReceived;
            Console.WriteLine("准备连接");
            await socket.ConnectAsync("localhost", 12345);
            Console.WriteLine("连接成功");
            int sendlen = await socket.SendAsyncVersionAsync(new byte[] { 1, 2, 3 });
            Console.WriteLine("发送成功: {0}", sendlen);
        }
        private static void Socket_MessageReceived(object sender, byte[] data, int len)
        {
            Console.WriteLine("接收成功: {0}", len);
        }
        static void Main(string[] args)
        {
            TrySocket();
            while (true) ;
        }

输出

准备连接
连接成功
发送成功: 3
接收成功: 13
  • 0
    点赞
  • 0
    收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
©️2022 CSDN 皮肤主题:大白 设计师:CSDN官方博客 返回首页
评论

打赏作者

六兆煮橙

你的鼓励将是我创作的最大动力

¥2 ¥4 ¥6 ¥10 ¥20
输入1-500的整数
余额支付 (余额:-- )
扫码支付
扫码支付:¥2
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值