Python3 與 C# 網路程式設計之~ 網路基礎篇
最新版本檢視:https://www.cnblogs.com/dotnetcrazy/p/9919202.html
入門篇
官方文件:https://docs.python.org/3/library/ipc.html(程序間通訊和網路)
例項程式碼:https://github.com/lotapp/BaseCode/tree/master/python/6.net
1.概念
1.1.Python方向
已經講了很多Python的知識了,那Python能幹啥呢?這個是我眼中的Python:
Python方向:
- 早期方向
- Web全棧
- 擅長專欄
- 爬蟲系列
- 資料分析
- 人工智慧
物聯網系
(lot
萬物互聯)- 自動化運維(
安全
與測試
)
- 其他系列
- 遊戲開發(最近很火)
如果想專攻Web
、爬蟲
、物聯網
、遊戲
等等方向,網路
這塊是硬條件,So ==> 不要不急,咱們繼續學習~
多句嘴,一般情況下需要什麼庫就去官方看下,沒有再考慮第三方:https://docs.python.org/3/library
1.2.拙見一點點
技術前景:(注意加粗方向)
- Python:
- 最常用:
Data
- 最看好:
LoT
- 最火是:
AI
- 經典是:
Web
- 壟斷是:
System
- 最常用:
- Web:
- 最看好:
小程式
- 最常見:
移動端
- 最火是:
Web端
- 最看好:
- Go(
高併發
、區塊鏈
)、C(基礎
) NetCore
(WebAPI
、EFCore
)
總的來說:Python最吃香,Go最有潛力,Web必不可少,NetCore價效比高
現在基本沒有單一方向的程式設計師了,如果有可以默默思考幾分鐘,一般都是JS
and Python
and (Go
or NetCore
)【二選一】
其他行業:(僅代表逆天個人看法)
- 設計師:
影視製作
(剪輯師、合成師、特效師)【目前最火,價效比很高】修圖師
(商業修片、影樓後期)【大咖特別多,創業很吃香】UI|UE
(最容易找工作)平面設計
(最常見)室內設計
(高手很吃香)
- 教育:
- **
幼兒程式設計
和中醫課
**最火 琴棋書畫武
+國學
需求頗高英語
一直是出國必學
- **
- 營銷:
新媒體+短視訊
- 旅遊:
出國遊
1.2.分層模型
1.OSI
7層模型
- 物理層:物理裝置標準,主要作用就是傳輸位元流(資料為bit)eg:網線介面、光纖介面、各種傳輸介質的傳輸速率
- 雙絞線,光纖(硬體)
- 資料鏈路層:對物理層的校驗(是否有丟失、錯誤)
- 資料的傳輸和資料檢測(網絡卡層)
- 網路層:指定傳輸過程中的路徑。eg:IP
- 為資料包選擇路由(保證資料傳達)
- 傳輸層:定義了傳輸資料的協議和埠號(主要就是攜帶了埠號,這樣可以找到對應的程序)
- 提供端對端的介面,eg:TCP、UDP
- 會話層:通過傳輸層,在端與端之間(埠)建立資料傳輸通道(裝置之間可以通過IP、Mac、主機名相互認識)
- 解除或者建立和別的節點之間的聯絡
- 表示層:保證一個系統應用發的訊息可以被另一個系統應用讀取到。eg:兩個應用傳送的訊息格式不同(eg:UTF和ASCII各自表示同一字元),有必要時會以一種通用格式來實現不同資料格式之間的轉換
- 資料格式化、程式碼轉化、資料加密
- 應用層:為使用者的應用程式提供網路服務
- 檔案傳輸、電子郵箱、檔案服務、虛擬終端
我用PPT畫了個圖:(物
數
網
傳
會
表
應
)
2.TCP/IP
4層模型
- 網路介面層:(
物、數
)- eg:乙太網幀協議
- 網路層:
- eg:IP、ARP協議
- 傳輸層:
- eg:TCP、UDP協議
- 應用層:(
會、表、應
)我們基本上都是關注這個- eg:FTP、SSH、HTTP協議...
1.3.協議相關
計算機和計算機網路通訊前達成的一種約定,舉個例子:以漢語為交流語言
再舉個傳送檔案的例子,PPT做個動畫:(自定義協議-檔案傳輸演示)
B/S
基本上都是HTTP
協議,C/S
開發的時候有時會使用自己的協議,比如某大型遊戲,比如很多框架都有自己的協議:
- Redis的
redis://
- Dubbo的
dubbo://
協議
總的來說,基本上都是HTTP
協議,對效能要求高的就使用TCP
協議,更高效能要求就自己封裝協議了,比如騰訊在UDP
基礎上封裝了自己的協議來保證通訊的可靠性
資料包的封裝
先看一個老外
的動畫(忽略水印廣告):https://v.qq.com/x/page/w01984zbrmy.html
以TCP/IP四層協議為例:資料包的逐層封裝
和解包
都是作業系統
來做的,我們只管應用層
傳送過程:
- 傳送訊息
- 應用層添加了協議頭
- 傳輸層新增
TCP
段首 - 網路層新增
IP
報頭 - 網路介面層(鏈路層)新增幀頭和幀尾
PPT動畫示意:
接收過程:
- 去除鏈路層的幀頭和幀尾
- 去除網路層**
IP
的報頭** - 去除傳輸層**
TCP
的段首** - 去除應用層的協議頭
- 獲取到資料
PPT動畫示意:
我們下面按照解包順序簡單說說各種格式:
1.乙太網幀格式
先看一下這個是啥?用上面動畫內容表示:
乙太網幀協議:根據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 地址
- 真實IP在網路層的IP協議之中,乙太網幀中的IP是下一跳的IP地址(路由)
- 每到一個路由都要解網路層的包(知道到底需要獲取哪個IP)
MAC
地址就是硬體地址,廠商向全球組織申請唯一編號(類似於身份證)- 最後附上手畫的ARP資料報圖示:(一般都不是一步得到MAC的,多數都是經過一個個路由節點最終獲取到MAC)
2.IP段格式
先貼一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,然後轉化) - 表示可以有255個ip用來自行分配(記得去除路由之類的佔用)
- 二進位制表示為:
擴充套件: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
2.UDP
例項程式碼:https://github.com/lotapp/BaseCode/tree/master/python/6.net/1.UDP
UDP
是無連線的傳輸協議,不保證可靠性。使用UDP
協議的應用程式需要自己完成丟包重發、訊息排序等工作(有點像寄信)
2.1.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)
除錯工具:
2.2.UDP接收訊息
先看一個簡單版本的: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)
其實如果你使用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之後關閉
)
2.3.手寫UDP網路除錯工具
除錯工具功能比較簡單,我們手寫一個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()
...
2.4.NetCore版
快速實現一下:
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");
}
}
}
3.TCP
示例程式碼:https://github.com/lotapp/BaseCode/tree/master/python/6.net/2.TCP
TCP
是一種面向連線的、可靠的協議,TCP
傳輸的雙方需要首先建立連線,之後由TCP
協議保證資料收發的可靠性,丟失的資料包自動重發,上層應用程式收到的總是可靠的資料流,通訊之後關閉連線(有點像打電話)
用過下載軟體的可能遇到過一種‘Bug’
==> 很多人為了防止自己本地檔案納入共享大軍,一般都是直接把網路上傳給禁了,然後發現檔案經常出問題?
其實這個就是TCP
的一個應用,檔案一般都很大,所以進行分割後批量下載,那少量的網路上傳其實是為了校驗一下檔案 ==> 正確做法是限制上傳速度而不是禁止(學生時代那會還經常蛋疼這個問題,現在想想還挺好玩的O(∩_∩)O
)
大多數連線都是可靠的TCP連線。建立TCP連線時,主動發起連線的叫客戶端,被動響應連線的叫伺服器
上面那個例子裡,我們的下載工具就是客戶端,每一小段檔案接收完畢後都會向伺服器傳送一個完成的指令來保證檔案的完整性
3.1.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
)
概括來說:
- TCP,有點像打電話,先撥號連通了(
connect
)才能通訊(send
,recv
),之後的通訊不用再撥號連通了 - UDP,有點像寄信封,每次寄過去都不確定能不能收到,每次通訊都得寫地址(
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()
模擬HTTP
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("",分割次數)
3.2.TCP服務端
服務端程式碼相比於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()
輸出:(先執行服務端,再執行客戶端。客戶端發了一個生日快樂的祝福,服務端回覆了一句)
3.2.TCP服務端除錯助手
如果像上面那般,並不能多客戶端通訊
這時候可以稍微改造一下:
客戶端:
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中十分簡單:
- 客戶端:主動連線伺服器的IP和指定埠
- 伺服器:先監聽指定埠,然後對每一個新的連線建立一個執行緒或程序來處理
3.3.NetCore版
Server版
大體流程和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("知道了~"));
}
});
}
}
}
}
}
Client版
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()
輸出:(你把埠換成常用埠列表就知道伺服器開了哪些服務了)
[email protected]:~/桌面/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]
[email protected]:~/桌面/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