最新版本查看:https://www.cnblogs.com/dotnetcrazy/p/9919202.htmlhtml
官方文檔:https://docs.python.org/3/library/ipc.html(進程間通訊和網絡)python
實例代碼:https://github.com/lotapp/BaseCode/tree/master/python/6.netgit
已經講了不少Python的知識了,那Python能幹啥呢?這個是我眼中的Python:程序員
Python方向:github
物聯網系
(lot
萬物互聯)安全
與測試
)若是想專攻Web
、爬蟲
、物聯網
、遊戲
等等方向,網絡
這塊是硬條件,So ==> 不要不急,我們繼續學習~redis
多句嘴,通常狀況下須要什麼庫就去官方看下,沒有再考慮第三方:https://docs.python.org/3/library
編程
技術前景:(注意加粗方向)小程序
Data
LoT
AI
Web
System
小程序
移動端
Web端
高併發
、區塊鏈
)、C(基礎
)NetCore
(WebAPI
、EFCore
)總的來講:Python最吃香,Go最有潛力,Web必不可少,NetCore性價比高
緩存
如今基本沒有單一方向的程序員了,若是有能夠默默思考幾分鐘,通常都是JS
and Python
and (Go
or NetCore
)【二選一】安全
其餘行業:(僅表明逆天我的見解)
影視製做
(剪輯師、合成師、特效師)【目前最火,性價比很高】修圖師
(商業修片、影樓後期)【大咖特別多,創業很吃香】UI|UE
(最容易找工做)平面設計
(最多見)室內設計
(高手很吃香)幼兒編程
和中醫課
最火琴棋書畫武
+國學
需求頗高英語
一直是出國必學新媒體+短視頻
出國遊
OSI
7層模型我用PPT畫了個圖:(物
數
網
傳
會
表
應
)
TCP/IP
4層模型物、數
)
會、表、應
)咱們基本上都是關注這個
計算機和計算機網絡通訊前達成的一種約定,舉個例子:以漢語爲交流語言
再舉個發送文件的例子,PPT作個動畫:(自定義協議-文件傳輸演示)
B/S
基本上都是HTTP
協議,C/S
開發的時候有時會使用本身的協議,好比某大型遊戲,好比不少框架都有本身的協議:
redis://
dubbo://
協議總的來講,基本上都是HTTP
協議,對性能要求高的就使用TCP
協議,更高性能要求就本身封裝協議了,好比騰訊在UDP
基礎上封裝了本身的協議來保證通訊的可靠性
先看一個老外
的動畫(忽略水印廣告):https://v.qq.com/x/page/w01984zbrmy.html
以TCP/IP四層協議爲例:數據包的逐層封裝
和解包
都是操做系統
來作的,咱們只管應用層
發送過程:
TCP
段首IP
報頭PPT動畫示意:
接收過程:
IP
的報頭TCP
的段首PPT動畫示意:
咱們下面按照解包順序簡單說說各類格式:
先看一下這個是啥?用上面動畫內容表示:
以太網幀協議:根據MAC
地址完成數據包傳遞
若是隻知道IP,並不知道MAC
地址,可使用ARP
請求來獲取:
ARP
數據報:根據IP
獲取MAC
地址(網卡編號)ARP
只適合IPv4
,IPv6
用ICMPV6
來代替ARP
TCP/IP
模型中,ARP
協議屬於IP
層;在OSI
模型中,ARP
協議屬於鏈路層PPT畫一張圖:1bit = 8byte
(1字節=8位)
課後思考:根據ARP原理想一想ARP欺騙
到底扎回事?(IP進行ARP請求後會緩存,緩存失效前不會再去ARP請求)
擴展:
RARP 是反向地址轉換協議,經過 MAC 地址肯定 IP 地址
MAC
地址就是硬件地址,廠商向全球組織申請惟一編號(相似於身份證)先貼一IP段格式圖片(網絡):
咱們在這不去詳細講解,擴展部分有課後拓展,我就說一個大多數人困惑的點:
查看IP
信息的時候常常會看到192.168.36.235/24
,這個/24
一直爭議很大
咱們來簡單解釋一下:IP爲192.168.36.235
192.168.36
:網絡標識235
:主機標識/24
:標識從頭數到多少位爲止屬於網絡標識(剩下的就是可分配的主機數了)
11111111 11111111 11111111 00000000
(24個1)255.255.255.0
(/多少
就數多少個1,而後轉化)擴展:IP屬於面向無鏈接行(IP
協議不保證傳輸的可靠性,數據包在傳輸過程當中可能丟失,可靠性能夠在上層協議或應用程序中提供支持)
面向鏈接
和面向無鏈接
區別如圖:(圖片來自網絡)
關於TCP和UDP的內容下次繼續~
課外拓展:
圖解TCP/IP第五版 連接: https://pan.baidu.com/s/1C4kpNd2MvljxfwTKO082lw 提取碼: 7qce Python網絡編程第三版 Code:https://github.com/brandon-rhodes/fopnp PDF:連接: https://pan.baidu.com/s/1jhW-Te-GCEFKrZVf46S_Tw 提取碼: d7fw 網絡基礎-含書籤(網絡文檔) 連接: https://pan.baidu.com/s/1WZ1D4BthA4qBk2QXBAjm4w 提取碼: jmdg 老外講解網絡數據包解析: 下載:https://pan.baidu.com/s/1uUjahs_b05y9Re9ROtzzIw 中文:http://video.tudou.com/v/XMjE3MTg0NzkzNg==.html 英文:http://video.tudou.com/v/XMTkyNjU5NDYwOA==.html
實例代碼:https://github.com/lotapp/BaseCode/tree/master/python/6.net/1.UDP
UDP
是無鏈接的傳輸協議,不保證可靠性。使用UDP
協議的應用程序須要本身完成丟包重發、消息排序等工做(有點像寄信)
看個UDP的簡單案例:
import socket def main(): # AF_INET ==> IPV4;SOCK_STREAM ==> 類型是TCP,stream 流 # SOCK_DGRAM ==> 類型是UDP,dgram 數據報、數據報套接字 with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as udp_sock: udp_sock.sendto("大兄弟,你好啊".encode("utf-8"), ("192.168.36.235", 8080)) print("over") if __name__ == '__main__': main()
接收到的消息:這時候端口是隨機的
看起來代碼還挺麻煩,我稍微分析下你就知道對比其餘語言真的太簡單了:
標識:
AF_INET
==> IPV4
SOCK_DGRAM
==> 類型是UDP
SOCK_STREAM
==> 類型是TCP
代碼三步走:
udp_sock=socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
udp_sock.sendto(Bytes內容,(IP,Port))
接收:udp_sock.recvfrom(count)
udp_sock.close()
藉助調試工具
(點我下載)能夠知道:上面程序每次運行,端口都不固定
那怎麼使用固定端口呢?==> udp_socket.bind(('', 5400))
import socket def main(): with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as udp_socket: # 綁定固定端口 udp_socket.bind(('', 5400)) # 發送消息 udp_socket.sendto("小明,你知道小張的生日嗎?\n".encode("utf-8"), ("192.168.36.235", 8080)) print("over") if __name__ == '__main__': main()
消息圖示:nc -ul 8080
(nc -l
是監聽TCP)
調試工具:
先看一個簡單版本的:udp_socket.recvfrom(1024)
from socket import socket, AF_INET, SOCK_DGRAM def main(): with socket(AF_INET, SOCK_DGRAM) as udp_socket: # 綁定端口 udp_socket.bind(('', 5400)) while True: # 發送消息 udp_socket.sendto("你能夠給我離線留言了\n".encode("utf-8"), ("192.168.36.235", 8080)) # 接收消息(data,(ip,port)) data, info = udp_socket.recvfrom(1024) print(f"[來自{info[0]}:{info[1]}的消息]:\n{data.decode('utf-8')}") if __name__ == '__main__': main()
圖示:接收消息(data,(ip,port))
其實若是你使用Nmap
來掃描的話並不能發現nc
打開的UDP
端口:
稍微解釋一下:掃描其實就是發了幾個空消息過去
-sU
表明掃描UDP,-sT
表明掃描TCP-Pn
這個主要是針對有些服務器禁用ping的處理(ping不通也嘗試)-p
指定端口號,若是是全部端口可使用-p-
sudo
是由於在Ubuntu
下沒權限,kali
下能夠直接使用nmap
可能有人對nc
輸出的你能夠給離線留意了
有疑惑,其實就是在給5400端口發空消息的時候~True循環了兩次
來張對比圖:
掃描TCP和UDP端口:sudo nmap -sTU 192.168.36.235 -Pn
課後擴展:
NC命令擴展:https://www.cnblogs.com/nmap/p/6148306.html Nmap基礎:https://www.cnblogs.com/dunitian/p/5074784.html
若是仍是用True循環來實現:
from socket import socket, AF_INET, SOCK_DGRAM def main(): with socket(AF_INET, SOCK_DGRAM) as udp_socket: # 綁定端口 udp_socket.bind(('', 5400)) while True: msg = input("請輸入發送的內容:") if msg == "dotnetcrazy": break else: udp_socket.sendto( msg.encode("utf-8"), ("192.168.36.235", 8080)) data, info = udp_socket.recvfrom(1024) print(f"[來自{info[0]}:{info[1]}的消息]:\n{data.decode('utf-8')}") if __name__ == '__main__': main()
你會發現,消息不能輪流發送,只能等對方方式後再發,雖然有處理方式,但太麻煩,這時候就可使用咱們以前說的多線程來改寫一下了:
from socket import socket, AF_INET, SOCK_DGRAM from multiprocessing.dummy import Pool as ThreadPool def send_msg(udp_socket): while True: msg = input("輸入須要發送的消息:\n") udp_socket.sendto(msg.encode("utf-8"), ("192.168.36.235", 8080)) def recv_msg(udp_socket): while True: data, info = udp_socket.recvfrom(1024) print(f"[來自{info[0]}:{info[1]}的消息]:\n{data.decode('utf-8')}") def main(): # 建立一個Socket with socket(AF_INET, SOCK_DGRAM) as udp_socket: # 綁定端口 udp_socket.bind(('', 5400)) # 建立一個線程池 pool = ThreadPool() # 接收消息 pool.apply_async(recv_msg, args=(udp_socket, )) # 發送消息 pool.apply_async(send_msg, args=(udp_socket, )) pool.close() # 再也不添加任務 pool.join() # 等待線程池執行完畢 print("over") if __name__ == '__main__': main()
輸出:(就一個注意點~socket在pool以後關閉
)
調試工具功能比較簡單,咱們手寫一個UDP
版的:
from socket import socket, AF_INET, SOCK_DGRAM from multiprocessing.dummy import Pool as ThreadPool def get_port(msg): """獲取用戶輸入的端口號""" while True: port = input(msg) try: port = int(port) except Exception as ex: print(ex) else: return port # 沒有錯誤就退出死循環 def recv_msg(udp_socket): """接收消息""" while True: data, info = udp_socket.recvfrom(1024) print(f"[來自{info[0]}:{info[1]}的消息]:\n{data.decode('utf-8')}") def send_msg(udp_socket): """發送消息""" ip = input("請輸入對方IP:") port = get_port("請輸入對方端口號:") while True: msg = input("請輸入發送的消息:\n") udp_socket.sendto(msg.encode("utf-8"), (ip, port)) def main(): with socket(AF_INET, SOCK_DGRAM) as udp_socket: # 綁定端口 udp_socket.bind(('', get_port("請輸網絡助手的端口號:"))) # 建立一個線程池 pool = ThreadPool() # 接收消息 pool.apply_async(recv_msg, args=(udp_socket, )) # 發送消息 pool.apply_async(send_msg, args=(udp_socket, )) pool.close() pool.join() if __name__ == '__main__': main()
CentOSIP
和Port
(192.168.36.123:5400
)
演示:(多PC演示)
簡單說下本機IP的綁定:
Net裏面習慣使用localhost
,不少人不知道究竟是啥,其實你打開host
文件就能夠看到 ==> 127.0.0.1
被重定向爲localhost
,在Linux裏面也是這樣的,每一個PC對應的都是lo
迴環地址:
本機通訊時,對方ip就可使用127.0.0.1
了,固然了綁定本機ip的時候也可使用127.0.0.1
(bind(('',))
中的空其實填的就是這個)(不少地方也會使用0.0.0.0
)
_LOCALHOST = '127.0.0.1' # 看這 _LOCALHOST_V6 = '::1' def socketpair(family=AF_INET, type=SOCK_STREAM, proto=0): if family == AF_INET: host = _LOCALHOST # 看這 elif family == AF_INET6: host = _LOCALHOST_V6 .... lsock = socket(family, type, proto) try: lsock.bind((host, 0)) # 看這 lsock.listen() ...
快速實現一下:
using System.Net; using System.Text; using System.Net.Sockets; namespace netcore { class Program { static void Main(string[] args) { // UDP通訊 using (var udp_socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp)) { var ip_addr = IPAddress.Parse("192.168.36.235"); // 綁定本地端口 udp_socket.Bind(new IPEndPoint(ip_addr, 5400)); // UDP發送消息 int i = udp_socket.SendTo(Encoding.UTF8.GetBytes("小明你好啊~"), new IPEndPoint(ip_addr, 8080)); Console.WriteLine($"發送計數:{i}"); } Console.WriteLine("over"); } } }
示例代碼:https://github.com/lotapp/BaseCode/tree/master/python/6.net/2.TCP
TCP
是一種面向鏈接的、可靠的協議,TCP
傳輸的雙方須要首先創建鏈接,以後由TCP
協議保證數據收發的可靠性,丟失的數據包自動重發,上層應用程序收到的老是可靠的數據流,通信以後關閉鏈接(有點像打電話)
用過下載軟件的可能遇到過一種‘Bug’
==> 不少人爲了防止本身本地文件歸入共享大軍,通常都是直接把網絡上傳給禁了,而後發現文件常常出問題?
其實這個就是TCP
的一個應用,文件通常都很大,因此進行分割後批量下載,那少許的網絡上傳實際上是爲了校驗一下文件 ==> 正確作法是限制上傳速度而不是禁止(學生時代那會還常常蛋疼這個問題,如今想一想還挺好玩的O(∩_∩)O
)
大多數鏈接都是可靠的TCP鏈接。建立TCP鏈接時,主動發起鏈接的叫客戶端,被動響應鏈接的叫服務器
上面那個例子裏,咱們的下載工具就是客戶端,每一小段文件接收完畢後都會向服務器發送一個完成的指令來保證文件的完整性
來看一個簡單的入門案例:
from socket import socket def main(): # 默認就是建立TCP Socket with socket() as tcp_socket: # 鏈接服務器(沒有返回值) tcp_socket.connect(("192.168.36.235", 8080)) # 發送消息(返回發送的字節數) tcp_socket.send("小張生日快樂~".encode("utf-8")) # 接收消息 msg = tcp_socket.recv(1024) print(f"服務器:{msg.decode('utf-8')}") if __name__ == '__main__': main()
輸出:(socket()
默認就是建立TCP Socket
)
歸納來講:
connect
)才能通訊(send
,recv
),以後的通訊不用再撥號連通了ip
+port
)代碼四步走:(TCP客戶端其實建立Socket
以後connect
一下服務器就OK了)
tcp_sock=socket.socket(socket.AF_INET, socket.SOCK_STREAM)
tcp_sock.connect((IP, Port))
tcp_sock.send(Bytes內容)
接收:tcp_sock.recv(count)
tcp_sock.close()
from socket import socket def get_buffer(tcp_socket): buffers = b'' while True: b = tcp_socket.recv(1024) if b: buffers += b else: break # 返回bytes return buffers def main(): with socket() as tcp_socket: # 鏈接服務器 tcp_socket.connect(("dotnetcrazy.cnblogs.com", 80)) # 發送消息(模擬HTTP) tcp_socket.send( b'GET / HTTP/1.1\r\nHost: dotnetcrazy.cnblogs.com\r\nConnection: close\r\n\r\n' ) # 以"\r\n\r\n"分割一次 header, data = get_buffer(tcp_socket).split(b"\r\n\r\n", 1) print(header.decode("utf-8")) with open("test.html", "wb") as f: f.write(data) print("over") if __name__ == '__main__': main()
輸出:(test.html
就是頁面源碼)
HTTP/1.1 200 OK Date: Thu, 01 Nov 2018 03:10:48 GMT Content-Type: text/html; charset=utf-8 Content-Length: 20059 Connection: close Vary: Accept-Encoding Cache-Control: private, max-age=10 Expires: Thu, 01 Nov 2018 03:10:58 GMT Last-Modified: Thu, 01 Nov 2018 03:10:48 GMT X-UA-Compatible: IE=10 X-Frame-Options: SAMEORIGIN over
注意\r\n
和Connection:close
;split("",分割次數)
服務端代碼相比於UDP,多了一個監聽和等待客戶端,其餘基本上同樣:
客戶端Code:(若是你想固定端口也能夠綁定一下Port
)
from socket import socket def main(): # 默認就是建立TCP Socket with socket() as tcp_socket: # 鏈接服務器(沒有返回值) tcp_socket.connect(("192.168.36.235", 8080)) print("Connected TCP Server...") # 鏈接提示 # 發送消息(返回發送的字節數) tcp_socket.send("小張生日快樂~\n".encode("utf-8")) # 接收消息 msg = tcp_socket.recv(1024) print(f"服務器:{msg.decode('utf-8')}") if __name__ == '__main__': main()
服務端Code:
from socket import socket def main(): with socket() as tcp_socket: # 綁定端口(便於客戶端找到) tcp_socket.bind(('', 8080)) # 變成被動接收消息(監聽) tcp_socket.listen() # 不指定鏈接最大數則會設置默認值 print("TCP Server is Running...") # 運行後提示 # 等待客戶端發信息 client_socket, client_addr = tcp_socket.accept() with client_socket: # 客戶端鏈接提示 print(f"[來自{client_addr[0]}:{client_addr[1]}的消息]\n") # 接收客戶端消息 data = client_socket.recv(1024) print(data.decode("utf-8")) # 回覆客戶端 client_socket.send("知道了".encode("utf-8")) if __name__ == '__main__': main()
輸出:(先運行服務端,再運行客戶端。客戶端發了一個生日快樂的祝福,服務端回覆了一句)
若是像上面那般,並不能多客戶端通訊
這時候能夠稍微改造一下:
from time import sleep from socket import socket from multiprocessing.dummy import Pool def send_msg(tcp_socket): with tcp_socket: while True: try: tcp_socket.send("小明同志\n".encode("utf-8")) sleep(2) # send是非阻塞的 print("向服務器問候了一下") except Exception as ex: print("服務端鏈接已斷開:", ex) break def recv_msg(tcp_socket): with tcp_socket: while True: # 這邊能夠不捕獲異常: # 服務端關閉時,send_msg會關閉,而後這邊也就關閉了 try: data = tcp_socket.recv(1024) if data: print("服務端回覆:", data.decode("utf-8")) except Exception as ex: print("tcp_socket已斷開:", ex) break def main(): with socket() as tcp_socket: # 鏈接TCP Server tcp_socket.connect(("192.168.36.235", 8080)) print("Connected TCP Server...") # 鏈接提示 pool = Pool() pool.apply_async(send_msg, args=(tcp_socket,)) pool.apply_async(recv_msg, args=(tcp_socket,)) pool.close() pool.join() if __name__ == '__main__': main()
服務器須要同時響應多個客戶端的請求,那麼每一個鏈接都須要一個新的進程或者線程來處理
from socket import socket from multiprocessing.dummy import Pool def wait_client(client_socket, ip_port): with client_socket: while True: data = client_socket.recv(1024) print(f"[來自{ip_port}的消息]:\n{data.decode('utf-8')}") client_socket.send(b"I Know") # bytes類型 def main(): with socket() as tcp_socket: # 綁定端口 tcp_socket.bind(('', 8080)) # 服務器監聽 tcp_socket.listen() print("TCP Server is Running...") # 運行後提示 p = Pool() while True: # 等待客戶端鏈接 client_socket, client_addr = tcp_socket.accept() ip_port = f"{client_addr[0]}:{client_addr[1]}" print(f"客戶端{ip_port}已鏈接") # 響應多個客戶端則須要多個線程來處理 p.apply_async(wait_client, args=(client_socket, ip_port)) if __name__ == '__main__': main()
演示:(死循環,Pool
都不用管了)
服務器掛了客戶端也會自動退出:
用TCP協議進行Socket
編程在Python中十分簡單:
大致流程和Python同樣:
using System; using System.Text; using System.Net; using System.Net.Sockets; using System.Threading.Tasks; namespace _2_TCP { class Program { static void Main(string[] args) { using (var tcp_socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp)) { var ip_addr = IPAddress.Parse("192.168.36.235"); // 服務器端綁定Port tcp_socket.Bind(new IPEndPoint(ip_addr, 8080)); // 服務器監聽 tcp_socket.Listen(5); while (true) { // 等待客戶端鏈接 var client_socket = tcp_socket.Accept(); // 遠程端口 var client_point = client_socket.RemoteEndPoint; Task.Run(() => { while (true) { byte[] buffer = new byte[1024]; int count = client_socket.Receive(buffer); Console.WriteLine($"來自{client_socket.RemoteEndPoint.ToString()}的消息:\n{Encoding.UTF8.GetString(buffer, 0, count)}"); client_socket.Send(Encoding.UTF8.GetBytes("知道了~")); } }); } } } } }
using System; using System.Text; using System.Net; using System.Net.Sockets; namespace client { class Program { static void Main(string[] args) { using (var tcp_socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp)) { // 鏈接服務器 tcp_socket.Connect(new IPEndPoint(IPAddress.Parse("192.168.36.235"), 8080)); while (true) { // 發送消息 tcp_socket.Send(Encoding.UTF8.GetBytes("服務器你好")); // 接收服務器消息 byte[] buffer = new byte[1024]; int count = tcp_socket.Receive(buffer); Console.WriteLine($"來自服務器的消息:{Encoding.UTF8.GetString(buffer, 0, count)}"); } } } } }
圖示:
示例代碼:https://github.com/lotapp/BaseCode/tree/master/python/6.net/3.Ext
上面忘記說了,Socket
是能夠設置超時時間的,eg:tcp_socket.settimeout(3)
localhost
代碼不變,若是把TCP客戶端的鏈接服務器IP
空着或者改爲127.0.0.1
,我們再看看效果:tcp_socket.connect(('', 8080))
圖示:(怎麼樣,這回知道本機問啥能夠不寫IP了吧)
端口掃描你們不陌生,本身實現一個簡單的TCP端口掃描工具:
from socket import socket from multiprocessing.dummy import Pool ip = "127.0.0.1" def tcp_port(port): """IP:服務端IP,Port:服務端Port""" with socket() as tcp_socket: try: tcp_socket.connect((ip, port)) print(f"[TCP Port:{port} is open]") except Exception: pass def main(): # 查看系統本地可用端口極限值 cat /proc/sys/net/ipv4/ip_local_port_range max_port = 60999 global ip ip = input("請輸入要掃描的IP地址:") print(f"正在對IP:{ip}進行端口掃描...") pool = Pool() pool.map_async(tcp_port, range(max_port)) pool.close() pool.join() if __name__ == '__main__': main()
輸出:(你把端口換成經常使用端口列表就知道服務器開了哪些服務了)
dnt@MZY-PC:~/桌面/work/BaseCode/python/6.net/3.Ext python3 1.port_scan.py 請輸入要掃描的IP地址:192.168.36.235 正在對IP:192.168.36.235進行端口掃描... [TCP Port:22 is open] [TCP Port:41004 is open] dnt@MZY-PC:~/桌面/work/BaseCode/python/6.net/3.Ext sudo nmap -sT 192.168.36.235 -Pn -p- Starting Nmap 7.60 ( https://nmap.org ) at 2018-11-02 18:15 CST Nmap scan report for MZY-PC (192.168.36.235) Host is up (0.000086s latency). Not shown: 65534 closed ports PORT STATE SERVICE 22/tcp open ssh Nmap done: 1 IP address (1 host up) scanned in 2.07 seconds
能夠自行研究拓展:
send
、sendto
)和接收(recv
、recvfrom
)都是兩個方法?(提示:方法名
、阻塞
)send
和sendall
有啥區別?內網映射
或者ShellCode
實現一個遠控課外拓展:
官方Socket編程文檔【推薦】 https://docs.python.org/3/library/socket.html Python核心編程之~網絡編程【推薦】 https://wizardforcel.gitbooks.io/core-python-2e/content/19.html TCP編程知識 https://dwz.cn/dDkXzqcV 網絡編程-基礎 https://www.jianshu.com/p/55c171ebe5f1 網絡編程-UDP https://www.jianshu.com/p/594870b1634b 網絡編程-TCP https://www.jianshu.com/p/be36d4db5618 Python總結之 recv與recv_from https://www.jianshu.com/p/5643e810123f https://blog.csdn.net/xvd217/article/details/38902081 https://blog.csdn.net/pengluer/article/details/8812333 端口掃描擴展:(Python2) https://thief.one/2018/05/17/1 Python socket藉助ngrok創建外網TCP鏈接 https://www.jianshu.com/p/913b2013a38f TCP協議知識: https://www.cnblogs.com/wcd144140/category/1313090.html