Socket异步编程——多客户的异步连接和收发

2 篇文章 0 订阅

同步编程的弊端

通过上篇我们熟悉了Socket的同步编程的方法,由于实际的需求,如果n个用户连接到服务器并且发送消息时,同步处理的模式是一接一个的处理,这样处理的优点在于可靠性高,但弊端是很明显的——效率太低,当然我们这里会迸发出一个想法——开多线程啊!在上篇中类似的开子线程完成多客户的接收发功能确实的提高了执行效率,但是线程的频繁创建和销毁在客户较多的时候也并不是很好的办法,当然.Net自然会准备好这些问题的解决方案,利用异步调用机制从线程池中抓取一个空闲的线程完成异步操作。

简单的异步处理机制

还是上代码举例速度!

public delegate int Calcu(int x, int y);

static void AsyncModel()
{
    Calcu c1 = new Calcu((x, y) =>
        {
            int sum = 1;
            for (int a = x; a <= y; a++)
            {
                sum *= a;
            }
            return sum;
        });

    Calcu c2 = new Calcu((x, y) =>
        {
            Thread.Sleep(2000);
            int sum = 0;
            for (int a = x; a <= y; a++)
            {
                sum += a;
            }
            return sum;
        });

    Console.WriteLine(c2(1,10));
    Console.WriteLine(c1(1,10));
}

当执行这段程序时,c1会等待c2的结束才会进行计算,这大大降低效率,.net中委托的定义会生成继承自MulticastDelegate的完整类,其中包含Invoke()、BeginInvoke()、EndInvoke()方法,直接调用时实际上调用了Invoke()方法,而BeginInvoke()、EndInvoke()则对应其异步调用(注意这两个方法是同时出现,EndInvoke用于返回委托的返回值),因此我们可以优化为

static void AsyncModel()
{
    Calcu c1 = new Calcu((x, y) =>
        {
            int sum = 1;
            for (int a = x; a <= y; a++)
            {
                sum *= a;
            }
            return sum;
        });

    Calcu c2 = new Calcu((x, y) =>
        {
            Thread.Sleep(2000);
            int sum = 0;
            for (int a = x; a <= y; a++)
            {
                sum += a;
            }
            return sum;
        });

    c1.BeginInvoke(1, 10, new AsyncCallback(asyncResult =>
        {
            string msg = asyncResult.AsyncState as string;
            Console.WriteLine(msg + "  value:\n" + c1.EndInvoke(asyncResult));
        }), "c1 is ok!");

    c2.BeginInvoke(1, 13, new AsyncCallback(asyncResult =>
        {
            string msg = asyncResult.AsyncState as string;
            Console.WriteLine(msg + "  value:\n" + c2.EndInvoke(asyncResult));
        }), "c2 is ok!");

    Console.ReadLine();
}
  • BenginInvoke()会动态的创建参数——根据委托所传递参数来动态创建
  • BenginInvoke()后两个参数:1. AsyncCallback——在委托触发时处理,亦可以说是完成时处理;2. Object——传递的对象,该对象会存储在IAsyncResult的AsyncState属性中(辅助传输,亦可设为null)
  • IAsyncResult : 该接口是BeginInvoke()的返回类型(AsyncResult的实现接口),主要的功能则是存储传递信息,所以在EndInvoke和回调函数中需要它作为参数进行解析

运行时很明显c1先执行完等待了c2
这里写图片描述
这里写图片描述

Socket的异步实现

好的,有了这样简单的异步编程基础,我们可以回归正题,在Socket中同样会通过Beginxxx和Endxxx完成一个异步操作,毕竟也是基于委托的调用实现。

服务器端实现

public class Token
{
    public Socket socket;

    public Token(Socket socket)
    {
        this.socket = socket;
    }

    /// <summary>
    /// Async receive msg and response
    /// </summary>
    /// <param name="handler">Dispose client handler</param>
    public void Receive(Socket handler)
    {
        byte[] buffer = new byte[1024];

        AsyncCallback receive = new AsyncCallback((asyncResult) =>
        {
            try
            {
                //Receive by sync
                int length = handler.EndReceive(asyncResult);
                //If length = 0 ——> Client close
                if (length == 0) Console.WriteLine("{0} is close!", handler.RemoteEndPoint);
                else if (length > 0)
                {
                    string msg = Encoding.UTF8.GetString(buffer, 0, length);
                    //Clear buffer to use for next
                    Array.Clear(buffer, 0, buffer.Length);
                    Console.WriteLine("{0}:\n{1}", handler.RemoteEndPoint, msg);

                    //Response client
                    Send(handler, string.Format("Hello,{0}", handler.RemoteEndPoint));

                    //Continue to receive
                    Receive(handler);
                }
            }
            catch (Exception e)
            {
                Console.WriteLine(e.Message);
            }
        });
        handler.BeginReceive(buffer, 0, buffer.Length, SocketFlags.None, receive, handler);

    }

    /// <summary>
    /// Send(Response) msg to client
    /// </summary>
    /// <param name="handler">Dispose client handler</param>
    /// <param name="msg">Send msg</param>
    private void Send(Socket handler, string msg)
    {
        byte[] buffer = Encoding.ASCII.GetBytes(msg);

        try
        {
            AsyncCallback send = new AsyncCallback(asyncResult =>
            {
                handler.EndReceive(asyncResult);
            });
            handler.BeginSend(buffer, 0, buffer.Length, SocketFlags.None, send, handler);
        }
        catch (Exception e)
        {
            Console.WriteLine(e.Message);
        }
    }
}

public class AsyncTcpServer
{
    private Socket server;
    private List<Token> pool;

    public AsyncTcpServer(int port)
    {
        server = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
        server.Bind(new IPEndPoint(IPAddress.Any, port));
        pool = new List<Token>(5);
        //Listen list length is 5
        server.Listen(5);

        Console.WriteLine("Async server is open,wait for connect...");

        //Open async accept
        Console.WriteLine("Async connect is open...");
        Console.WriteLine("————————————————————————————————————————");
        Accept();
    }

    /// <summary>
    /// Server accept by async
    /// </summary>
    private void Accept()
    {
        AsyncCallback accept = new AsyncCallback(Accept_Completed);
        server.BeginAccept(accept, server);
    }

    /// <summary>
    /// </summary>
    /// Accept call back method
    /// <param name="ir">Async result info</param>
    private void Accept_Completed(IAsyncResult ir)
    {
        try
        {
            Socket s = (Socket)ir.AsyncState;
            Socket handler = s.EndAccept(ir) as Socket;
            Token token = new Token(handler);
            token.Receive(handler);
            pool.Add(token);
            //Open receive model
        }
        catch (Exception e)
        {
            Close();
            Console.WriteLine(e.Message);
        }

        //Call back wait for next
        Accept();
    }

    /// <summary>
    /// End server
    /// </summary>
    public void Close()
    {
        server.Close();
    }
}
  1. 和同步编程一样先绑定IP和端口,监听队列长度为5。
  2. 开启异步连接操作(BeginAccept同样只会接收一次连接操作,这里为了简单实现,用了类似上篇同步实现多客户端的方法,对客户进行了简单的封装,不同的是在每次连接一个客户完成后,递归异步Accept方法,这样每次连接一个客户后等待一个新的客户进行连接,毕竟是异步处理不能置于while中),最初的Socket持续监听,每次监听到连接对象,分发出一个新的Socket存储连接者。
  3. 在存储的客户List表中,每个客户进行异步的接收消息,在其回调函数中递归接收函数,达到循环监听信息处理,同时每次接收消息回发“Hello xxxx”。
  4. 注意事项与同步一样,当消息流销毁时Receive会持续返回0,需要if判断;当客户机断开时会报错,需要try catch处理。
  5. 关闭服务器函数

客户端实现

public class AsyncTcpClient
{
    public Socket client;

    public AsyncTcpClient(IPEndPoint iport)
    {
        client = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
        AsyncCallback conn = new AsyncCallback(Connect_Completed);
        client.BeginConnect(iport, conn, client);
    }

    /// <summary>
    /// Call back method——End completed
    /// </summary>
    /// <param name="ir">Async info</param>
    private void Connect_Completed(IAsyncResult ir)
    {
        client.EndConnect(ir);
        Send(string.Format("{0} will connect!", client.LocalEndPoint));
        //Open receive model
        Receive();
    }

    /// <summary>
    /// Send msg
    /// </summary>
    /// <param name="msg">message</param>
    public void Send(string msg)
    {
        byte[] buffer = Encoding.ASCII.GetBytes(msg);

        AsyncCallback send = new AsyncCallback(asyncResult =>
        {
            client.EndSend(asyncResult);
        });

        client.BeginSend(buffer, 0, buffer.Length, SocketFlags.None, send, client);
    }

    /// <summary>
    /// Receive by while
    /// </summary>
    private void Receive()
    {
        byte[] buffer = new byte[1024];

        AsyncCallback receive = new AsyncCallback(asyncResult =>
        {
            try
            {
                int length = client.EndReceive(asyncResult);
                if (length == 0) Console.WriteLine("Server is close!");
                else if (length > 0)
                {
                    string msg = Encoding.UTF8.GetString(buffer, 0, length);
                    Array.Clear(buffer, 0, buffer.Length);
                    Console.WriteLine("Server:\n" + msg);
                    //call back to continue
                    Receive();
                }
            }
            catch (Exception e)
            {
                Console.WriteLine(e.Message);
            }
        });

        client.BeginReceive(buffer, 0, buffer.Length, SocketFlags.None, receive, client);
    }

    /// <summary>
    /// End client
    /// </summary>
    public void Close()
    {           
        client.Close();
    }
}
  1. 异步连接服务器
  2. 连接完成时异步发送连接提示,并且开启异步接收模块
  3. 关闭客户端连接

Main函数调用函数

static void AsyncTcpAnyClientModel()
{
    int index = -1;
    AsyncTcpServer server = new AsyncTcpServer(8080);
    IPAddress ip = Dns.GetHostEntry(Dns.GetHostName()).AddressList[1];

    if (Console.ReadLine() == "j")
    {
        List<AsyncTcpClient> token = new List<AsyncTcpClient>()
        {
            new AsyncTcpClient(new IPEndPoint(ip,8080)),
            new AsyncTcpClient(new IPEndPoint(ip,8080)),
            new AsyncTcpClient(new IPEndPoint(ip,8080))
        };

        Thread.Sleep(100);
        while (true)
        {
            int count = token.Count;
            if (count > 0)
            {
                index++;
                index %= count;

                Console.WriteLine("\n{0}'s turn:", token[index].client.LocalEndPoint);

                string msg = Console.ReadLine();
                if (msg.ToLower() == "close")
                {
                    token[index].Close();
                    token.Remove(token[index]);
                }
                else token[index].Send(msg);

                Thread.Sleep(100);
            }
            else break;
        }
    }
}

同样的在这里开启3个客户端请求,命令控制台输出为
这里写图片描述
在这里我们很明显看出服务器回复3个客户机请求时发送的信息错开的,验证了为非同步处理即异步并发操作。

总结与反馈

这里我们的多客户的异步并发处理模式基本就实现了,在实际传输数据过程中还需要注意数据量偏大时的分转存储和发送,有兴趣的可以进一步优化

  • 1
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值