深入Python中的網(wǎng)絡(luò)通信
TCP/IP
計(jì)算機(jī)與網(wǎng)絡(luò)設(shè)備兩情侶要談戀愛(ài),相互通信,那么雙方就必須有規(guī)則?;谙嗤姆椒?,不同的硬件、操作系統(tǒng)之間的通信,都需要一種規(guī)則。而我們就把這種規(guī)則稱(chēng)為協(xié)議(protocol)。
TCP/IP 是互聯(lián)網(wǎng)相關(guān)各類(lèi)協(xié)議族的總稱(chēng)。TCP/IP是指TCP和IP這兩種協(xié)議。TCP/IP是在IP協(xié)議的通信過(guò)程中,使用到的協(xié)議族的統(tǒng)稱(chēng)。
TCP/IP協(xié)議族按層次分別為 應(yīng)用層,傳輸層,網(wǎng)絡(luò)層,數(shù)據(jù)鏈路層,物理層??梢园凑詹煌哪P头?層或者是7層。
將TCP/IP分為5層,越靠下越接近硬件。
應(yīng)用層:應(yīng)用程序收到傳輸層的數(shù)據(jù)后,接下來(lái)就是要進(jìn)行解讀,解讀必須要先規(guī)定好格式,而應(yīng)用層就是規(guī)定應(yīng)用程序的數(shù)據(jù)格式,主要協(xié)議有HTTP等。
傳輸層:該層為兩臺(tái)主機(jī)上的應(yīng)用程序提供端到端的通信,傳輸層有兩個(gè)傳輸協(xié)議為T(mén)CP(傳輸控制協(xié)議)和UDP(用戶數(shù)據(jù)報(bào)協(xié)議),TCP是一個(gè)可靠的面向連接的協(xié)議,UDP是不可靠或者說(shuō)無(wú)連接的協(xié)議。
網(wǎng)絡(luò)層:決定如何將數(shù)據(jù)從發(fā)送方到接收方,是建立主機(jī)到主機(jī)的通信。
數(shù)據(jù)鏈路層:控制網(wǎng)絡(luò)層與物理層之間的通信,主要功能是保證物理線路上進(jìn)行可靠的數(shù)據(jù)傳遞。
物理層:該層負(fù)責(zé)物理傳輸,與鏈路有關(guān),也與傳輸?shù)慕橘|(zhì)有關(guān)。

客戶端和服務(wù)器具體的
HTTP
圖片出自《圖解HTTP》書(shū)籍
三次握手,四次揮手
TCP三次握手,四次揮手,Runsen也不會(huì)怎么說(shuō),就把網(wǎng)上最通俗的圖放在下面 了,還是別看我很牛逼,牛逼的是做圖的大佬。
三次握手
四次揮手

圖片出自公眾號(hào)(程序員小小溪),更多的名詞和概念查找參考公眾號(hào)程序員小小溪的文章~[1]
Socket
網(wǎng)絡(luò)編程有一個(gè)重要的概念 socket(套接字),應(yīng)用程序可以通過(guò)它發(fā)送或接收數(shù)據(jù),套接字允許應(yīng)用程序?qū)?I/O 插入到網(wǎng)絡(luò)中,并與網(wǎng)絡(luò)中的其他應(yīng)用程序進(jìn)行通信。
我是來(lái)偷窺Python中的網(wǎng)絡(luò)通信Socket,不小心偷窺到了一個(gè)非常不錯(cuò)的Socket好圖

將上面的圖片整理步驟
1.建立連接:
- 服務(wù)器:socket--->address--->bind--->listen--->accept
- 客戶端:socket--->connect
2.通信:收一發(fā):recv(1024)<---send(byte)/sendall(byte)
3.關(guān)閉連接:close()
實(shí)現(xiàn)簡(jiǎn)單的通訊程序
服務(wù)端,server.py
- #導(dǎo)入socket模塊
- import socket
- #創(chuàng)建套接字 或使用server = socket.socket()
- server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
- #定義綁定的ip和端口,用元組定義
- ip_port = ('127.0.0.1', 8888)
- #綁定監(jiān)聽(tīng):bind(address),在AF_INET下,以元組(ip,port)的形式表示地址
- server.bind(ip_port)
- #設(shè)置最大連接數(shù),默認(rèn)為1
- server.listen(5)
- #不斷接受連接:one by one
- while True:
- print("等待數(shù)據(jù)連接中……")
- #接受客服端數(shù)據(jù)請(qǐng)求
- conn, address = server.accept()
- '''
- 向客服端返回信息
- (注意:python3.x以上,網(wǎng)絡(luò)數(shù)據(jù)的發(fā)送接收都是byte類(lèi)型,
- 發(fā)送接收String類(lèi)型數(shù)據(jù)時(shí)需要對(duì)數(shù)據(jù)進(jìn)行編碼(發(fā)送:messages.enconde();接收后轉(zhuǎn)為String類(lèi)型:messages.deconde()),pyhon2.x則直接發(fā)送數(shù)據(jù)無(wú)須編碼)
- '''
- messages = "連接成功!"
- conn.send(messages.encode())
- #計(jì)數(shù)信息條數(shù)
- count = 0
- #一個(gè)連接中,不斷的接受客戶端發(fā)來(lái)的數(shù)據(jù)
- while True:
- data = conn.recv(1024)
- #打印客戶端發(fā)來(lái)的數(shù)據(jù)信息
- print(data.decode())
- #判斷是否退出當(dāng)前連接,等在下一個(gè)連接
- if data == b'exit':
- break
- #處理客戶端數(shù)據(jù)(如:響應(yīng)請(qǐng)求等)
- count = count + 1
- string = "第" + str(count) + "條信息:" + data.decode()
- conn.send(string.encode())
- #主動(dòng)關(guān)閉連接
- conn.close()
客戶端,client.py
- import socket
- #創(chuàng)建套接字
- client = socket.socket()
- #訪問(wèn)的服務(wù)器的ip和端口,用元組定義
- ip_port = ("127.0.0.1", 8888)
- #連接服務(wù)器主機(jī)
- client.connect(ip_port)
- #同一鏈接中,不斷向服務(wù)器發(fā)生數(shù)據(jù)或請(qǐng)求
- while True:
- #接收服務(wù)器發(fā)送或響應(yīng)的數(shù)據(jù)
- data = client.recv(1024)
- #打印接收的數(shù)據(jù);python3.x以上數(shù)據(jù)要編碼(發(fā)送:data.enconde();接收后轉(zhuǎn)為String類(lèi)型:data.deconde())
- print(data.decode())
- messages = input("請(qǐng)輸入發(fā)生或請(qǐng)求的數(shù)據(jù)(exit退出):")
- client.send(messages.encode())
- if messages == 'exit':
- break
- '''
- #接收服務(wù)器發(fā)送或響應(yīng)的數(shù)據(jù)
- data = client.recv(1024)
- #打印接收的數(shù)據(jù);python3.x以上數(shù)據(jù)要編碼,發(fā)送enconde();接收deconde()
- print(data.decode())
- '''
- #關(guān)閉連接
- client.close()
具體效果如下圖所示。

多線程通信TCP服務(wù)器與多個(gè)TCP客戶端同時(shí)進(jìn)行連續(xù)通信,只需要通過(guò)threading創(chuàng)建多線程任務(wù)handle_client就可以了。
- import socket
- import threading
- import random
- def handle_client():
- # 接受客戶端請(qǐng)求鏈接
- client, address = server.accept()
- print("[*] Accept connection from: %s:%d" % (address[0], address[1]))
- messages = "Hello World!"
- client.send(messages.encode())
- # 連續(xù)與當(dāng)前連接的客戶端通信
- while True:
- # 接受客戶端數(shù)據(jù)
- request = (client.recv(1024)).decode()
- # 判斷是否結(jié)束通信
- if request == 'exit':
- break
- print("[*] Received from %s:%d : %s" % (address[0], address[1], request))
- # 發(fā)送響應(yīng)信息給客戶端
- client.send((str(random.randint(1, 1000)) + ":" + "ACK!").encode())
- # 關(guān)閉當(dāng)前連接
- client.close()
- if __name__ == "__main__":
- # 創(chuàng)建套接字
- server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
- # 定義綁定ip和端口
- ip = '127.0.0.1'
- port = 8888
- # 綁定監(jiān)聽(tīng)
- server.bind((ip, port))
- # 設(shè)置最大連接數(shù),默認(rèn)為1
- server.listen(5)
- print("[*] Listening on %s:%d" % (ip, port))
- # 循環(huán)開(kāi)啟線程,接受多個(gè)客戶端的鏈接通信
- while True:
- # 創(chuàng)建一個(gè)線程
- client_handler = threading.Thread(target=handle_client)
- # 開(kāi)啟線程
- client_handler.start()
python3.x以上,網(wǎng)絡(luò)數(shù)據(jù)messages的發(fā)送接收都是byte類(lèi)型,若要發(fā)送接收String類(lèi)型數(shù)據(jù)時(shí)需要通過(guò)messages.enconde()對(duì)數(shù)據(jù)進(jìn)行編碼,接收后通過(guò)messages.deconde()轉(zhuǎn)為String類(lèi)型。pyhon2.x則直接發(fā)送數(shù)據(jù)無(wú)須編碼。
本文已收錄 GitHub,傳送門(mén)~[2] ,里面更有大廠面試完整考點(diǎn),歡迎 Star。
Reference
[1] 參考公眾號(hào)程序員小小溪的文章:
https://mp.weixin.qq.com/s/KK1dnNoHrbjMyuhQptaBAQ
[2] 傳送門(mén)~:
https://github.com/MaoliRUNsen/runsenlearnpy100