基于tcp先制作一個(gè)遠(yuǎn)程執(zhí)行命令的程序(命令ls -l ; lllllll ; pwd)
注意
res=subprocess.Popen(cmd.decode('utf-8'),
shell=True,
stderr=subprocess.PIPE,
stdout=subprocess.PIPE)
的結(jié)果的編碼是以當(dāng)前所在的系統(tǒng)為準(zhǔn)的,如果是windows,那么res.stdout.read()讀出的就是GBK編碼的,在接收端需要用GBK解碼
且只能從管道里讀一次結(jié)果
同時(shí)執(zhí)行多條命令之后,得到的結(jié)果很可能只有一部分,在執(zhí)行其他命令的時(shí)候又接收到之前執(zhí)行的另外一部分結(jié)果,這種顯現(xiàn)就是黏包。
tcp - server
from socket import *
import subprocess
ip_port=('127.0.0.1',8888)
BUFSIZE=1024
tcp_socket_server=socket(AF_INET,SOCK_STREAM)
tcp_socket_server.setsockopt(SOL_SOCKET,SO_REUSEADDR,1)
tcp_socket_server.bind(ip_port)
tcp_socket_server.listen(5)
while True:
conn,addr=tcp_socket_server.accept()
print('客戶端',addr)
while True:
cmd=conn.recv(BUFSIZE)
if len(cmd)==0:break
res=subprocess.Popen(cmd.decode('utf-8'),shell=True,
stdout=subprocess.PIPE,
stdin=subprocess.PIPE,
stderr=subprocess.PIPE)
stderr=res.stderr.read()
stdout=res.stdout.read()
conn.send(stderr)
conn.send(stdout)
udp - client
from socket import *
ip_port=('127.0.0.1',9000)
bufsize=1024
udp_client=socket(AF_INET,SOCK_DGRAM)
while True:
msg=input('>>: ').strip()
udp_client.sendto(msg.encode('utf-8'),ip_port)
err,addr=udp_client.recvfrom(bufsize)
out,addr=udp_client.recvfrom(bufsize)
if err:
print('error : %s'%err.decode('utf-8'),end='')
if out:
print(out.decode('utf-8'), end='')
注意:只有TCP有粘包現(xiàn)象,UDP永遠(yuǎn)不會(huì)粘包
黏包成因
TCP協(xié)議中的數(shù)據(jù)傳遞
當(dāng)發(fā)送端緩沖區(qū)的長度大于網(wǎng)卡的MTU時(shí),tcp會(huì)將這次發(fā)送的數(shù)據(jù)拆成幾個(gè)數(shù)據(jù)包發(fā)送出去。
MTU是Maximum Transmission Unit的縮寫。意思是網(wǎng)絡(luò)上傳送的最大數(shù)據(jù)包。MTU的單位是字節(jié)。
大部分網(wǎng)絡(luò)設(shè)備的MTU都是1500。如果本機(jī)的MTU比網(wǎng)關(guān)的MTU大,大的數(shù)據(jù)包就會(huì)被拆開來傳送,
這樣會(huì)產(chǎn)生很多數(shù)據(jù)包碎片,增加丟包率,降低網(wǎng)絡(luò)速度。
面向流的通信特點(diǎn)和Nagle算法
TCP(transport control protocol,傳輸控制協(xié)議)是面向連接的,面向流的,提供高可靠性服務(wù)。
收發(fā)兩端(客戶端和服務(wù)器端)都要有一個(gè)成對(duì)的socket,因此,發(fā)送端為了將多個(gè)發(fā)往接收端的包,更有效的發(fā)到對(duì)方,使用了優(yōu)化方法(Nagle算法),將多次間隔較小且數(shù)據(jù)量小的數(shù)據(jù),合并成一個(gè)大的數(shù)據(jù)塊,然后進(jìn)行封包。
這樣,接收端,就難于分辨出來了,必須提供科學(xué)的拆包機(jī)制。 即面向流的通信是無消息保護(hù)邊界的。
對(duì)于空消息:tcp是基于數(shù)據(jù)流的,于是收發(fā)的消息不能為空,這就需要在客戶端和服務(wù)端都添加空消息的處理機(jī)制,防止程序卡住,而udp是基于數(shù)據(jù)報(bào)的,即便是你輸入的是空內(nèi)容(直接回車),也可以被發(fā)送,udp協(xié)議會(huì)幫你封裝上消息頭發(fā)送過去。
可靠黏包的tcp協(xié)議:tcp的協(xié)議數(shù)據(jù)不會(huì)丟,沒有收完包,下次接收,會(huì)繼續(xù)上次繼續(xù)接收,己端總是在收到ack時(shí)才會(huì)清除緩沖區(qū)內(nèi)容。數(shù)據(jù)是可靠的,但是會(huì)粘包。
基于tcp協(xié)議特點(diǎn)的黏包現(xiàn)象成因
發(fā)送端可以是一K一K地發(fā)送數(shù)據(jù),而接收端的應(yīng)用程序可以兩K兩K地提走數(shù)據(jù),當(dāng)然也有可能一次提走3K或6K數(shù)據(jù),或者一次只提走幾個(gè)字節(jié)的數(shù)據(jù)。
也就是說,應(yīng)用程序所看到的數(shù)據(jù)是一個(gè)整體,或說是一個(gè)流(stream),一條消息有多少字節(jié)對(duì)應(yīng)用程序是不可見的,因此TCP協(xié)議是面向流的協(xié)議,這也是容易出現(xiàn)粘包問題的原因。
而UDP是面向消息的協(xié)議,每個(gè)UDP段都是一條消息,應(yīng)用程序必須以消息為單位提取數(shù)據(jù),不能一次提取任意字節(jié)的數(shù)據(jù),這一點(diǎn)和TCP是很不同的。
怎樣定義消息呢?可以認(rèn)為對(duì)方一次性write/send的數(shù)據(jù)為一個(gè)消息,需要明白的是當(dāng)對(duì)方send一條信息的時(shí)候,無論底層怎樣分段分片,TCP協(xié)議層會(huì)把構(gòu)成整條消息的數(shù)據(jù)段排序完成后才呈現(xiàn)在內(nèi)核緩沖區(qū)。
UDP不會(huì)發(fā)生黏包
UDP(user datagram protocol,用戶數(shù)據(jù)報(bào)協(xié)議)是無連接的,面向消息的,提供高效率服務(wù)。
不會(huì)使用塊的合并優(yōu)化算法,, 由于UDP支持的是一對(duì)多的模式,所以接收端的skbuff(套接字緩沖區(qū))采用了鏈?zhǔn)浇Y(jié)構(gòu)來記錄每一個(gè)到達(dá)的UDP包,在每個(gè)UDP包中就有了消息頭(消息來源地址,端口等信息),這樣,對(duì)于接收端來說,就容易進(jìn)行區(qū)分處理了。 即面向消息的通信是有消息保護(hù)邊界的。
對(duì)于空消息:tcp是基于數(shù)據(jù)流的,于是收發(fā)的消息不能為空,這就需要在客戶端和服務(wù)端都添加空消息的處理機(jī)制,防止程序卡住,而udp是基于數(shù)據(jù)報(bào)的,即便是你輸入的是空內(nèi)容(直接回車),也可以被發(fā)送,udp協(xié)議會(huì)幫你封裝上消息頭發(fā)送過去。
不可靠不黏包的udp協(xié)議:udp的recvfrom是阻塞的,一個(gè)recvfrom(x)必須對(duì)唯一一個(gè)sendinto(y),收完了x個(gè)字節(jié)的數(shù)據(jù)就算完成,若是y;x數(shù)據(jù)就丟失,這意味著udp根本不會(huì)粘包,但是會(huì)丟數(shù)據(jù),不可靠。
補(bǔ)充說明
用UDP協(xié)議發(fā)送時(shí),用sendto函數(shù)最大能發(fā)送數(shù)據(jù)的長度為:65535- IP頭(20) – UDP頭(8)=65507字節(jié)。用sendto函數(shù)發(fā)送數(shù)據(jù)時(shí),如果發(fā)送數(shù)據(jù)長度大于該值,則函數(shù)會(huì)返回錯(cuò)誤。(丟棄這個(gè)包,不進(jìn)行發(fā)送)
用TCP協(xié)議發(fā)送時(shí),由于TCP是數(shù)據(jù)流協(xié)議,因此不存在包大小的限制(暫不考慮緩沖區(qū)的大小),這是指在用send函數(shù)時(shí),數(shù)據(jù)長度參數(shù)不受限制。而實(shí)際上,所指定的這段數(shù)據(jù)并不一定會(huì)一次性發(fā)送出去,如果這段數(shù)據(jù)比較長,會(huì)被分段發(fā)送,如果比較短,可能會(huì)等待和下一次數(shù)據(jù)一起發(fā)送。
會(huì)發(fā)生黏包的兩種情況
情況一 發(fā)送方的緩存機(jī)制
發(fā)送端需要等緩沖區(qū)滿才發(fā)送出去,造成粘包(發(fā)送數(shù)據(jù)時(shí)間間隔很短,數(shù)據(jù)了很小,會(huì)合到一起,產(chǎn)生粘包)
服務(wù)器
服務(wù)器from socket import *
ip_port=('127.0.0.1',8080)
tcp_socket_server=socket(AF_INET,SOCK_STREAM)
tcp_socket_server.bind(ip_port)
tcp_socket_server.listen(5)
conn,addr=tcp_socket_server.accept()
data1=conn.recv(10)
data2=conn.recv(10)
print('----->',data1.decode('utf-8'))
print('----->',data2.decode('utf-8'))
conn.close()
客戶端
import socket
BUFSIZE=1024
ip_port=('127.0.0.1',8080)
s=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
res=s.connect_ex(ip_port)
s.send('hello tom'.encode('utf-8'))
總結(jié)
黏包現(xiàn)象只發(fā)生在tcp協(xié)議中:
1.從表面上看,黏包問題主要是因?yàn)榘l(fā)送方和接收方的緩存機(jī)制、tcp協(xié)議面向流通信的特點(diǎn)。
2.實(shí)際上,主要還是因?yàn)榻邮辗讲恢老⒅g的界限,不知道一次性提取多少字節(jié)的數(shù)據(jù)所造成的
解決方案一
問題的根源在于,接收端不知道發(fā)送端將要傳送的字節(jié)流的長度,所以解決粘包的方法就是圍繞,如何讓發(fā)送端在發(fā)送數(shù)據(jù)前,把自己將要發(fā)送的字節(jié)流總大小讓接收端知曉,然后接收端來一個(gè)死循環(huán)接收完所有數(shù)據(jù)
服務(wù)端
import socket,subprocess
ip_port=('127.0.0.1',8080)
s=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
s.bind(ip_port)
s.listen(5)
while True:
conn,addr=s.accept()
print('客戶端',addr)
while True:
msg=conn.recv(1024)
if not msg:break
res=subprocess.Popen(msg.decode('utf-8'),shell=True,\
stdin=subprocess.PIPE,\
stderr=subprocess.PIPE,\
stdout=subprocess.PIPE)
err=res.stderr.read()
if err:
ret=err
else:
ret=res.stdout.read()
data_length=len(ret)
conn.send(str(data_length).encode('utf-8'))
data=conn.recv(1024).decode('utf-8')
if data=='recv_ready':
conn.sendall(ret)
conn.close()
客戶端
import socket,time
s=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
res=s.connect_ex(('127.0.0.1',8080))
while True:
msg=input('>>: ').strip()
if len(msg)==0:continue
if msg=='quit':break
s.send(msg.encode('utf-8'))
length=int(s.recv(1024).decode('utf-8'))
s.send('recv_ready'.encode('utf-8'))
send_size=0
recv_size=0
data=b''
while recv_size < length:
data+=s.recv(1024)
recv_size+=len(data)
print(data.decode('utf-8'))
存在的問題
程序的運(yùn)行速度遠(yuǎn)快于網(wǎng)絡(luò)傳輸速度,所以在發(fā)送一段字節(jié)前,先用send去發(fā)送該字節(jié)流長度,這種方式會(huì)放大網(wǎng)絡(luò)延遲帶來的性能損耗
解決方案進(jìn)階
剛剛的方法,問題在于我們我們在發(fā)送
我們可以借助一個(gè)模塊,這個(gè)模塊可以把要發(fā)送的數(shù)據(jù)長度轉(zhuǎn)換成固定長度的字節(jié)。這樣客戶端每次接收消息之前只要先接受這個(gè)固定長度字節(jié)的內(nèi)容看一看接下來要接收的信息大小,那么最終接受的數(shù)據(jù)只要達(dá)到這個(gè)值就停止,就能剛好不多不少的接收完整的數(shù)據(jù)了。
struct模塊
該模塊可以把一個(gè)類型,如數(shù)字,轉(zhuǎn)成固定長度的bytes
>>> struct.pack('i',1111111111111)
struct.error: 'i' format requires -2147483648 <=number <=2147483647 #這個(gè)是范圍
import json,struct
#假設(shè)通過客戶端上傳1T:1073741824000的文件a.txt
#為避免粘包,必須自定制報(bào)頭
header={'file_size':1073741824000,'file_name':'/a/b/c/d/e/a.txt','md5':'8f6fbf8347faa4924a76856701edb0f3'} #1T數(shù)據(jù),文件路徑和md5值
#為了該報(bào)頭能傳送,需要序列化并且轉(zhuǎn)為bytes
head_bytes=bytes(json.dumps(header),encoding='utf-8') #序列化并轉(zhuǎn)成bytes,用于傳輸
#為了讓客戶端知道報(bào)頭的長度,用struck將報(bào)頭長度這個(gè)數(shù)字轉(zhuǎn)成固定長度:4個(gè)字節(jié)
head_len_bytes=struct.pack('i',len(head_bytes)) #這4個(gè)字節(jié)里只包含了一個(gè)數(shù)字,該數(shù)字是報(bào)頭的長度
#客戶端開始發(fā)送
conn.send(head_len_bytes) #先發(fā)報(bào)頭的長度,4個(gè)bytes
conn.send(head_bytes) #再發(fā)報(bào)頭的字節(jié)格式
conn.sendall(文件內(nèi)容) #然后發(fā)真實(shí)內(nèi)容的字節(jié)格式
#服務(wù)端開始接收
head_len_bytes=s.recv(4) #先收?qǐng)?bào)頭4個(gè)bytes,得到報(bào)頭長度的字節(jié)格式
x=struct.unpack('i',head_len_bytes)[0] #提取報(bào)頭的長度
head_bytes=s.recv(x) #按照?qǐng)?bào)頭長度x,收取報(bào)頭的bytes格式
header=json.loads(json.dumps(header)) #提取報(bào)頭
#最后根據(jù)報(bào)頭的內(nèi)容提取真實(shí)的數(shù)據(jù),比如
real_data_len=s.recv(header['file_size'])
s.recv(real_data_len)
關(guān)于struct的詳細(xì)用法
#_*_coding:utf-8_*_
#http://www.cnblogs.com/coser/archive/2011/12/17/2291160.html
__author__='Linhaifeng'
import struct
import binascii
import ctypes
values1=(1, 'abc'.encode('utf-8'), 2.7)
values2=('defg'.encode('utf-8'),101)
s1=struct.Struct('I3sf')
s2=struct.Struct('4sI')
print(s1.size,s2.size)
prebuffer=ctypes.create_string_buffer(s1.size+s2.size)
print('Before : ',binascii.hexlify(prebuffer))
# t=binascii.hexlify('asdfaf'.encode('utf-8'))
# print(t)
s1.pack_into(prebuffer,0,*values1)
s2.pack_into(prebuffer,s1.size,*values2)
print('After pack',binascii.hexlify(prebuffer))
print(s1.unpack_from(prebuffer,0))
print(s2.unpack_from(prebuffer,s1.size))
s3=struct.Struct('ii')
s3.pack_into(prebuffer,0,123,123)
print('After pack',binascii.hexlify(prebuffer))
print(s3.unpack_from(prebuffer,0))
使用struct解決黏包
借助struct模塊,我們知道長度數(shù)字可以被轉(zhuǎn)換成一個(gè)標(biāo)準(zhǔn)大小的4字節(jié)數(shù)字。因此可以利用這個(gè)特點(diǎn)來預(yù)先發(fā)送數(shù)據(jù)長度。
服務(wù)端自定制報(bào)頭
import socket,struct,json
import subprocess
phone=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
phone.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1) #就是它,在bind前加
phone.bind(('127.0.0.1',8080))
phone.listen(5)
while True:
conn,addr=phone.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()
conn.send(struct.pack('i',len(back_msg))) #先發(fā)back_msg的長度
conn.sendall(back_msg) #在發(fā)真實(shí)的內(nèi)容
conn.close()
客戶端
from socket import *
import struct,json
ip_port=('127.0.0.1',8080)
client=socket(AF_INET,SOCK_STREAM)
client.connect(ip_port)
while True:
cmd=input('>>: ')
if not cmd:continue
client.send(bytes(cmd,encoding='utf-8'))
head=client.recv(4)
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('utf-8'))
#print(recv_data.decode('gbk')) #windows默認(rèn)gbk編碼
#網(wǎng)絡(luò)通信##網(wǎng)絡(luò)##python##Python基礎(chǔ)##科技新星創(chuàng)作營#
焊機(jī)在點(diǎn)焊鋁和鋁合金時(shí),或在點(diǎn)焊涂覆有鋅、鎘、鉛等低熔點(diǎn)金屬的鋼板時(shí),電極的粘附現(xiàn)象比變形還嚴(yán)重,例如,用鋯銅或鉻銅電極點(diǎn)焊鋁合金板,不到50點(diǎn)焊件表面已發(fā)黑,電極出現(xiàn)粘附,但是從允許電極工作面直徑變形量增加20%計(jì)算。
那么至少可以焊接數(shù)千個(gè)焊點(diǎn),用鉻銅合金電極點(diǎn)焊1.3mmLF3鋁合金時(shí)出現(xiàn)粘附的焊點(diǎn)數(shù),電極出現(xiàn)粘附現(xiàn)象雖有先后,但是都在焊接幾十個(gè)點(diǎn)的范圍內(nèi)發(fā)生粘附,在焊接鍍鋅鋼板時(shí)電極的粘附和變形多同時(shí)發(fā)生。
焊接鍍鋅鋼板時(shí)電極出現(xiàn)粘附的原因,首先是因電極接觸到鍍鋅層熔點(diǎn)低、硬度低。電導(dǎo)率高,在電極力的作用下,接觸面積迅速擴(kuò)大,接觸電阻降低,測試數(shù)據(jù)表明,鍍鋅鋼板的接觸電阻只有普通低碳鋼板的1/10-1/20,其次,焊接電流一經(jīng)過焊件,低熔點(diǎn)的鋅層最先熔化,并立即填滿了電極的接觸部位和板隙,使導(dǎo)電面積擴(kuò)大,電流密度減低。
因此,焊接鍍鋅鋼板一般都要用大電流強(qiáng)冷卻的焊接規(guī)范,和低碳鋼相比,焊接電流要增加30%-50%,電極力增加10%-30%,焊接時(shí)間不變或減少20%,有時(shí)也可在電流和電極力不變的條件下,適當(dāng)延長焊接時(shí)間,由于焊接電流與電極力的增加,或是焊接時(shí)間增加,都會(huì)使電極頭部的發(fā)熱和變形更加嚴(yán)重。
在電極工作面上強(qiáng)烈粘附上鋅層,粘附在電極上的鋅在高溫下會(huì)向銅擴(kuò)散,生成銅合金,使電極表面的導(dǎo)電性能變差,硬度降低,變形增加,而增大了的電極表面又會(huì)與更多的鋅層接觸,形成惡性循環(huán),因而必須采用強(qiáng)制冷卻,增加電極的修銼頻率。
電極的粘附現(xiàn)象與電極的材料和頭部的形狀有關(guān),在相同的焊接電流條件下,彌散強(qiáng)化銅出現(xiàn)粘附的焊接時(shí)間最長,鉻銅出現(xiàn)粘附的焊接時(shí)間最短,而純銅和鉻銅是介于上述兩者之間,有資料表明,焊接鋁合金時(shí),用銀銅和鎂硼銅電極焊接500點(diǎn),焊件表面才會(huì)發(fā)黑。
鉻銅電極焊接300點(diǎn),而用鋯銅和鉻鋯銅電極只能焊50點(diǎn),這個(gè)現(xiàn)象反應(yīng)了不同電導(dǎo)率的電極銅合金具有不同焊接效果,電導(dǎo)率高的電極與焊件間的接觸電阻小,焊接發(fā)熱小。
反之,電導(dǎo)率低的電極,與焊件的接觸電阻大,焊接發(fā)熱嚴(yán)重,此外,電極合金的晶粒組織大小也會(huì)對(duì)粘附產(chǎn)生影響,在焊接過程中,由于高溫和焊接力的作用,電極合金的組織會(huì)發(fā)生變化,此時(shí)粗晶粒組織要比細(xì)晶粒組織的嚴(yán)重,實(shí)驗(yàn)證明,焊接鋁合金時(shí)電極表面溫度只有673-723K,此時(shí)細(xì)晶粒組織的電極合金更能穩(wěn)定的抵抗粘附和變形。
電極頭部的形狀對(duì)焊接粘附的影響,在點(diǎn)焊0.9mm的單面鍍鋅鋼板時(shí)間,用球面帽式電極焊3000點(diǎn)左右,已不能形成熔核,而用軸頸帽式電極,點(diǎn)焊8000點(diǎn)熔核直徑仍維持不變。
文章來源:https://www.szagera.com/Article/dhjhjwsmhc.html
近遇見一個(gè)吸頂效果的需求,要想實(shí)現(xiàn)這個(gè)效果,使用UI庫也可以,但如果使用position的sticky粘黏屬性可以輕易實(shí)現(xiàn),但在實(shí)際使用中需要注意一些細(xì)節(jié),于是寫了個(gè)簡單demo,效果如下
<script src="https://lf6-cdn-tos.bytescm.com/obj/cdn-static-resource/tt_player/tt.player.js?v=20160723"></script>
代碼如下
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>sticky</title>
<style>
html,body{
height: 100%;
}
.wrap{
overflow: auto;
height: 100%;
}
.div1{
height: 30px;
background: red;
text-align: center;
line-height: 30px;
color: aliceblue;
position: sticky;
top: 0;
}
.div2{
height: 500px;
background: #333;
margin-bottom: 20px;
}
</style>
</head>
<body>
<div class="wrap">
<div class="div1">A</div>
<div class="div2"></div>
<div class="div1">B</div>
<div class="div2"></div>
<div class="div1">C</div>
<div class="div2"></div>
<div class="div1">D</div>
<div class="div2"></div>
</div>
</body>
</html>
ps:
*請(qǐng)認(rèn)真填寫需求信息,我們會(huì)在24小時(shí)內(nèi)與您取得聯(lián)系。