Socket套接字

Socket概览:

image-20240623134438949

什么是 Socket?

Socket(套接字)是网络通信中的一种机制,可以将其类比为电话插座,负责连接通信双方。Socket 就像电话插座上的孔,端口负责连接,而创建连接就像把插头插在插座上。Socket 实例创建后会监听消息,当有消息传入时会自动接通。

实际上,Socket 是应用层和传输层之间的抽象层,它将 TCP/IP 层复杂的操作封装成简单接口,供应用层调用,从而实现进程间的网络通信。Socket 起源于 UNIX,在 UNIX 的“一切皆文件”思想下,进程间通信通过文件描述符(file descriptor)进行,Socket 就是这种“打开-读/写-关闭”模式的实现。服务器和客户端各自维护一个“文件”,通过文件读写来传输数据,通讯结束时关闭文件。

Socket 的位置

Socket 位于应用层和传输层之间。它将复杂的网络协议封装成简洁的接口,便于程序进行网络通信。

img


Socket 的类型

Socket 主要分为两种类型:流格式套接字(SOCK_STREAM)和数据报格式套接字(SOCK_DGRAM)。

流格式套接字(SOCK_STREAM)

流格式套接字也叫“面向连接的套接字”,是一种可靠、双向的通信数据流,可以确保数据准确无误地到达另一台计算机。如果数据损坏或丢失,可以重新发送。

特点:

  • 数据不会在传输过程中丢失。
  • 数据按照顺序传输。
  • 数据发送和接收不同步。

可以将 SOCK_STREAM 比作一条传送带,数据按顺序传输,不会丢失。数据传输时,无需接收端和发送端同步,接收端可以根据自己的节奏读取数据。

应用场景:

  • 浏览器使用的 HTTP 协议:基于面向连接的套接字,确保数据准确无误。

流格式套接字使用 TCP 协议(Transmission Control Protocol,传输控制协议),TCP 确保数据按照顺序到达且无错误。

数据报格式套接字(SOCK_DGRAM)

数据报格式套接字也叫“无连接的套接字”,它传输数据时不进行数据校验。如果数据在传输中损坏或丢失,无法补救。这种套接字传输效率高,但数据传输顺序不保证,可能丢失或损坏。

特点:

  • 强调快速传输而非传输顺序。
  • 数据可能丢失或损毁。
  • 每次传输的数据大小有限。
  • 数据的发送和接收同步。

可以将 SOCK_DGRAM 比作快递,一些包裹可能丢失或损坏,但传输效率高。

应用场景:

  • QQ 视频聊天和语音聊天:使用 SOCK_DGRAM 来传输数据,保证通信效率,即使丢失少量数据,也不会影响整体通信质量。

数据报格式套接字使用 UDP 协议(User Datagram Protocol,用户数据报协议),它注重快速传输,而非可靠性。


Socket 通信过程

Socket 保证了不同计算机之间的通信,也就是网络通信。对于网站,通信模型是服务器与客户端之间的通信。两端都建立了一个 Socket 对象,然后通过 Socket 对象对数据进行传输。通常服务器处于一个无限循环,等待客户端的连接。

下面是面向连接的 TCP 时序图:

img

客户端过程

客户端的通信过程较为简单,创建 Socket 后连接服务器,然后发送数据、接收响应数据,最后关闭连接。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import socket
import sys

if __name__ == '__main__':
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # 创建 Socket 连接
sock.connect(('127.0.0.1', 8001)) # 连接服务器
while True:
data = input('Please input data:')
if not data:
break
try:
sock.sendall(data)
except socket.error as e:
print('Send Failed...', e)
sys.exit(0)
print('Send Successfully')

res = sock.recv(4096) # 获取服务器返回的数据
print(res)
sock.close()

sendall() 方法会尝试发送所有数据,成功时返回 None,失败时抛出异常。recv() 方法用于接收服务器返回的数据。

服务端过程

服务端先初始化 Socket,与本机地址及端口绑定,准备接收连接。调用 accept() 阻塞等待客户端连接,连接建立后,接收并处理请求,发送响应数据,最后关闭连接。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
import socket
import sys

if __name__ == '__main__':
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # 创建 Socket 连接(TCP)
print('Socket Created')

try:
sock.bind(('127.0.0.1', 8001)) # 配置 Socket,绑定 IP 地址和端口号
except socket.error as e:
print('Bind Failed...', e)
sys.exit(0)

sock.listen(5) # 设置最大允许连接数

while True:
conn, addr = sock.accept()
try:
conn.settimeout(10) # 设置超时时间
while True:
data = conn.recv(1024)
print('Get value ' + data.decode(), end='\n\n')
if not data:
print('Exit Server', end='\n\n')
break
conn.sendall(b'OK') # 返回数据
except socket.timeout:
print('Time out')

conn.close() # 关闭连接
sock.close()

accept() 方法阻塞等待客户端连接,返回新的 Socket 对象 conn,通过 conn 与客户端通信。


从 TCP 连接的视角看 Socket 过程

TCP 三次握手的 Socket 过程

img

  1. 服务器初始化 Socket,调用 socket()bind()listen(),调用 accept() 阻塞等待。
  2. 客户端调用 connect() 向服务器发送 SYN 请求。
  3. 服务器发送 SYN 和 ACK 应答,完成第一次握手。
  4. 客户端收到应答后返回,再发送一个 ACK。
  5. 服务器接收 ACK 确认,accept() 返回,建立连接。

TCP 四次挥手的 Socket 过程

img

  1. 某个应用进程调用 close() 主动关闭,发送一个 FIN。
  2. 另一端接收到 FIN 后被动关闭,发送 ACK。
  3. 被动关闭的一端调用 close() 关闭 Socket,发送 FIN。
  4. 接收端发送 ACK 确认,连接关闭。

注意: 服务端代码在实际应用中需要并发处理,服务器通常会 fork 新的进程或线程来处理请求,以提高处理能力。


Socket 是网络通信的基础,通过理解 Socket,可以更好地进行网络编程和应用开发。希望这篇文章能帮助你理解 Socket 的概念及其应用场景。如果有疑问,欢迎在评论区讨论!