mysql数据库tftp快捷键_TFTP网络编程

本文介绍了TFTP协议及其在Python中的应用,包括使用Tftpd32工具进行文件共享,客户端和服务器的交互流程。接着详细讲述了Python中实现TFTP客户端下载器的代码,通过struct模块构造数据包。此外,还探讨了TCP协议的特性,如三次握手、四次挥手、半连接和TIME_WAIT状态,以及TCP服务器的创建,包括单进程、多进程和多线程实现。最后提到了Python的socketserver库简化并发服务器的创建,并讨论了TCP粘包问题及其解决方案。
摘要由CSDN通过智能技术生成

TFTP介绍

TFTP(Trivial File Transfer Protocol,简单⽂件传输协议)是TCP/IP协议簇中的⼀个⽤来在客户端与服务器之间进⾏简单⽂件传输的协议

使用tftp这个协议,就可以实现简单文件的下载

特点:

简单、占⽤资源⼩、适合传递⼩⽂件、适合在局域⽹进⾏传递、端⼝号为69、基于UDP实现

13649c9ea3c110d355eb53cbe8203f67.png

Tftpd32:共享服务器(可以从本机共享文件)

Tftpd32工具下载

30489a6aeb9ba3e48083ec03d3475ef1.png

browse:选择一个文件夹,确定给客户端文件时的搜索路径

客户端:数据接收方

服务器:数据发送方

代码实现客户端TFTP下载器

流程图

ce483dda754c998e36913ad9c354afef.png

格式

f4d893f04732b97609491009ed638452.png

#构造下载请求数据:“1test.jpg0octet0”

import struct

cmb_buf = struct.pack(“!H8sb5sb”,1,b“test.jpg”,0,b“octet”,0)

#如何保证操作码(1/2/3/4/5)占两个字节?如何保证0占一个字节?

#!H8sb5sb: ! 表示按照网络传输数据要求的形式来组织数据(占位的格式)

#!固定写法

#H 表示将后面的 1 替换成占两个字节

#8s 相当于8个s(ssssssss)占8个字节;s:1字节,字符型数组存字符串

#b 占一个字节

struct模块使用

struct模块可以按照指定格式将Python数据转换为字符串,该字符串为字节流

struct模块中最重要的三个函数是pack(), unpack(), calcsize()

# 按照给定的格式(fmt),把数据封装成字符串(实际上是类似于c结构体的字节流)pack(fmt, v1, v2, ...) 将v1,v2等参数的值进行一层包装,包装的方法由fmt指定。被包装的参数必须严格符合fmt。最后返回一个包装后的字符串。

# 按照给定的格式(fmt)解析字节流string,返回解析出来的元组unpack(fmt, string)

# 计算给定的格式(fmt)占用多少字节的内存calcsize(fmt)

struct.pack(“!H8sb5sb”,1,“test.jpg”,0,“octet”,0)

struct.pack("!HH",4,p_num)

cmdTuple = struct.unpack("!HH", recvData[:4])

9abc034ef45b8fc3f198623a5be39f6b.png

TFTP客户端编程(下载)

import struct

from socket import *

filename = 'vvue.jpg'

server_ip = '192.168.0.5'

send_data = struct.pack('!H%dsb5sb' % len(filename), 1, filename.encode(), 0, 'octet'.encode(), 0)

s = socket(AF_INET, SOCK_DGRAM)

s.sendto(send_data, (server_ip, 69)) # 第一次发送, 连接服务器69端口

f = open(filename, 'ab') #a:以追加模式打开(必要时可以创建)append;b:表示二进制

while True:

recv_data = s.recvfrom(1024) # 接收数据

print(recv_data)

caozuoma, ack_num = struct.unpack('!HH', recv_data[0][:4]) # 获取数据块编号

rand_port = recv_data[1][1] # 获取服务器的随机端口

if int(caozuoma) == 5:

print('文件不存在...')

break

print("操作码:%d,ACK:%d,服务器随机端口:%d,数据长度:%d"%(caozuoma, ack_num, rand_port, len(recv_data[0])))

f.write(recv_data[0][4:])#将数据写入

if len(recv_data[0]) < 516:

f.close()

break

ack_data = struct.pack("!HH", 4, ack_num)

s.sendto(ack_data, (server_ip, rand_port)) # 回复ACK确认包

0f3366901e31c4ac2c56537594f82fc0.png

TFTP客户端编程(上传)

import struct

from socket import *

filename = "1.jpg"

data = struct.pack(f"!H{len(filename.encode('gb2312'))}sb5sb",2,filename.encode("gb2312"),0,b"octet",0)

s = socket(type=SOCK_DGRAM)

s.sendto(data,("127.0.0.1",69))

f = open(filename,"rb")

while True:

ret = s.recvfrom(1024)

addr = ret[1]

data1,data2 = struct.unpack("!HH",ret[0][:4])

if data1 == 4:

data = f.read(512)

dabao = struct.pack(f"!HH{len(data)}s",3,data2+1,data)

s.sendto(dabao,addr)

if len(data) < 512:

break

UDP广播

cc51919accfd3484316027961ffc2ae6.png

发送端

import socket

dest = ('',7788)#自动识别当前网络的广播地址

#创建udp套接字

s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)

#对这个需要发送广播数据的套接字进行修改设置,否则不能发送广播数据

s.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1) #允许s发送广播数据

#setsocketopt 设置套接字选项

#以广播形式发送数据到本网络的所有电脑中

s.sendto(b'Hi',dest)

print("等待回复")

while True:

(buf, address) = s.recvfrom(2048)

print(address,buf.decode('GB2312'))

接收端

# udp广播案例

from socket import *

s=socket(AF_INET,SOCK_DGRAM)

# 设置套接字

s.setsockopt(SOL_SOCKET,SO_BROADCAST,1)

# 选择一个接收地址

s.bind(('0.0.0.0',7788))

while True:

try:

msg,addr=s.recvfrom(1024)

print('from %s bg %s'% (addr,msg.decode()))

s.sendto(b'ku',addr)

except KeyboardInterrupt:

break

except Exception as e:

print(e)

s.close()

TCP

TCP:传输控制协议(使用情况多于udp)

稳定:保证数据一定能收到

相对UDP会慢一点web服务器一般都使用TCP(银行转账,稳定比快要重要)

TCP通信模型:

在通信之前,必须先等待建立链接

d396b4daefdaa19fc4e744b9a583c9b0.png

TCP的三次握手

4d463958b160e3d733dc0639d6def82c.png

TCP四次挥手

f54399eb546d0d6db7c1a6505ec01644.png

第一次挥手:主动关闭方调用close,会发送一个长度为0的数据包以及FIN(结束标志)用来关闭主动方到被动关闭方的数据传送,这表示Client没有数据要发送给Server了,但是,此时主动关闭方还可以接受数据

第二次挥手:被动关闭方收到FIN包后,发送一个ACK给对方,确认序号为收到序号+1

第三次挥手:被动关闭方发送一个FIN,用来关闭被动关闭方到主动关闭方的数据传送,也就是告诉主动关闭方,我的数据也发送完了,不会再给你发数据了

第四次挥手:主动关闭方收到FIN后,发送一个ACK给被动关闭方,确认序号为收到序号+1,至此,完成四次挥手

常见问题

【问题1】为什么连接的时候是三次握手,关闭的时候却是四次握手?

答:因为当Server端收到Client端的SYN连接请求报文后,可以直接发送SYN+ACK报文。其中ACK报文是用来应答的,SYN报文是用来同步的。但是关闭连接时,当Server端收到FIN报文时,很可能并不会立即关闭SOCKET,所以只能先回复一个ACK报文,告诉Client端,"你发的FIN报文我收到了"。只有等到我Server端所有的报文都发送完了,我才能发送FIN报文,因此不能一起发送。故需要四步握手。

【问题2】什么是TCP半连接?

在三次握手过程中,服务器端发送SYN、ACK后,收到客户端的ACK之前的TCP连接称为半连接,此时服务器端处于SYN_RCVD状态,当收到ACK之后,服务器端进入Established状态。

【问题3】为什么不能用两次握手进行连接?

答:3次握手完成两个重要的功能,既要双方做好发送数据的准备工作(双方都知道彼此已准备好),也要允许双方就初始序列号进行协商,这个序列号在握手过程中被发送和确认。

​ 现在把三次握手改成仅需要两次握手,死锁是可能发生的。作为例子,考虑计算机S和C之间的通信,假定C给S发送一个连接请求分组,S收到了这个分组,并发 送了确认应答分组。按照两次握手的协定,S认为连接已经成功地建立了,可以开始发送数据分组。可是,C在S的应答分组在传输中被丢失的情况下,将不知道S 是否已准备好,不知道S建立什么样的序列号,C甚至怀疑S是否收到自己的连接请求分组。在这种情况下,C认为连接还未建立成功,将忽略S发来的任何数据分 组,只等待连接确认应答分组。而S在发出的分组超时后,重复发送同样的分组。这样就形成了死锁。

【问题4】如果已经建立了连接,但是客户端突然出现故障了怎么办?

TCP还设有一个计时器,显然,客户端如果出现故障,服务器不能一直等下去,白白浪费资源。服务器每收到一次客户端的请求后都会重新复位这个计时器,时间通常是设置为2小时,若两小时还没有收到客户端的任何数据,服务器就会发送一个探测报文段,以后每隔75秒钟发送一次。若一连发送10个探测报文仍然没反应,服务器就认为客户端出了故障,接着就关闭连接。

【问题5】为什么TIME_WAIT状态需要经过2MSL(最大报文段生存时间)才能返回到CLOSE状态?

答:虽然按道理,四个报文都发送完毕,我们可以直接进入CLOSE状态了,但是我们必须假象网络是不可靠的,有可以最后一个ACK丢失。所以TIME_WAIT状态就是用来重发可能丢失的ACK报文。在Client发送出最后的ACK回复,但该ACK可能丢失。Server如果没有收到ACK,将不断重复发送FIN片段。所以Client不能立即关闭,它必须确认Server接收到了该ACK。Client会在发送出ACK之后进入到TIME_WAIT状态。Client会设置一个计时器,等待2MSL的时间。如果在该时间内再次收到FIN,那么Client会重发ACK并再次等待2MSL。所谓的2MSL是两倍的MSL(Maximum Segment Lifetime)。MSL指一个片段在网络中最大的存活时间,2MSL就是一个发送和一个回复所需的最大时间。如果直到2MSL,Client都没有再次收到FIN,那么Client推断ACK已经被成功接收,则结束TCP连接。

TCP服务器

长连接:

三次握手四次挥手之间分多次传递完所有数据(优酷看视频、在线游戏),长时间占用某个套接字

短连接:

三次握手四次挥手之间传递少部分数据,多次握手挥手才传递完所有数据(浏览器),短时间占用

tcp服务器流程如下:

socket创建⼀个套接字

bind绑定ip和port

listen设置最大连接数,收到连接请求后,这些请求需要排队,如果队列满,就拒绝请求

accept等待客户端的链接、接收连接请求

recv/send接收发送数据

简易TCP服务器代码

from socket import *

tcpSerSocket = socket(AF_INET, SOCK_STREAM)

address = ('', 7788)

tcpSerSocket.bind(address)

tcpSerSocket.listen(5)#设置最大连接数

newSocket, clientAddr = tcpSerSocket.accept()

# 如果有新的客户端来链接服务器, 那么就产⽣⼀个新的套接字

# newSocket⽤来为这个客户端服务

# tcpSerSocket就可以省下来等待其他新客户端的链接

# 接收对⽅发送过来的数据, 最⼤接收1024个字节

recvData = newSocket.recv(1024) #接收tcp数据

# 发送⼀些数据到客户端

newSocket.send(b"thank you !") #发送tcp数据

# 关闭为这个客户端服务的套接字, 只要关闭了, 就意味着为不能再为这个客户端服务

newSocket.close()

# 关闭监听套接字, 只要这个套接字关闭了, 就意味着整个程序不能再接收任何新的客户端的连接

tcpSerSocket.close()

TCP客户端

from socket import *

clientSocket = socket(AF_INET, SOCK_STREAM)

serAddr = ('192.168.0.5', 7788)

#链接服务器

clientSocket.connect(serAddr)

clientSocket.send(b"hello")

recvData = clientSocket.recv(1024)

print("接收到的数据为:", recvData)

clientSocket.close()

TCP服务器(单进程)

每次只能服务一个客户端

from socket import *

serSocket = socket(AF_INET, SOCK_STREAM)

localAddr = ('',7788)

serSocket.bind(localAddr)

serSocket.listen(5)

while True:

print("主进程等待新客户端")

newSocket,destAddr = serSocket.accept()

print("主进程接下来负责处理",str(destAddr))

try:

while True:

recvData = newSocket.recv(1024)

if len(recvData)>0: #如果收到的客户端数据长度为0,代表客户端已经调用close()下线

print("接收到", str(destAddr),recvData)

else:

print("%s-客户端已关闭" %str(destAddr))

break

finally:

newSocket.close()

serSocket.close()

TCP并发服务器(多进程)

serSocket.setsockopt(SOL_SOCKET,SO_REUSEADDR,1)

重新设置套接字选项,重复使用绑定的信息

当有一个有相同本地地址和端口的socket1处于TIME_WAIT状态时,而你启动的程序的socket2要占用该地址和端口,你的程序就要用到SO_REUSEADDR选项。

from socket import *

from multiprocessing import *

from time import sleep

# 处理客户端的请求并为其服务

def dealWithClient(newSocket,destAddr):

while True:

recvData = newSocket.recv(1024)

if len(recvData)>0:

print('recv[%s]:%s'%(str(destAddr), recvData))

else:

print('[%s]客户端已经关闭'%str(destAddr))

break

newSocket.close()

def main():

serSocket = socket(AF_INET, SOCK_STREAM)

serSocket.setsockopt(SOL_SOCKET, SO_REUSEADDR , 1)

localAddr = ('', 7788)

serSocket.bind(localAddr)

serSocket.listen(5)

try:

while True:

print('-----主进程,等待新客户端的到来------')

newSocket,destAddr = serSocket.accept()

print('-----主进程,接下来创建⼀个新的进程负责数据处理')

client = Process(target=dealWithClient, args=(newSocket,destAddr))

client.start()

#因为已经向⼦进程中copy了⼀份(引⽤) ,并且⽗进程中这个套接字也没有用处了

#所以关闭

newSocket.close()

finally:

#当为所有的客户端服务完之后再进⾏关闭,表示不再接收新的客户端的链接

serSocket.close()

if __name__ == '__main__':

main()

TCP服务器(多线程)

耗费的资源比多进程小一些

from socket import *

from threading import Thread

# 处理客户端的请求并执⾏事情

def dealWithClient(newSocket,destAddr):

while True:

recvData = newSocket.recv(1024)

if len(recvData)>0:

print('recv[%s]:%s'%(str(destAddr), recvData))

else:

print('[%s]客户端已经关闭'%str(destAddr))

break

newSocket.close()

def main():

serSocket = socket(AF_INET, SOCK_STREAM)

serSocket.setsockopt(SOL_SOCKET, SO_REUSEADDR , 1)

localAddr = ('', 7788)

serSocket.bind(localAddr)

serSocket.listen(5)

try:

while True:

print('-----主进程, , 等待新客户端的到来------')

newSocket,destAddr = serSocket.accept()

print('主进程接下来创建⼀个新的线程负责处理 ', str(destAddr))

client = Thread(target=dealWithClient, args=(newSocket,destAddr))

client.start()

#因为线程中共享这个套接字, 如果关闭了会导致这个套接字不可⽤,

#但是此时在线程中这个套接字可能还在收数据, 因此不能关闭

finally:

serSocket.close()

if __name__ == '__main__':

main()

socketsever

可以使用socketserver来创建socket用来简化并发服务器

socketserver可以实现和多个客户端通信(实现并发处理多个客户端请求的Socket服务端)

它是在socket的基础上进行了一层封装,也就是说底层还是调用的socket

服务器接受客户端连接请求——》实例化一个请求处理程序——》根据服务器类和请求处理程序类,调用处理方法。

例如:

基本请求程序类(BaseRequestHandler)调用方法 handle 。此方法通过属性 self.request 来访问客户端套接字

#创建socketserver的基本步骤:

#首先import socketserver

#创建一个请求处理类,继承 BaseRequestHandler 并且重写父类中的 handle()

#在handle()中处理和客户端所有的交互,建立链接时会自动执行handle方法

socketserver.TCPServer.allow_reuse_address = True # 允许地址(端口)重用

#实例化 TCPServer对象 ,将服务器IP/端口号和请求处理类传给 TCPServer

server = socketserver.ThreadingTCPServer(ip_port,MyServer)

#对socketserver.ThreadingTCPServer 类实例化对象,将ip地址,端口号以及自己定义的类名传入,并返回一个对象

#多线程:ThreadingTCPServer

#多进程:ForkingTCPServer -only in Linux

#对象执行serve_forever方法,开启服务端(handle_request()只处理一个请求)

server.serve_forever() #处理多个请求,永远执行

socketsever服务端

import socketserver

import time

# 自定义类来实现通信循环

class MyServer(socketserver.BaseRequestHandler):

# 必须写入handle方法,建立链接时会自动执行handle方法

def handle(self):

while True:

data = self.request.recv(1024)

# handle 方法通过属性 self.request 来访问客户端套接字

print('->client:', data)

self.request.send(data.upper())

time.sleep(2)

socketserver.TCPServer.allow_reuse_address = True

server = socketserver.ThreadingTCPServer(('', 8080), MyServer)

server.serve_forever()

socketsever客户端

import socket

import time

client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

client.connect(('192.168.0.5', 8080))

while True:

time.sleep(2)

client.send('hello'.encode('utf-8'))

data = client.recv(1024)

print(data)

subprocess远程执行命令

Python可以使用subprocess下的Popen类中的封装的方法来执行命令

构造方法 popen() 创建popen类的实例化对象

obj = Subprocess.Popen(data,shell=True,stdout=subprocess.PIPE,stderr=subprocess.PIPE)

data 命令内容

shell = True 命令解释器,相当于调用cmd 执行指定的命令

stdout 正确结果丢到管道中

stderr 错了丢到另一个管道中

PIPE 将结果转移到当前进程

stdout.read() 可以获取命令执行的结果

指定结果后会将执行结果封装到指定的对象中

然后通过对象.stdout.read()获取执行命令的结果,如果不定义stdout会将结果进行标准输出

import subprocess

obj = subprocess.Popen('net user',shell=True,stdout=subprocess.PIPE,stderr=subprocess.PIPE)

print(obj.stdout.read().decode('gbk')) # 正确命令

print(obj.stderr.read().decode('gbk')) #错误命令

subprocess服务端

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:

print("服务结束")

break

conn.close()

phone.close()

subprocess客户端

import socket

phone = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

phone.connect(('127.0.0.1', 8080))

while 1:

cmd = input('>>>')

if cmd == "zaijian":

break

phone.send(cmd.encode('utf-8'))

from_server_data = phone.recv(1024)

print(from_server_data.decode('gbk'))

phone.close()

解决沾包问题

TCP协议是面向流的协议,容易出现粘包问题

704842cfd512e57998d8118d22a6727d.png

不管是recv还是send都不是直接接收对方的数据(不是一个send一定对应一个recv),而是操作自己的缓存区(产生沾包的根本原因)

例如基于tcp的套接字客户端往服务端上传数据,发送时数据内容是按照一段一段的字节流发送的,

在接收方看了,根本不知道该文件的字节流从何处开始,在何处结束

所谓粘包问题主要还是因为接收方不知道消息之间的界限,不知道一次性提取多少字节的数据所造成的。

而UDP是面向消息的协议,每个UDP段都是一条消息,应用程序必须以消息为单位提取数据,

不能一次提取任意字节的数据,这一点和TCP是很不同的

只有TCP有粘包现象,UDP永远不会粘包

粘包不一定会发生

如果发生了:1.可能是在客户端已经粘了

2.客户端没有粘,可能是在服务端粘了

客户端粘包

发送端需要等缓冲区满才发送出去,造成粘包

(发送数据时间间隔很短,数据量很小,TCP优化算法会当做一个包发出去,产生粘包)

服务端粘包

接收方没能及时接收缓冲区的包(或没有接收完),造成多个包接收(客户端发送了一段数据,服务端只收了一小部分,

服务端下次再收的时候还是从缓冲区拿上次遗留的数据,产生粘包)

客户端沾包

server端

import socket

server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

server.bind(('127.0.0.1', 9904))

server.listen(5)

conn, addr = server.accept()

res1 = conn.recv(1024)

print('第一次', res1)

res2 = conn.recv(1024)

print('第二次', res2)

client端

import socket

client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

client.connect(('127.0.0.1', 9904))

client.send('hello'.encode('utf-8'))

client.send('world'.encode('utf-8'))

打印结果:

第一次 b'helloworld'

第二次 b''

不合适的解决方案:

send时加上时间间隔,虽然可以解决,但是会影响效率。不可取。

服务端沾包

server端

import socket

server =socket.socket(socket.AF_INET ,socket.SOCK_STREAM)

server.bind(('127.0.0.1' ,9904))

server.listen(5)

conn,addr =server.accept()

res1 = conn.recv(2) # 第一没有接收完整

print('第一次' ,res1)

res2 =conn.recv(10)# 第二次会接收旧数据,再收取新的

print('第二次', res2)

client端

import socket

client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

client.connect(('127.0.0.1', 9904))

client.send('hello'.encode('utf-8'))

client.send('world'.encode('utf-8'))

打印结果:

第一次 b'he'

第二次 b'lloworld'

不合适的解决方案:

提升recv的接收数量的上限。 不可取

因为没有上限。8G数据,一次接收8G撑爆内存了

如何解决

问题的根源在于:接收端不知道发送端将要传送的字节流的长度

所以解决粘包的方法就是发送端在发送数据前,发一个头文件包,告诉发送的字节流总大小,然后接收端来一个死循环接收完所有数据

使用struct模块可以用于将Python的值根据格式符,转换为固定长度的字符串(byte类型)

struct模块中最重要的三个函数是pack(), unpack(), calcsize()

pack(fmt, v1, v2, ...) 按照给定的格式(fmt),把数据封装成字符串(实际上是类似于c结构体的字节流)

unpack(fmt, string) 按照给定的格式(fmt)解析字节流string,返回解析出来的tuple

calcsize(fmt) 计算给定的格式(fmt)占用多少字节的内存

server端

import socket,struct,json

import subprocess

gd_server=socket.socket(socket.AF_INET,socket.SOCK_STREAM)

gd_server.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1) #在bind前加

gd_server.bind(('127.0.0.1',9991))

gd_server.listen(5)

while True:

conn, addr = gd_server.accept()

while True:

cmd = conn.recv(1024)

if not cmd:

break

print('cmd: %s' % cmd)

# 命令操作

res=subprocess.Popen(cmd.decode('utf-8'),

shell=True,

stdout=subprocess.PIPE,

stderr=subprocess.PIPE)

err=res.stderr.read()

print(err)

if err:

back_msg=err

else:

back_msg=res.stdout.read()

headers = {'data_size': len(back_msg)}

head_json = json.dumps(headers) # 字典转换成字符串

head_json_bytes = bytes(head_json, encoding='utf-8')

conn.send(struct.pack('i', len(head_json_bytes))) # 先发报头长度

conn.send(head_json_bytes) # 再发报头

conn.sendall(back_msg) # 再发正式内容

conn.close()

client端

import socket, json, struct

ip_port = ('127.0.0.1',9991)

client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

client.connect(ip_port)

while True:

cmd=input('>>: ').strip()

if not cmd :

continue

client.send(bytes(cmd,encoding='utf-8'))

head = client.recv(4) # 先收4个bytes,这里4个bytes里包含了报头的长度

head_json_len = struct.unpack('i', head)[0] # 解出报头的长度

head_json = json.loads(client.recv(head_json_len).decode('utf-8')) # 拿到报头

data_len = head_json['data_size'] # 取出报头内包含的信息

# 开始接收数据

recv_size = 0

recv_data = b''

while recv_size < data_len:

recv_data += client.recv(1024)

recv_size += len(recv_data)

print(recv_data.decode('gbk'))

# 命令结果的编码是以当前所在的系统为准的,如果是windows,那么res.stdout.read()读出的就是GBK编码的,mac 默认 utf-8 编码

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
1 目标检测的定义 目标检测(Object Detection)的任务是找出图像中所有感兴趣的目标(物体),确定它们的类别和位置,是计算机视觉领域的核心问题之一。由于各类物体有不同的外观、形状和姿态,加上成像时光照、遮挡等因素的干扰,目标检测一直是计算机视觉领域最具有挑战性的问题。 目标检测任务可分为两个关键的子任务,目标定位和目标分类。首先检测图像中目标的位置(目标定位),然后给出每个目标的具体类别(目标分类)。输出结果是一个边界框(称为Bounding-box,一般形式为(x1,y1,x2,y2),表示框的左上角坐标和右下角坐标),一个置信度分数(Confidence Score),表示边界框中是否包含检测对象的概率和各个类别的概率(首先得到类别概率,经过Softmax可得到类别标签)。 1.1 Two stage方法 目前主流的基于深度学习的目标检测算法主要分为两类:Two stage和One stage。Two stage方法将目标检测过程分为两个阶段。第一个阶段是 Region Proposal 生成阶段,主要用于生成潜在的目标候选框(Bounding-box proposals)。这个阶段通常使用卷积神经网络(CNN)从输入图像中提取特征,然后通过一些技巧(如选择性搜索)来生成候选框。第二个阶段是分类和位置精修阶段,将第一个阶段生成的候选框输入到另一个 CNN 中进行分类,并根据分类结果对候选框的位置进行微调。Two stage 方法的优点是准确度较高,缺点是速度相对较慢。 常见Tow stage目标检测算法有:R-CNN系列、SPPNet等。 1.2 One stage方法 One stage方法直接利用模型提取特征值,并利用这些特征值进行目标的分类和定位,不需要生成Region Proposal。这种方法的优点是速度快,因为省略了Region Proposal生成的过程。One stage方法的缺点是准确度相对较低,因为它没有对潜在的目标进行预先筛选。 常见的One stage目标检测算法有:YOLO系列、SSD系列和RetinaNet等。 2 常见名词解释 2.1 NMS(Non-Maximum Suppression) 目标检测模型一般会给出目标的多个预测边界框,对成百上千的预测边界框都进行调整肯定是不可行的,需要对这些结果先进行一个大体的挑选。NMS称为非极大值抑制,作用是从众多预测边界框中挑选出最具代表性的结果,这样可以加快算法效率,其主要流程如下: 设定一个置信度分数阈值,将置信度分数小于阈值的直接过滤掉 将剩下框的置信度分数从大到小排序,选中值最大的框 遍历其余的框,如果和当前框的重叠面积(IOU)大于设定的阈值(一般为0.7),就将框删除(超过设定阈值,认为两个框的里面的物体属于同一个类别) 从未处理的框中继续选一个置信度分数最大的,重复上述过程,直至所有框处理完毕 2.2 IoU(Intersection over Union) 定义了两个边界框的重叠度,当预测边界框和真实边界框差异很小时,或重叠度很大时,表示模型产生的预测边界框很准确。边界框A、B的IOU计算公式为: 2.3 mAP(mean Average Precision) mAP即均值平均精度,是评估目标检测模型效果的最重要指标,这个值介于0到1之间,且越大越好。mAP是AP(Average Precision)的平均值,那么首先需要了解AP的概念。想要了解AP的概念,还要首先了解目标检测中Precision和Recall的概念。 首先我们设置置信度阈值(Confidence Threshold)和IoU阈值(一般设置为0.5,也会衡量0.75以及0.9的mAP值): 当一个预测边界框被认为是True Positive(TP)时,需要同时满足下面三个条件: Confidence Score > Confidence Threshold 预测类别匹配真实值(Ground truth)的类别 预测边界框的IoU大于设定的IoU阈值 不满足条件2或条件3,则认为是False Positive(FP)。当对应同一个真值有多个预测结果时,只有最高置信度分数的预测结果被认为是True Positive,其余被认为是False Positive。 Precision和Recall的概念如下图所示: Precision表示TP与预测边界框数量的比值 Recall表示TP与真实边界框数量的比值 改变不同的置信度阈值,可以获得多组Precision和Recall,Recall放X轴,Precision放Y轴,可以画出一个Precision-Recall曲线,简称P-R
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值