• 异步Socket服务器与客户端


    本文灵感来自Andre Azevedo 在CodeProject上面的一片文章,An Asynchronous Socket Server and Client,讲的是异步的Socket通信。

    关于技术博客,我觉得永远是老外的比较好~Andre Azevedo的这篇文章里,给出了一个很复杂的例子,内容涉及如下

    • Socket连接(Socket Connection)
    • Socket服务(Socket Service)
    • 连接主机(Connection Host)
    • 加密与压缩(Encrypt与Compress)
    • 请求入队(Enqueuing Requests)
    • 确保发送和接收(Ensure send and recieve)
    • 检查消息头(Check message header)
    • 检查空闲连接(Checking idle connections)
    • 加密服务
    • SSL认证(SSL authentication)
    • 对称认证(Symmetric authentication)
    • 连接创建者(Connection Creator)
    • Socket服务器与Socket侦听者(SocketServer and SocketListener)
    • Socket服务器构造函数与方法(SocketServer constructor and methods)
    • Socket客户端与Socket连接者(SocketClient and SocketConnector)
    • Socket客户端构造函数与方法(SocketClient constructor and methods)
    • 应答演示项目(Echo Demo Project)
    • 主机(Hosts)
    • 服务(Services)
    • 结语(Conclusion)
    • 版本历史(History)

    本文仅实现一个相对简单的异步Socket服务器与客户端通信示例。

    首先需要说明如下2个问题

    1.同步、异步、多线程是什么关系?答:同步是等待返回,相当于阻塞式;异步是不等待返回,是非阻塞式,可以用多线程实现。

    2.有些异步方法有两种实现方式, 如BeginAccept()和AcceptAsync(), 这两个方法有什么区别呢?答:  以 Begin 和 End 开头的方法是以 APM(Asynchronous Programming Model)设计方法实现的异步操作, 以 Async 结尾的方法是利用称为 EAP (Event-based Asynchronous Pattern) 的设计方法实现的异步操作。

    界面简单如下:

    主要代码如下:

    using System;
    using System.Net;
    using System.Net.Sockets;
    using System.Text;
    
    namespace Chatting
    {
        public abstract class SocketFunc
        {
            //不管是服务端还是客户端, 建立连接后用这个Socket进行通信
            public Socket communicateSocket = null;
    
            //服务端和客户端建立连接的方式稍有不同, 子类会重载
            public abstract void Access(string IP, System.Action AccessAciton);
    
            //发送消息的函数
            public void Send(string message)
            {
                if (communicateSocket.Connected == false)
                {
                    throw new Exception("还没有建立连接, 不能发送消息");
                }
                Byte[] msg = Encoding.UTF8.GetBytes(message);
                communicateSocket.BeginSend(msg,0, msg.Length, SocketFlags.None,
                    ar => {
                    
                    }, null);
            }
    
            //接受消息的函数
            public void Receive(System.Action<string> ReceiveAction)
            {
                //如果消息超过1024个字节, 收到的消息会分为(总字节长度/1024 +1)条显示
                Byte[] msg = new byte[1024];
                //异步的接受消息
                communicateSocket.BeginReceive(msg, 0, msg.Length, SocketFlags.None,
                    ar => {
                        //对方断开连接时, 这里抛出Socket Exception
                        //An existing connection was forcibly closed by the remote host 
                            communicateSocket.EndReceive(ar); 
                        ReceiveAction(Encoding.UTF8.GetString(msg).Trim('\0',' '));
                        Receive(ReceiveAction);
                    }, null);
            }
        }
    
    
        public class ServerSocket:SocketFunc
        {
            //服务端重载Access函数
            public override void Access(string IP, System.Action AccessAciton)
            {
                Socket serverSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
                //本机预使用的IP和端口
                IPEndPoint serverIP = new IPEndPoint(IPAddress.Any, 9050);
                //绑定服务端设置的IP
                serverSocket.Bind(serverIP);
                //设置监听个数
                serverSocket.Listen(1);
                //异步接收连接请求
                serverSocket.BeginAccept(ar =>
                    {
                        base.communicateSocket = serverSocket.EndAccept(ar);
                        AccessAciton();
                    }, null);
            }
        }
    
        public class ClientSocket:SocketFunc
        {
            //客户端重载Access函数
            public override void Access(string IP, System.Action AccessAciton)
            {
                base.communicateSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
                base.communicateSocket.Bind(new IPEndPoint(IPAddress.Any, 9051));
                
                //服务器的IP和端口
                IPEndPoint serverIP;
                try
                {
                    serverIP = new IPEndPoint(IPAddress.Parse(IP), 9050);
                }
                catch
                {
                    throw new Exception(String.Format("{0}不是一个有效的IP地址!", IP));
                }
                
                //客户端只用来向指定的服务器发送信息,不需要绑定本机的IP和端口,不需要监听
                try
                {
                    base.communicateSocket.BeginConnect(serverIP, ar =>
                    {
                        AccessAciton();
                    }, null);
                }
                catch
                {
                    throw new Exception(string.Format("尝试连接{0}不成功!", IP));
                }
            }
        }
    }
    

    相关的事件处理程序,如下:

    View Code
    using System;
    using System.Drawing;
    using System.Windows.Forms;
    using System.Net.Sockets;
    
    namespace Chatting
    {
        public partial class MainForm : Form
        {
    
            public MainForm()
            {
                InitializeComponent();
            }
    
            SocketFunc socket;
            System.Action<string> ReceiveAction;
            System.Action AccessAction;
    
            private void MainForm_Load(object sender, EventArgs e)
            {
                //异步建立连接回调
                AccessAction = () =>
                {
                    this.Invoke((MethodInvoker)delegate()
                    {
                        lblFriendIP.Visible = false;
                        txtIP.Visible = false;
                        btnConnect.Visible = false;
                        btnWaitAccess.Visible = false;
    
                        String friendIP = socket.communicateSocket.RemoteEndPoint.ToString();
                        lblState.Text = String.Format("连接成功. 对方IP:{0}", friendIP);
    
                        try
                        {
                            socket.Receive(ReceiveAction);
                        }
                        catch (Exception exp)
                        {
                            MessageBox.Show(exp.Message, "错误");
                        }
                    });
    
                };
                //异步接收消息回调
                ReceiveAction = msg =>
                {
                    txtGetMsg.Invoke((MethodInvoker)delegate()
                    {
                        UpdateGetMsgTextBox(false, msg, Color.Red);
                    });
                };
            }
    
            private void btnWaitAccess_Click(object sender, EventArgs e)
            {
                this.socket = new ServerSocket();
                try
                {
                    this.socket.Access("", this.AccessAction);
                }
                catch (Exception ecp)
                {
                    MessageBox.Show(ecp.Message, "错误");
                }
    
                lblState.Text = "等待对方连接...";
            }
    
            private void btnConnect_Click(object sender, EventArgs e)
            {
                this.socket = new ClientSocket();
                try
                {
                    this.socket.Access(txtIP.Text, this.AccessAction);
                }
                catch (Exception ecp)
                {
                    MessageBox.Show(ecp.Message, "错误");
                }
                lblState.Text = "正在连接对方...";
            }
    
            private void btnSendMsg_Click(object sender, EventArgs e)
            {
                string message = txtSendMsg.Text.Trim();
                if (string.IsNullOrEmpty(message))
                {
                    MessageBox.Show("消息内容不能为空!", "错误");
                    txtSendMsg.Focus();
                    return;
                }
    
                try
                {
                    socket.Send(message);
                }
                catch(Exception ecp)
                {
                    MessageBox.Show(ecp.Message, "错误");
                    return;
                }
    
                UpdateGetMsgTextBox(true, message, Color.Blue);
                txtSendMsg.Text = "";
            }
    
            private void UpdateGetMsgTextBox(bool sendMsg, string message, Color color)
            {
                string appendText;
                if (sendMsg == true)
                {
                    appendText = "Client:           " + System.DateTime.Now.ToString()
                        + Environment.NewLine
                        + message + Environment.NewLine;
                }
                else
                {
                    appendText = "Server:           " + System.DateTime.Now.ToString()
                        + Environment.NewLine
                        + message + Environment.NewLine;
                }
                int txtGetMsgLength = txtGetMsg.Text.Length;
                txtGetMsg.AppendText(appendText);
                txtGetMsg.Select(txtGetMsgLength, appendText.Length - Environment.NewLine.Length*2 -message.Length);
                txtGetMsg.SelectionColor = color;
    
                txtGetMsg.ScrollToCaret();
            }
    
            private void txtSendMsg_Click(object sender, EventArgs e)
            {
                SetStyle(ControlStyles.SupportsTransparentBackColor, true);
                BackColor = Color.FromArgb(50, 40, 60, 82);
            }
        }
    }

    效果,如下:

  • 相关阅读:
    jQuery-1.9.1源码分析系列(九) CSS操作
    jQuery-1.9.1源码分析系列(八) 属性操作
    jQuery-1.9.1源码分析系列(七) 钩子(hooks)机制及浏览器兼容续
    由一次虚拟内存耗尽看32bits elf在x86_64下运行方式及地址空间布局
    关于TCP关闭想到的一些问题
    pure virtual method called的一种情况
    linux下内存分配时overcommit使用
    gcc对C++局部静态变量初始化相关
    为什么cat binary之后可能出现乱码
    gcc的模版匹配及其它
  • 原文地址:https://www.cnblogs.com/DebugLZQ/p/2508002.html
Copyright © 2020-2023  润新知