Socket是众多IPC的一种,在其它特定的场景下,其它方式的IPC性能可能会更佳, 但是对于跨平台这点,Socket绝对是唯一的选择。
Socket最初出现在1971年的ARPANET,源自RFC 147,而如今的Socket实现,绝大多数 都来源于Berkeley sockets(1983)。Socket最常见的就是C/S应用上,当然,也有用于同一 主机间进程通信的——Unix domain sockets。
在Python里有两个和Socket有关的主要模块:socket和socketserver,其中,socket模块提供底层网络接口,即标准的BSD Socket API, socketserver提供了网络服务的框架,可以简化对应的socket开发。Socket API可以在官方文档上查阅得到,主要的Socket API函数有以下几个:
- socket()
- bind()
- listen()
- accept()
- connect()
- connect_ex()
- send()
- recv()
- close()
以TCP Socket为例:
import socket
后,使用socket.scoket()
创建一个默认类型为socket.SOCK_STREAM
的socket,并默认使用TCP协议,完整的写法应该是socket.socket(socket.AF_INET, socket.SOCK_STREAM)
,AF_INET
表示IPV4,相对应的有AF_UNIX
(表示UNIX系统本机进程通信)和
AF_INET6
(表示IPV6);SOCK_STREAM
代表基于TCP的流式socket,而对应的SOCK_DGRAM
则代表
基于UDP的数据报式socket。
接下来,服务端的socket绑定相应的地址端口——bind((HOST,PORT))
,在bind的参数中,括号内的形式取决于socket的地址族,socket.AF_INET
IPv4将返回(HOST,PORT)
形式的二元组。
下面这个问题有助于理解为什么服务端绑定地址端口:
In socket programming, why a client is not bind to an address?
HOST说明及需要注意的是:
-
A pair (host, port) is used for the AF_INET address family, where host is a string representing either a hostname in Internet domain notation like 'daring.cwi.nl' or an IPv4 address like '100.50.200.5', and port is an integer.
- For IPv4 addresses, two special forms are accepted instead of a host address: '' represents INADDR_ANY, which is used to bind to all interfaces, and the string '' represents INADDR_BROADCAST. This behavior is not compatible with IPv6, therefore, you may want to avoid these if you intend to support IPv6 with your Python programs.
If you use a hostname in the host portion of IPv4/v6 socket address, the program may show a nondeterministic behavior, as Python uses the first address returned from the DNS resolution. The socket address will be resolved differently into an actual IPv4/v6 address, depending on the results from DNS resolution and/or the host configuration. For deterministic behavior use a numeric address in host portion.
完成地址端口的关联,listen([backlog])
函数让服务端可以接收连接请求,backlog
参数从Python 3.5开始可选,它指定在拒绝新的连接之前系统将允许使用的未接受的连接数量。
如果服务器需要同时接收很多连接请求,增加 backlog 参数的值可以加大等待链接请求队列的长度,最大长度取决于操作系统。比如在 Linux 下,参考will-increasing-net-core-somaxconn-make-a-difference。
accept()
函数用来接收客户端的一个连接,返回值是一对(conn, address)
,值得注意的是,conn是一个在连接上可以send和receive数据的新socket对象,address是一个由主机、端口号组成的 IPv4/v6 连接的元组。也就是说这时有两个socket对象,刚才那个监听socket是用来接收新的连接请求。
而现在这个socket对象将用来和客户端通信。
这时候,服务端已经做好准备工作:
#!/usr/bin/env python3
import socket
HOST = "127.0.0.1" # Standard loopback interface address (localhost)
PORT = 65432 # Port to listen on (non-privileged ports are > 1023)
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
s.bind((HOST, PORT))
s.listen()
conn, addr = s.accept()
with conn:
print("Connected by", addr)
while True:
data = conn.recv(1024)
if not data:
break
conn.sendall(data)
从
accept()
获取客户端socket
连接对象conn
后,使用一个无限while
循环来阻塞调用conn.recv()
,无论客户端传过来什么数据都会使用conn.sendall()
打印出来。如果conn.recv()
方法返回一个空byte
对象(b'')
,然后客户端关闭连接,循环结束,with
语句和conn
一起使用时,通信结束的时候会自动关闭socket
连接。——socket-programming-in-python
#!/usr/bin/env python3
import socket
HOST = '127.0.0.1' # 服务器的主机名或者 IP 地址
PORT = 65432 # 服务器使用的端口
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
s.connect((HOST, PORT))
s.sendall(b'Hello, world')
data = s.recv(1024)
print('Received', repr(data))
客户端也需要创建一个socket对象,调用connect()
连接到服务端,并开始三次握手。