日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

网络编程---黏包

發(fā)布時(shí)間:2023/12/1 编程问答 37 豆豆
生活随笔 收集整理的這篇文章主要介紹了 网络编程---黏包 小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

基于UDP協(xié)議的socket

udp的server 不需要進(jìn)行監(jiān)聽也不需要建立連接,在啟動(dòng)服務(wù)之后只能被動(dòng)的等待客戶端發(fā)送消息過來。

客戶端發(fā)送消息的同時(shí)還會(huì) 自帶地址信息,消息回復(fù)的時(shí)候 不僅需要發(fā)送消息 還需把對方的地址填上。

udp的client 不需要connect 因?yàn)閁DP協(xié)議是不需要連接的,直接了解到對方的ip和端口信息就發(fā)送數(shù)據(jù)就行了。

sendto和recvfrom的使用方法完全和server端一樣。

udp是無鏈接的,先啟動(dòng)哪一端都不會(huì)報(bào)錯(cuò)

import socket udp_sk = socket.socket(type=socket.SOCK_DGRAM) #創(chuàng)建一個(gè)服務(wù)器的套接字 udp_sk.bind(('127.0.0.1',9000)) #綁定服務(wù)器套接字 msg,addr = udp_sk.recvfrom(1024) print(msg) udp_sk.sendto(b'hi',addr) # 對話(接收與發(fā)送) udp_sk.close() # 關(guān)閉服務(wù)器套接字 server端 import socket ip_port=('127.0.0.1',9000) udp_sk=socket.socket(type=socket.SOCK_DGRAM) udp_sk.sendto(b'hello',ip_port) back_msg,addr=udp_sk.recvfrom(1024) print(back_msg.decode('utf-8'),addr) client端

用udp協(xié)議的socket寫一個(gè)簡易的qq聊天

import socket ip_port=('127.0.0.1',8081) udp_server_sock=socket.socket(socket.AF_INET,socket.SOCK_DGRAM) udp_server_sock.bind(ip_port)while True:qq_msg,addr=udp_server_sock.recvfrom(1024)print('來自[%s:%s]的一條消息:\033[1;44m%s\033[0m' %(addr[0],addr[1],qq_msg.decode('utf-8')))back_msg=input('回復(fù)消息: ').strip()udp_server_sock.sendto(back_msg.encode('utf-8'),addr) server端 import socket BUFSIZE=1024 udp_client_socket=socket.socket(socket.AF_INET,socket.SOCK_DGRAM)qq_name_dic={'sole':('127.0.0.1',8081),'喜羊羊':('127.0.0.1',8081),'灰太狼':('127.0.0.1',8081),'xxx':('127.0.0.1',8081), }while True:qq_name=input('請選擇聊天對象: ').strip()while True:msg=input('請輸入消息,回車發(fā)送,輸入q結(jié)束和他的聊天: ').strip()if msg == 'q':breakif not msg or not qq_name or qq_name not in qq_name_dic:continueudp_client_socket.sendto(msg.encode('utf-8'),qq_name_dic[qq_name])back_msg,addr=udp_client_socket.recvfrom(BUFSIZE)print('來自[%s:%s]的一條消息:\033[1;44m%s\033[0m' %(addr[0],addr[1],back_msg.decode('utf-8')))udp_client_socket.close() client端

時(shí)間服務(wù)器

import time import socket sk = socket.socket(type=socket.SOCK_DGRAM) sk.bind(('127.0.0.1',8090)) while True:strf,addr = sk.recvfrom(1024)strf = strf.decode('utf-8')res = time.strftime(strf).encode('utf-8')sk.sendto(res,addr) sk.close() import socket sk = socket.socket(type=socket.SOCK_DGRAM) addr = ('127.0.0.1',8090) info = input('>>>').encode('utf-8') sk.sendto(info,addr) ret,addr = sk.recvfrom(1024) print(ret.decode('utf-8'))sk.close()

socket參數(shù)的詳解

創(chuàng)建socket對象的參數(shù)說明:

?

family地址系列應(yīng)為AF_INET(默認(rèn)值),AF_INET6,AF_UNIX,AF_CAN或AF_RDS。
(AF_UNIX 域?qū)嶋H上是使用本地 socket 文件來通信)
type套接字類型應(yīng)為SOCK_STREAM(默認(rèn)值),SOCK_DGRAM,SOCK_RAW或其他SOCK_常量之一。
SOCK_STREAM?是基于TCP的,有保障的(即能保證數(shù)據(jù)正確傳送到對方)面向連接的SOCKET,多用于資料傳送。
SOCK_DGRAM?是基于UDP的,無保障的面向消息的socket,多用于在網(wǎng)絡(luò)上發(fā)廣播信息。
proto協(xié)議號(hào)通常為零,可以省略,或者在地址族為AF_CAN的情況下,協(xié)議應(yīng)為CAN_RAW或CAN_BCM之一。
fileno如果指定了fileno,則其他參數(shù)將被忽略,導(dǎo)致帶有指定文件描述符的套接字返回。
與socket.fromfd()不同,fileno將返回相同的套接字,而不是重復(fù)的。
這可能有助于使用socket.close()關(guān)閉一個(gè)獨(dú)立的插座。

黏包

黏包現(xiàn)象

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協(xié)議實(shí)現(xiàn)的黏包

import socket sk = socket.socket() sk.bind(('127.0.0.1',8090)) sk.listen()conn,addr = sk.accept() while True:cmd = input('>>>')conn.send(cmd.encode('utf-8'))ret = conn.recv(1024).decode('utf-8')print(ret)conn.close() sk.close() import socket import subprocess sk = socket.socket() sk.connect(('127.0.0.1',8090)) while True:cmd = sk.recv(1024).decode('gbk')ret = subprocess.Popen(cmd,shell=True,stdout=subprocess.PIPE,stderr=subprocess.PIPE)std_out = 'stdout :'+(ret.stdout.read()).decode('gbk')std_err = 'stderr :'+(ret.stderr.read()).decode('gbk')print(std_out)print(std_err)sk.send(std_out.encode('utf-8'))sk.send(std_err.encode('utf-8')) sk.close()

注意:只有TCP有黏包現(xiàn)象但不丟包,UDP永遠(yuǎn)不會(huì)黏包只會(huì)丟包

基于udp協(xié)議實(shí)現(xiàn)的黏包

import socket sk = socket.socket(type=socket.SOCK_DGRAM) sk.bind(('127.0.0.1',8090)) msg,addr = sk.recvfrom(10240) while True:cmd = input('>>>')if cmd == 'q':breaksk.sendto(cmd.encode('utf-8'),addr)msg,addr = sk.recvfrom(10240)print(msg.decode('utf-8'))sk.close() import socket import subprocess sk = socket.socket(type=socket.SOCK_DGRAM) addr = ('127.0.0.1',8090) sk.sendto('吃了么'.encode('utf-8'),addr) while True:cmd,addr = sk.recvfrom(10000)ret = subprocess.Popen(cmd.decode('gbk'),shell=True,stdout=subprocess.PIPE,stderr=subprocess.PIPE)std_out = 'stdout :'+(ret.stdout.read()).decode('gbk')std_err = 'stderr :'+(ret.stderr.read()).decode('gbk')print(std_out)print(std_err)sk.sendto(std_out.encode('utf-8'),addr)sk.sendto(std_err.encode('utf-8'),addr) sk.close()

為什么會(huì)出現(xià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é)對應(yīng)用程序是不可見的,因此TCP協(xié)議是面向流的協(xié)議,這也是容易出現(xiàn)粘包問題的原因。 而UDP是面向消息的協(xié)議,每個(gè)UDP段都是一條消息,應(yīng)用程序必須以消息為單位提取數(shù)據(jù),不能一次提取任意字節(jié)的數(shù)據(jù),這一點(diǎn)和TCP是很不同的。 怎樣定義消息呢?可以認(rèn)為對方一次性write/send的數(shù)據(jù)為一個(gè)消息,需要明白的是當(dāng)對方send一條信息的時(shí)候,無論底層怎樣分段分片,TCP協(xié)議層會(huì)把構(gòu)成整條消息的數(shù)據(jù)段排序完成后才呈現(xiàn)在內(nèi)核緩沖區(qū)。

所謂粘包問題——主要還是因?yàn)榻邮辗讲恢老⒅g的界限,不知道一次性提取多少字節(jié)的數(shù)據(jù)所造成的。

此外,發(fā)送方引起的粘包是由TCP協(xié)議本身造成的,TCP為提高傳輸效率,發(fā)送方往往要收集到足夠多的數(shù)據(jù)后才發(fā)送一個(gè)TCP段。若連續(xù)幾次需要send的數(shù)據(jù)都很少,通常TCP會(huì)根據(jù)優(yōu)化算法把這些數(shù)據(jù)合成一個(gè)TCP段后一次發(fā)送出去,這樣接收方就收到了粘包數(shù)據(jù)。

TCP(transport control protocol,傳輸控制協(xié)議)是面向連接的,面向流的,提供高可靠性服務(wù)。收發(fā)兩端(客戶端和服務(wù)器端)都要有一一成對的socket,因此,發(fā)送端為了將多個(gè)發(fā)往接收端的包,更有效的發(fā)到對方,使用了優(yōu)化方法(Nagle算法),將多次間隔較小且數(shù)據(jù)量小的數(shù)據(jù),合并成一個(gè)大的數(shù)據(jù)塊,然后進(jìn)行封包。這樣,接收端,就難于分辨出來了,必須提供科學(xué)的拆包機(jī)制。 即面向流的通信是無消息保護(hù)邊界的。 UDP(user datagram protocol,用戶數(shù)據(jù)報(bào)協(xié)議)是無連接的,面向消息的,提供高效率服務(wù)。不會(huì)使用塊的合并優(yōu)化算法,, 由于UDP支持的是一對多的模式,所以接收端的skbuff(套接字緩沖區(qū))采用了鏈?zhǔn)浇Y(jié)構(gòu)來記錄每一個(gè)到達(dá)的UDP包,在每個(gè)UDP包中就有了消息頭(消息來源地址,端口等信息),這樣,對于接收端來說,就容易進(jìn)行區(qū)分處理了。 即面向消息的通信是有消息保護(hù)邊界的。 對于空消息: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)必須對唯一一個(gè)sendinto(y),收完了x個(gè)字節(jié)的數(shù)據(jù)就算完成,若是y>x數(shù)據(jù)就丟失,這意味著udp根本不會(huì)粘包,但是會(huì)丟數(shù)據(jù),不可靠。 可靠黏包的tcp協(xié)議:tcp的協(xié)議數(shù)據(jù)不會(huì)丟,沒有收完包,下次接收,會(huì)繼續(xù)上次繼續(xù)接收,己端總是在收到ack時(shí)才會(huì)清除緩沖區(qū)內(nèi)容。數(shù)據(jù)是可靠的,但是會(huì)粘包。 recv里指定的1024意思是從緩存里一次拿出1024個(gè)字節(jié)的數(shù)據(jù) send的字節(jié)流是先放入己端緩存,然后由協(xié)議控制將緩存內(nèi)容發(fā)往對端,如果待發(fā)送的字節(jié)流大小大于緩存剩余空間,那么數(shù)據(jù)丟失,用sendall就會(huì)循環(huán)調(diào)用send,數(shù)據(jù)不會(huì)丟失

會(huì)發(fā)生黏包的兩種情況

情況一 發(fā)送方的緩存機(jī)制

發(fā)送端需要等緩沖區(qū)滿才發(fā)送出去,造成粘包(發(fā)送數(shù)據(jù)時(shí)間間隔很短,數(shù)據(jù)了很小,會(huì)合到一起,產(chǎn)生粘包)

情況二 接收方的緩存機(jī)制

接收方不及時(shí)接收緩沖區(qū)的包,造成多個(gè)包接收(客戶端發(fā)送了一段數(shù)據(jù),服務(wù)端只收了一小部分,服務(wù)端下次再收的時(shí)候還是從緩沖區(qū)拿上次遺留的數(shù)據(jù),產(chǎn)生粘包)?

轉(zhuǎn)載于:https://www.cnblogs.com/soleZ/p/8359858.html

總結(jié)

以上是生活随笔為你收集整理的网络编程---黏包的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。