天天看點

Python 基于TCP協定通信的簡單套接字程式設計

原部落格:https://blog.csdn.net/sinat_29214327/article/details/80574955

前言:學習套接字程式設計需要掌握的網絡基礎知識

    包含(TCP/IP 5層模型,TCP協定建立的三次握手與4次斷開,網絡通信過程等)

    http://www.cnblogs.com/linhaifeng/articles/5937962.html

一、套接字(socket)

    1.1、什麼是套接字(socket)

    位于應用層與傳輸層之間,用來把傳輸層以下的協定封裝好,并提供一個簡單的接口,那麼在編寫基于網絡架構的C/S軟體的話,就可以考慮使用套接字及按照套接字的标準去編寫。

    Socket是應用層與TCP/IP協定族通信的中間軟體抽象層,它是一組接口。在設計模式中,Socket其實就是一個門面模式,它把複雜的TCP/IP協定族隐藏在Socket接口後面,對使用者來說,一組簡單的接口就是全部,讓Socket去組織資料,以符合指定的協定。

    是以,我們無需深入了解tcp/udp協定,socket已經為我們封裝好了,我們隻需要遵循socket的規定去程式設計,寫出的程式自然就是遵循tcp/udp标準的。

    1.2、套接字工作流程

    一個生活中的場景。你要打電話給一個朋友,先撥号,朋友聽到電話鈴聲後提起電話,這時你和你的朋友就建立起了連接配接,就可以講話了。等交流結束,挂斷電話結束此次交談。 生活中的場景就解釋了這工作原理。

    先從伺服器端說起。伺服器端先初始化Socket,然後與端口綁定(bind),對端口進行監聽(listen),調用accept阻塞,等待用戶端連接配接。在這時如果有個用戶端初始化一個Socket,然後連接配接伺服器(connect),如果連接配接成功,這時用戶端與伺服器端的連接配接就建立了。用戶端發送資料請求,伺服器端接收請求并處理請求,然後把回應資料發送給用戶端,用戶端讀取資料,最後關閉連接配接,一次互動結束

    1.3、用戶端/伺服器架構之 C/S,B/S 模式

        client --------server

        browser -----server

        其中:server端應該注意的問題:

            1、服務端應該保持穩定

            2、服務端要支援并發

            3、服務端要綁定唯一的位址 (ip + port)

二、套接字程式設計(基于TCP協定通信的C/S模式)   

    TCP是基于連結的,必須先啟動服務端,然後再啟動用戶端去連結服務端

    2.1、TCP服務端套接字函數

    server.bind()     綁定(主機,端口号)到套接字

    server.listen()    開始TCP監聽

    server.accept()  被動接受TCP客戶的連接配接,(阻塞式)等待連接配接的到來

import socket

# 1、買手機 #AF:address family INET:internet 合在一起稱為"基于網絡通信的套接字"

server=socket.socket(socket.AF_INET,socket.SOCK_STREAM) #SOCK_STREAM==流式協定:指的就是TCP協定

server.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1) #重用服務端的IP和端口 (如果服務端的IP及端口在短時間内釋放掉,那麼就把之前的IP及端口重用上,就可以解決端口被占用問題)

# 2、插卡

server.bind(('127.0.0.1',8080)) #服務端的IP位址加端口

# 3、開機

server.listen(5) #backlog:半連接配接池(用戶端發送一次請求(syn請求)到服務端(服務端會接收syn并回複一個ack及syn給用戶端),那麼在用戶端回ack之前,用戶端已服務端都是出于半連接配接狀态)

#服務端會接收大量用戶端的請求,如此多的請求都會堆積在伺服器的記憶體裡,如果不做處理的話,服務端的記憶體會被撐爆,為了保護服務端,不允許大量的用戶端請求到達服務端,那麼就要對用戶端的syn請求加以限制。此處有了半連接配接池的概念

#什麼是半連接配接池?

'''

在服務端會有一個隊列,如果隊列的大小為5,代表服務端最高隻能接受5個請求數(這裡并不是連接配接數)

當用戶端的連接配接請求到服務端時,請求會到服務端的半連接配接池裡面(最大請求數為5),服務端直接從半連接配接池裡面取出連接配接請求。(出去一個請求,那麼辦連接配接池就會在進去一個連接配接請求)

半連接配接池的大小根據服務端的伺服器性能來調整(半連接配接池應該盡量大,但不能無限大)

在linux中,核心參數調優:tcp_backlog 指的就是半連接配接池得大小

# 4、等待電話連接配接

print('等待連接配接。。。')

while True: #等待用戶端的SYN請求(也就是用戶端的coon操作 ·1)

coon,client_addr=server.accept() #accept就是從半連接配接池裡面取出連接配接請求數,accept對應用戶端的connect

# server.accept() #拿到的就是tcp的連接配接對象,用戶端的ip和端口

# coon指的是:接受某一個唯一位址的用戶端的連接配接請求

# print(coon)

# <socket.socket fd=536, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('127.0.0.1', 8080), raddr=('127.0.0.1', 54165)>

#其中:laddr=('127.0.0.1', 8080) #表示的是服務端的IP和端口

#其中:raddr=('127.0.0.1', 54165) #表示的是用戶端的IP和端口

# print(client_addr)

# ('127.0.0.1', 54165) # 用戶端的IP加端口

# 5、接收消息

while True: #通信循環:服務端與用戶端的連接配接需要互動式的操作

try: #用戶端挂掉,服務端不受任何影響

data=coon.recv(1024) # 1024表示1024位元組,用戶端發送過來的位元組,服務端最多隻能收1024個

if not data:break

# coon指的是:與某個唯一位址的用戶端連接配接請求建立後,傳回消息給這個用戶端

print('用戶端資料:%s' %data)

# 6、發消息

coon.send(data.upper()) #接受到用戶端消息後,發送消息給用戶端

except ConnectionResetError:

break

# 7、挂電話

coon.close() #關閉連接配接狀态 (回收的是作業系統的資源)

# 8、關機

server.close() #關閉服務端 (回收的是作業系統的資源)

# 關閉python程式時,理論上講會回收掉python程式和作業系統的資源。但是作業系統對應的端口号回不回收取決于作業系統。而不取決于python應用程式

# 在Linux 可以通過調整核心參數設定。

    1、什麼是半連接配接池(backlog)

        1.1.半連接配接池的概念:

        1).半連接配接池(用戶端發送一次請求(syn請求)到服務端(服務端會接收syn并回複一個ack及syn給用戶端),那麼在用戶端回ack之前,用戶端已服務端都是出于半連接配接狀态)

        2).服務端會接收大量用戶端的請求,如此多的請求都會堆積在伺服器的記憶體裡,如果不做處理的話,服務端的記憶體會被撐爆,為了保護服務端,不允許大量的用戶端請求到達服務端,那麼就要對用戶端的syn請求加以限制。此處有了半連接配接池的概念

        1.2.什麼是半連接配接池

        在服務端會有一個隊列,如果隊列的大小為5,代表服務端最高隻能接受5個請求數(這裡并不是連接配接數)

        當用戶端的連接配接請求到服務端時,請求會到服務端的半連接配接池裡面(最大請求數為5),服務端直接從半連接配接池裡面取出連接配接請求。(出去一個請求,那麼辦連接配接池就會在進去一個連接配接請求)

    半連接配接池的大小根據服務端的伺服器性能來調整(半連接配接池應該盡量大,但不能無限大)

        在linux中,核心參數調優:tcp_backlog 指的就是半連接配接池得大小

    2、服務端消息發送與接收

        1).coon,client_addr=server.accept()    

            accept就是從半連接配接池裡面取出連接配接請求數,accept對應用戶端的connect

        2).server.accept() 

            拿到的就是tcp的連接配接對象,用戶端的ip和端口

            coon指的是:接受某一個唯一位址的用戶端的連接配接請求

        3).print(coon)

        <socket.socket fd=536, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('127.0.0.1', 8080), raddr=('127.0.0.1', 54165)>

        其中:laddr=('127.0.0.1', 8080)      表示的是服務端的IP和端口

        其中:raddr=('127.0.0.1', 54165)   表示的是用戶端的IP和端口

        4).print(client_addr)

        ('127.0.0.1', 54165)     用戶端的IP加端口

    3、資源回收

        關閉python程式時,理論上講會回收掉python程式和作業系統的資源。但是作業系統對應的端口号回不回收取決于作業系統。而不取決于python應用程式。

        在Linux 可以通過調整核心參數設定。

    2.2、TCP用戶端套接字函數

    client.connect()        主動初始化TCP伺服器連接配接

    client.connect_ex()  connect()函數的擴充版本,出錯時傳回出錯碼,而不是抛出異常

# 1、買手機

client=socket.socket(socket.AF_INET,socket.SOCK_STREAM) #SOCK_STREAM==流式協定:指的就是TCP協定

client.connect(('127.0.0.1',8080)) #這裡的IP和端口都是服務端的

while True:

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

if not msg:continue

# 3、發消息

print('send>>:')

client.send(msg.encode('utf-8')) #在網絡中發送資訊需要通過位元組(二進制的方式發送),是以需要encode('utf-8')制定字元集的方式發送

# client.send 這裡的發送指的是:應用程式把資料傳給作業系統,由作業系統(經過層層封裝)把資料發給服務端,而不是應用程式本身直接發送資料到服務端。

# 4、收消息

print('recv>>:')

data=client.recv(1024) #接受服務端的消息 #recv裡的1024指的是從作業系統的緩存裡一次拿出1024個位元組的資料

# client.recv 這裡的接收指的是:應用程式(并不是從服務端直接接收資料)而是從自己的作業系統的記憶體池裡面擷取從服務端發送過來的資料。

print(data.decode('utf-8')) #這裡接受的是普通的字元集是以指定字元集'utf-8',如果接收的是系統指令那麼就要使用(windows:'gbk',linux:'utf-8')

# 5、挂電話

client.close() #關閉用戶端

    1、TCP通信流程

        1.1、client.send(msg.encode('utf-8'))   

        1).在網絡中發送資訊需要通過位元組(二進制的方式發送),是以需要encode('utf-8')指定字元集的方式發送。

        2).應用程式隻負責把資料傳給作業系統,不負責資料是否能夠到達服務端,由作業系統(經過層層封裝)把資料發給服務端,而不是應用程式本身直接發送資料到服務端。

        1.2、data=client.recv(1024)

        1).應用程式拿到的資料(并不是從服務端直接接收資料)而是從自己的作業系統的記憶體池裡面擷取,服務端機器的作業系統把資料發送給用戶端的機器,用戶端機器收到資料後,把資料放在記憶體裡面,然後用戶端應用程式從本身機器的記憶體裡面拿到從服務端機器發送過來的資料。

        2).print(data.decode('utf-8')),顯示輸出内容需要使用decode('utf-8')解碼。

    三、粘包現象               #隻有TCP有粘包現象,UDP永遠不會粘包

        3.1、為什麼會發生粘包現象

        1.首先了解socket收發消息的原理

        1).服務端->用戶端資料發送流程:服務端的應用程式把資料包發給作業系統,緩存到機器的記憶體裡面,然後由作業系統(經過層層封裝)把資料發給用戶端的機器,用戶端機器收到資料包後存到機器的緩存裡面,然後由用戶端的應用程式從機器的記憶體裡面擷取資料。

        2.粘包的原因

        針對TCP:服務端可以是1K,1K地發送資料,而接收端的應用程式可以2K,2K地提走資料,當然也有可能一次提走3K或6K資料,或者一次隻提走幾個位元組的資料,也就是說,應用程式所看到的資料是一個整體,或說是一個流(stream),一條消息有多少位元組對應用程式是不可見的,是以TCP協定是面向流的協定,這也是容易出現粘包問題的原因。而UDP是面向消息的協定,每個UDP段都是一條消息,應用程式必須以消息為機關提取資料,不能一次提取任意位元組的資料,這一點和TCP是很不同的。怎樣定義消息呢?可以認為對方一次性write/send的資料為一個消息,需要明白的是當對方send一條資訊的時候,無論底層怎樣分段分片,TCP協定層會把構成整條消息的資料段排序完成後才呈現在核心緩沖區。

        基于tcp的套接字用戶端往服務端上傳檔案,發送時檔案内容是按照一段一段的位元組流發送的,在接收方看了,根本不知道該檔案的位元組流從何處開始,在何處結束

        所謂粘包問題主要還是因為接收方不知道消息之間的界限,不知道一次性提取多少位元組的資料所造成的。此外,發送方引起的粘包是由TCP協定本身造成的,TCP為提高傳輸效率,發送方往往要收集到足夠多的資料後才發送一個TCP段。若連續幾次需要send的資料都很少,通常TCP會根據優化算法把這些資料合成一個TCP段後一次發送出去,這樣接收方就收到了粘包資料。

        通俗了解:服務端應用程式(等機器記憶體緩沖區滿後)然後一次性發送資料包(發送資料時間間隔很短,資料流很小,會合到一起,産生粘包)到達用戶端伺服器,而用戶端應用程式在接收資料包時(用戶端不及時接收緩沖區的包,造成多個包接收),服務端發送了一段資料,用戶端隻收了一小部分,用戶端下次再收的時候還是從緩沖區拿上次遺留的資料,産生粘包。

        3.TCP 協定  (又稱為流式協定)

    TCP(transport control protocol,傳輸控制協定)是面向連接配接的,面向流的,提供高可靠性服務(可靠協定)。收發兩端(用戶端和伺服器端)都要有一一成對的socket,是以,發送端為了将多個發往接收端的包,更有效的發到對方,使用了優化方法(Nagle算法:将多次間隔較小且資料量小的資料合并成一個大的資料塊,然後進行封包)。這樣接收端就難于分辨出來了,必須提供科學的拆包機制。 即面向流的通信是無消息保護邊界的。

    TCP是基于資料流的,于是收發的消息不能為空,這就需要在用戶端和服務端都添加空消息的處理機制,防止程式卡住,而udp是基于資料報的,即便是你輸入的是空内容(直接回車),那也不是空消息,udp協定會幫你封裝上消息頭。

    TCP的協定資料不會丢,沒有收完包,下次接收,會繼續上次繼續接收,己端總是在收到ack時才會清除緩沖區内容。資料是可靠的,但是會粘包。

    TCP協定的資料并不是 "一發(資料發送)" 對應 "一收(資料接收)",每次發送都是由作業系統決定的,作業系統可能把多個資料合并成一個包發送。

        4.UDP 協定

    UDP(user datagram protocol,使用者資料報協定)是無連接配接的,面向消息的,提供高效率服務(不可靠協定)。不會使用塊的合并優化算法,, 由于UDP支援的是一對多的模式,是以接收端的skbuff(套接字緩沖區)采用了鍊式結構來記錄每一個到達的UDP包,在每個UDP包中就有了消息頭(消息來源位址,端口等資訊),這樣,對于接收端來說,就容易進行區分處理了。 即面向消息的通信是有消息保護邊界的。

    UDP協定在傳輸層通過本身自帶的報頭屬性,以及一發(發送資料包)一收(接收資料包)的機制解決了資料粘包的問題。UDP協定一般不用來傳檔案,通常用來做與查詢相關的資料包的發送,UDP協定穩定有效的資料包傳輸量最大為512位元組(協定本身的原因造成)。

    UDP的recvfrom是阻塞的,一個recvfrom(x)必須對唯一一個sendinto(y),收完了x個位元組的資料就算完成,若是y>x資料就丢失,這意味着UDP根本不會粘包,但是會丢資料,不可靠。

    UDP協定一般用在:DNS查詢,NTP時間伺服器

        5.TCP/UDP 協定的可靠性

    TCP協定在資料傳輸時,發送端先把資料發送到自己的緩存中,然後協定控制将緩存中的資料發往對端,對端傳回一個ack=1,發送端則清理緩存中的資料,對端傳回ack=0,則重新發送資料,是以tcp是可靠的,而udp發送資料,對端是不會傳回确認資訊的,是以不可靠

        6.send(位元組流)和recv(1024)及sendall

        recv裡指定的1024意思是從緩存裡一次拿出1024個位元組的資料。

        send的位元組流是先放入己端緩存,然後由協定控制将緩存内容發往對端,如果待發送的位元組流大小大于緩存剩餘空間,那麼資料丢失,用sendall就會循環調用send,資料不會丢失。

        3.2、解決粘包現象

        方法:為位元組流加上自定義固定長度報頭,報頭中包含位元組流長度,然後一次send到對端,對端在接收時,先從緩存中取出定長的報頭,然後再取真實資料(直到收幹淨為止)。

        通俗解釋:給位元組流加上自定義固定長度報頭:用戶端在接收時,先去讀報頭的長度,進而拿到資料包的長度。就相當于手動給資料包劃分成一段一段的,用戶端每次都會接收完一段在接受另外一段。

        通過在應用層通過封裝報頭的形式來解決粘包問題,但是并沒有改變TCP協定(流式協定)發送資料包的屬性。

        3.2.1、解決粘包問題需要先了解一個struct子產品的用法:

        該子產品可以把一個類型,如數字,轉成固定長度的bytes

    使用例子一:

import struct

obj=struct.pack('i',1231231) #i表示:int 整型,1231231表示:整型的大小為 1231231

print(obj,len(obj))

>>:b'\x7f\xc9\x12\x00' 4 #struct.pack得到的是一個二進制形式的資料,長度為4

res=struct.unpack('i',obj)[0] #使用struct.unpack解包得到整型i的值為1231231

print(res)

>>:1231231

    使用例子二:(在解決粘包問題有用到這種方法)

import json

header_dic={ #定義字典的格式與内容

'filenema': 'a.txt',

'total_size': 123123123123123123123123123123123123123123123123123,

'md5':'sssxxxadwc123asd123',

}

header_json=json.dumps(header_dic) #由字典序列化為json格式

print(header_json) #得到序列化内容如下

# >>:{"filenema": "a.txt", "total_size": 123123123123123123123123123123123123123123123123123, "md5": "sssxxxadwc123asd123"}

header_bytes=header_json.encode('utf-8') #在由json格式轉化為bytes格式

print(header_bytes) #得到bytes格式内容如下

# >>:b'{"filenema": "a.txt", "total_size": 123123123123123123123123123123123123123123123123123, "md5": "sssxxxadwc123asd123"}'

print(len(header_bytes)) #bytes格式的長度

# >>:118

res=struct.pack('i',len(header_bytes)) #使用struct.pack把'i(int)'及長度len(header_bytes)轉換成固定長度的bytes

print(res,len(res)) #bytes的值為:b'v\x00\x00\x00'

# >>:b'v\x00\x00\x00' 4 #bytes的長度為:4

obj=struct.unpack('i',res)[0] #使用struct.unpack解包得到bytes格式的長度(得到的值是一個元組:(118,)),這裡隻需要取118即可

print(obj)

    解決粘包問題--初級版:

    服務端代碼:

from socket import *

import subprocess

server=socket(AF_INET,SOCK_STREAM)

server.setsockopt(SOL_SOCKET,SO_REUSEADDR,1)

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

server.listen(5)

coon,client_addr=server.accept()

while True: #通信循環

try:

cmd=coon.recv(1024)

if not cmd:break

obj=subprocess.Popen(cmd.decode('utf-8'),shell=True,

stdout=subprocess.PIPE,

stderr=subprocess.PIPE,

)

stdout=obj.stdout.read()

stderr=obj.stderr.read()

# 1、先發送固定長度的報頭 #如何制作固定長度的報頭(用到struct)

total_size=len(stdout) + len(stderr) #資料的總長度

coon.send(struct.pack('i',total_size)) #把包含資料長度的報頭發送到服務端

# 2、發送真實資料

coon.send(stdout)

coon.send(stderr)

    用戶端代碼:

client=socket(AF_INET,SOCK_STREAM) #SOCK_STREAM==流式協定:指的就是TCP協定

client.connect(('127.0.0.1',8086)) #這裡的IP和端口都是服務端的

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

if not cmd:continue

client.send(cmd.encode('utf-8')) #在網絡中發送資訊需要通過位元組(二進制的方式發送),是以需要encode('utf-8')制定字元集的方式發送

print('send..')

# 1、先收報頭,從報頭裡取出對真實資料的描述資訊

header=client.recv(4) #接收4個位元組即可,struct.pack('i',1231231)的長度為4,是以隻用接收4個即可

total_sisz=struct.unpack('i', header)[0] #解包報頭,拿到資料的總長度

# 2、循環接收真實資料,直到收完為止

recv_size=0 #接收資料包的大小

res=b'' #把接收到的資料包拼接到一起

while recv_size < total_sisz:

recv_data=client.recv(1024) #循環接收服務端傳過來的資料

res+=recv_data #res把接收到的資料全部拼接起來

recv_size+=len(recv_data) #接收到的資料的長度直到等于資料的總長度為止

print(res.decode('gbk')) #就收用戶端作業系統(windows預設使用gbk)發過來的資料,想要輸出到螢幕得使用gbk解碼

    解決粘包問題--終極版:

    我們可以把報頭做成字典,字典裡包含将要發送的真實資料的詳細資訊,然後json序列化,然後用struck将序列化後的資料長度打包成4個位元組(4個自己足夠用了)

    發送時(服務端):

        先發報頭長度。

        再編碼報頭内容然後發送。

        最後發真實内容。

    接收時(用戶端):

        先手報頭長度,用struct取出來。

        根據取出的長度收取報頭内容,然後解碼,反序列化。

        從反序列化的結果中取出待取資料的詳細資訊,然後去取真實的資料内容。

服務端代碼:

# 通信循環

while True:

stderr=obj.stderr.read()

#制作報頭

header_dic = { #設定報頭為字典格式

'filenema': 'a.txt', #檔案名

'total_size': len(stdout) + len(stderr), #資料總長度

'md5': 'sssxxxadwc123asd123', #md5值

}

header_json = json.dumps(header_dic) #把報頭,序列化為json格式

header_bytes = header_json.encode('utf-8') #在由json格式轉化為bytes格式(資料包發送通過bytes形式發送)

#1、先發送報頭的長度(用戶端拿到報頭的長度後可以知道要接受的資料大小)

coon.send(struct.pack('i',len(header_bytes))) #struct.pack用法:#i表示:int 整型,len(header_bytes)表示:報頭的長度(int類型)

#2、在發送報頭(報頭的内容)

coon.send(header_bytes)

#3、最後發送真實資料

coon.send(stdout)

# 關閉連接配接狀态 (回收的是作業系統的資源)

coon.close()

#關閉服務端 (回收的是作業系統的資源)

server.close()

用戶端代碼:

client=socket(AF_INET,SOCK_STREAM) #SOCK_STREAM==流式協定:指的就是TCP協定

client.connect(('127.0.0.1',8086)) #這裡的IP和端口都是服務端的

client.send(cmd.encode('utf-8')) #在網絡中發送資訊需要通過位元組(二進制的方式發送),是以需要encode('utf-8')制定字元集的方式發送

# 1、先收報頭的長度(服務端先發送的是報頭的長度,所有要先接收報頭的長度)

obj=client.recv(4) #報頭長度

header_size=struct.unpack('i',obj)[0] #拿到報頭長度後,通過struct.unpack拿到報頭的大小(即服務端header_dic的大小)

# 2、在接收報頭

header_bytes=client.recv(header_size) #通過client.recv接收報頭

header_json=header_bytes.decode('utf-8') #接收報頭後對報頭的格式做反序列化處理(因為報頭在服務端被json序列化了)

header_dic=json.loads(header_json) #通過json.loads拿到報頭的字典格式及内容

print(header_dic)

total_size=header_dic['total_size'] #拿到了真實資料的總長度

# 3、循環接收真實資料,直到收完為止

recv_size=0 #接收資料包的大小

res=b'' #把接收到的資料包拼接到一起

while recv_size < total_size:

recv_data=client.recv(1024) #循環接收服務端傳過來的資料

res+=recv_data #res把接收到的資料全部拼接起來

recv_size+=len(recv_data) #接收到的資料的長度直到等于資料的總長度為止

print(res.decode('gbk')) #就收用戶端作業系統(windows預設使用gbk)發過來的資料,想要輸出到螢幕得使用gbk解碼

# 5、關閉用戶端

client.close()

四、套接字程式設計(基于UDP協定通信套接字程式設計)  

    服務端代碼:

server=socket(AF_INET,SOCK_DGRAM) #SOCK_DGRAM == 資料報協定(UDP協定) -- 在發送資料庫時每一條資料UDP協定都會做報頭處理,

#那麼在接受端,就會根據資料報的内容接受資料,而不會發生粘包問題。

#1、基于UDP協定每發送的一條資料都自帶邊界,即UDP協定沒有粘包問題,

#2、基于UDP協定的通信,一定是一發對應一收

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

msg,client_addr=server.recvfrom(1024) #server.recvfrom(1024)拿到的就是"用戶端發送過來的資料,及IP+端口"

server.sendto(msg.upper(),client_addr)

    用戶端代碼:

client=socket(AF_INET,SOCK_DGRAM)

client.sendto(msg.encode('utf-8'),('127.0.0.1',8092)) #UDP協定沒有與服務端建立連接配接,是以在發送資料時需要指定服務端的IP和端口

res,server_addr=client.recvfrom(1024)

print(res)

# msg,client_addr=server.recvfrom(1024)

# server.sendto(msg.upper(),client_addr)

證明UDP協定是一收一發機制:

    服務端代碼:  連續接收三個資料,但是資料沒有粘包

server=socket(AF_INET,SOCK_DGRAM)

msg1,client_addr=server.recvfrom(1024)

print(msg1)

msg2,client_addr=server.recvfrom(1024)

print(msg2)

msg3,client_addr=server.recvfrom(1024)

print(msg3)

>>:b'hello' #用戶端發送一條資料,服務端接收一條資料,沒有粘包

>>:b'world' #用戶端發送一條資料,服務端接收一條資料,沒有粘包

>>:b'sudada' #用戶端發送一條資料,服務端接收一條資料,沒有粘包

    用戶端代碼: 連續發送三個資料

client.sendto('hello'.encode('utf-8'),('127.0.0.1',8092)) #發送資料

client.sendto('world'.encode('utf-8'),('127.0.0.1',8092)) #發送資料

client.sendto('sudada'.encode('utf-8'),('127.0.0.1',8092)) #發送資料

    基于UDP協定編寫的套接字程式設計中,服務端不能實作并發的效果(看起來像是并發的效果,實際上并不是。因為UDP協定是一發對應一收的,資料發完就删除掉,每次都可以快速執行,是以看起來像是并發的效果。)

當用戶端發送的資料量大于服務端接收的資料量時,會出現報錯,提示:"使用者接收資料報的緩沖區比資料包小",這種情況在不同的作業系統分不同的情況:

    1、在windows系統:會報錯。

    2、在linux系統:能接收多少就接收多少,接收不了的都丢棄,不會報錯。

msg1,client_addr=server.recvfrom(1)

msg2,client_addr=server.recvfrom(1)

msg3,client_addr=server.recvfrom(1)

>>:OSError: [WinError 10040] 一個在資料報套接字上發送的消息大于内部消息緩沖區或其他一些網絡限制,或該使用者用于接收資料報的緩沖區比資料報小。