澳门网络娱乐游戏平台-澳门电子游戏娱乐网址-官方直营

一文读懂窒碍、非梗塞、同步、异步IO

介绍

    在谈及网络IO的时候总避不开堵塞、非梗塞、同步、异步、IO多路复用、select、poll、epoll等这个词语。在面试的时候也会被日常问到这多少个的区分。本文就来说一下那么些词语的意思、分歧以至利用情势。
Unix网络编程后生可畏书中小编给出了种种IO模型:
澳门云顶集团娱乐中心,1、BlockingIO - 阻塞IO
2、NoneBlockingIO - 非阻塞IO
3、IO multiplexing - IO多路复用
4、signal driven IO - 信号驱动IO
5、asynchronous IO - 异步IO
这三种IO模型中前多个都以少年老成道的IO,独有最后二个是异步IO。功率信号驱动IO使用的可比少,珍视介绍其余二种IO甚至在Java中的应用。

python之IO多路复用

1. 多路复用概念:
监听多个描述符(文件叙述符(windows下暂不扶持)、网络描述符)的情景,要是描述符状态改动则会被基本改善标识位,进而被进程拿到从而开展读写操作
I/O多路复用是用来进级效能,单个进程能够并且监听四个网络连接IO
I/O是指Input/Output
I/O多路复用,通过豆蔻梢头种体制,能够监视七个文件描述符,朝气蓬勃旦描述符就绪(读就绪和写就绪),能通告顺序进行相应的读写操作。
I/O多路复用制止拥塞在io上,原来为多进度或八线程来抽取八个三番两次的消息成为单进度或单线程保存多少个socket的景观后轮询管理.
2. 多路复用二种触发方式(epool):
水平触发 level-triggered,epoll对于fd的暗中同意事件模型就是程度触发,即监察和控制到fd可读写时,就能够接触而且再次来到fd,比如fd可读时,可是利用recv没有任何读取达成,这后一次还有可能会将fd触发再次来到,相对来讲,这么些更安全一些
边缘触发 edge-triggered, epoll能够对某些fd实行边缘触发,边缘触发的意趣正是每便只要接触二次小编就能够给您回到三遍,纵然你管理完毕八分之四,小编也不会给您回去了,除非他后一次再度发生贰个平地风波。
接收例子:epoll.register(serversocket.fileno(卡塔尔, select.EPOLLIN | select.EPOLLET卡塔尔
3. 阻塞/非阻塞 模式:
阻塞:
生龙活虎经打断情势则等待数据
非阻塞:
只要非拥塞形式有数据重返数据、无数据直接再次来到报错
4. I/O模型:
同步I/O:
有求必应 等待数据(拥塞方式)或 不管有很少都回去(非窒碍情势)
异步I/O:
用户进度问完之后干别的管理结果出来现在告诉客商进度

> SELECT

select是透过系统调用来监视风度翩翩组由多少个文本叙述符组成的数组,通过调用select(卡塔尔重回结果,数组中一点儿也不动的文书陈述符会被基本标志出来,然后经过就能够收获那些文件描述符,然后开展对应的读写操作
select的实际实施进程如下:
select供给提供要监督的数组,然后由客户态拷贝到内核态
内核态线性循环监察和控制数组,每便都亟需遍历整个数组
水源开采文件陈说符状态相符操作结果,将其归来
故而对于我们监察和控制的socket都要安装为非窒碍的,只犹如此技巧作保不会被打断
可取:基本顺序平台都扶持
缺点:
老是调用select,都亟待把fd会集由客户态拷贝到内核态,在fd多的时候开销会不小
单个进度能够监督的fd数量存在最大面积,因为其行使的数据构造是数组
历次select都是线性遍历整个数组,当fd十分大的时候,遍历的开采也一点都不小
使用:
r, w, e = select.select( rlist, wlist, errlist [,timeout] )
rlist,wlist和errlist均是waitable object; 都以文本描述符,正是八个整数,或许叁个具备重临文件叙述符的函数fileno(卡塔尔(قطر‎的对象。
rlist: 等待读就绪的文本陈述符数组
wlist: 等待写就绪的文件汇报符数组
errlist: 等待万分的数组
在linux下那八个列表能够是空驶列车表,不过在windows上非常
当rlist数组中的文件陈说符产生可读时(调用accept只怕read函数),则赢得文件叙述符并增添到r数组中。
当wlist数组中的文件陈说符暴发可写时,则赢得文件呈报符增添到w数组中
当errlist数组中的文件叙述符产生错误时,将会将文件陈述符加多到e队列中
当超时时间从没设置时,借使监听的文本汇报符未有别的变动,将会平素不通到发出变化停止
当超时时间设置为1时,若是监听的呈报符未有生成,则select会窒碍1秒,之后回来多个空驶列车表。 如若由变化,则直接推行并再次回到。
3个list中可抽取的参数,可以是Python的file对象,比方sys.stdin,os.open,open重临的对象等等。socket对象将会回去socket.socket(卡塔尔(英语:State of Qatar),也得以自定义类,只要由十分的fileno函数就能够,前提是动真格的的公文名描述符

 1 #sync_server
 2 import socket, select
 3 import queue
 4 
 5 server = socket.socket()
 6 server.bind(('localhost', 9000))
 7 server.listen(100)
 8 
 9 server.setblocking(False) #不阻塞
10 
11 #监控的队列
12 input_list = [server, ]
13 output_list = []
14 
15 #返回的消息词典
16 message_dic = {}
17 
18 while True:
19     #开始监听
20     rList,wList,eList = select.select(input_list, output_list, input_list)
21     print(rList)
22 
23     for r in rList:
24         #如果监听到的是server,则说明有新连结
25         if r is server:
26             conn, addr = server.accept()
27             print('建立了新的连接,', addr)
28             #监听列表中把新连接进来的添加
29             input_list.append(conn)
30             #建立消息队列
31             message_dic[conn] = queue.Queue()
32         #如果不是server,则说明收到的是连接发来了消息
33         else:
34             try:
35                 recv_data = r.recv(1024)
36                 if recv_data:
37                     print('收到消息', recv_data)
38                     #添加到监听的队列output_list
39                     output_list.append(r)
40                     #消息队列中增加消息
41                     message_dic[r].put(recv_data)
42             except ConnectionResetError as e:
43                 print('连接断开')
44                 if e in output_list:
45                     output_list.remove(r)
46                 input_list.remove(r)
47                 del message_dic[r]
48                 continue
49 
50     for m in wList:
51         #消息队列中取消息
52         send_data = message_dic[m].get()
53         m.send(send_data)
54         #从队列output_list中移除
55         output_list.remove(m)

 1 #client
 2 import socket
 3 
 4 client = socket.socket()
 5 client.connect(('localhost',9000))
 6 
 7 while True:
 8     send_date = input('>>')
 9     client.send(send_date.encode())
10     data = client.recv(1024)
11     print(data)
12 
13 client.close()

 

> pool(只适用于Unix/Linux操作系统)

poll本质上与select基本相符,只可是监控的最厦门接数上相较于select未有了节制,因为poll使用的数据布局是链表,而select使用的是数组,数组是要开端化长度大小的,且不能够退换

接触情势:水平触发

poll原理

将fd列表,由客户态拷贝到内核态
内核态遍历,开掘fd状态成为就绪后,再次来到fd列表
poll状态
  POLLIN   有数量读取   
  POLLPRT  有数据紧迫读取   
  POLLOUT  筹划输出:输出不会卡住   
  POLLE牧马人揽胜极光  某个错误意况现身   
  POLLHUP   挂起   
  POLLNVAL  无效央浼:描述无法展开
缺点:
历次调用select,都亟待把fd会集由顾客态拷贝到内核态,在fd多的时候开支会十分大
每一趟select都是线性遍历整个列表,当fd相当的大的时候,遍历的花销也相当的大
poll方法
  1. register,将在监察和控制的公文陈述符注册到poll中,并累积监察和控制的事件类型
  2. unregister,注销毁文件件叙述符监察和控制
  3. modify, 更改文件叙述符监控事件类型
  4. poll([timeout]卡塔尔(英语:State of Qatar),轮流培训注册监察和控制的公文描述符,重临元祖列表,元祖内容是二个文本陈说符及监控项目(POLLIN,POLLOUT等等),如果设置了timeout,则会阻塞timeout秒,然后回来控列表,若无安装timeout 阿秒,则会卡住到有再次回到值结束

 

 1 # -*- coding: utf-8 -*-
 2 
 3 import select
 4 import socket
 5 import datetime
 6 
 7 sock = socket.socket()
 8 sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
 9 sock.bind(("localhost", 10000))
10 sock.listen(5)
11 # 设置为非阻塞
12 sock.setblocking(0)
13 
14 poll = select.poll()
15 poll.register(sock, select.POLLIN)
16 
17 connections = {}
18 
19 while True:
20     # 遍历被监控的文件描述符
21     print(datetime.datetime.now())
22     for fd, event in poll.poll(10000):
23         if event == select.POLLIN:
24             if fd == sock.fileno():
25                 # 如果是当前的sock,则接收请求
26                 con, addr = sock.accept()
27                 poll.register(con.fileno(), select.POLLIN)
28                 connections[con.fileno()] = con
29             else:
30                 # 如果是监听的请求,读取其内容,并设置其为等待写监听
31                 con = connections[fd]
32                 data = con.recv(1024)
33                 if data:
34                     print("%s accept %s" % (fd, data))
35                     poll.modify(fd, select.POLLOUT)
36         else:
37             con = connections[fd]
38             try:
39                 con.send(b"Hello, %d" % fd)
40                 print("con >>> ", con)
41             finally:
42                 poll.unregister(con)
43                 connections.pop(fd)
44                 con.close()

 

>epoll(只适用于Unix/Linux操作系统)

epoll约等于是linux内核扶植的章程,而epoll首若是解决select,poll的大器晚成对劣势
1.数总监度限制
减轻方案:fd上限是最大能够张开文件的多少,具体多少能够查看/proc/sys/fs/file-max。日常会和内部存款和储蓄器有关
2.内需每第一轮询将数组全体拷贝到内核态
应用方案:每一回注册事件的时候,会把fd拷贝到内核态,并不是每趟poll的时候拷贝,那样就确认保障种种fd只必要拷贝三次。
3.每一遍遍历都急需列表线性遍历
消灭净尽方案:不再使用遍历的方案,给种种fd内定多少个回调函数,fd就绪时,调用回调函数,这一个回调函数会把fd插手到安妥的fd列表中,所以epoll只必要遍历就绪的list就可以。
接触方式:边缘触发

 

>selector 模块使用比如:

 1 __author__ = 'Administrator'
 2 
 3 import selectors #基于select模块实现的IO多路复用
 4 import socket
 5 sel = selectors.DefaultSelector() #根据平台选择最佳的IO多路机制,比如linux就会选择epoll windows会选择select
 6 
 7 def accept(sock, mask):
 8     conn, addr = sock.accept()
 9     print('accept', conn, 'from', addr, 'mask', mask)
10     conn.setblocking(False)
11     sel.register(conn, selectors.EVENT_READ, read)
12 
13 def read(conn, mask):
14     data = conn.recv(1024)
15     if data:
16         print('echoing', repr(data), conn)
17         conn.send(data)
18     else:
19         print('closing', conn)
20         sel.unregister()
21         conn.close()
22 
23 server= socket.socket()
24 server.bind(('localhost',9002))
25 server.listen(10)
26 
27 server.setblocking(False)
28 sel.register(server, selectors.EVENT_READ, accept) #注册sock,有连接进来则调用accept
29 
30 while True:
31     events = sel.select() #默认阻塞,有活动连接就返回活动的连接列表
32     for key,mask in events:
33         #key.data 有活动的绑定函数
34         #key.fileobj 有活动的文件描述符
35         callback = key.data
36         callback(key.fileobj, mask)

 试行进度:

澳门云顶集团娱乐中心 1

 

 

前言

在上后生可畏篇博客中,大家应用了BIO,也正是同步拥塞式IO达成了Socket通讯。
Java Socket编制程序那多少个事(1))
现行反革命大家应用jdk1.4随后的NIO来完成,NIO(new io / no-blocking io卡塔尔国,同步非窒碍IO。

卡住、非堵塞、同步、异步以致IO多路复用

    在扩充互连网IO的时候会涉及到客商态和内核态,并且在客户态和内核态之间会产生数据调换,从这几个角度来讲我们能够把IO抽象成多个等级:1、用户态等待内核态数据打算好,2、将数据从内核态拷贝到客商态。之所以会有三头、异步、拥塞和非拥塞这两种说法正是依据程序在此三个品级的管理情势分化而发生的。

基本原理

服务端张开贰个通路(ServerSocketChannel),并向通道中登记二个采用器(Selector),这么些接受器是与一些感兴趣的操作的标志(SelectionKey,即透过那几个标记能够固定到实际的操作,进而进行响应的管理)相关联的,然后依据选用器(Selector)轮询通道(ServerSocketChannel)上登记的事件,并开展对应的管理。
客商端在伸手与服务端通讯时,也足以向服务器端相符注册(比服务端少了三个SelectionKey.OP_ACCEPT操作群集),并由此轮询来管理钦点的风云,而不用拥塞。

一同拥塞

        澳门云顶集团娱乐中心 2
    当在客商态调用read操作的时候,假若这时候kernel还还未备选好数据,那么顾客态会平昔不通等待,直到有数量重临。当kernel寻思好数据之后,客户态继续伺机kernel把多少从内核态拷贝到客户态之后才具够使用。这里会发生二种等待:三个是顾客态等待kernel有多少能够读,别的叁个是当有多少可读时顾客态等待kernel把数据拷贝到客户态。
    在Java中三头窒碍的落到实处对应的是观念的文本IO操作以至Socket的accept的进度。在Socket调用accept的时候,程序会一贯等候知道有描述符就绪,何况把伏贴的数额拷贝到客户态,然后程序中就能够得到相应的多少。

服务端

package com.richstonedt.socket;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.nio.charset.Charset;
import java.util.Iterator;

/**
 * @author zhangguoji
 * @date 2017/9/8 20:47
 */
public class NIOServer {
    private Selector selector;

    /**
     * 获得一个ServerSocket通道,并对该通道做一些初始化的工作
     * @param port 绑定的端口号
     * @throws IOException
     */
    public void initServer(int port) throws IOException {
        // 获得一个ServerSocket通道
        ServerSocketChannel serverChannel = ServerSocketChannel.open();
        // 设置通道为非阻塞
        serverChannel.configureBlocking(false);
        // 将该通道对应的ServerSocket绑定到本地port端口
        serverChannel.socket().bind(new InetSocketAddress(port));
        // 获得一个通道管理器
        this.selector = Selector.open();
        //将通道管理器和该通道绑定,并为该通道注册SelectionKey.OP_ACCEPT事件,注册该事件后,
        //当该事件到达时,selector.select()会返回,如果该事件没到达selector.select()会一直阻塞。
        serverChannel.register(selector, SelectionKey.OP_ACCEPT);
    }

    /**
     * 采用轮询的方式监听selector上是否有需要处理的事件,如果有,则进行处理
     *
     * @throws IOException
     */
    public void listen() throws IOException {
        System.out.println("服务端启动成功!");
        // 轮询访问selector
        while (true) {
            //当注册的事件到达时,方法返回;否则,该方法会一直阻塞
            selector.select();
            // 获得selector中选中的项的迭代器,选中的项为注册的事件
            Iterator<SelectionKey> iterator = this.selector.selectedKeys().iterator();
            while (iterator.hasNext()) {
                SelectionKey key = iterator.next();
                // 删除已选的key,以防重复处理
                iterator.remove();
                // 客户端请求连接事件
                if (key.isAcceptable()) {
                    ServerSocketChannel server = (ServerSocketChannel) key
                            .channel();
                    // 获得和客户端连接的通道
                    SocketChannel channel = server.accept();
                    // 设置成非阻塞
                    channel.configureBlocking(false);

                    //在这里可以给客户端发送信息
                    channel.write(ByteBuffer.wrap("向客户端发送了一条信息".getBytes()));
                    //在和客户端连接成功之后,为了可以接收到客户端的信息,需要给通道设置读的权限。
                    channel.register(this.selector, SelectionKey.OP_READ);

                    // 获得了可读的事件
                } else if (key.isReadable()) {
                    read(key);
                }
            }

        }
    }

    /**
     * 处理读取客户端发来的信息 的事件
     * @param key
     * @throws IOException
     */
    public void read(SelectionKey key) throws IOException {
        // 服务器可读取消息:得到事件发生的Socket通道
        SocketChannel channel = (SocketChannel) key.channel();
        // 创建读取的缓冲区
        ByteBuffer buffer = ByteBuffer.allocate(512);
        channel.read(buffer);
        byte[] data = buffer.array();
        String msg = new String(data).trim();
        System.out.println("服务端收到信息:" + msg);
        ByteBuffer outBuffer = ByteBuffer.wrap(msg.getBytes());
        channel.write(outBuffer);// 将消息回送给客户端
    }

    /**
     * 启动服务端测试
     *
     * @throws IOException
     */
    public static void main(String[] args) throws IOException {
        NIOServer server = new NIOServer();
        server.initServer(8000);
        server.listen();
    }
}

服务端连接进度
1、制造ServerSocketChannel实例serverSocketChannel,并bind到钦定端口。
2、创建Selector实例selector;
3、将serverSocketChannel注册到selector,并内定事件OP_ACCEPT。
4、while循环执行:
4.1、调用select方法,该方法会堵塞等待,直到有二个或四个通道准备好了I/O操作或等待超时。
4.2、获取选拔的键列表;
4.3、循环键聚集的每种键:
4.3.a、获取通道,并从键中获取附属类小构件(假诺加多了附属类小构件);
4.3.b、鲜明筹划妥帖的决定并实践,倘使是accept操作,将接收的信道设置为非梗塞形式,并登记到接收器;
4.3.c、假设急需,改进键的乐趣操作集;
4.3.d、从已选键聚焦移除键

同步非梗塞

        澳门云顶集团娱乐中心 3
        相比较第一张联合拥塞IO的图就能发觉,在生机勃勃道非拥塞模型下第二个级次是不等待的,无论有未有数据思量好,都以随时回去。第3个等第仍然是供给等待的,顾客态必要静观其变内核态把多少拷贝过来手艺运用。对于联合非梗塞情势的拍卖,供给每间隔黄金年代段时间就去打听一下根本数据是还是不是足以读了,要是基本说可以,那么就从头第二阶段等待。

客户端

package com.richstonedt.socket;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
import java.util.Iterator;

/**
 * NIO客户端
 *
 * @author zhangguoji
 * @date 2017/9/8 21:43
 */
public class NIOClient {
    //通道管理器
    private Selector selector;

    /**
     * 获得一个Socket通道,并对该通道做一些初始化的工作
     *
     * @param ip   连接的服务器的ip
     * @param port 连接的服务器的端口号
     * @throws IOException
     */
    public void initClient(String ip, int port) throws IOException {
        // 获得一个Socket通道
        SocketChannel channel = SocketChannel.open();
        // 设置通道为非阻塞
        channel.configureBlocking(false);
        // 获得一个通道管理器
        this.selector = Selector.open();

        // 客户端连接服务器,其实方法执行并没有实现连接,需要在listen()方法中调
        //用channel.finishConnect();才能完成连接
        channel.connect(new InetSocketAddress(ip, port));
        //将通道管理器和该通道绑定,并为该通道注册SelectionKey.OP_CONNECT事件。
        channel.register(selector, SelectionKey.OP_CONNECT);
    }

    /**
     * 采用轮询的方式监听selector上是否有需要处理的事件,如果有,则进行处理
     *
     * @throws IOException
     */
    public void listen() throws IOException {
        // 轮询访问selector
        while (true) {
            selector.select();
            // 获得selector中选中的项的迭代器
            Iterator<SelectionKey> iterator = this.selector.selectedKeys().iterator();
            while (iterator.hasNext()) {
                SelectionKey key = iterator.next();
                // 删除已选的key,以防重复处理
                iterator.remove();
                // 连接事件发生
                if (key.isConnectable()) {
                    SocketChannel channel = (SocketChannel) key
                            .channel();
                    // 如果正在连接,则完成连接
                    if (channel.isConnectionPending()) {
                        channel.finishConnect();

                    }
                    // 设置成非阻塞
                    channel.configureBlocking(false);

                    //在这里可以给服务端发送信息哦
                    channel.write(ByteBuffer.wrap("向服务端发送了一条信息".getBytes()));
                    //在和服务端连接成功之后,为了可以接收到服务端的信息,需要给通道设置读的权限。
                    channel.register(this.selector, SelectionKey.OP_READ);

                    // 获得了可读的事件
                } else if (key.isReadable()) {
                    read(key);
                }

            }

        }
    }

    /**
     * 处理读取服务端发来的信息 的事件
     *
     * @param key
     * @throws IOException
     */
    public void read(SelectionKey key) throws IOException {
        // 客户端可读取消息:得到事件发生的Socket通道
        SocketChannel channel = (SocketChannel) key.channel();
        // 创建读取的缓冲区
        ByteBuffer buffer = ByteBuffer.allocate(512);
        channel.read(buffer);
        byte[] data = buffer.array();
        String msg = new String(data).trim();
        System.out.println("客户端收到信息:" + msg);
        ByteBuffer outBuffer = ByteBuffer.wrap(msg.getBytes());
        channel.write(outBuffer);// 将消息回送给服务端
    }


    /**
     * 启动客户端测试
     *
     * @throws IOException
     */
    public static void main(String[] args) throws IOException {
        NIOClient client = new NIOClient();
        client.initClient("localhost", 8000);
        client.listen();
    }
}

顾客端连接进程:(和劳务器端形似卡塔尔(قطر‎
1、创制SocketChannel实例socketChannel,并一连到服务器端口
2、创建Selector实例selector;
3、将socketChannel注册到selector,并点名事件OP_CONNECT。
4、while循环推行:
4.1、调用select方法,该方法会堵塞等待,直到有一个或多少个通道计划好了I/O操作或等待超时。
4.2、获取接受的键列表;
4.3、循环键聚集的每种键:
4.3.a、获取通道,并从键中获取附属类小零件(要是加多了附属类小零件);
4.3.b、鲜明计划妥善的决定并推行,假设是accept操作,将收取的信道设置为非拥塞情势,并登记到选择器;
4.3.c、借使急需,校正键的兴味操作集;
4.3.d、从已选键聚焦移除键

IO多路复用

    IO多路复用也是同步的。
        澳门云顶集团娱乐中心 4
    IO多路复用的方法看起来跟一齐梗塞是相符的,八个级次都以梗塞的,然则IO多路复用能够兑现以很小的代价同一时候监听多个IO。经常景况下是透过八个线程来还要监听多少个描述符,只要任何一个满意就绪条件,那么内核态就回到。IO多路复用使得守旧的每央求每线程的管理格局拿到解耦,几个线程能够何况管理多个IO要求,然后交到前面的线程池里管理,这也是netty等框架的管理情势,所谓的reactor情势。IO多路复用的完毕依赖于操作系统的select、poll和epoll,前面会详细介绍那多少个系统调用。
    IO多路复用在Java中的实现方式是在Socket编制程序中动用非堵塞形式,然后配置感兴趣的平地风波,通过调用select函数来达成。select函数正是呼应的首先个品级。倘使给select配置了晚点参数,在指准时期内并未有感兴遗闻件时有发生的话,select调用也会回到,那也是为啥要做非窒碍情势下运营。

运营结果

最后这两段代码的运作结果正是客商端和服务器之间不断发送音信
server:

服务端收到信息:向服务端发送了一条信息向客户端发送了一条信息
服务端收到信息:向服务端发送了一条信息向客户端发送了一条信息
服务端收到信息:向服务端发送了一条信息向客户端发送了一条信息
服务端收到信息:向服务端发送了一条信息向客户端发送了一条信息
服务端收到信息:向服务端发送了一条信息向客户端发送了一条信息
服务端收到信息:向服务端发送了一条信息向客户端发送了一条信息
服务端收到信息:向服务端发送了一条信息向客户端发送了一条信息
服务端收到信息:向服务端发送了一条信息向客户端发送了一条信息
服务端收到信息:向服务端发送了一条信息向客户端发送了一条信息
服务端收到信息:向服务端发送了一条信息向客户端发送了一条信息
服务端收到信息:向服务端发送了一条信息向客户端发送了一条信息
服务端收到信息:向服务端发送了一条信息向客户端发送了一条信息
服务端收到信息:向服务端发送了一条信息向客户端发送了一条信息
服务端收到信息:向服务端发送了一条信息向客户端发送了一条信息
服务端收到信息:向服务端发送了一条信息向客户端发送了一条信息
服务端收到信息:向服务端发送了一条信息向客户端发送了一条信息
...

client:

客户端收到信息:向服务端发送了一条信息向客户端发送了一条信息
客户端收到信息:向服务端发送了一条信息向客户端发送了一条信息
客户端收到信息:向服务端发送了一条信息向客户端发送了一条信息
客户端收到信息:向服务端发送了一条信息向客户端发送了一条信息
客户端收到信息:向服务端发送了一条信息向客户端发送了一条信息
客户端收到信息:向服务端发送了一条信息向客户端发送了一条信息
客户端收到信息:向服务端发送了一条信息向客户端发送了一条信息
客户端收到信息:向服务端发送了一条信息向客户端发送了一条信息
客户端收到信息:向服务端发送了一条信息向客户端发送了一条信息
客户端收到信息:向服务端发送了一条信息向客户端发送了一条信息
客户端收到信息:向服务端发送了一条信息向客户端发送了一条信息
客户端收到信息:向服务端发送了一条信息向客户端发送了一条信息
客户端收到信息:向服务端发送了一条信息向客户端发送了一条信息
客户端收到信息:向服务端发送了一条信息向客户端发送了一条信息
客户端收到信息:向服务端发送了一条信息向客户端发送了一条信息
客户端收到信息:向服务端发送了一条信息向客户端发送了一条信息
客户端收到信息:向服务端发送了一条信息向客户端发送了一条信息
客户端收到信息:向服务端发送了一条信息向客户端发送了一条信息
客户端收到信息:向服务端发送了一条信息向客户端发送了一条信息
客户端收到信息:向服务端发送了一条信息向客户端发送了一条信息
...

本文由澳门网络娱乐游戏平台发布于编程,转载请注明出处:一文读懂窒碍、非梗塞、同步、异步IO

相关阅读