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);
            }
        }
    }
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,186评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,858评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,620评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,888评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,009评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,149评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,204评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,956评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,385评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,698评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,863评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,544评论 4 335
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,185评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,899评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,141评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,684评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,750评论 2 351

推荐阅读更多精彩内容

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