赞
踩
Socket通信编程传输协议分析的python代码及wireshark抓取文件包
1、掌握 Socket 通信协议实现的方法,能够使用至少一种语言编写通信程序;
2、理解 TCP 与 UDP 通信实现的方法和过程,理解报文段封装的作用,掌握可靠传输
实现的方法;
3、理解 TCP 三次握手建立连接、数据传输、四次挥手拆除连接的方法和过程。
1、Socket
Socket本质是编程接口(API),对 TCP/IP 的封装,TCP/IP 为网络服务和应用提供 Socket 编程接口,当前主要的 Socket 编程主要有SOCK_STREAM (TCP)、SOCK_DGRAM (UDP) 工作在传输层,SOCK_RAW 工作在网络层。
2、 TCP 报文封装及通信过程
TCP 在IP 层提供的不可靠服务基础上实现可靠数据传输服务,流水线机制传输,使用累积确认确认传输,并使用单一重传定时器和收到重复ACK 确认传输失败,进行重传。
TCP 段结构包含。
源地址端口、目的端口,16 位字段,发送接收该报文段的主机中应用程序的端口号。
序号(segment 第一个字节的编号)、确认号(接收方期望从对方接受的字节编号), Flag(URG: 紧急数据标志位;ACK:确认标志位;PSH:请求推送位,发送了数据;RST:连接复位;SYN:建立连接, 让连接双方同步序列号;FIN:释放连接)。
窗口大小:TCP 的窗口大小,以字节为单位。最大长度是 65535 字节(16 位)。
检验和:将传输层传输层伪首部与首部字段求和并校验,保证数据的完整性和准确性。
这里我使用了python来编写端口扫描socket_connect的代码,部分代码如下:
- from os import name
- import threading
- from socket import *
-
- lock = threading.Lock() # 确保 多个线程在共享资源的时候不会出现脏数据
- openNum=0 # 端口开放数量统计
- threads=[] # 线程池
- def portscanner(host,port):
- global openNum
- try:
- s=socket(AF_INET,SOCK_STREAM)
- s.connect((host,port))
- lock.acquire()
- openNum+=1
- print(f"{port} open")
- lock.release()
- s.close()
- except:
- pass
- def main(ip,ports=range(65535)): # 设置 端口缺省值0-65535
- setdefaulttimeout(1)
- for port in ports:
- t=threading.Thread(target=portscanner,args=(ip,port))
- threads.append(t)
- t.start()
- for t in threads:
- t.join()
- print(f"PortScan is Finish ,OpenNum is {openNum}")
- if __name__ == '__main__':
- ip='127.0.0.1'
- main(ip) # 全端口扫描
端口扫描结果
socket.connect_ex((ip, port)),端口开放则返回0,否则返回错误代码,扫描全端口,成功打开的端口显示在终端,并最后显示本次扫描的结果及端口数
TCP三次握手
刚开始客户端处于 Closed 的状态,服务端处于 Listen 状态。
(1)第一次握手:客户端给服务端发一个 SYN 报文,并进入SYN_SEND状态,等待服务器确认。(SYN->1,其他为0)
此时首部的同步位SYN=1,初始序号seq=x(0),SYN=1的报文段不能携带数据,但要消耗掉一个序号。
(2)第二次握手:服务器收到syn包,必须确认客户的SYN,同时自己也发送一个SYN包,即SYN+ACK包,此时服务器进入SYN_RECV状态。(ACK->1,SYN->1,其他为0),在确认报文段中SYN=1,ACK=1,确认号ack=x+1(1),初始序号seq=y(0)。
(3)第三次握手:客户端收到服务器的SYN+ACK包,向服务器发送确认包ACK,此包发送完毕,客户端和服务器进入ESTABLISHED状态,完成三次握手。(ACK->1,其他为0)
确认报文段ACK=1,确认号ack=y+1(1),序号seq=x+1(1)(初始为seq=x(0),第二个报文段所以要+1),ACK报文段可以携带数据,不携带数据则不消耗序号。
刚开始双方都处于ESTABLISHED状态,当客户端先发起关闭请求。
(1)第一次挥手:主动关闭方(客户端)发送一个FIN,用来关闭主动方到被动关闭方的数据传送,也就是主动关闭方告诉被动关闭方:我已经不会再给你发数据了,但是,此时主动关闭方还可以接受数据。此时客户端处于FIN_WAIT1状态。(ACK->1,FIN->1)
即连接释放报文段(FIN=1,序号seq=u),并停止再发送数据,主动关闭TCP连接,进入FIN_WAIT1(终止等待1)状态,等待服务端的确认。
(2)第二次挥手:服务端收到FIN之后,会发送ACK报文,且把客户端的序列号值+1作为ACK报文的序列号值,表明已经收到客户端的报文了,此时服务端处于CLOSE_WAIT状态。(ACK->1)
即服务端收到连接释放报文段后即发出确认报文段(ACK=1,确认号ack=u+1,序号seq=v),服务端进入CLOSE_WAIT(关闭等待)状态,此时的TCP处于半关闭状态,客户端到服务端的连接释放。客户端收到服务端的确认后,进入FIN_WAIT2(终止等待2)状态,等待服务端发出的连接释放报文段。
(3)第三次挥手:被动关闭方(服务端)发送一个FIN,用来关闭被动关闭方到主动关闭方的数据传送,也就是告诉主动关闭方,我的数据也发送完了,不会再给你发数据了。此时服务端处于LAST_ACK的状态。(ACK->1,FIN->1)
即服务端没有要向客户端发出的数据,服务端发出连接释放报文段(FIN=1,ACK=1,序号seq=w,确认号ack=u+1),服务端进入LAST_ACK(最后确认)状态,等待客户端的确认。
(4)第四次挥手:主动关闭方收到FIN后,发送一个ACK给被动关闭方,确认序号为收到序号+1,至此,完成四次挥手,此时客户端处于 TIME_WAIT 状态。(ACK->1)
即客户端收到服务端的连接释放报文段后,对此发出确认报文段(ACK=1,seq=u+1,ack=w+1),客户端进入TIME_WAIT(时间等待)状态。此时TCP未释放掉,需要经过时间等待计时器设置的时间后,客户端才进入CLOSED状态。
这里我出现了报错,报错截图如下:
主动关闭方正常收到FIN后,分析信息如下:
我同样使用了python来进行socket通信的代码,以下是方法的解释:
sk=socket.socket() :括号里面包含两个参数,一个参数默认是ip地址蔟的socket.AF_INET,也就是IPv4;还有一个默认是传输TCP协议,也就是socket.SOCK_STREAM
sk.bind(()) :最里面的括号里包含两个参数,分别是客户端的ip地址与端口号
sk.listen(n): 里面的n表示阻塞连接n个进程,也就是最大等待数为n个,当连接上某个进程后,其他的都不会连接上,会处于等待状态,只有当断开与这个进程的连接,其他的进程才会依次连上
a,b=sk.accept():返回的是一个元组,元组的第一个参数是连接的信息,第二个参数是客户端的ip地址与端口号
a.recv(1024): recv()表示接受信息,里面的1024表示最多接受的字节数,如果有多余的字符就下次再进行传输
客户端(client)部分代码如下:
- # client
-
- BUFFSIZE = 1024
- # connect
- while True:
- iproot = ("10.225.146.226", 8888)#ip和端口 ip为服务端ipv4地址
- global socket_client
- socket_client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
- try:
- socket_client.connect(iproot)
- except Exception as message:
- print('connect error, %s' % message)
- time.sleep(1)
- continue
- else:
- print('connect sucess!')
- break
-
- # main
- while True:
- msg = input('client send: ')
- socket_client.send(msg.encode("utf-8"))
- a = socket_client.recv(1024)
-
- print('server send:' + a.decode("utf-8"))
- time.sleep(1)
- if msg == 'end':
- break
服务端(server)部分代码如下:
- # server
-
- iproot = ('', 8888)
- BUFFSIZE = 1024
- s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
- s.bind(iproot)
- s.listen(5)
- print('waiting client..')
-
- clientsocket, addr = s.accept()
- while True:
- data = clientsocket.recv(BUFFSIZE)
- print(data.decode('utf-8'))
- message=input("server send: ")
- clientsocket.send(message.encode('utf-8'))
- if message == 'end':
- break
执行代码,发送学号姓名的信息,客户端、服务端互相通信的结果即可,这里不做具体说明,以”end”退出通信
分析过程及序列号和确认号计算:
我以两台设备,互相通信,以下是对socket通信信息进行报文捕捉:
分析过程及序列号和确认号计算:
分析:
通过TCP三次握手:
第一次握手seq=0。
第二次握手seq=0,ack=seq(第一次)+1=1。
第三次握手seq=seq(第一次)+1=1,ack=seq(第二次的)+1=1。
客户端发送 “20215120807lyh” 长度为14的字符串
此时[PSH,ACK] seq=1,ack=1,len=14
并且服务端返回ACK=1表示确认收到,此时seq=1,ack=1+14=15。
服务端发送 “20215120808ZSJ” 长度为14的字符串
此时[PSH,ACK] seq=1,ack=15,len=14
并且客户端返回ACK=1表示确认收到,此时seq=15,ack=15。
客户端向服务端发送断开请求以及服务端发送断开请求。(四次挥手)
第一次挥手seq=15,ack=15。
第二次挥手seq=ack(第一次)=15,ack=seq(第一次)+1=16。
第三次挥手seq=seq(第二次)=15,ack=ack(第二次)=16。
第四次挥手seq=ack(第三次)=16,ack=seq(第三次)+1=16。
准备的字符串长度:14+2*1460=2934(学号加姓名缩写占14,“xx”2*1460)
发送一个长度为2934的字符串,分了3段,分别是1460,1460,14。
没有三次握手,UDP和网络层只提供尽力而为的服务,客户端未必能接收(无法分解到进程)
重连发生RST的抓包情况
发送数据时,因为服务端还未完全接收到缓冲区的数据而客户端断开连接可造
成RST包(RST->1,ACK->1)
问题1:与同学设备进行连接时失败,报错如下
解决方案:排查发现防火墙只关闭了域,使用公共局域网且没有关闭,关闭后解决
问题2:运行通信程序不断连接失败,并报错[WinError 10061] 由于目标计算机积极拒绝,无法连接
解决方案:由于服务端和客户端的ip地址和端口浑绕,改正后正常运行
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。