0x00 前言「网络」一直以来都是黑客最热衷的竞技场。数据在网络中肆意传播:主机扫描、代码注入、网络嗅探、数据篡改重放、拒绝服务攻击……黑客的功底越深厚,能做的就越多。 Python 作为一种解释型脚本语言,自 1991 年问世以来,其简洁、明确、可读性强的语法深受黑客青睐,特别在网络工具的编写上,避免了繁琐的底层语法,没有对运行速度的高效要求,使得 Python 成为安全工作者的必备杀手锏。 本文作为「Python 绝技」系列工具文章的开篇,先介绍因特网的核心协议 TCP ,再以 Python 的 socket 模块为例介绍网络套接字,最后给出 TCP 服务器与客户端的 Python 脚本,并演示两者之间的通信过程。 0x01 TCP 协议TCP(Transmission Control Protocol,传输控制协议)是一种面向连接、可靠的、基于字节流的传输层通信协议。 TCP 协议的执行过程分为连接创建(Connection Establishment)、数据传送(Data Transfer)和连接终止(Connection Termination)三个阶段,其中「连接创建」与「连接终止」分别是耳熟能详的 TCP 协议三次握手(TCP Three-way Handshake)与四次挥手(TCP Four-way Handshake),也是理解本文 TCP 服务器与客户端通信过程的两个核心阶段。 为了能更好地理解下述过程,对 TCP 协议头的关键区段做以下几点说明:
连接创建(Connection Establishment) 所谓的「三次握手」,即 TCP 服务器与客户端成功建立通信连接必经的三个步骤,共需通过三个报文完成。 一般而言,首先发送 SYN 报文的一方是客户端,服务器则是监听来自客户端的建立连接请求。 Handshake Step 1 客户端向服务器发送 SYN 报文(SYN=1 SYN=1)请求建立连接。 此时报文的初始序列号为 seq=x seq=x,确认号为 ack=0 ack=0。发送完毕后,客户端进入 SYN_SENT 状态。 Handshake Step 2 服务器接收到客户端的 SYN 报文后,发送 ACK + SYN 报文(ACK=1,SYN=1 ACK=1,SYN=1)确认客户端的建立连接请求,并也向其发起建立连接请求。 此时报文的序列号为 seq=y seq=y,确认号为 ack=x+1 ack=x+1。发送完毕后,服务器进入 SYN_RCVD 状态。 Handshake Step 3 客户端接收到服务器的 SYN 报文后,发送 ACK 报文(ACK=1 ACK=1)确认服务器的建立连接请求。 此时报文的序列号为 seq=x+1 seq=x+1,确认号为 ack=y+1 ack=y+1。发送完毕后,客户端进入 ESTABLISHED 状态;当服务器接收该报文后,也进入了 ESTABLISHED 状态。 至此,「三次握手」过程全部结束,TCP 通信连接成功建立。 读者可参照以下「三次握手」的示意图进行理解: ![]() 连接终止(Connection Termination) 所谓的「四次挥手」,即 TCP 服务器与客户端完全终止通信连接必经的四个步骤,共需通过四个报文完成。 由于 TCP 通信连接是全双工的,因此每个方向的连接可以单独关闭,即可视为一对「二次挥手」,或一对单工连接。主动先发送 FIN 报文的一方,意味着想要关闭到另一方的通信连接,即在此方向上不再传输数据,但仍可以接收来自另一方传输过来的数据,直到另一方也发送 FIN 报文,双方的通信连接才完全终止。 注意,首先发送 FIN 报文的一方,既可以是客户端,也可以是服务器。下面以客户端先发起关闭请求为例,对「四次挥手」的过程进行讲解。 Handshake Step 1 当客户端不再向服务器传输数据时,则向其发送 FIN 报文(FIN=1 FIN=1)请求关闭连接。 此时报文的初始序列号为 seq=u seq=u,确认号为 ack=0 ack=0(若此报文中 ACK=1 ACK=1,则 ack ack 的值与客户端的前序接收报文有关)。发送完毕后,客户端进入 FIN_WAIT_1 状态。 Handshake Step 2 服务器接收到客户端的 FIN 报文后,发送 ACK 报文(ACK=1 ACK=1)确认客户端的关闭连接请求。 此时报文的序列号为 seq=v seq=v,确认号为 ack=u+1 ack=u+1。发送完毕后,服务器进入 CLOSE_WAIT 状态;当客户端接收该报文后,进入 FIN_WAIT_2 状态。 注意,此时 TCP 通信连接处于半关闭状态,即客户端不再向服务器传输数据,但仍可以接收服务器传输过来的数据。 Handshake Step 3 当服务器不再向客户端传输数据时,则向其发送 FIN + ACK 报文(FIN=1,ACK=1 FIN=1,ACK=1)请求关闭连接。 此时报文的序列号为 seq=w seq=w(若在半关闭状态,服务器没有向客户端传输过数据,则 seq=v+1 seq=v+1 ),确认号为 ack=u+1 ack=u+1。发送完毕后,服务器进入 LAST_ACK 状态。 Handshake Step 4 客户端接收到服务器的 FIN + ACK 报文后,发送 ACK 报文(ACK=1 ACK=1)确认服务器的关闭连接请求。 此时报文的序列号为 seq=u+1 seq=u+1,确认号为 ack=w+1 ack=w+1。发送完毕后,客户端进入 TIME_WAIT 状态;当服务器接收该报文后,进入 CLOSED 状态;当客户端等待了 2MSL 后,仍没接到服务器的响应,则认为服务器已正常关闭,自己也进入 CLOSED 状态。 至此,「四次挥手」过程全部结束,TCP 通信连接成功关闭。 读者可参照以下「四次挥手」的示意图进行理解: ![]() 0x02 Network SocketNetwork Socket(网络套接字)是计算机网络中进程间通信的数据流端点,广义上也代表操作系统提供的一种进程间通信机制。 进程间通信(Inter-Process Communication,IPC)的根本前提是能够唯一标示每个进程。在本地主机的进程间通信中,可以用 PID(进程 ID)唯一标示每个进程,但 PID 只在本地唯一,在网络中不同主机的 PID 则可能发生冲突,因此采用「IP 地址 + 传输层协议 + 端口号」的方式唯一标示网络中的一个进程。
所有支持网络通信的编程语言都各自提供了一套 socket API,下面以 Python 3 为例,讲解服务器与客户端建立 TCP 通信连接的交互过程: ![]() 脑海中先对上述过程产生一定印象后,更易于理解下面两节 TCP 服务器与客户端的 Python 实现。 0x03 TCP 服务器![]()
0x04 TCP 客户端![]()
0x05 TCP 进程间通信将 TCP 服务器与客户端的脚本分别命名为 tcp_server.py 与 tcp_client.py,然后存至桌面,笔者将在 Windows 10 系统下用 PowerShell 进行演示。
单服务器 VS 单客户端 ![]()
单服务器 VS 多客户端 ![]()
0x06 Python API Referencesocket 模块 本节介绍上述代码中用到的内建模块 socket,是 Python 网络编程的核心模块。 socket() 函数 socket() 函数用于创建网络通信中的套接字对象。函数原型如下:
创建完套接字对象后,需使用对象的内置函数完成网络通信过程。注意,以下函数原型中的「socket」是指 socket 对象,而不是上述的 socket 模块。 bind() 函数 bind() 函数用于向套接字对象绑定 IP 地址与端口号。注意,套接字对象必须未被绑定,并且端口号未被占用,否则会报错。函数原型如下: ![]()
listen() 函数 listen() 函数用于 TCP 服务器开启套接字的监听功能。函数原型如下: ![]()
connect() 函数 connect() 函数用于 TCP 客户端向 TCP 服务器发起连接请求。函数原型如下: ![]()
accept() 函数 accept() 函数用于 TCP 服务器接受 TCP 客户端的连接请求。函数原型如下: ![]() accept() 函数的返回值是二元组 (conn, address),其中 conn 是服务器用来与客户端交互数据的套接字对象,address 是客户端的 IP 地址与端口号,用二元组 (host, port) 表示。 send() 函数 send() 函数用于向远程套接字对象发送数据。注意,本机套接字必须与远程套接字成功连接后才能使用该函数,否则会报错。可见,send() 函数只能用于 TCP 进程间通信,而对于 UDP 进程间通信应该用 sendto() 函数。函数原型如下: ![]()
send() 函数的返回值是发送数据的字节数。 recv() 函数 recv() 函数用于从远程套接字对象接收数据。注意,与 send() 函数不同,recv() 函数既可用于 TCP 进程间通信,也能用于 UDP 进程间通信。函数原型如下: ![]()
recv() 函数的返回值是接收到的 bytes 对象数据。例如,接收到 bytes 对象 b"hello world!",最好用 decode() 函数转换为字符串 "hello world!" 再打印输出。 close() 函数 close() 函数用于关闭本地套接字对象,释放与该套接字连接的所有资源。 ![]() threading 模块 本节介绍上述代码中用到的内建模块 threading,是 Python 多线程的核心模块。 Thread() 类 Thread() 类可以创建线程对象,用于调用 start() 函数启动新线程。类原型如下: class threading.Thread(group=None, target=None, name=None, args=(), kwargs={}, *, daemon=None)
创建完线程对象后,需使用对象的内置函数控制多线程活动。 start() 函数 start() 函数用于开启线程活动。函数原型如下: ![]() 注意,每个线程对象只能调用一次 start() 函数,否则会导致 RuntimeError 错误。 0x07 总结本文介绍了 TCP 协议与 socket 编程的基础知识,再用 Python 3 实现并演示了 TCP 服务器与客户端的通信过程,其中还运用了简单的多线程技术,最后将脚本中涉及到的 Python API 做成了的参考索引,有助于理解实现过程。 笔者水平有限,若文中出现不足或错误之处,还望大家不吝相告,多多包涵,欢迎读者前来交流技术,感谢阅读。 ![]() |
| 本文出处: https://www.toutiao.com/a6592073847074193934/ |
|
声明:文章版权归原作者所有 部分文章转自互联网 如有侵权请联系
[邮箱地址] 删除
|