「这是我参与11月更文挑战的第9天,活动详情查看:2021最后一次更文挑战」。
TCP 编程属于 socket 编程的一部分。也是整个 TCP/IP 协议栈的核心部分。
作为一种通信手段,它有一些非常好的特点,例如面向连接、可靠。它为我们提供了一种全双工的通信方式。除此之外,它的连接方式是相对松耦合的。这些特性都让人会优先考虑使用它来设计程序。甚至进程间通信也经常会采用 socket 而不是其他的通信方式。
处于通用的目的考虑,我选择使用 Python 来完成程序的编写。因为 Python 中的套接字是基于 posix 标准的,而且比起 C 语言的 socket API 多了异常处理,初学时实际更容易调试。
既然是用于通信,我们就先从一个接受数据的例子开始,而不是使用很多程序都喜欢使用的请求回复模型开始。
首先我们给出用于接受数据的代码:
from socket import AF_INET,SOCK_STREAM,socket
server_name = // a host
server_port = // a port number
client = socket(AF_INET, SOCK_STREAM)
client.connect((server_name, server_port))
buffer_size = 1024
try:
while True:
recv = client.recv(buffer_size)
print('从服务端接受到的数据: ', recv.decode())
except KeyboardInterrupt:
client.close()
整个过程我们可以看作是从文件中去读取数据(如果是 linux 平台,那么实际也是如此,因为 linux 下这些都是文件)。
与读取文件需要的文件名相对比,socket 也需要一个地址,这里就是一个 IP 地址和端口就是类似的概念。 创建 socket 对象时,传入的两个 AF_INET 和 SOCK_STREAM 两个参数分别表示我们使用 TCP/IP 协议栈,以及我们现在使用的 TCP 协议。
而后 connect 方法是尝试建立连接,可以类比到尝试打开文件(注意这里传入的是元组)。
之后的读取数据操作可以看到与文件操作基本一致。
这里最值得注意的是元组中的两个值,其实程序是假设我们应该知道要和谁连接的。比如说我们在现实世界里要找某个人,某个东西总归有个目标。程序也是这样,它假设我们知道要找谁,而这也是服务端(或者说发送数据的程序)不同的一个地方。
IP 地址显然我们的程序是会有的,而这个端口号我们怎么知道呢。所以 TCP 为我们设计的方式是,在服务端的程序中手动指定一个端口绑定,把程序和端口捆绑。这样接收数据的客户端就可以基于这个来连接。
程序会像下面这样:
from socket import socket, AF_INET, SOCK_STREAM
port = // a port number
server = socket(AF_INET, SOCK_STREAM)
server.bind(('', port))
server.listen()
while True:
conn, addr = server.accept()
conn.send("some data".encode())
会发现我们做了一个 bind 操作绑定了所谓的端口。这里 host 为空表示会绑定到主机拥有的全部 IP 地址。 之后使用了 listen 方法,这个 socket 会开始准备接受外部的连接。 而当有连接时 accept 方法(实际上它在这里是阻塞式的,不过暂时可以不管)会得到对方的 socket 连接(conn 这个对象)和一个地址结构(这个留到后面再讲),同样的这里就是类似于文件的写操作。
到这里我们就实现了数据的单向传输,也就是最基础的单向数据传输。可以看到的是他们之间只依赖于IP 和端口号,耦合程度是属于比较低的。