Socket 网络编程和IO模型

最近做了一个织机数据采集的服务器程序。

结构也非常简单,织机上的嵌入式设备,会通过Tcp 不停的往服务器发送一些即时数据。织机大改有个几十台到几百台不定把

 刨去业务,先分析一下网络层的大概情况。每台织机发送数据的频率,我先假设每秒钟有个20次把。那就是50毫秒一次。  

几百台客户端,每台每秒20来次的访问,对一个服务器来说,压力也算不大不小了把。 

想想以前搞C++的时候用到过的IO模型,其实我也忘的差不多了,抱着温故知新的态度,再次梳理一下其中的差别。

这里有一个前提条件需要清楚。那就是CPU执行指令的速度很快,而IO操作的速度相对很慢。做一个Socket.Recv操作的时间,可能可以执行成百上千条 a++类似这样的语句()。

一个阻塞的Socket.Recv 就类似一个把水烧开倒进热水瓶的过程,Recv阻塞等待就好比等水烧开,有数据可读Recv返回数据 就好比 水烧开了倒水进热水瓶。

假设小明是一个cpu,在家他可能再看电视,看书,打扫,锻炼等,可以看成CPU同时执行多个程序。当小明需要烧一壶水时,可以比作做一个cpu 执行一个 Socket.Recv操作。

■ 同步(一个Socket连接起一个线程send/recv 。教科书常见);   --相当于在烧水时,人一直再旁边看着等待水开,水开后就把开水倒进热水瓶。再去干其他看书,打扫等事。 。并且此时如果需要再烧一壶水,那么需要叫上爸爸去一直看着另一壶水,等水开。

■ 选择(select);  --- 稍微改进了一下,同时可以烧多壶时,小明一直循环的去看1号水壶有没有开,2号水壶有没有开,3号水壶..... 如果哪壶开了,就把那壶水倒进热水瓶。这样就不需要爸爸帮忙了,但是,小明最多只能看个几十壶水,再多他就忙不过来了

■ 异步选择(WSAAsyncSelect); --又改进了一下,给水壶加了个哨子,水开的时候会呜呜响的那种,这样,小明就不用自己循环的去看哪壶水开没开,只要听呜呜声,哪壶响了就去哪壶倒水进热水瓶就行了。
■ 事件选择(WSAEventSelect);-- 和上面的类似,只不过不用勺子,用指示灯了,哪壶开了哪壶亮

        - 上面的改进过程中,虽然小明不需要时时刻刻盯着水壶了,但是,水开后,小明还是得做一个动作,把水倒进热水壶--这个动作是比较花时间的。 
■ 重叠I/O(Overlapped I/O); ----小明不想自己倒水了,发明了一个Overlapped的装置,只要把热水瓶装在这个装置上,然后把这个装置装到水壶上,等水开时,水就自动进热水瓶啦。     
■ 完成端口(Completion Port) 。---- 后来小明家里开了热水厂,小明每天需要烧几万壶水-前面的模式都忙不过来啦,就买了一个高级的设备,它能同时烧几万壶水,也能把水自动灌进热水瓶

--以上,网络接收层只管到 把开水到进热水瓶。至于后面拿热水瓶的水去泡茶,还是冲咖啡,还是洗头, 那是业务层该做的事

 根据我的需求应该也就重叠IO和完成端口能胜任了把。

C/C++也有些日子没搞了有些生疏,于是抱着试试看的想法,用C# 的 Socket 来试试看。

C# 的 Socket  已经高度封装了,我随便拿了个TcpServer 类来玩,光看接口和用法我是完全看不出来它用的是啥IO模型.

但就是这么一个简简单单的异步读 BeginRead 。我测试用500个套接字,20毫秒发送一次。居然毫无压力。。。。他娘的当初我用winsock2搞 完成端口,那代码起码得几百行吧.

 public void StartListen()
        {
            int port = int.Parse(listenPort);
            TcpListener tcpListener = new TcpListener(IPAddress.Any, port);
            tcpListener.Start();

            while (true)
            {
                TcpClient client = tcpListener.AcceptTcpClient();//接受一个Client  
                NetworkStream networkStream = client.GetStream();
                IPEndPoint remoteip = client.Client.RemoteEndPoint as IPEndPoint;

                byte[] buffer = new byte[buffersize];
                SyncObj syncObj = new SyncObj()
                {
                    tcpClient =  client,
                    buffer = buffer

                };                   
                IAsyncResult ret = networkStream.BeginRead(buffer, 0, buffersize, ReadAsyncCallback, syncObj);
            }

        }
        public void ReadAsyncCallback(IAsyncResult ar)
        {
            SyncObj syncObj = ar.AsyncState as SyncObj;
            try
            {
                if (ar.IsCompleted)
                {
                    NetworkStream networkStream = syncObj.tcpClient.GetStream();
                    int len = networkStream.EndRead(ar);
                    string msg = System.Text.Encoding.UTF8.GetString(syncObj.buffer,0, len);
                    Console.WriteLine(msg);
                    networkStream.BeginRead(syncObj.buffer, 0, buffersize, ReadAsyncCallback, syncObj);

                }
            }
            catch (System.IO.IOException ex)
            {

                syncObj.tcpClient.Close();

            }
        }

抱着疑惑,我查询了一些资料,发现C# 的异步,其实底层也是完成端口来搞的

http://blog.sina.com.cn/s/blog_494305f30100sb2n.html

并且C# 除了BeginXXX 类型的接口外,还有更高效的  XXXAsync 接口,见下方链接

https://docs.microsoft.com/zh-cn/dotnet/framework/network-programming/socket-performance-enhancements-in-version-3-5

原文地址:https://www.cnblogs.com/CSSZBB/p/15030961.html