【2】Socket通信
【转自:https://www.zhihu.com/question/29637351/answer/2278820437】
1. 什么是socket
socket中文叫套接字,就是TCP/IP协议栈中不同主机应用进程之间进行双向通信的端点抽象。一个socket就是网络上进程通信的一端,提供了应用进程利用网络协议交换数据的机制。socket上联应用进程,下联操作系统TCP/IP协议栈。是应用程序通过网络协议进行通信的接口。换句话说,你能通过socket 的接口,来控制协议栈工作,从而实现网络通信,达到跨主机通信。
协议栈的上半部分有两块,分别是负责收发数据的 TCP 和 UDP 协议,它们两会接受应用层的委托执行收发数据的操作。
协议栈的下面一半是用 IP 协议控制网络包收发操作,在互联网上传数据时,数据会被切分成一块块的网络包,而将网络包发送给对方的操作就是由 IP 负责的。
此外 IP 中还包括 ICMP 协议和 ARP 协议:
- ICMP 用于告知网络包传送过程中产生的错误以及各种控制信息;
- ARP 用于根据 IP 地址查询相应的 以太网 MAC 地址;
IP 下面的网卡驱动程序负责控制网卡硬件,而最下面的网卡则负责完成实际的收发操作,也就是对网线中的信号执行发送和接收操作。
2. 使用C/S架构通讯的Socket
【转自:https://www.zhihu.com/question/29637351/answer/1934423848】
socket 一般分为 TCP 网络编程和 UDP 网络编程。
2.1 TCP 网络编程
基于 TCP 协议的客户端和服务器:
-
服务端和客户端初始化
socket
,得到文件描述符; -
服务端调用
bind
,绑定 IP 地址和端口; -
服务端调用
listen
,进行监听; -
服务端调用
accept
,等待客户端连接; -
客户端调用
connect
,向服务器端的地址和端口发起连接请求; -
服务端
accept
返回 用于传输的socket
的文件描述符; -
客户端调用
write
写入数据;服务端调用read
读取数据; -
客户端断开连接时,会调用
close
,那么服务端read
读取数据的时候,就会读取到了EOF
,待处理完数据后,服务端调用close
,表示连接关闭。
这里需要注意的是,服务端调用 accept 时,连接成功了会返回一个已完成连接的 socket,后续用来传输数据。
所以,监听的 socket 和真正用来传送数据的 socket,是「两个」 socket,一个叫作监听 socket,一个叫作已完成连接 socket。
成功连接建立之后,双方开始通过 read 和 write 函数来读写数据,就像往一个文件流里面写东西一样。
2.2 结合三次握手连接的 TCP socket
- 客户端的协议栈向服务器端发送了 SYN 包,并告诉服务器端当前发送序列号 client_isn,客户端进入 SYN_SENT 状态;
- 服务器端的协议栈收到这个包之后,和客户端进行 ACK 应答,应答的值为 client_isn+1,表示对 SYN 包 client_isn 的确认,同时服务器也发送一个 SYN 包,告诉客户端当前我的发送序列号为 server_isn,服务器端进入 SYN_RCVD 状态;
- 客户端协议栈收到 ACK 之后,使得应用程序从 connect 调用返回,表示客户端到服务器端的单向连接建立成功,客户端的状态为 ESTABLISHED,同时客户端协议栈也会对服务器端的 SYN 包进行应答,应答数据为 server_isn+1;
- 应答包到达服务器端后,服务器端协议栈使得 accept 阻塞调用返回,这个时候服务器端到客户端的单向连接也建立成功,服务器端也进入 ESTABLISHED 状态。
2.3 结合四次次挥手的 TCP socket
- 客户端调用 close,表明客户端没有数据需要发送了,则此时会向服务端发送 FIN 报文,进入 FIN_WAIT_1 状态;
- 服务端接收到了 FIN 报文,TCP 协议栈会为 FIN 包插入一个文件结束符 EOF 到接收缓冲区中,应用程序可以通过 read 调用来感知这个 FIN 包。这个 EOF 会被放在已排队等候的其他已接收的数据之后,这就意味着服务端需要处理这种异常情况,因为 EOF 表示在该连接上再无额外数据到达。此时,服务端进入 CLOSE_WAIT 状态;
- 接着,当处理完数据后,自然就会读到 EOF ,于是也调用 close 关闭它的套接字,这会使得服务端会发出一个 FIN 包,之后处于 LAST_ACK 状态;
- 客户端接收到服务端的 FIN 包,并发送 ACK 确认包给服务端,此时客户端将进入 TIME_WAIT 状态;
- 服务端收到 ACK 确认包后,就进入了最后的 CLOSE 状态;
- 客户端经过 2MSL 时间之后,也进入 CLOSE 状态;
2.4 TCP服务端程序
使用一个服务端程序监听本机TCP-60000端口,使用一个客户端程序,向本机TCP-60000发送字符串消息。
# ! /usr/bin/env python3
# _*_ coding: utf-8 _*_
from socket import *
# 变量名大写,约定作为配置项的意思,这里定义了三个配置变量作为实例化的参数
# 定义IP地址、端口、缓存,缓存的单位是Byte
IP = '127.0.0.1'
PORT = 60000
BUFLEN = 1024
# 实例化一个socket赋值给变量listen_socket
# 参数AF_INET就是IP地址族
# 参数SOCK_STREAM就是TCP、参数SOCK_DGRAM就是UDP、参数SOCK_RAW是原始套接字,为TCP、UDP之外的协议提供接口
listen_socket = socket(AF_INET, SOCK_STREAM)
# 为这个socket绑定IP和端口,bind的对象必须是一个包含IP和端口元组
listen_socket.bind((IP, PORT))
# 使socket处于监听状态,等待客户端的请求,只有服务端能使用监听
# 参数5表示,最多接收5个等待连接的客户端
listen_socket.listen(5)
print(f'Server: 服务启动成功,在{PORT}端口,等待 Client 连接... ...')
# 监听socket的accept方法,用来接收客户端的连接,如果没有客户端连接,就一直处于监听状态(阻塞状态)直到有客户端连接
# 一旦客户段发起连接(TCP三次握手)accept方法就会返回一个元组,一个data_socket来传输数据,一个ipaddress包含IP和PORT
data_socket, ip_port = listen_socket.accept()
print(f'Server>>> 接受一个客户端{ip_port}连接... ...')
while True:
# 尝试读取客户端发来的消息
# BUFLEN指定从接收缓冲里最多读取多少Bytes,recved是一个字节串变量
recved = data_socket.recv(BUFLEN)
# 如果返回空的Bytes,意味着对方关闭了连接
# 退出循环,结束消息收发
if not recved:
break
# 把读取的Bytes数据,转换成字符串编码,赋值给变量info,然后打印出来
info = recved.decode('utf-8')
print(f'Server>>> 收到客户端{ip_port}消息: {info}')
# 通知客户端的字符串,编码为utf-8的Bytes类型数据
data_socket.send(f'Server>>> 服务器{(IP, PORT)}收到消息 {info}'.encode('utf-8'))
data_socket.close()
listen_socket.close()
2.5 TCP客户端程序
# ! /usr/bin/env python3
# _*_ coding: utf-8 _*_
from socket import *
IP = '127.0.0.1'
PORT = 60000
BUFLEN = 1024
# 实例化了一个socket对象,赋给变量data_socket
data_socket = socket(AF_INET, SOCK_STREAM)
# 调用connect方法,连接服务器,connet就是连接正在等待的服务器端的listen_socket
data_socket.connect((IP, PORT))
while True:
toSend = input('client>>> ')
if toSend == 'exit':
break
data_socket.send(toSend.encode('utf-8'))
recved = data_socket.recv(BUFLEN)
if not recved:
break
print(recved.decode('utf-8'))
data_socket.close()
运行TCP服务端程序:
运行TCP客户端程序:
查看TCP服务端程序:
TCP客户端程序输入exit退出连接: