1.1 什么是Socket
Socket(套接字)是操作系统中I/O系统的延伸部分,它可以使进程和机器之间的通信成为可能。Socket实际上是一个通信端点,它包含了一个 IP 地址和一个端口号。IP 地址标识了主机,而端口号标识了主机上的进程。一个套接字就是网络上进程通信的一端,提供了应用层进程利用网络协议交换数据的机制。
1.2 Socket类型
在python网络编程中,Socket有三种主要类型
- TCP流套接字(socket.SOCK_STREAM):基于TCP(传输控制协议,The Transmission Control Protocol),提供面向连接的、可靠的、双向的数据流通信。TCP需要进行连接的建立和关闭过程,以及数据的确认与重传,延迟较高,适用于大量数据传输的场景,如文件传输、视频流等。
- UDP数据报套接字(socket.SOCK_DGRAM):基于UDP(用户数据报协议, User DatagramProtocol),提供无连接的、不可靠的通信。UDP不需要建立连接,延迟较低,且支持广播和多播,可以将数据包发送给多个接收者,适用于对实时性要求不高的小数据量传输场景。
- 原始套接字(socket.SOCK_RAW):允许程序直接访问网络层协议,如IP协议,程序可以直接接收和发送原始数据包,而无需进行传输层(如TCP和UDP)的处理。
1.3 建立Socket对象
在建立Socket对象的时候,我们需要告诉系统两件事情:协议家族和socket类型。通信类型指明用什么Socket类型来传输数据,常见的协议包括IPv4、IPv6、IPX/SPX、AFP等等,目前最常用的是IPv4。python的socket模块支持以下几种常见的协议家族。
常见协议族 | 表示方法 | 应用场景 | 备注 |
---|---|---|---|
IPv4协议族 | AF_INET | 用于Internet网络 | 使用IP地址和端口号来标识套接字。最常用的协议族,用于TCP和UDP通信。 |
IPv6协议族 | AF_INET6 | 用于Internet网络 | 与IPv4类似,但使用IPv6地址。 |
Unix域协议族 | AF_UNIX | 用于同一台计算机上进程间通信 | 使用文件系统路径作为套接字地址。 |
Netlink协议族 | AF_NETLINK | 用于与Linux内核通信 | 主要用于配置网络和获取网络状态信息 |
蓝牙协议族 | AF_BLUETOOTH | 用于蓝牙设备之间的通信 | |
原始套接字协议族 | AF_PACKET | 用于直接访问数据链路层的数据包 |
import socket
# 我们通过socket()函数创建一个套接字对象
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 通过connect()函数进行连接,我们需要提供一个元组,包含远程主机名或IP地址、远程端口
s.connect(('127.0.0.1', 12345))
1.3.1 getservbyname()查询端口号
getservbyname() 函数是socket 模块中用于根据服务名称获取对应端口号的函数。它通过查询系统的服务配置文件(通常是 /etc/services)来查找服务名称对应的端口号。
使用方法如下:
import socket
# 根据服务名获取端口号
'''
未指定传输协议类型
如果在服务器配置文件中只有一个对应的端口号,则返回该端口号
如果服务名称对应多个端口号,会引发‘socket.error’错误
'''
port1 = socket.getservbyname('http')
print('HTTP服务的默认端口号是:', port1)
'''
指定传输协议(tcp),返回在指定传输协议下的端口号
如果该服务名称在系统配置文件中没有对应的端口号,或者指定的传输协议不支持该服务,则会引发‘socket.error’错误
'''
port2 = socket.getservbyname('https', 'tcp')
print('HTTPS服务的默认端口号是:', port2)
1.3.2 从socket获取信息
我们可以通过getsockname()和getpeername()来分别获取本机和远程机器的IP地址和端口号
import socket
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(('www.baidu.com', 443))
# 获取本机IP地址和端口号
print('本机IP及端口:', s.getsockname())
# 获取远程机器的IP地址和端口号
print('远程机器IP及端口:', s.getpeername())
# 获取返回类型(元组)
print(type(s.getsockname()))
程序运行结果如下:
注意:在客户端,端口号是由操作系统随机分配的,所以每次运行程序的时候会发现端口号不一样!
1.3.3 socket通信
利用socket模块进行通信通常包括以下步骤:
- 创建套接字:使用socket()函数创建套接字对象,指定地址族和套接字类型。
- 绑定地址和端口(可选):如果是服务器,可以使用bind()方法绑定地址和端口。
- 监听连接(仅服务器端):使用listen()方法开始监听连接。
- 接受连接(仅服务器端):使用accept()方法接受客户端的连接请求,返回新的套接字对象和客户端地址。
- 发送和接收数据:使用send()方法发送数据,使用recv()方法接收数据。
- 关闭连接:使用close()方法关闭套接字。
下面是一个例子,演示客户端和服务器端的通信:
关于服务器程序具体实现请参考第二章 TCP与UDP服务器简单实现及测试
服务器端
import socket
# 创建套接字对象
server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 绑定地址和端口
server_socket.bind(('127.0.0.1', 12345))
# 开始监听连接
server_socket.listen(1)
print('Wating for connection...')
# 接受连接
client_socket, client_addr = server_socket.accept()
print('Connection from', client_addr)
# 接收数据
data = client_socket.recv(1024)
print('Received', data.decode())
# 发送响应数据
client_socket.send(b'Hello,client!')
# 关闭连接
client_socket.close()
server_socket.close()
客户端
import socket
# 创建套接字对象
client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 连接服务器
client_socket.connect(('127.0.0.1', 12345))
# 发送数据
client_socket.send(b'Hello Server!')
# 接收响应数据
data = client_socket.recv(1024)
print('Received:', data.decode())
# 关闭连接
client_socket.close()
运行测试
我们先运行服务器端代码,控制台显示如下:
再运行客户端代码,控制台显示如下:
再回到服务器端,控制台显示如下:
这个例子中,服务器端创建了一个TCP套接字,绑定到地址"127.0.0.1"的端口12345,并开始监听连接。客户端创建了一个TCP套接字,并连接到服务器。然后,客户端发送数据到服务器,服务器接收并处理数据,然后发送响应数据给客户端。从而实现了客户端与服务器间的通信。
1.4 socket异常
使用socket进行网络编程时,常见的有以下异常出现:
异常 | 原因 |
---|---|
socket.error | 所有socket相关错误的基类 |
socket.timeout | 操作超时 |
socket.herror | DNS解析错误 |
socket.gaierror | 获取地址信息引发的错误 |
socket.ConnectionAbortedError | 连接被对端终止 |
socket.ConnectionRefuseError | 连接被对端拒绝 |
socket.ConnectionResetError | 连接被对端重置 |
socket.NotConnectedError | 未连接时尝试操作 |