二十二、網絡編程
阿新 • • 發佈:2018-05-10
數據報 dport 同時 CP eight listen charm get server
3.udp協議:UDP---用戶數據報協議,是一個簡單的面向數據報的運輸層協議。UDP不提供可靠性,它只是把應用程序傳給IP層的數據報發送出去,但是並不能保證它們能到達目的地。由於UDP在傳輸數據報前不用在客戶和服務器之間建立一個連接,且沒有超時重發等機制,故而傳輸速度很快
網絡編程的一些基本概念:
1.地址解析協議,即ARP(Address Resolution Protocol),是根據IP地址獲取物理地址的一個TCP/IP協議。
主機發送信息時將包含目標IP地址的ARP請求廣播到網絡上的所有主機,並接收返回消息,以此確定目標的物理地址。 收到返回消息後將該IP地址和物理地址存入本機ARP緩存中並保留一定時間,下次請求時直接查詢ARP緩存以節約資源。 2.tcp協議:TCP---傳輸控制協議,提供的是面向連接、可靠的字節流服務。當客戶和服務器彼此交換數據前,必須先在雙方之間建立一個TCP連接,之後才能傳輸數據。TCP提供超時重發,丟棄重復數據,檢驗數據,流量控制等功能,保證數據能從一端傳到另一端。 tcp 的鏈接有三次握手,斷開有四次揮手,四次揮手的原因是因為tcp下一的半關閉原則。互聯網協議與osi模型
互聯網協議按照功能不同分為osi七層或tcp/ip五層或tcp/ip四層
每層運行常見物理設備
一.套接字(socket)初使用
基於TCP協議的socket
tcp是基於鏈接的,必須先啟動服務端,然後再啟動客戶端去鏈接服務端
服務端 import socket s = socket.socket() #買電話 id_port = (‘127.0.0.1‘,8000) #買電話卡 s.bind(id_port) #把電話裝上電話卡
s.listen() #等待電話打進來 conn,adress = s.accept() #接受到了消息 msg = conn.recv(1024) #接受信息,接受的一點是字節類型 print(msg.decode()) # 打印,解碼 inp = input(‘<<<<‘) #輸入要發送的信息 new_msg = conn.send(inp.encode(‘utf-8)) # 把發送的信息編碼,發送s.close()
客戶端 import socket s = socket.socket() id_port = (‘127.0.0.1‘,8000) s.connect(id_port) msg = input(‘<<<‘) s.send(msg.encode(‘utf-8‘) new_msg = s.recv(1024) print(new_msg.decode())
s.close()
基於UDP協議的socket
udp是無鏈接的,啟動服務之後可以直接接受消息,不需要提前建立鏈接
簡單使用
服務端 import socket udp_s = socket.socket(type = socket.SOCK_DGRAM)# 創建套接字 id_port = (‘127.0.01‘,8000) udp_s.bind(id_port) #為套接字綁定ip msg,addr = udp_s.recvfrom(1024) print(msg.decode()) inp = input(‘<<<‘) udp_s.sendto(inp.encode(‘utf-8),addr) #這裏發送消息給客戶端要帶上地址, udp_s.close()
客戶端 import socket s = socket.socket(type = socket.SOCK_DRGAM) id_port = (‘127.0.0.1‘,8000) msg = input(‘<<<‘) s.sendto(msg.encode(‘utf-8‘),idport) msg,addr = s.recv(1024) print(msg.decode(‘utf-8))
s.close()
udp協議和tcp的差別:
服務端:tcp的服務端需要s.listen()這個過程 並且是conn,addr = s.accept() 後面的send 和recv 都是通過conn來進行,所以後面的發送不需要把addr加上。
udp的服務端沒有listen這個過程,直接是msg,addr = s.recv(1024) 後面的是s.sendto(mag,addr)
客戶端:tcp的客戶端是id_port 與s綁定是s.connect(id_port) s.send() msgr = s.recv(1024)
udp的客戶端是不需要客戶端與ip_port 綁定的的,直接發送的時候 s.sendto(msg,id_port) msg,addr = s.recv(1024)
二、黏包問題
tcp協議的黏包成因詳談:
首先明確一點,黏包只發生在TCP協議,udp協議不會產生黏包,udp要麽報錯,要麽發送丟失,也就是不完整。至於為什麽我們稍後來談。這裏只搞清楚一點只有tcp會有黏包現象。 為什麽tcp會有黏包:表面上看是由於發送方和接受方的緩存機制,也就是說的拆包,和tcp‘協議是面向通信流(也就是那種byte流的形式)的特點,造成了這些情況,而真正的罪魁禍首其實是,接受端根本不知道要接受的數據怎麽斷句和接受數據的大小,所以才造成了坑爹的黏包現象。 深層次的分析:1.tcp協議在發送消息 如果消息的數量量大於的網卡的mtu值,就會把這個數據包拆包,分幾次發送過去,這樣就容易造成一次性接受消息不完整的情況,分幾次接受,這是容易造成黏包的第一個原因。但是同時也是tcp可靠的點,只要沒有發送完會一直發送。 2.由於tcp協議面向流的通信特點和nagle算法。如果在發送兩條間隔很短的消息且這樣兩條消息的長度特別短 ,這時候就要采用nagle算法,把這兩條消息整合成一條消息打包發送。這時候接收端就無法合理的拆包。就造成消息混亂,也就是黏包。同時這也是tcp協議面向流通信的特點,這也是。 udp協議不產生黏包的原因: 主要是udp協議的通信是面向消息的,每次不管是接受還是發送都是接受一整條消息,不會去拆包,除非這個消息大於接受範圍就會發送不完整,且下次也不會再繼續發送,即使發送為,且每次發送消息都會自動帶上端口號和ip地址,所以即使發送為空接收端也會收到消息。tcp協議如果發空消息,接收端會阻塞。 三、用struct解決黏包問題借助struct模塊,我們知道長度數字可以被轉換成一個標準大小的4字節數字。因此可以利用這個特點來預先發送數據長度。
發送時 | 接收時 |
先發送struct轉換好的數據長度4字節 | 先接受4個字節使用struct轉換成數字來獲取要接收的數據長度 |
再發送數據 | 再按照長度接收數據 |
server端 #!user/bin/python3 #Author:Mr.Yuan #-*- coding:utf-8 -*- #@time: 2018/5/7 19:00 import socket import os import struct import json import time id_port = (‘127.0.0.1‘,9000) s = socket.socket() s.bind(id_port) s.listen() cnng,addr = s.accept() dic = {‘filename‘:r‘H:\pycharm文件\a‘, ‘filesize‘:os.path.getsize(r‘D:\feiq\Recv Files\python11期day35\video2.mp4‘)} str_dic = json.dumps(dic).encode(‘utf-8‘) struct_dic = struct.pack(‘i‘,len(str_dic)) cnng.send(struct_dic) cnng.send(str_dic) f = open(r‘D:\feiq\Recv Files\python11期day35\video2.mp4‘,‘rb‘) while dic[‘filesize‘]: content = f.read(1024) dic[‘filesize‘]-=len(content) print(dic[‘filesize‘]) cnng.sendall(content) cnng.close() s.close()
client端 #!user/bin/python3 #Author:Mr.Yuan #-*- coding:utf-8 -*- #@time: 2018/5/7 19:00 import socket import json import struct id_port = (‘127.0.0.1‘,9000) s = socket.socket() s.connect(id_port) struct_message = s.recv(4) dic_len = struct.unpack(‘i‘,struct_message)[0] str_dic = json.loads(s.recv(dic_len).decode()) print(str_dic) with open(‘H:\pycharm文件\mp6.mp4‘,‘wb‘) as f : while str_dic[‘filesize‘]: recv_content = s.recv(1024) str_dic[‘filesize‘] -= len(recv_content) print(str_dic[‘filesize‘]) f.write(recv_content) s.close()
四、一個服務端連接多個客戶端寫法
#服務端 import socketserver class MyServe(socketserver.BaseRequestHandler): def handle(self):#必須寫這個函數名 最先執行這個方法 self.request.sendall(bytes(‘歡迎致電10086...巴拉巴拉一大推‘,encoding=‘utf-8‘)) while True: date = self.request.recv(1024) print(‘%s:%s‘% (self.client_address,date.decode())) self.request.sendall(bytes(‘我收到了‘,encoding=‘utf-8‘)) if __name__ == ‘__main__‘: server = socketserver.ThreadingTCPServer((‘127.0.0.1‘,8000),MyServe) server.serve_forever() #讓handle方法永遠執行下去
#客戶端 import socket ip_port = (‘127.0.0.1‘,8000) s = socket.socket() s.connect(ip_port) msg = s.recv(1024) print(msg.decode()) while True: inp = input(‘<<<<‘) if len(inp)==0:continue s.send(bytes(inp,encoding=‘utf-8‘)) data = s.recv(1024) print(data.decode()) s.close()
二十二、網絡編程