1. 程式人生 > >二十二、網絡編程

二十二、網絡編程

數據報 dport 同時 CP eight listen charm get server

網絡編程的一些基本概念:

1.地址解析協議,即ARP(Address Resolution Protocol),是根據IP地址獲取物理地址的一個TCP/IP協議。

 主機發送信息時將包含目標IP地址的ARP請求廣播到網絡上的所有主機,並接收返回消息,以此確定目標的物理地址。  收到返回消息後將該IP地址和物理地址存入本機ARP緩存中並保留一定時間,下次請求時直接查詢ARP緩存以節約資源。 2.tcp協議:TCP---傳輸控制協議,提供的是面向連接、可靠的字節流服務。當客戶和服務器彼此交換數據前,必須先在雙方之間建立一個TCP連接,之後才能傳輸數據。TCP提供超時重發,丟棄重復數據,檢驗數據,流量控制等功能,保證數據能從一端傳到另一端。 tcp 的鏈接有三次握手,斷開有四次揮手,四次揮手的原因是因為tcp下一的半關閉原則。
技術分享圖片 3.udp協議:UDP---用戶數據報協議,是一個簡單的面向數據報的運輸層協議。UDP不提供可靠性,它只是把應用程序傳給IP層的數據報發送出去,但是並不能保證它們能到達目的地。由於UDP在傳輸數據報前不用在客戶和服務器之間建立一個連接,且沒有超時重發等機制,故而傳輸速度很快

互聯網協議與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:rH:\pycharm文件\a,
       filesize:os.path.getsize(rD:\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(rD:\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()

二十二、網絡編程