Bootstrap

一、Socket通信

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模块进行通信通常包括以下步骤:

  1. 创建套接字:使用socket()函数创建套接字对象,指定地址族和套接字类型。
  2. 绑定地址和端口(可选):如果是服务器,可以使用bind()方法绑定地址和端口。
  3. 监听连接(仅服务器端):使用listen()方法开始监听连接。
  4. 接受连接(仅服务器端):使用accept()方法接受客户端的连接请求,返回新的套接字对象和客户端地址。
  5. 发送和接收数据:使用send()方法发送数据,使用recv()方法接收数据。
  6. 关闭连接:使用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.herrorDNS解析错误
socket.gaierror获取地址信息引发的错误
socket.ConnectionAbortedError连接被对端终止
socket.ConnectionRefuseError连接被对端拒绝
socket.ConnectionResetError连接被对端重置
socket.NotConnectedError未连接时尝试操作
;