1. 首页
  2. 网络安全

Python 绝技 —— TCP 服务器与客户端

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 服务器与客户端通信过程的阶段。

连接创建(Connection Establishment)

所谓的「三次握手」,即 TCP 服务器与客户端成功建立通信连接必经的三个步骤,共需通过三个报文完成。

Handshake Step 1

客户端向服务器发送 SYN 报文(SYN=1SYN=1)请求连接。此时报文的初始序列号为 seq=xseq=x,确认号为 ack=0ack=0

Handshake Step 2

服务器接收到客户端的 SYN 报文后,发送 ACK + SYN 报文(ACK=1SYN=1ACK=1,SYN=1)确认客户端的连接请求,并也向其发起连接请求。此时报文的序列号为 seq=yseq=y,确认号为 ack=x+1ack=x+1

Handshake Step 3

客户端接收到服务器的 SYN 报文后,发送 ACK 报文(ACK=1ACK=1)确认服务器的连接请求。此时报文的序列号为 seq=x+1seq=x+1,确认号为 ack=y+1ack=y+1

对于上述过程的理解,需要注意以下几点:

  • 报文的功能在 TCP 协议头的标记符(Flags)区段中定义,该区段位于第 104~111 比特位,共占 8 比特,每个比特位对应一种功能,置 1 代表开启,置 0 代表关闭。例如,SYN 报文的标记符为 00000010,ACK + SYN 报文的标记符为 00010010
  • 报文的序列号在 TCP 协议头的序列号(Sequence Number)区段中定义,该区段位于第 32~63 比特位,共占 32 比特。在「三次握手」过程中,初始序列号 seqseq 由数据发送方随机生成。
  • 报文的确认号在 TCP 协议头的确认号(Acknowledgement Number)区段中定义,该区段位于第 64~95 比特位,共占 32 比特。在「三次握手」过程中,确认号 ackack 为前序接收报文的序列号加 1。

为了更方便地理解,下面给出一张 TCP 协议三次握手的示意图:

Python 绝技 —— TCP 服务器与客户端

0x02 Network Socket

Network Socket(网络套接字)是计算机网络中进程间通信的数据流端点,广义上也代表操作系统提供的一种进程间通信机制。

进程间通信(Inter-Process Communication,IPC)的根本前提是能够唯一标示每个进程。在本地主机的进程间通信中,可以用 PID(进程 ID)唯一标示每个进程,但 PID 只在本地唯一,在网络中不同主机的 PID 则可能发生冲突,因此采用「IP 地址 + 传输层协议 + 端口号」的方式唯一标示网络中的一个进程。

小贴士:网络层的 IP 地址可以唯一标示主机,传输层的 TCP/UDP 协议和端口号可以唯一标示该主机的一个进程。注意,同一主机中 TCP 协议与 UDP 协议的可以使用相同的端口号。

所有支持网络通信的编程语言都各自提供了一套 socket API,下面以 Python 3 为例,讲解服务器与客户端建立 TCP 通信连接的交互过程:

Python 绝技 —— TCP 服务器与客户端

脑海中先对上述过程产生一定印象后,更易于理解下面两节 TCP 服务器与客户端的 Python 实现。

0x03 TCP 服务器

Python脚本下载:脚本下载

  • Line 6:定义一个 tcplink() 函数,第一个 conn 参数为服务器与客户端交互数据的套接字对象,第二个 addr 参数为客户端的 IP 地址与端口号,用二元组 (host, port) 表示。
  • Line 8:连接成功后,向客户端发送问候信息 "Welcome!\n"
  • Line 9:进入与客户端交互数据的循环阶段。
  • Line 10:向客户端发送询问信息 "What's your name?"
  • Line 11:接收客户端发来的非空字符串。
  • Line 12:如果非空字符串为 "exit",则向客户端发送结束信息 "Good bye!\n",并结束与客户端交互数据的循环阶段。
  • Line 15:如果非空字符串不为 "exit",则向客户端发送问候信息 "Hello %s!\n",其中 %s是客户端发来的非空字符串。
  • Line 16:关闭套接字,不再向客户端发送数据。
  • Line 19:创建 socket 对象,第一个参数为 socket.AF_INET,代表采用 IPv4 协议用于网络通信,第二个参数为 socket.SOCK_STREAM,代表采用 TCP 协议用于面向连接的网络通信。
  • Line 20:向 socket 对象绑定服务器主机地址 (“127.0.0.1”, 6000),即本地主机的 TCP 6000 端口。
  • Line 21:开启 socket 对象的监听功能,等待客户端的连接请求。
  • Line 24:进入监听客户端连接请求的循环阶段。
  • Line 25:接收客户端的连接请求,并获得与客户端交互数据的套接字对象 conn 与客户端的 IP 地址与端口号 addr,其中 addr 为二元组 (host, port)。
  • Line 26:利用多线程技术,为每个请求连接的 TCP 客户端创建一个新线程,实现了一台服务器同时与多台客户端进行通信的功能。
  • Line 27:开启新线程的活动。

0x04 TCP 客户端

Python脚本下载:脚本下载

  • Line 5:创建 socket 对象,第一个参数为 socket.AF_INET,代表采用 IPv4 协议用于网络通信,第二个参数为 socket.SOCK_STREAM,代表采用 TCP 协议用于面向连接的网络通信。
  • Line 6:向 (“127.0.0.1”, 6000) 主机发起连接请求,即本地主机的 TCP 6000 端口。
  • Line 7:连接成功后,接收服务器发送过来的问候信息 "Welcome!\n"
  • Line 9:创建一个非空字符串变量 data,并赋初值为 "client"(只要是非空字符串即可),用于判断是否接收来自服务器发来的询问信息 "What's your name?"
  • Line 10:进入与服务器交互数据的循环阶段。
  • Line 12:当用户的输入非空且不等于 "exit"(记为非法字符串)时,则接收服务器发来的询问信息。
  • Line 13:要求用户输入名字,一条合法字符串即可。
  • Line 14:当用户输入非空,则重新开始循环,要求用户重新输入合法字符串。
  • Line 16:当用户输入合法字符串时,则将字符串转换为 bytes 对象后发送至服务器。
  • Line 17:接收服务器的响应数据,并将 bytes 对象转换为字符串后打印输出。
  • Line 18:当用户输入字符串 "exit" 时,则结束与服务器交互数据的循环阶段,即将关闭套接字。
  • Line 21:关闭套接字,不再向服务器发送数据。

0x05 TCP 进程间通信

将 TCP 服务器与客户端的脚本分别命名为 tcp_server.pytcp_client.py,然后存至桌面,笔者将在 Windows 10 系统下用 PowerShell 进行演示。

小贴士:读者进行复现时,要确保本机已安装 Python 3,注意笔者已将默认的启动路径名 python 改为了 python3

单服务器 VS 单客户端

Python 绝技 —— TCP 服务器与客户端

  1. 在其中一个 PowerShell 中运行命令 python3 ./tcp_server.py,服务器显示 Waiting for connection...,并监听本地主机的 TCP 6000 端口,进入等待连接状态;
  2. 在另一个 PowerShell 中运行命令 python3 ./tcp_client.py,服务器显示 Accept new connection from 127.0.0.1:42101,完成与本地主机的 TCP 42101 端口建立通信连接,并向客户端发送问候信息与询问信息,客户端接收到信息后打印输出;
  3. 若客户端向服务器发送字符串 AliceBob,则收到服务器的问候响应信息;
  4. 若客户端向服务器发送空字符串,则要求重新输入字符串;
  5. 若客户端向服务器发送字符串 exit,则收到服务器的结束响应信息;
  6. 客户端与服务器之间的通信连接已关闭,服务器显示 Connection from 127.0.0.1:42101 is closed,并继续监听客户端的连接请求。

0x07 总结

本文介绍了 TCP 协议与 socket 编程的基础知识,再用 Python 3 实现并演示了 TCP 服务器与客户端的通信过程,其中还运用了简单的多线程技术,最后将脚本中涉及到的 Python API 做成了的参考索引,有助于理解实现过程。

原创文章,作者:lichun,如若转载,请注明出处:http://www.lichunseo.com/wlaq/1745.html