TCP/IP异步模型 心得

转载引用 TCP/IP异步模型

Server

using System;
using System.Collections.Generic;
using System.Text;
using System.Net;
using System.Net.Sockets;

namespace AsyncServer
{
    public class AsyncTCPServer
    {
        public void Start()
        {
            //创建套接字
            IPEndPoint ipe = new IPEndPoint(IPAddress.Parse("0.0.0.0"), 6065);
            Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
            //绑定端口和IP
            socket.Bind(ipe);
            //设置监听
            socket.Listen(10);
            //连接客户端
            AsyncAccept(socket);
        }

        /// <summary>
        /// 连接到客户端
        /// </summary>
        /// <param name="socket"></param>
        private void AsyncAccept(Socket socket)
        {
            socket.BeginAccept(asyncResult =>
            {
                //获取客户端套接字
                Socket client = socket.EndAccept(asyncResult);
                Console.WriteLine(string.Format("客户端{0}请求连接...", client.RemoteEndPoint));
                AsyncSend(client, "服务器收到连接请求");
                AsyncSend(client, string.Format("欢迎你{0}",client.RemoteEndPoint));
                AsyncReveive(client);
            }, null);
        }

        /// <summary>
        /// 接收消息
        /// </summary>
        /// <param name="client"></param>
        private void AsyncReveive(Socket socket)
        {
            byte[] data = new byte[1024];
            try
            {
                //开始接收消息
                socket.BeginReceive(data, 0, data.Length, SocketFlags.None,
                asyncResult =>
                {
                    int length = socket.EndReceive(asyncResult);
                    Console.WriteLine(string.Format("客户端发送消息:{0}", Encoding.UTF8.GetString(data)));
                }, null);
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
            }
        }

        /// <summary>
        /// 发送消息
        /// </summary>
        /// <param name="client"></param>
        /// <param name="p"></param>
        private void AsyncSend(Socket client, string p)
        {
            if (client == null || p == string.Empty) return;
            //数据转码
            byte[] data = new byte[1024];
            data = Encoding.UTF8.GetBytes(p);
            try
            {
                //开始发送消息
                client.BeginSend(data, 0, data.Length, SocketFlags.None, asyncResult =>
                {
                    //完成消息发送
                    int length = client.EndSend(asyncResult);
                    //输出消息
                    Console.WriteLine(string.Format("服务器发出消息:{0}", p));
                }, null);
            }
            catch (Exception e)
            {
                Console.WriteLine(e.Message);
            }
        }
    }
}

服务端
基本流程
创建套接字
绑定套接字的IP和端口号——Bind()/Connect()
以下引自个人的git库 Notes : share云笔记
bind的参数是自己的地址,而connect的参数是对方的地址
127.0.0.1和0.0.0.0的区别
一般我们用来做c/s测试通常是在本地的环回口(127.0.0.1),研究了四天,发现这个地址完全是个鸡肋,环回口的定义是,从本地数据接口发出的数据,由数据接口接受,这个接口的地址是固定不变的(收发地址都是127.0.0.1),打个比方,我们按照OSI七层网络模型举例,当一个数据包发出后,按理讲应该从物理层进入,达到最顶层的应用层再返回,但环回口仅到ip层就被接受了,也就是说我们发出的数据包不知道发给哪一个主机
拟定PC1地址为192.168.0.1 pc2为127.0.0.1
pc2按照环回地址发出数据包,但由于到ip层就被接受了,这意味着他并没有在应用层查看到其他主机的路由地址,那就不知道发给PC1,还是PC3,PC4
0.0.0.0(接受所有主机发来的数据包),这意味着服务端只要有一个程序调用listen方法监听一个特定端口,从其他网段发来的数据包就能找到这台主机
本人所用服务器主机版本为centos7,绑定0.0.0.0:9095(9095为socket异步发送数据端口),查询 netstat -ano 即可勘查绑定的所有端口

服务端TCP连接步骤:
使套接字处于监听状态等待客户端的连接请求——Listen()
当请求到来后,使用BeginAccept()和EndAccept()方法接受请求,返回新的套接字
使用BeginSend()/EndSend和BeginReceive()/EndReceive()两组方法与客户端进行收发通信
返回,再次等待新的连接请求
关闭套接字

socket异步编程之传输堵塞
从tcp的质询协议来看,建立连接需要三次握手,第一次由客户端发出begin请求,绑定套接字之后对服务器调用connect方法,将要传输的字节流发送至服务端,服务端经过bind方法在防火墙上打开响应的接受端口之后,listen等待来自客户端的请求,若套接字中的ip能够到达服务端但我防火墙 则调用accept方法进行接受
第二次握手,服务端监听到客户端调用send发来的字节数组,立即在本地查询路由地址,确认发来数据包的ip,从定义的文件路径回调文本数据,客户端accept到数据后,切换为receive方法,准备对下一次连接请求进行数据回调,
第三次握手,这次握手比较难,大二上学期想了足足两周才明白。第二次的传输过程中,由于我们尽可能排除数据包超时重传的情况,以防数据包因为双方主机的突发情况(即突然断电或者关机)而丢包出现网络延迟。

Discuss :

  • 客户端对服务端建立连接
  • 服务端是客户端建立连接请求,第二次需要经过第一次的成功连接才能开始本次连接
  • 就是保证第二次的时候,我们能够确认数据包全部传递,且未发生丢包,否则重传。在这过程中,需要在服务端开一个线程监听本次传输内receive方法需要接受的数据包长度。

Tips

从路由基础中我们可以得知,在主机A到达主机B的过程中,需要经历几百跳以及几千跳的中继路由,有时候主机A可能会与同一网段内主机发来的数据包发生拥塞,造成网络数据包丢失的情况,在进程管理中这是无法避免的,称之为进程并发性。
不妨试想一下以下代码的执行结果

byte[] bytes = new Byte[1024];
IPAddress ipAddress = IPAddress.Parse("119.29.88.13");
IPEndPoint localEndPoint = new IPEndPoint(ipAddress, 11000);
    // 生成一个TCP的socket
    Socket listener = new Socket(AddressFamily.InterNetwork,
      SocketType.Stream, ProtocolType.Tcp);
    listener.Bind(localEndPoint);
    listener.Listen(100);
    while (true)
    {
      // Set the event to nonsignaled state.
      allDone.Reset();
      //开启异步监听socket
      Console.WriteLine("Waiting for a connection");
      listener.BeginAccept(
          new AsyncCallback(AcceptCallback),
          listener);
      // 让程序等待,直到连接任务完成。在AcceptCallback里的适当位置放置allDone.Set()语句.
      allDone.WaitOne();
      }
  Console.WriteLine("\nPress ENTER to continue");
  Console.Read();
连接行为回调函数AcceptCallback:
  public static void AcceptCallback(IAsyncResult ar)
  {
    //添加此命令,让主线程继续.
    allDone.Set();
    // 获取客户请求的socket
    Socket listener = (Socket)ar.AsyncState;
    Socket handler = listener.EndAccept(ar);
    // 造一个容器,并用于接收命令.
    StateObject state = new StateObject();
    state.workSocket = handler;
    handler.BeginReceive(state.buffer, 0, StateObject.BufferSize, 0,
      new AsyncCallback(ReadCallback), state);
  }

注意到allDone方法了么,这是一个每当服务端调用accept方法的时候,就会更改连接的事务状态,将receive方法通过一个线程唤醒,与同步的不同就在于不用每时每刻监听端口立即回调方法,直到端口有来自其他主机发来数据的时候,才更改receive的事务状态,回调数据,大大避免了listen与receive在同一个时间内的事务并发。
以上是异步处理事务并发的方式,

客户端
基本流程
创建套接字并保证与服务器的端口一致
使用BeginConnect()和EndConnect()这组方法向服务端发送连接请求
使用BeginSend()/EndSend和BeginReceive()/EndReceive()两组方法与服务端进行收发通信
关闭套接字

using System;
using System.Collections.Generic;
using System.Text;
using System.Net;
using System.Net.Sockets;

namespace AsyncClient
{
    public class AsyncTCPClient
    {
        /// <summary>
        /// 连接到服务器
        /// </summary>
        public void AsynConnect()
        {
            //端口及IP
            IPEndPoint ipe = new IPEndPoint(IPAddress.Parse("119.29.88.13"), 9095);
            //创建套接字
            Socket client = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
            //开始连接到服务器
            client.BeginConnect(ipe, asyncResult =>
            {
                client.EndConnect(asyncResult);
                //向服务器发送消息
                AsynSend(client,"你好我是客户端");
                AsynSend(client, "第一条消息");
                AsynSend(client, "第二条消息");
                //接受消息
                AsynRecive(client);
            }, null);
        }

        /// <summary>
        /// 发送消息
        /// </summary>
        /// <param name="socket"></param>
        /// <param name="message"></param>
        public void AsynSend(Socket socket, string message)
        {
            if (socket == null || message == string.Empty) return;
            //编码
            byte[] data = Encoding.UTF8.GetBytes(message);
            try
            {
                socket.BeginSend(data, 0, data.Length, SocketFlags.None, asyncResult =>
                {
                    //完成发送消息
                    int length = socket.EndSend(asyncResult);
                    Console.WriteLine(string.Format("客户端发送消息:{0}", message));
                }, null);
            }
            catch (Exception ex)
            {
                Console.WriteLine("异常信息:{0}", ex.Message);
            }
        }

        /// <summary>
        /// 接收消息
        /// </summary>
        /// <param name="socket"></param>
        public void AsynRecive(Socket socket)
        {
            byte[] data = new byte[1024];
            try
            {
                //开始接收数据
                socket.BeginReceive(data, 0, data.Length, SocketFlags.None,
                asyncResult =>
                {
                    int length = socket.EndReceive(asyncResult);
                    Console.WriteLine(string.Format("收到服务器消息:{0}", Encoding.UTF8.GetString(data)));
                    AsynRecive(socket);
                }, null);
            }
            catch (Exception ex)
            {
                Console.WriteLine("异常信息:", ex.Message);
            }
        }
    }
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 135,408评论 19 139
  • 名词延伸 通俗的说,域名就相当于一个家庭的门牌号码,别人通过这个号码可以很容易的找到你。如果把IP地址比作一间房子...
    杨大虾阅读 20,683评论 2 56
  • 个人认为,Goodboy1881先生的TCP /IP 协议详解学习博客系列博客是一部非常精彩的学习笔记,这虽然只是...
    贰零壹柒_fc10阅读 10,479评论 0 8
  • 参考:http://www.2cto.com/net/201611/569006.html TCP HTTP UD...
    F麦子阅读 8,032评论 0 14
  • 他们是高中同学,二狗从高一就开始暗恋着皮皮。皮皮神经大条不拘小节玩心很重,在班级里和男生打成一片,二狗喜欢看小说、...
    金大大大大大金阅读 3,184评论 0 2