入門篇¶
官方文檔: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==>IPV4SOCK_DGRAM==> 類型是UDPSOCK_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()
輸出:(你把端口換成常用端口列表就知道服務器開了哪些服務了)
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
