網絡編程
定義:所為網絡編程即是對信息的發送和接收。
主要工作:
(1)發送端:將信息以規定的協議組裝成數據包。
(2)接收端:對收到的數據包解析,以提取所需要的信息。
Socket:兩個在網絡上的程序通過一個雙向的通信連接,實現數據的交換,此連接的一端稱為一個socket。
Socket的本質:Socket是一個編程接口(API),TCP/IP協議需要向開發者提供做網絡開發用的接口,這就是Socket接口,它是對TCP/IP協議網絡通信的封裝。
python中用有標准庫socket,要進行socket編程,只需導入這個模塊即可。
例一(實現一個單對單,只能發送一次消息的一次性服務端和客戶端):

1 #服務端
2 import socket 3
4 address = ("localhost", 6666) #寫明服務端要監聽的地址,和端口號
5 server = socket.socket() #生成一個socket對象
6 server.bind(address) #用socket對象綁定要監聽的地址和端口
7 server.listen() #開始監聽
8
9 conn,addr = server.accept() #等帶新連接接入服務端,返回一個新的socket對象和地址,地址格式同前面格式
10 '''
11 Wait for an incoming connection. Return a new socket 12 representing the connection, and the address of the client. 13 '''
14 data = conn.recv(1024) #接收信息,寫明要接收信息的最大容量,單位為字節
15 print("server recv:", data) 16 conn.send(data.upper()) #對收到的信息處理,返回到客戶端
17
18 server.close() #關閉服務端

1 #客戶端
2 import socket 3
4 address = ('localhost', 6666) #寫明要發送消息的服務端的地址和端口號
5 client = socket.socket() 6 client.connect(address) #連接服務端
7
8 client.send(b"hell world") #發送信息,注意在python3中socket的發送只支持bytes類型
9 data = client.recv(1024) #等待接收服務端返回的信息
10 print("client recv:", data) 11
12 client.close() #關閉客戶端
例二(對上面的代碼進行改進,可以掛起多個連接,使每個連接可以進行多次對話且上一個連接斷開后下一個連接馬上接入):

1 #服務端 2 import socket 3 4 address = ("localhost", 6666) #寫明服務端要監聽的地址,和端口號 5 server = socket.socket() #生成一個socket對象 6 server.bind(address) #用socket對象綁定要監聽的地址和端口 7 server.listen(5) #開始監聽 8 9 while True: 10 #一條連接關閉后,接入下一條連接 11 conn,addr = server.accept() #等帶新連接接入服務端,返回一個新的socket對象和地址,地址格式同前面格式 12 ''' 13 Wait for an incoming connection. Return a new socket 14 representing the connection, and the address of the client. 15 ''' 16 while True: 17 #使其可以接收多次消息 18 data = conn.recv(1024) #接收信息,寫明要接收信息的最大容量,單位為字節 19 # 沒收到消息,斷開本次連接 20 if not data: 21 break 22 print("server recv:", data) 23 conn.send(data.upper()) #對收到的信息處理,返回到客戶端 24 25 server.close() #關閉服務端

1 #客戶端 2 import socket 3 4 address = ('localhost', 6666) #寫明要發送消息的服務端的地址和端口號 5 client = socket.socket() 6 client.connect(address) #連接服務端 7 8 while True: 9 #使其可以向服務端多次發送消息 10 msg = input(">>>:").strip() 11 #如果發送的消息為空,則不再發送 12 if len(msg) == 0: 13 break 14 msg = msg.encode('utf-8') #將要發送的消息轉為bytes類型 15 client.send(msg) #發送信息,注意在python3中socket的發送只支持bytes類型 16 data = client.recv(1024) #等待接收服務端返回的信息 17 print("client recv:", data.decode()) 18 19 client.close() #關閉客戶端
例三(對例二稍加改造,就可實現一個簡單的ssh的服務端和客戶端):

1 #服務端
2 import socket 3 import os 4
5 address = ("localhost", 8888) #寫明服務端要監聽的地址,和端口號
6 server = socket.socket() #生成一個socket對象
7 server.bind(address) #用socket對象綁定要監聽的地址和端口
8 server.listen() #開始監聽
9
10 while True: 11 #一條連接關閉后,接入下一條連接
12 conn,addr = server.accept() #等帶新連接接入服務端,返回一個新的socket對象和地址,地址格式同前面格式
13 '''
14 Wait for an incoming connection. Return a new socket 15 representing the connection, and the address of the client. 16 '''
17 while True: 18 data = conn.recv(1024) #接收信息,寫明要接收信息的最大容量,單位為字節
19 # 沒收到消息,斷開本次連接
20 if not data: 21 break
22 cmd_result = os.popen(data.decode(), 'r').read() #執行命令,將命令執行結果保存到cmd_result
23 if len(cmd_result) == 0: 24 '''命令執行結果為空,認為接收到錯誤命令'''
25 cmd_result = "It's a wrong command..."
26
27 while True: 28 conn.send(str(len(cmd_result)).encode('utf-8')) #發送命令執行結果的長度
29 confirm = conn.recv(1024).decode() 30 '''客戶端確認收到數據長度,發送數據,否則重傳;且解決粘包問題'''
31 if confirm == "OK": 32 conn.send(cmd_result.encode('utf-8')) #對收到的信息處理,返回到客戶端
33 break
34 else : 35 continue
36
37
38 server.close() #關閉服務端

1 import socket 2
3 address = ("localhost", 8888) 4 client = socket.socket() 5 client.connect(address) 6
7 while True: 8 cmd = input("(command)>>>:").strip() 9 if len(cmd) == 0: 10 '''發送空命令時,結束本次循環'''
11 continue
12 if cmd == "#exit": 13 '''當檢測到#exit,客戶端與服務端斷開連接'''
14 break
15
16 client.send(cmd.encode()) #向服務端發送命令
17
18
19 cmd_result = '' #目前已接收的數據
20 size_data = 0 #目前已接收數據的長度
21 size_cmd_result = int(client.recv(1024).decode()) #接收命令執行結果的長度
22 client.send("OK".encode("utf-8")) #向服務端確認收到數據長度
23 while size_data < size_cmd_result: 24 '''命令的執行結果可能大於設置的接收buffersize,多次接收'''
25 data = client.recv(1024).decode() #每次接收的數據
26 size_data += len(data) 27 cmd_result += data 28
29 print(cmd_result) 30
31 client.close()
注:提供另一種接收思路,服務端可以在每次返送完命令執行結果后,再發送一個結束標志,當客戶端檢測到結束標志時停止循環接收。
例四(改造例三,就可以實現一個簡單的ftp的服務端和客戶端)

1 #/usr/bin/python3
2 #服務端
3 import socket 4 import os 5 import hashlib 6
7 address = ("0.0.0.0", 8888) #寫明服務端要監聽的地址,和端口號
8 server = socket.socket() #生成一個socket對象
9 server.bind(address) #用socket對象綁定要監聽的地址和端口
10 server.listen() #開始監聽
11
12 while True: 13 #一條連接關閉后,接入下一條連接
14 conn,addr = server.accept() #等帶新連接接入服務端,返回一個新的socket對象和地址,地址格式同前面格式
15 '''
16 Wait for an incoming connection. Return a new socket 17 representing the connection, and the address of the client. 18 '''
19 while True: 20 content = os.popen('ls', 'r').read() 21 conn.send(content.encode('utf-8')) #與客戶端建立連接后,將服務端有哪些文件發給客戶端,供客戶端選擇
22 filename = conn.recv(1024).decode() #接收客戶端發來的文件名
23
24 if os.path.isfile(filename): 25 '''文件存在,開始發送文件'''
26 file_md5 = hashlib.md5() #初始化MD5對象,用於傳輸完成后的校驗
27 file_size = os.stat(filename)[6] #讀取文件大小
28 conn.send(str(file_size).encode('utf-8')) #將文件size發給客戶端
29 confirm = conn.recv(1024).decode() #等待客戶端確認接收
30 if confirm == "OK": 31 '''發送文件數據'''
32 with open(filename, 'rb') as fp: 33 for line in fp: 34 file_md5.update(line) 35 conn.send(line) 36
37 client_md5 = conn.recv(1024).decode() #傳輸完成后接收客戶端發來的MD5
38 if file_md5.hexdigest() == client_md5: 39 '''確認文件傳輸未出錯'''
40 conn.send("Success...".encode('utf-8')) 41 else : 42 '''文件傳輸出錯,提示客戶端刪除重傳'''
43 conn.send("This file is changed, please delete it and try again...".encode('utf-8')) 44
45 #客戶端未確認接收,重試
46 else : 47 conn.send("Error, try again...".encode('utf-8')) 48 continue
49
50 else : 51 '''文件不存在,讓客戶端重新發送文件名'''
52 conn.send("The file name is wrong and try again".encode('utf-8')) 53
54 server.close() #關閉服務端

1 #/usr/bin/python3
2 import socket 3 import hashlib 4
5 address = ("192.168.56.50", 8888) 6 client = socket.socket() 7 client.connect(address) 8
9 while True: 10 content = client.recv(4096) #接收並打印服務端有哪些文件
11 print("Files List".center(75, '-')) 12 print(content.decode()) 13
14 #向服務端發送想要接收的文件名
15 filename = input("(You want to get)>>>:").strip() 16 if filename == '#exit': 17 break
18 client.send(filename.encode('utf-8')) 19
20 file_size = client.recv(1024).decode() 21 if file_size.isdigit(): 22 '''文件大小是不小於0的數字,則文件存在,准備接收'''
23 file_size = int(file_size) 24 if file_size >= 0: 25 data_size = 0 26 data_md5 = hashlib.md5() #初始化MD5對象,用以向服務端校驗
27 client.send("OK".encode('utf-8')) #向服務端確認接收數據
28 with open(filename, 'wb') as fp: 29 while data_size < file_size: 30 data = client.recv(1024) 31 data_md5.update(data) 32 data_size += len(data) 33 fp.write(data) 34 client.send(data_md5.hexdigest().encode('utf-8')) #發送服務端發送數據的MD5碼
35 message = client.recv(1024).decode() #接收並打印服務端的校驗信息
36 print(message) 37 print('\n\n\n') 38 else : 39 '''文件大小不是數字,則出錯,打印服務端的提示信息'''
40 print(file_size) 41 continue
42
43 client.close()
注意:如果代碼中有兩個(或兩個以上)socket.send()連在一起(中間無阻塞(比如time.slee(), socket.recv()等)),有粘包風險。
通過上面的代碼編寫,可以發現盡管socket已經簡化了網絡編程的過程,但還是給人一種面向過程的感覺。記下來的socketserver便解決了網絡服務端編程過於繁瑣的問題。
socketserver:
socketserver
模塊簡化了編寫網絡服務器的任務。
有四個基本的具體服務器類:
-
class
socketserver.
TCPServer
(server_address, RequestHandlerClass, bind_and_activate=True) -
這使用Internet TCP協議,它在客戶端和服務器之間提供連續的數據流。如果bind_and_activate為true,構造函數將自動嘗試調用
server_bind()
和server_activate()
。其他參數傳遞到BaseServer
基類。
-
class
socketserver.
UDPServer
(server_address, RequestHandlerClass, bind_and_activate=True) -
這使用數據報,其是可能在運輸中不按順序到達或丟失的信息的離散分組。參數與
TCPServer
相同。
-
class
socketserver.
UnixStreamServer
(server_address, RequestHandlerClass, bind_and_activate=True) -
class
socketserver.
UnixDatagramServer
(server_address, RequestHandlerClass, bind_and_activate=True) -
這些更常用的類與TCP和UDP類類似,但使用Unix域套接字;它們在非Unix平台上不可用。參數與
TCPServer
相同。
這四個類同時處理請求;每個請求必須在下一個請求開始之前完成。如果每個請求需要很長時間來完成,這是不合適的,因為它需要大量的計算,或者因為它返回了很多客戶端處理速度慢的數據。解決方案是創建一個單獨的進程或線程來處理每個請求; ForkingMixIn
和ThreadingMixIn
混合類可以用於支持異步行為。
創建服務器需要幾個步驟。首先,您必須通過對BaseRequestHandler
類進行子類化並覆蓋其handle()
方法來創建請求處理程序類;此方法將處理傳入請求。其次,您必須實例化一個服務器類,將它傳遞給服務器的地址和請求處理程序類。然后調用服務器對象的handle_request()
或serve_forever()
方法來處理一個或多個請求。最后,調用server_close()
關閉套接字。
當從ThreadingMixIn
繼承線程連接行為時,應該明確聲明您希望線程在突然關閉時的行為。ThreadingMixIn
類定義了一個屬性daemon_threads,它指示服務器是否應該等待線程終止。如果您希望線程自主行為,您應該明確地設置標志;默認值為False
,這意味着Python不會退出,直到ThreadingMixIn
創建的所有線程都退出。
#四個基本類的繼承關系
+------------+ | BaseServer | +------------+ | v +-----------+ +------------------+ | TCPServer |------->| UnixStreamServer | +-----------+ +------------------+ | v +-----------+ +--------------------+ | UDPServer |------->| UnixDatagramServer | +-----------+ +--------------------+
例五(我們使用socketserver實現例二):

1 import socketserver 2 3 class MyTCPRequestHandler(socketserver.BaseRequestHandler): 4 """ 5 The request handler class for our server. 6 7 It is instantiated once per connection to the server, and must 8 override the handle() method to implement communication to the 9 client. 10 """ 11 def handle(self): 12 """ 13 服務端和客戶端連接后,數據的交互由這個方法實現,這個方法必須重寫 14 """ 15 while True: 16 try: 17 self.data = self.request.recv(1024).strip() 18 print("server recv:", self.data.decode()) 19 self.request.send(self.data.upper()) 20 except ConnectionResetError: 21 break 22 23 if __name__ == "__main__": 24 HOST, PORT = "localhost", 6666 25 server = socketserver.TCPServer((HOST, PORT), MyTCPRequestHandler) 26 server.serve_forever() #處理請求 27 server.server_close() #關閉套接字
注:服務器類具有相同的外部方法和屬性,無論它們使用什么網絡協議。
例六(使用ThreadingMixIn可以輕松實現一對多同時服務(多線程)
):

1 import socketserver 2 3 class MyTCPRequestHandler(socketserver.BaseRequestHandler): 4 """ 5 The request handler class for our server. 6 7 It is instantiated once per connection to the server, and must 8 override the handle() method to implement communication to the 9 client. 10 """ 11 def handle(self): 12 """ 13 服務端和客戶端連接后,數據的交互由這個方法實現,這個方法必須重寫 14 """ 15 while True: 16 try: 17 self.data = self.request.recv(1024).strip() 18 print("server recv:", self.data.decode()) 19 self.request.send(self.data.upper()) 20 except ConnectionResetError: 21 break 22 23 if __name__ == "__main__": 24 HOST, PORT = "localhost", 6666 25 server = socketserver.ThreadingTCPServer((HOST, PORT), MyTCPRequestHandler) #同時處理多個連接 26 server.serve_forever() #處理請求 27 server.server_close() #關閉套接字 28 29 30 31 32 #ThreadingTCPServer的源碼 33 ''' 34 class ThreadingTCPServer(ThreadingMixIn, TCPServer): pass 35 '''
注:其他協議的多線程實現同上。
PS(本例中所涉及到的模塊使用參考):
socket模塊:http://www.cnblogs.com/God-Li/p/7625825.html
hashlib模塊:http://www.cnblogs.com/God-Li/p/7631604.html
os模塊:http://www.cnblogs.com/God-Li/p/7384227.html
socketserver模塊:http://python.usyiyi.cn/translate/python_352/library/socketserver.html#module-socketserver