# Socket通信

  - [概要](#概要)
    - [什么是socket](#什么是socket)
    - [什么是TCP/IP、UDP?](#什么是tcpipudp)
    - [套接字主要类型](#套接字主要类型)
    - [套接字实现TCP/UDP通信过程](#套接字实现tcpudp通信过程)
  - [socket模块 API详解](#socket模块-api详解)
    - [宏](#宏)
    - [函数](#函数)
      - [创建套接字序列](#创建套接字序列)
      - [创建套接字](#创建套接字)
    - [socket.socket 详解](#socketsocket-详解)
      - [服务器端套接字](#服务器端套接字)
        - [绑定](#绑定)
        - [监听](#监听)
        - [接收TCP客户端连接](#接收tcp客户端连接)
      - [客户端套接字](#客户端套接字)
        - [连接](#连接)
      - [公共用途的套接字函数](#公共用途的套接字函数)
        - [设置套接字](#设置套接字)
        - [发送数据](#发送数据)
        - [接收数据](#接收数据)
        - [设置超时时间](#设置超时时间)
        - [设置是否阻塞](#设置是否阻塞)
        - [socket相关联的文件对象](#socket相关联的文件对象)
        - [socket文件描述](#socket文件描述)
        - [读取数据](#读取数据)
        - [写数据](#写数据)
        - [关闭套接字](#关闭套接字)
  - [示例](#示例)

## 概要

  socket几乎是整个网络通信的基础,本节介绍Micropython中的Socket模块。

### 什么是socket

  Socket是应用层与TCP/IP协议族通信的中间软件抽象层,它是一组接口。在设计模式中,Socket其实就是一个门面模式,它把复杂的TCP/IP协议族隐藏在Socket接口后面,对用户来说,一组简单的接口就是全部,让Socket去组织数据,以符合指定的协议.

  socket已经封装好了tcp/udp协议,只需要遵循socket的规定去编程,写出的程序自然就是遵循tcp/udp标准的。

### 什么是TCP/IP、UDP?

​     TCP/IP(Transmission Control Protocol/Internet Protocol)即传输控制协议/网间协议,是一个工业标准的协议集,它是为广域网(WANs)设计的。
​     UDP(User Data Protocol,用户数据报协议)是与TCP相对应的协议。它是属于TCP/IP协议族中的一种。  

![img](Image/InkedTCP_IP_UDP_relation_LI.jpg)

### 套接字主要类型

  **1.流套接字(SOCK_STREAM)**

  流套接字用于提供面向连接、可靠的数据传输服务。该服务将保证数据能够实现无差错、无重复送,并按顺序接收。流套接字之所以能够实现可靠的数据服务,原因在于其使用了传输控制协议,即TCP(The Transmission Control Protocol)协议 。

  **2.数据报套接字(SOCK_DGRAM)**

  数据报套接字提供一种无连接的服务。该服务并不能保证数据传输的可靠性,数据有可能在传输过程中丢失或出现数据重复,且无法保证顺序地接收到数据。数据报套接字使用UDP( User DatagramProtocol)协议进行数据的传输。由于数据报套接字不能保证数据传输的可靠性,对于有可能出现的数据丢失情况,需要在程序中做相应的处理 。

  **3.原始套接字(SOCK_RAW)**

  原始套接字与标准套接字(标准套接字指的是前面介绍的流套接字和数据报套接字)的区别在于:原始套接字可以读写内核没有处理的IP数据包,而流套接字只能读取TCP协议的数据,数据报套接字只能读取UDP协议的数据。因此,如果要访问其他协议发送的数据必须使用原始套接

### 套接字实现TCP/UDP通信过程

  套接字实现TCP通信的工作流程

  先从服务器端说起。服务器端先初始化Socket,然后与端口绑定(bind),对端口进行监听(listen),调用accept阻塞,等待客户端连接。在这时如果有个客户端初始化一个Socket,然后连接服务器(connect),如果连接成功,这时客户端与服务器端的连接就建立了。客户端发送数据请求,服务器端接收请求并处理请求,然后把回应数据发送给客户端,客户端读取数据,最后关闭连接,一次交互结束

![](Image/TCP.jfif)


  套接字实现UDP通信的工作流程

  TCP是建立可靠连接, 并且通信双方都可以以流的形式发送数据。 相对TCP, UDP则是面向无连接的协议。使用UDP协议时, 不需要建立连接, 只需要知道对方的IP地址和端口号, 就可以直接发数据包。 但是, 能不能到达就不知道了。

  和TCP类似, 使用UDP的通信双方也分为客户端和服务器,服务器首先需要绑定端口。但不需要监听客户端的连接,通过recvfrom()接收客户端数据,客户端使用UDP时, 首先仍然创建基于UDP的Socket, 然后, 不需要调用 connect() , 直接通过 sendto() 给服务器发数据。

![图片描述](Image/UDP.png)

## socket模块 API详解

  使用`import usocket as socket`导入`socket`模块

  再使用`TAB` 按键来查看`socket`中所包含的内容:

```python
>>> import usocket as socket
>>> socket.
__name__        AF_INET         AF_INET6        IPPROTO_IP
IPPROTO_TCP     IPPROTO_UDP     IP_ADD_MEMBERSHIP
SOCK_DGRAM      SOCK_RAW        SOCK_STREAM     SOL_SOCKET
SO_REUSEADDR    getaddrinfo     socket
```

### 宏

  `socket`模块中定义了许多和协议相关的宏:

| 宏定义名称            | 值(int) | 功能           | 含义          |
| :-------------------- | :-------- | :------------- | :------------ |
| `socket.AF_INET`      | 2         | 地址簇         | TCP/IP – IPv4 |
| `socket.AF_INET6`     | 0         | 地址簇         | TCP/IP - IPv6 |
| `socket.SOCK_STREAM`  | 1         | 套接字类型     | TCP流         |
| `socket.SOCK_DGRAM`   | 2         | 套接字类型     | UDP数据报     |
| `socket.SOCK_RAW`     | 3         | 套接字类型     | 原始套接字    |
| `socket.SO_REUSEADDR` | 4         | 套接字类型     | socket可重用  |
| `socket.IPPROTO_IP`   | 0         | IP协议号       |               |
| `socket.IPPROTO_TCP`  | 6         | IP协议号       | TCP协议       |
| `socket.IPPROTO_UDP`  | 17        | IP协议号       | UDP协议       |
| `socket.SOL_SOCKET`   | 1         | 套接字选项级别 |               |

### 函数

#### 创建套接字序列

  `socket.getaddrinfo(host, port)`

  函数说明:将主机域名(host)和端口(port)转换为用于创建套接字的5元组序列。元组列表的结构如下:

  (family, type, proto, canonname, sockaddr)

- `family`:地址簇,可为0(IPv4)或2(IPv6)
- `type`:套接字类型,可为1(TCP流)或2(UDP数据报)
- `proto`:IP协议号,可为0、6(TCP协议)、17(UDP协议)
- `canoname`:套接字域名
- `sockaddr`:套接字地址,包含域名和端口号

  示例:

```python
>>> import usocket as socket
>>> socket.getaddrinfo('127.0.0.1',5000)#创建主机地址的套接字序列
[(2, 1, 0, '127.0.0.1', ('127.0.0.1', 5000))]
>>> socket.getaddrinfo('192.168.50.66',3160)#创建192.168.50.66地址的套接字序列
[(2, 1, 0, '192.168.50.66', ('192.168.50.66', 3160))]
```

#### 创建套接字

  `socket.socket([af, type, proto])`

  函数说明:创建套接字。

- `af`:地址
- `type`:类型
- `proto`:协议号

  **注意: 一般不指定proto参数,因为有些Micropython固件提供默认参数。 **
示例:

```python
>>> import usocket as socket
>>> s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)#创建socket对象
>>> print(s)
<socket>
```

### socket.socket 详解

&emsp;&emsp;使用`import usocket as socket`导入`socket`模块

&emsp;&emsp;再使用`TAB` 按键来查看`socket.socket.`中所包含的内容:

```python
>>> import usocket as socket
>>> socket.socket
close           read            readinto        readline
send            write           __del__         accept
bind            connect         fileno          listen
makefile        recv            recvfrom        sendall
sendto          setblocking     setsockopt      settimeout
```

#### 服务器端套接字

##### 绑定

&emsp;&emsp;`socket.socket.bind(address)`

&emsp;&emsp;函数说明:以列表或元组的方式绑定地址和端口号。

- `address`:一个包含地址和端口号的列表或元组

&emsp;&emsp;示例:

```python
>>> import usocket as socket
>>> s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)#创建socket对象
>>> addr=('0.0.0.0',5000)
>>> s.bind(addr)#绑定目标地址和端口号
```

##### 监听

&emsp;&emsp;` socket.socket.listen([backlog])`

&emsp;&emsp;函数说明:监听套接字,使服务器能够接收连接,绑定后才可监听。

- `backlog`:接受套接字的最大个数,至少为0,如果没有指定,则默认一个合理值

&emsp;&emsp;示例:

```python
>>> import usocket as socket
>>> s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)#创建socket对象
>>> addr=('0.0.0.0',5000)
>>> s.bind(addr)#绑定本机地址和端口号
>>> s.listen(5)#设置最大连接数为5,超过后排队
```

##### 接收连接

&emsp;&emsp;`conn,addr= socket.socket.accept()`

&emsp;&emsp;函数说明:接收连接请求,绑定端口并监听后才可以接受客户端连接请求。

- conn:新的套接字对象,可以用来收发消息
- address:连接到服务器的客户端地址

&emsp;&emsp;示例:

```python
>>> import usocket as socket
>>> s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)#创建socket对象
>>> addr=('0.0.0.0',5000)
>>> s.bind(addr)#绑定本机地址和端口号
>>> s.listen(5)#设置最大连接数为5,超过后排队
>>> conn,addr=s.accept()#接收TCP客户端连接
```

#### 客户端套接字

##### 连接

&emsp;&emsp;`socket.socket.connect(address)`


&emsp;&emsp;函数说明:接收连接请求。

- address:服务器地址和端口号的元组或列表

&emsp;&emsp;示例:

```python
>>> import usocket as socket
>>> s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)#创建socket对象
>>> addr=('192.168.50.66',5000)
>>> s.connect(addr)#接收连接请求
```

#### 公共用途的套接字函数

##### 设置套接字

&emsp;&emsp;`socket.socket.setsockopt(level, optname, value)`

&emsp;&emsp;函数说明:根据选项值设置套接字。

- `level`:套接字选项级别,可为`socket.SOL_SOCKET`
- `optname`:套接字的选项,可为`socket.SOCK_STREAM` 、 `socket.SOCK_DGRAM` 、  `socket.SOCK_RAW`  或 `socket.SO_REUSEADDR`。
- `value`:可以是一个整数,也可以是一个表示缓冲区的bytes类对象。

&emsp;&emsp;示例:

```python
>>> import usocket as socket
>>> s=socket.socket(socket.AF_INET, socket.SOCK_STREAM)##创建socket对象
>>> s.setsockopt(socket.SOL_SOCKET, socket.SOCK_RAW, 1)#设置套接字为原始套接字
```

##### 发送数据

&emsp;&emsp;`socket.socket.send(bytes)`

&emsp;&emsp;函数说明:发送数据,并返回发送的字节数。

&emsp;&emsp;用接收TCP客户端连接时新的套接字对象conn发送数据

- bytes:bytes类型数据

&emsp;&emsp;示例:

```python
import usocket as socket
s=socket.socket(socket.AF_INET, socket.SOCK_STREAM)##创建socket对象
addr=('0.0.0.0',5000)
s.bind(addr)#绑定本机地址和端口号
s.listen(5)#设置最大连接数为5,超过后排队
conn,addr=s.accept()#接收TCP客户端连接
msg="hello BlackWalnut Labs, I am TCP Client"
conn.send(str(msg,'utf-8'))#将数据编码成utf-8格式发送出去
```

&emsp;&emsp;`socket.socket.sendall(bytes)`

&emsp;&emsp;函数说明:与send()函数类似,区别是sendall()函数该方法会通过连续发送尽量发送所有数据。

- bytes:bytes类型数据

&emsp;&emsp;示例:

```python
import usocket as socket
s=socket.socket(socket.AF_INET, socket.SOCK_STREAM)##创建socket对象
addr=('0.0.0.0',5000)
s.bind(addr)#绑定本机地址和端口号
s.listen(5)#设置最大连接数为5,超过后排队
conn,addr=s.accept()#接收TCP客户端连接
msg="hello BlackWalnut Labs, I am TCP Client"
conn.sendall(str(msg,'utf-8')#将数据编码成utf-8格式发送出去
```

&emsp;&emsp;`socket.socket.sendto(bytes, address)`

&emsp;&emsp;函数说明:与send()函数类似,发送数据,目标由address决定,用于UDP通信,返回发送的数据大小。

- bytes:bytes类型数据
- address:目标地址和端口号的元组

&emsp;&emsp;示例:

```python
import usocket as socket
s = socket.socket(socket.AF_INET,socket.SOCK_DGRAM)#创建UDP数据报类型的socket对象
addr=('192.168.50.66',5000)#目标地址和端口号的元组
send_data='hello,i am cilent'#数据
s.sendto(send_data,addr)##将数据编码成utf-8格式发送出去,用于UDP通信
```

##### 接收数据

&emsp;&emsp;`socket.socket.recv(bufsize)`

&emsp;&emsp;函数说明:接收数据,返回接收到的数据对象。

- bufsize:指定一次接收的最大数据量

&emsp;&emsp;示例:

```python
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)#创建socket对象
addr=('192.168.50.66',5000)
s.connect(addr)#客户端接收连接请求
data=s.recv(1024)
print(str(data,'utf8'))#将数据解码成utf-8格式接收
```

&emsp;&emsp;`socket.socket.recvfrom(bufsize)`

&emsp;&emsp;函数说明:接收数据,用于UDP通信,并返回接收到的数据对象和对象的地址。

- bufsize:指定一次接收的最大数据量

&emsp;&emsp;示例:

```python
import usocket as socket
s = socket.socket(socket.AF_INET,socket.SOCK_DGRAM)#创建UDP数据报类型的socket对象
addr=('192.168.50.66',5000)#目标地址和端口号的元组
send_data='hello,i am cilent'#数据
s.sendto(send_data,addr)##将数据编码成utf-8格式发送出去,用于UDP通信
data,address=s.recvfrom(1024)#接收数据,用于UDP通信
print(str(data,'utf-8'))#将数据解码成'utf-8'格式打印
```

##### 设置超时时间

&emsp;&emsp;`socket.socket.settimeout(value)`

&emsp;&emsp;函数说明:设置超时时间,单位:秒。

&emsp;&emsp;值参数可以是一个表示秒或None的非负浮点数。若给定一个非零值,且在操作完成前超时时间已过期,则后续的socket操作将会引发异常。若给定0值,则socket采用非阻塞模式。若给定None,则socket给定阻塞模式。

&emsp;&emsp;示例:

```python
import usocket as socket
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)#创建socket对象
s.settimeout(2.0)#设置超时时间为2s
```

##### 设置是否阻塞

&emsp;&emsp;`socket.socket.setblocking(flag)`

&emsp;&emsp;函数说明:设置socket的阻塞或非阻塞模式:若标记为false,则将该socket设置为非阻塞模式,而非阻塞模式。

&emsp;&emsp;示例:

```python
import usocket as socket
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)#创建TCP数据报的socket对象
s.setblocking(True)#将socket对象设置成阻塞模式       
```

##### socket相关联的文件对象

&emsp;&emsp;`socket.socket.makefile(mode=, buffering=)`

&emsp;&emsp;函数说明:返回一个与socket相关联的文件对象。具体的返回类型取决于给定makefile()的参数。

&emsp;&emsp;mode:支持的模式,仅限于二进制模式'rb'或'wb'

&emsp;&emsp;buffering:缓冲,可为0(输出原始数据),None(buffering为-1),负数(buffering 为默认缓冲大小)

&emsp;&emsp;socket须为阻塞模式;允许超时存在,但若出现超时,文件对象的内部缓冲区可能会以不一致状态结束。

&emsp;&emsp;示例:

```python
import usocket as socket
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)#创建TCP数据报的socket对象
s.settimeout(10.0)#设置超时时间为10s
print(s.makefile('rb',0))#只读形式的socket相关联对象
<socket>
```

##### socket文件描述

&emsp;&emsp;`socket.socket.fileno()`

&emsp;&emsp;函数说明:返回套接字的文件描述。

&emsp;&emsp;示例:

```python
import usocket as socket
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)#创建TCP数据报的socket对象
print(s.fileno())#打印套接字文件描述符号
#388
```

##### 读取数据

&emsp;&emsp;`socket.socket.read([size])`

&emsp;&emsp;函数说明:从socket中读取size字节。返回一个字节对象。若未给定 `size` ,则按照类似 `socket.readall()` 的模式运行

&emsp;&emsp;示例:

```python
import usocket as socket
s=socket.socket(socket.AF_INET, socket.SOCK_STREAM)##创建socket对象
addr=('0.0.0.0',5000)
s.bind(addr)#绑定本机地址和端口号
s.listen(5)#设置最大连接数为5,超过后排队
conn,addr=s.accept()#接收TCP客户端连接
data=conn.read(1024)#读取1024字节数据
print(str(data, 'utf8'), end='')#以utf8格式打印出
```

&emsp;&emsp;`socket.socket.readinto(buf[,nbytes])`

&emsp;&emsp;函数说明:将字节读取入缓冲区。若指定 *nbytes* ,则最多读取该数量的字节。否则,最多读取 *len(buf)* 数量的字节

&emsp;&emsp;示例:

```python
import usocket as socket
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)#创建socket对象
addr=('0.0.0.0',5000)
s.bind(addr)#绑定本机地址和端口号
s.listen(5)#设置最大连接数为5,超过后排队
conn,addr=s.accept()#接收TCP客户端连接
buf=bytearray(8)
data=conn.readinto(buf,1024)#读取1024字节数据
print(str(data))#以utf8格式打印出
```

&emsp;&emsp;`socket.socket.readline()`

&emsp;&emsp;函数说明:接收一行数据,遇换行符结束,并返回接收数据的对象 。

&emsp;&emsp;示例:

```python
import usocket as socket
s=socket.socket(socket.AF_INET, socket.SOCK_STREAM)##创建socket对象
addr=('0.0.0.0',5000)
s.bind(addr)#绑定本机地址和端口号
s.listen(5)#设置最大连接数为5,超过后排队
conn,addr=s.accept()#接收TCP客户端连接
data=conn.readline()#读取一行数据
print(str(data), end='')#以utf8格式打印出
```

##### 写数据

&emsp;&emsp;`socket.socket.write(buf)`

&emsp;&emsp;函数说明:将字节类型数据写入套接字,并返回写入数据的大小。

&emsp;&emsp;示例:

```python
import usocket as socket
s=socket.socket(socket.AF_INET, socket.SOCK_STREAM)##创建socket对象
addr=('0.0.0.0',5000)
s.bind(addr)#绑定本机地址和端口号
s.listen(5)#设置最大连接数为5,超过后排队
conn,addr=s.accept()#接收TCP客户端连接
msg="hello BlackWalnut Labs, I am TCP Client"
print(conn.write(msg))
```

##### 关闭套接字

&emsp;&emsp;`socket.socket.close()`

&emsp;&emsp;函数说明:关闭套接字。

&emsp;&emsp;示例:

```python
import usocket as socket
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)#创建socket对象
s.close()#关闭套接字
```

## 示例

&emsp;&emsp;**TCP服务端和客户端通信**

&emsp;&emsp;waffle nano做TCP服务端

&emsp;&emsp;服务端要做的事有这些:

> **Step 1**:创建ServerSocket对象,绑定监听的端口
>
> **Step 2**:调用accept()方法监听客户端的请求
>
> **Step 3**:连接建立后,通过输入流读取客户端发送的请求信息
>
> **Step 4**:通过输出流向客户端发送响应信息
>
> **Step 5**:关闭相关资源

```python
import network
import usocket as socket

wl = network.WLAN()#创建WLAN对象
wl.active(1)#激活WLAN
wl.connect('Noah-BlackWalnut','123abc456d',security=network.AUTH_PSK)#连接网络

s=socket.socket(socket.AF_INET, socket.SOCK_STREAM)##创建socket对象
addr=('0.0.0.0',5000)#目标地址
s.bind(addr)#绑定本机地址和端口号
s.listen(5)#设置最大连接数为5,超过后排队
while True:
    conn,addr=s.accept()#接收TCP客户端连接
    msg=input()#发送数据为输入数据
    conn.send(str(msg,'utf-8'))#将数据编码成utf-8格式发送出去
s.close()
```

&emsp;&emsp;TCP客户端

&emsp;&emsp;waffle nano做TCP客户端

&emsp;&emsp;客户端要做的事有这些:

> **Step 1**:创建Socket对象,指明需要链接的服务器的地址和端号
>
> **Step 2**:链接建立后,通过输出流向服务器发送请求信息
>
> **Step 3**:通过输出流获取服务器响应的信息
>
> **Step 4**:关闭相关资源

```python
import network
import usocket as socket

wl = network.WLAN()#创建WLAN对象
wl.active(1)#激活WLAN
wl.connect('Noah-BlackWalnut','123abc456d',security=network.AUTH_PSK)#连接网络

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)#创建socket对象
addr=("192.168.50.66",5000)#waffle nano地址
s.connect(addr)#客户端接收连接请求
data=s.recv(1024)#客户端接收数据
print(str(data,'utf8'))#将数据解码成utf-8格式接收
s.close()
```

&emsp;&emsp;先点击`run`运行服务端,输入任意发送数据,再点击`run`运行客户端,可以看到服务器发送数据已被客户端接收并打印在显示台。

&emsp;&emsp;**UDP服务端和客户端通信**

&emsp;&emsp;waffle nano做UDP服务端

&emsp;&emsp;服务端要做的事有这些:

> **Step 1**:创建ServerSocket对象
>
> **Step 2**:调用recvfrom()方法接收客户端的发送的数据
>
> **Step 3**:通过sendto()方法向客户端发送数据
>
> **Step 4**:关闭相关资源

```python
import network
import usocket as socket

wl = network.WLAN()#创建WLAN对象
wl.active(1)#激活WLAN
wl.connect('Noah-BlackWalnut','123abc456d',security=network.AUTH_PSK)#连接网络

s = socket.socket(socket.AF_INET,socket.SOCK_DGRAM)#创建UDP数据报类型的socket对象
addr=('0.0.0.0',5000)#目标地址和端口号的元组
s.bind(addr)#绑定目标地址和端口号
while True:
    data,address=s.recvfrom(1024)#接收客户端发出数据
    print(str(data,'utf8'),end='')#将接收数据解码成utf-8格式打印出来
    send_data='hello,i am server'#数据
    s.sendto(send_data,address)#将数据编码成utf-8格式发送到客户端,用于UDP通信
s.close()#关闭socket
```

&emsp;&emsp;UDP客户端

&emsp;&emsp;客户端要做的事有这些:

> **Step 1**:创建Socket对象,指明需要链接的服务器的地址和端号
>
> **Step 2**:通过sendto()方法向客户端发送数据
>
> **Step 3**:调用recvfrom()方法接收服务端的发送的数据
>
> **Step 4**:关闭相关资源

```python
import network
import usocket as socket

wl = network.WLAN()#创建WLAN对象
wl.active(1)#激活WLAN
wl.connect('Noah-BlackWalnut','123abc456d',security=network.AUTH_PSK)#连接网络

s = socket.socket(socket.AF_INET,socket.SOCK_DGRAM)
add=("192.168.50.66",5000)
while True:
    send_data='hello,i am cilent'#数据
    s.sendto(send_data,addr)#将数据编码成utf-8格式发送到服务端,用于UDP通信
    data,address=s.recvfrom(1024)#接收服务端发送数据,用于UDP通信
    print(str(data,'utf8'), end='')#将数据编码成utf-8格式发送到客户端,用于UDP通信
s.close()#关闭socket
```
&emsp;&emsp;先点击`run`运行服务端,输入任意发送数据,再点击`run`运行客户端,可以看到服务器发送数据已被客户端接收并打印在显示台,客户端发送数据也被显示在服务器显示台。