服务热线
15527777548/18696195380
发布时间:2018-05-25
简要描述:
i春秋作家:wasrehpic0x00 前言「网络」一直以来都是黑客最热衷的竞技场。数据在网络中肆意传播:主机扫描、代码注入、网络嗅探、数据篡改重放、拒绝服务攻击......黑客的功底...
「网络」一直以来都是黑客最热衷的竞技场。数据在网络中肆意传播:主机扫描、代码注入、网络嗅探、数据篡改重放、拒绝服务攻击......黑客的功底越深厚,能做的就越多。
Python 作为一种解释型脚本语言,自 1991 年问世以来,其简洁、明确、可读性强的语法深受黑客青睐,特别在网络工具的编写上,避免了繁琐的底层语法,没有对运行速度的高效要求,使得 Python 成为安全工作者的必备杀手锏。
本文作为「Python 绝技」系列工具文章的开篇,先介绍因特网的核心协议——TCP 协议,再以 Python 的 socket 模块为例介绍网络套接字,最后给出 TCP 服务器与客户端的 Python 脚本,并演示两者之间的通信过程。
TCP(Transmission Control Protocol,传输控制协议)是一种面向连接、可靠的、基于字节流的传输层通信协议。
TCP 协议的运行分为连接创建(Connection Establishment)、数据传送(Data Transfer)和连接终止(Connection Termination)三个阶段,其中「连接创建」阶段是耳熟能详的 TCP 协议三次握手(TCP Three-way Handshake),也是理解本文 TCP 服务器与客户端通信过程的阶段。
所谓的「三次握手」,即 TCP 服务器与客户端成功建立通信连接必经的三个步骤,共需通过三个报文完成。
客户端向服务器发送 SYN 报文(SYN = 1)请求连接。此时报文的初始序列号为 seq = x,确认号为 ack = 0。
服务器接收到客户端的 SYN 报文后,发送 ACK + SYN 报文(ACK = 1,SYN = 1)确认客户端的连接请求,并也向其发起连接请求。此时报文的序列号为 seq = y,确认号为 ack = x + 1。
客户端接收到服务器的 SYN 报文后,发送 ACK 报文(ACK = 1)确认服务器的连接请求。此时报文的序列号为 seq = x + 1,确认号为 ack = y + 1。
对于上述过程的理解,需要注意以下几点:
00000010
,ACK + SYN 报文的标记符为 00010010
。为了更方便地理解,下面给出一张 TCP 协议三次握手的示意图:
Network Socket(网络套接字)是计算机网络中进程间通信的数据流端点,广义上也代表操作系统提供的一种进程间通信机制。
进程间通信(Inter-Process Communication,IPC)的根本前提是能够唯一标示每个进程。在本地主机的进程间通信中,可以用 PID(进程 ID)唯一标示每个进程,但 PID 只在本地唯一,在网络中不同主机的 PID 则可能发生冲突,因此采用「IP 地址 + 传输层协议 + 端口号」的方式唯一标示网络中的一个进程。
小贴士:网络层的 IP 地址可以唯一标示主机,传输层的 TCP/UDP 协议和端口号可以唯一标示该主机的一个进程。注意,同一主机中 TCP 协议与 UDP 协议的可以使用相同的端口号。
所有支持网络通信的编程语言都各自提供了一套 socket API,下面以 Python 3 为例,讲解服务器与客户端建立 TCP 通信连接的交互过程:
脑海中先对上述过程产生一定印象后,更易于理解下面两节 TCP 服务器与客户端的 Python 实现。
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import socket
import threading
def tcplink(conn, addr):
print("Accept new connection from %s:%s" % addr)
conn.send(b"Welcome!\n")
while True:
conn.send(b"What's your name?")
data = conn.recv(1024).decode()
if data == "exit":
conn.send(b"Good bye!\n")
break
conn.send(b"Hello %s!\n" % data.encode())
conn.close()
print("Connection from %s:%s is closed" % addr)
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.bind(("127.0.0.1", 6000))
s.listen(5)
print("Waiting for connection...")
while True:
conn, addr = s.accept()
t = threading.Thread(target = tcplink, args = (conn, addr))
t.start()
"Welcome!\n"
。"What's your name?"
。"exit"
,则向客户端发送结束信息 "Good bye!\n"
,并结束与客户端交互数据的循环阶段。"exit"
,则向客户端发送问候信息 "Hello %s!\n"
,其中 %s
是客户端发来的非空字符串。#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import socket
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(("127.0.0.1", 6000))
print(s.recv(1024).decode())
data = "client"
while True:
if data:
print(s.recv(1024).decode())
data = input("Please input your name: ")
if not data:
continue
s.send(data.encode())
print(s.recv(1024).decode())
if data == "exit":
break
s.close()
"Welcome!\n"
。"client"
(只要是非空字符串即可),用于判断是否接收来自服务器发来的询问信息 "What's your name?"
。"exit"
(记为非法字符串)时,则接收服务器发来的询问信息。"exit"
时,则结束与服务器交互数据的循环阶段,即将关闭套接字。将 TCP 服务器与客户端的脚本分别命名为 tcp_server.py
与 tcp_client.py
,然后存至桌面,笔者将在 Windows 10 系统下用 PowerShell 进行演示。
小贴士:读者进行复现时,要确保本机已安装 Python 3,注意笔者已将默认的启动路径名
python
改为了python3
。
python3 ./tcp_server.py
,服务器显示 Waiting for connection...
,并监听本地主机的 TCP 6000 端口,进入等待连接状态;python3 ./tcp_client.py
,服务器显示 Accept new connection from 127.0.0.1:42101
,完成与本地主机的 TCP 42101 端口建立通信连接,并向客户端发送问候信息与询问信息,客户端接收到信息后打印输出;Alice
与 Bob
,则收到服务器的问候响应信息;exit
,则收到服务器的结束响应信息;Connection from 127.0.0.1:42101 is closed
,并继续监听客户端的连接请求。python3 ./tcp_server.py
,服务器显示 Waiting for connection...
,并监听本地主机的 TCP 6000 端口,进入等待连接状态;python3 ./tcp_client.py
,服务器同时与本地主机的 TCP 42719、42721、42722 端口建立通信连接,并分别向客户端发送问候信息与询问信息,客户端接收到信息后打印输出;Client1
、Client2
、Client3
,并收到服务器的问候响应信息;exit
,并收到服务器的结束响应信息;此小节介绍上述代码中用到的 socket 模块内置函数,也是 socket 编程的核心函数。
socket() 函数用于创建网络通信中的套接字对象。函数原型如下:
socket.socket(family=AF_INET, type=SOCK_STREAM, proto=0, fileno=None)
此小节介绍上述代码中用到的 socket 对象内置函数,也是 socket 编程的常见函数。注意,以下函数原型中的「socket」是指 socket 对象,而不是 socket 模块。
bind() 函数用于向套接字对象绑定 IP 地址与端口号。注意,套接字对象必须未被绑定,并且端口号未被占用,否则会报错。函数原型如下:
socket.bind(address)
listen() 函数用于 TCP 服务器开启套接字的监听功能。函数原型如下:
socket.listen([backlog])
connect() 函数用于 TCP 客户端向 TCP 服务器发起连接请求。函数原型如下:
socket.connect(address)
accept() 函数用于 TCP 服务器接受 TCP 客户端的连接请求。函数原型如下:
socket.accept()
accept() 函数的返回值是一个二元组 (conn, address),其中 conn 是服务器用来与客户端交互数据的套接字对象,address 是客户端的 IP 地址与端口号,用二元组 (host, port) 表示。
send() 函数用于向远程套接字对象发送数据。注意,本机的套接字必须与远程的套接字成功连接后才能使用该函数,否则会报错。可见,send() 函数只能用于 TCP 进程间通信,而对于 UDP 进程间通信应该用 sendto() 函数。函数原型如下:
socket.send(bytes[, flags])
"hello world!"
而言,需要用 encode() 函数转换为 bytes 对象 b"hello world!"
才能进行网络传输。|
隔开。详情可参考 Unix 函数手册中的 send(2),flags 参数的常见取值有 MSG_OOB、MSG_PEEK、MSG_WAITALL 等。send() 函数的返回值是发送数据的字节数。
recv() 函数用于从远程套接字对象接收数据。注意,与 send() 函数不同,recv() 函数既可用于 TCP 进程间通信,也能用于 UDP 进程间通信。函数原型如下:
socket.recv(bufsize[, flags])
|
隔开。详情可参考 Unix 函数手册中的 recv(2),flags 参数的常见取值有 MSG_OOB、MSG_PEEK、MSG_WAITALL 等。recv() 函数的返回值是接收到的 bytes 对象数据。例如,接收到 bytes 对象 b"hello world!"
,最好用 decode() 函数转换为字符串 "hello world!"
再打印输出。
close() 函数用于关闭本地套接字对象,释放与该套接字连接的所有资源。
socket.close()
此小节介绍上述代码中用到的 threading 模块内置类,也是 Python 多线程编程的核心。
Thread() 类可以创建线程对象,用于调用 start() 函数启动新线程。类原型如下:
class threading.Thread(group=None, target=None, name=None, args=(), kwargs={}, *, daemon=None)
()
。{}
。此小节介绍上述代码中用到的 threading 对象内置函数,也是多线程编程的必用函数。注意,以下函数原型中的「threading」是指 threading 对象,而不是 threading 模块。
start() 函数用于开启线程活动。函数原型如下:
threading.start()
注意,每个线程对象只能调用一次 start() 函数,否则会导致 RuntimeError 错误。
本文介绍了 TCP 协议与 socket 编程的基础知识,再用 Python 3 实现并演示了 TCP 服务器与客户端的通信过程,其中还运用了简单的多线程技术,最后将脚本中涉及到的 Python API 做成了的参考索引,有助于理解实现过程。
笔者水平有限,若文中出现不足或错误之处,还望大家不吝相告,多多包涵,欢迎读者前来交流技术,感谢阅读。
本文的相关参考请移步至:
有问题大家可以留言哦,也欢迎大家到春秋论坛中来耍一耍 >>>点击跳转
如果您有任何问题,请跟我们联系!
联系我们