TCP/UDP协议 与socket网络通信 学这一篇就够啦
任务目标:建立 socket 连接通道,可以相互之间传输数据
使用环境:python
任务要求:
1、理解TCP、UDP协议的原理及特点。
2、分别使用 TCP、UDP 协议实现数据通讯。
拓展任务:
实现客户端发送命令,服务端接收命令并执行。
前言
好令人蒙蔽,大雾。
tcp,udp,socket 这些陌生的词汇。与网络通信又有怎样的关系?
全然未知的探索,具有极大的挑战性,同时蕴含着更多的乐趣。
让我们出发,去了解这些生涩的词汇,以及背后的原理。
不同电脑上的进程之间如何通信?
本篇报告的题目叫网络通信开发基础。那么我们首先要知道,不同电脑上,不同进程之间是如何通信的。
实现网络通信,首要解决的问题就是如何唯一的标识出一个进程。标识为何重要呢?假如标识不唯一,那么就意味着两个人可以用一张嘴说话。试想,如果爸爸和儿子用一张嘴说话,那么你该如何回答?世界不就乱套了。同理,实现网络通信,必须要实现唯一标识。
在1台电脑上可以通过进程进程号(PID)来唯一标识一个进程,但是在网络中这是行不通的(因为不同的电脑上关于某进程的进程号可能都不一样,无法统一)
好在TCP/IP协议族已经帮我们解决了这个问题。网络层的**“ip地址”可以唯一标识网络中的主机,而传输层的“协议+端口”可以唯一标识主机中的应用进程(进程)。利用ip地址,协议+端口**,我们就可以唯一标识主机中的进程了。网络中的进程通信就可以利用这个标志与其他进程进行交互。
socket简介
socket(简称 套接字) 是进程间通信的一种方式,他与其他进程间通信方式的主要不同是,它能实现不同主机之间的进程间通信。我们上网的大多通信服务,都是基于socket的 如浏览网页,qq聊天,收发Email等。
创建socket
在python中,使用socket模块的函数就可以完成。
基本格式
import socket
socket.socket(AddressFamily, Type, protocal=0)
# 获取tcp/ip套接字
tcpSock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 获取udp/ip套接字
udpSock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
# ...这里是使用套接字的功能(省略)...
# 不用的时候,关闭套接字
s.close()
常用函数与方法
参数说明:
Address Family:可以选择 AF_INET(用于 Internet 进程间通信) 或者 AF_UNIX(用于同一台机器进程间通信),实际工作中常用AF_INET Type:套接字类型,可以是 SOCK_STREAM(流式套接字,主要用于 TCP 协议)或者 SOCK_DGRAM(数据报套接字,主要用于 UDP 协议) protocol 一般不填,默认值为 0。
服务端套接字函数
s.bind() 绑定(主机,端口号)到套接字
s.listen() 开始TCP监听
s.accept() 被动接受TCP客户的连接,(阻塞式)等待连接的到来
注意:**bind()**其中INADDR_ANY就是指定地址为0.0.0.0的地址,这个地址事实上表示不确定地址,或“所有地址”、“任意地址”。
客户端套接字函数
s.connect() 主动初始化TCP服务器连接
s.connect_ex() connect()函数的扩展版本,出错时返回出错码,而不是抛出异常
公共用途的套接字函数
s.recv() 接收TCP数据
s.send() 发送TCP数据(send在待发送数据量大于己端缓存区剩余空间时,数据丢失,不会发完)
s.sendall() 发送完整的TCP数据(本质就是循环调用send,sendall在待发送数据量大于己端缓存区剩余空间时,数据不丢失,循环调用send直到发完)
s.recvfrom() 接收UDP数据
s.sendto() 发送UDP数据
s.getpeername() 连接到当前套接字的远端的地址
s.getsockname() 当前套接字的地址
s.getsockopt() 返回指定套接字的参数
s.setsockopt() 设置指定套接字的参数
s.close() 关闭套接字
面向锁的套接字方法
s.setblocking() 设置套接字的阻塞与非阻塞模式
s.settimeout() 设置阻塞套接字操作的超时时间
s.gettimeout() 得到阻塞套接字操作的超时时间
面向文件的套接字的函数
s.fileno() 套接字的文件描述符
s.makefile() 创建一个与该套接字相关的文件
TCP、UDP的原理及特点
TCP原理
TCP全称为 “传输控制协议(Transmission Control Protocol”). 从名字就能看出来,TCP协议 要对数据的传输进行一个详细的控制。
源/目的端口号: 表示数据是从哪个进程来, 到哪个进程去。
TCP报头长度(4位): 表示该TCP头部有多少个32位bit(有多少个4字节); 所以TCP头部最大长度是15 * 4 = 60
六位标志位
URG: 紧急指针是否有效 ACK: 确认号是否有效 PSH: 提示接收端应用程序立刻从TCP缓冲区把数据读走 RST: 对方要求重新建立连接; 我们把携带RST标识的称为复位报文段 SYN: 请求建立连接; 我们把携带SYN标识的称为同步报文段 FIN: 通知对方, 本端要关闭了, 我们称携带FIN标识的为结束报文段
16位校验和: 发送端填充, CRC校验. 接收端校验不通过, 则认为数据有问题.
16位紧急指针: 标识哪部分数据是紧急数据;
TCP ACK 确认应答机制
当主机a传输数据给主机b,主机b收到数据后会返回一个字节的确认应答数据,表示数据被正常运输。
TCP 超时重传机制
主机A发送数据给主机B后,可能因为网络拥堵等原因,数据包无法抵达,如果主机a在特定时间期限内没有收到应答,则会重新发送数据包。以下是超时重传的两种情况示意图。
TCP总结
TCP 为何可靠?
- 校验和
- 序列号
- 确认应答
- 超时重传
- 连接管理
- 拥塞控制
- 流量控制
TCP提高性能的方式
TCP实现socket的简单通信(python)
首先再把socket简介中的图再搬下来一遍~
服务器端
首先回顾一下tcp实现socket简单通信中服务器端都做了些什么
首先,初始化socket,建立一个套接字。然后用bind函数来绑定一个端口号和地址。接下来调用listen函数,使服务器的这个端口和ip处于监听状态。进入监听状态后,服务器调动accept阻塞,等待客户端连接。
客户机请求连接后,服务器使用accept函数接收远程计算机的连接。连接,客户写入读取数据后。服务器用read函数读取客户机发送来的数据(也可以用write函数发送数据)。最后,完成socket通信后,服务器用close函数关闭socket连接
加粗字体基本包含了我们要代码实现的内容
from socket import *
tcp_server_socket =socket(AF_INET,SOCK_STREAM)
tcp_server_socket.setsockopt(SOL_SOCKET,SO_REUSEADDR,1)
address =('server_ip',server_port)
tcp_server_socket.bind(address)
tcp_server_socket.listen(128)
client_socket, clientAddr = tcp_server_socket.accept()
recv_num = client_socket.recv(1024)
print ('接收数据:',recv_num.decode('gbk'))
client_socket.close()
客户端
和服务器端一样,回顾一下,tcp实现socket通信过程中,客户端做了什么呢? 首先,客户端同样建立了一个socket,然后设置了远程ip和端口。之后调用connect函数与远程ip建立连接。等待服务器端建立连接后,客户机用 write 函数向 socket 中写入数据。也可以用 read 函数读取服务器发送来的数据。最后也是关闭socket。
下面使用python实现 加粗字体的内容
from socket import *
tcp_client_socket =socket(AF_INET,SOCK_STREAM)
tcp_client_socket.connect((server_ip, server_port))
send_data= input("请输入要发送的数据:")
tcp_client_socket.send(send_data.encode("gbk"))
tcp_client_socket.close()
客户端发送命令,服务端接收命令并执行的实现(TCP)。
首先,在服务器端的代码中,输入本地ip和任意未占用端口,这里我选择了7788端口。然后运行
运行成功,服务器端看样子已经调动了accept阻断,静待我们连接了。
然后在用户端的代码中输入同样的本地ip和端口。运行
连接成功!输入要发送的数据
轻击回车
提示Process finished with exit code 0
此时再回到服务器端查看接收数据
接收成功!
以上就是TCP实现socket的简单通信的全过程。
UDP原理
UDP协议称为用户数据报协议(UDP,User Datagram Protocol)。UDP 为应用程序提供了一种无需建立连接就可以发送封装的 IP 数据包的方法。怪不得叫用户数据报协议,想必是因为对用户比较友好。
UDP传输的过程类似与寄信,其特点是 无连接(知道对端ip和端口就可直接进行传输,不需要建立连接(类似于寄信只需要知道地址,))、不可靠(没有确认机制,没有重传机制,不返回任何错误信息)、面向数据(应用层交给UDP多长的报文, UDP原样发送, 既不会拆分, 也不会合并,导致控制读取不够灵活)
我们可以注意到,UDP协议首部中有一个16位的最大长度. 也就是说一个UDP能**传输的数据最大长度是64K(**包含UDP首部).然而64K在当今的互联网环境下, 是一个非常小的数字. 如果我们需要传输的数据超过64K, 就需要在应用层手动的分包, 多次发送, 并在接收端手动拼装(面向数据的特点)
UDP/TCP对比
总结到此处,我已经产生了一个疑惑,**是否TCP一定优于UDP呢?**但更加深入的了解后我明白了,TCP和UDP之间的优点和缺点, 不能简单, 绝对的进行比较。
TCP用于可靠传输的情况, 应用于文件传输, 重要状态更新等场景。 UDP用于对高速传输和实时性要求较高的通信领域, 例如, 早期的QQ, 视频传输等. 另外UDP可以用于广播。
归根结底, TCP和UDP都是程序员的工具, 什么时机用, 具体怎么用, 还是要根据具体的需求场景去判定.
UDP实现socket的简单通信
老规矩,上一张UDP socket工作流程图
服务器端
还是让我们来看看,UDP实现socket的通信过程中,服务器端做了些什么事。首先,创建socket套接字,然后**利用bind()函数绑定本地信息(ip和端口)**之后等待接受客户端发送的数据。输出客户端发送的数据,最后关闭socket
此处要注意,UDP协议接收到的数据是一个元组,第一个元素为数据,第二个元素为对方的ip和端口。
其实与TCP的实现区别并不算大可能最大的区别就在于接受的数据类型不同。
from socket import *
udp_server_socket=socket(AF_INET, SOCK_DGRAM)
local_addr=("UDP_ip",UDP_port)
udp_server_socket.bind(local_addr)
recv_data = udp_socket.recvfrom(1024)
print('得到的数据为:',recv_data[0].decode('gbk'))
print('发送的ip端口为',recv_data[1])
udp_socket.close()
客户端
让我们再来看看 客户端做了些什么。
首先创建了socket,然后准备接收方的地址、端口。再然后从键盘获取数据,之后打包数据和地址、端口发送给服务器端。最后关闭socket通道。
from socket import *
udp_clinet_socket=socket(AF_INET,SOCK_DGRAM)
dest_addr=('UDP_ip',UDP_port)
client_data=input("请输入数据 ")
udp_clinet_socket.sendto(client_data.encode('utf-8'), dest_addr)
udp_clinet_socket.close()
客户端发送命令,服务端接收命令并执行的实现(UDP)。
与tcp协议的实现一样,修改服务器和客户端的ip为本地ip,端口为任意端口。
由于不需要建立连接就可发送,所以可以不先运行服务器端就可运行客户端。
输入123 再运行服务器端,即可成功接收到数据(数据和ip 端口的打包)
至此UDP实现socket的简单通信完成。
后记
在实际的渗透中,协议是建立据点网络通道的基础,可以通过网络通道对内部的服务器进行控制。有了这个基础可以实现一些比如远控木马、端口扫描、服务爆破方面的工具。
更加现实的收获是,我弄懂了TCP,SOCKET,UDP等之前陌生的词汇,并通过实践有了深入的了解,更加深刻的认识到网络通信交互的过程原理。信安之路或许就是这样,在实践中不断深入理解原理,原理的深入了解又会反哺实验能力。
学会了socket的简单通信,我有了一个更大胆的想法,是否能够自己编写一个简易聊天室呢?先立个flag把,立个flag又不要钱不是么。我的理解总是有梦可做总比畏手畏脚要好。
后记之后
利用TCP socket实现微型聊天室
服务器端
from socket import *
address=('127.0.0.1',1777)
tcp_server_socket=socket(AF_INET,SOCK_STREAM)
tcp_server_socket.setsockopt(SOL_SOCKET,SO_REUSEADDR,1)
tcp_server_socket.bind(address)
tcp_server_socket.listen(5)
client_socket,client_addr=tcp_server_socket.accept()
print("request from:"+str(address))
while True:
a=input("<<")
client_socket.send(a.encode('gbk'))
re=client_socket.recv(512)
print(">>"+re.decode('gbk'))
tcp_server_socket.close()
客户端
from socket import *
tcp_client_socket =socket(AF_INET,SOCK_STREAM)
tcp_client_socket.connect(('127.0.0.1',1777))
while True:
data=tcp_client_socket.recv(512)
print(data.decode('gbk'))
send_data= input(">>")
tcp_client_socket.send(send_data.encode("gbk"))
tcp_client_socket.close()
效果如下
让我们愉快的聊天 趴。虽然只能在内网里qwq
|