天天看點

第十七章 Python網絡程式設計

Socket簡介

在網絡上的兩個程式通過一個雙向的通信連接配接實作資料的交換,這個連結的一端稱為一個Socket(套接字),用于描述IP位址和端口。

建立網絡通信連接配接至少要一對端口号(Socket),Socket本質是程式設計接口(API),對TCP/IP的封裝,提供了網絡通信能力。

每種服務都打開一個Socket,并綁定到端口上,不同的端口對應不同的服務,就像http對應80端口。

Socket是面向C/S(用戶端/伺服器)模型設計,用戶端在本地随機申請一個唯一的Socket号,伺服器擁有公開的socket,任何用戶端都可以向它發送連接配接請求和資訊請求。

比如:用手機打電話給10086客服,你的手機号就是用戶端,10086客服是服務端。必須在知道對方電話号碼前提下才能與對方通訊。

Socket資料處理流程如圖:

<a href="http://s1.51cto.com/wyfs02/M00/8B/1E/wKiom1hFB3ihlqXzAAD9zEGLSeA858.png" target="_blank"></a>

17.1 socket

在Python中提供此服務的子產品是socket和SocketServer,下面是socket常用的類、方法:

方法

描述

socket.socket([family[, type[, proto]]])

socket初始化函數,(位址族,socket類型,協定編号)協定編号預設0

socket.AF_INET

IPV4協定通信

socket.AF_INET6

IPV6協定通信

socket.SOCK_STREAM

socket類型,TCP

socket.SOCK_DGRAM

socket類型,UDP

socket.SOCK_RAW

原始socket,可以處理普通socker無法處理的封包,比如ICMP

socket.SOCK_RDM

更可靠的UDP類型,保證對方收到資料

socket.SOCK_SEQPACKET

可靠的連續資料包服務

socket.socket()對象有以下方法:

accept()

接受連接配接并傳回(socket object, address info),address是用戶端位址

bind(address)

綁定socket到本地位址,address是一個雙元素元組(host,port)

listen(backlog)

開始接收連接配接,backlog是最大連接配接數,預設1

connect(address)

連接配接socket到遠端位址

connect_ex(address)

連接配接socket到遠端位址,成功傳回0,錯誤傳回error值

getpeername()

傳回遠端端位址(hostaddr, port)

gettimeout()

傳回目前逾時的值,機關秒,如果沒有設定傳回none

recv(buffersize[, flags])

接收來自socket的資料,buffersize是接收資料量

send(data[, flags])

發送資料到socket,傳回值是發送的位元組數

sendall(data[, flags])

發送所有資料到socket,成功傳回none,失敗抛出異常

setblocking(flag)

設定socket為阻塞(flag是true)或非阻塞(flag是flase)

溫習下TCP與UDP差別:

TCP和UDP是OSI七層模型中傳輸層提供的協定,提供可靠端到端的傳輸服務。

TCP(Transmission Control Protocol,傳輸控制協定),面向連接配接協定,雙方先建立可靠的連接配接,再發送資料。适用于可靠性要求高的應用場景。

UDP(User Data Protocol,使用者資料報協定),面向非連接配接協定,不與對方建立連接配接,直接将資料包發送給對方,是以相對TCP傳輸速度快 。适用于可靠性要求低的應用場景。

17.1.1 TCP程式設計

下面建立一個服務端TCP協定的Socket示範下。

先寫一個服務端:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

<code>#!/usr/bin/python</code>

<code># -*- coding: utf-8 -*-</code>

<code>import</code> <code>socket</code>

<code>HOST </code><code>=</code> <code>''                 </code><code># 為空代表所有可用的網卡</code>

<code>PORT </code><code>=</code> <code>50007</code>              <code># 任意非特權端口</code>

<code>s </code><code>=</code> <code>socket.socket(socket.AF_INET, socket.SOCK_STREAM)</code>

<code>s.bind((HOST, PORT))</code>

<code>s.listen(</code><code>1</code><code>)   </code><code># 最大連接配接數</code>

<code>conn, addr </code><code>=</code> <code>s.accept()   </code><code># 傳回用戶端位址</code>

<code>print</code> <code>'Connected by'</code><code>, addr</code>

<code>while</code> <code>1</code><code>:</code>

<code>    </code><code>data </code><code>=</code> <code>conn.recv(</code><code>1024</code><code>)   </code><code># 每次最大接收用戶端發來資料1024位元組</code>

<code>    </code><code>if</code> <code>not</code> <code>data: </code><code>break</code>       <code># 當沒有資料就退出死循環 </code>

<code>    </code><code>print</code> <code>"Received: "</code><code>, data </code><code># 列印接收的資料</code>

<code>    </code><code>conn.sendall(data)       </code><code># 把接收的資料再發給用戶端</code>

<code>conn.close()</code>

再寫一個用戶端:

<code>HOST </code><code>=</code> <code>'192.168.1.120'</code>    <code># 遠端主機IP</code>

<code>PORT </code><code>=</code> <code>50007</code>              <code># 遠端主機端口</code>

<code>s.connect((HOST, PORT))</code>

<code>s.sendall(</code><code>'Hello, world'</code><code>) </code><code># 發送資料</code>

<code>data </code><code>=</code> <code>s.recv(</code><code>1024</code><code>)       </code><code># 接收服務端發來的資料</code>

<code>s.close()</code>

<code>print</code> <code>'Received: '</code><code>, data</code>

寫好後,打開一個終端視窗執行:

<code># python socket-server.py</code>

<code>監聽中...</code>

<code># 直到用戶端運作會接收到下面資料并退出</code>

<code>Connected by (</code><code>'192.168.1.120'</code><code>, </code><code>37548</code><code>)</code>

<code>Received:  Hello, world</code>

再打開一個終端視窗執行:

# 如果端口監聽說明服務端運作正常

<code># netstat -antp |grep 50007</code>

<code>tcp        </code><code>0</code>      <code>0</code> <code>0.0</code><code>.</code><code>0.0</code><code>:</code><code>50007</code>           <code>0.0</code><code>.</code><code>0.0</code><code>:</code><code>*</code>               <code>LISTEN      </code><code>72878</code><code>/</code><code>python</code>

<code># python socket-client.py</code>

<code>Received: Hello, world</code>

通過實驗了解搭到Socket服務端工作有以下幾個步驟:

1)打開socket

2)綁定到一個位址和端口

3)監聽進來的連接配接

4)接受連接配接

5)處理資料

17.1.2 UDP程式設計

服務端:

<code>HOST </code><code>=</code> <code>''               </code>

<code>PORT </code><code>=</code> <code>50007</code>             

<code>s </code><code>=</code> <code>socket.socket(socket.AF_INET, socket.SOCK_DGRAM)</code>

<code>    </code><code>data, addr </code><code>=</code> <code>s.recvfrom(</code><code>1024</code><code>)</code>

<code>    </code><code>print</code> <code>'Connected by'</code><code>, addr</code>

<code>    </code><code>print</code> <code>"Received: "</code><code>, data</code>

<code>    </code><code>s.sendto(</code><code>"Hello %s"</code><code>%</code> <code>repr</code><code>(addr), addr)</code>

用戶端:

<code>HOST </code><code>=</code> <code>'192.168.1.99'</code>                 

<code>s.sendto(data, (HOST, PORT))</code>

<code>data </code><code>=</code> <code>s.recv(</code><code>1024</code><code>)</code>

運作方式與TCP程式設計一樣。

使用UDP協定時,服務端就少了listen()和accept(),不需要建立連接配接就直接接收用戶端的資料,也是把資料直接發送給用戶端。

用戶端少了connect(),同樣直接通過sendto()給伺服器發資料。

而TCP協定則前提先建立三次握手。

17.1.3 舉一個更直覺的socket通信例子

用戶端發送bash指令,服務端接收到并執行,把傳回結果回應給用戶端。

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

<code>import</code> <code>sys</code>

<code>import</code> <code>subprocess</code>

<code>try</code><code>:</code>

<code>    </code><code>s </code><code>=</code> <code>socket.socket(socket.AF_INET, socket.SOCK_STREAM)</code>

<code>    </code><code>s.bind((HOST, PORT))</code>

<code>    </code><code>s.listen(</code><code>1</code><code>)</code>

<code>except</code> <code>socket.error as e:</code>

<code>    </code><code>s.close()</code>

<code>    </code><code>print</code> <code>e</code>

<code>    </code><code>sys.exit(</code><code>1</code><code>)</code>

<code>    </code><code>conn, addr </code><code>=</code> <code>s.accept()</code>

<code>    </code><code>while</code> <code>1</code><code>:</code>

<code>        </code><code># 每次讀取1024位元組</code>

<code>        </code><code>data </code><code>=</code> <code>conn.recv(</code><code>1024</code><code>)</code>

<code>        </code><code>if</code> <code>not</code> <code>data: </code><code># 用戶端關閉服務端會收到一個空資料</code>

<code>            </code><code>print</code> <code>repr</code><code>(addr) </code><code>+</code> <code>" close."</code>

<code>            </code><code>conn.close()</code>

<code>            </code><code>break</code>     

<code>        </code><code>print</code> <code>"Received: "</code><code>, data</code>

<code>        </code><code>cmd </code><code>=</code> <code>subprocess.Popen(data, stdout</code><code>=</code><code>subprocess.PIPE, stderr</code><code>=</code><code>subprocess.PIPE, shell</code><code>=</code><code>True</code><code>)</code>

<code>        </code><code>result_tuple </code><code>=</code> <code>cmd.communicate()</code>

<code>        </code><code>if</code> <code>cmd.returncode !</code><code>=</code> <code>0</code> <code>or</code> <code>cmd.returncode </code><code>=</code><code>=</code> <code>None</code><code>:</code>

<code>            </code><code>result </code><code>=</code> <code>result_tuple[</code><code>1</code><code>]</code>

<code>            </code><code># result = cmd.stderr.read()</code>

<code>        </code><code>else</code><code>:</code>

<code>            </code><code>result </code><code>=</code> <code>result_tuple[</code><code>0</code><code>]</code>

<code>            </code><code># result = cmd.stdout.read()  # 讀不到标準輸出,不知道為啥,是以不用</code>

<code>        </code><code>if</code> <code>result:</code>

<code>            </code><code>conn.sendall(result)</code>

<code>            </code><code>conn.sendall(</code><code>"return null"</code><code>)</code>

<code>HOST </code><code>=</code> <code>'192.168.1.120'</code>   

<code>    </code><code>s.connect((HOST, PORT))</code>

<code>    </code><code>cmd </code><code>=</code> <code>raw_input</code><code>(</code><code>"Please input command: "</code><code>)</code>

<code>    </code><code>if</code> <code>not</code> <code>cmd: </code><code>continue</code>

<code>    </code><code>s.sendall(cmd)</code>

<code>    </code><code>recv_data </code><code>=</code> <code>s.recv(</code><code>1024</code><code>)</code>

<code>    </code><code>print</code> <code>'Received: '</code><code>, recv_data</code>

檢視運作效果,先運作服務端,再運作用戶端:

<code>Connected by (</code><code>'192.168.1.120'</code><code>, </code><code>45620</code><code>)</code>

<code>Received:  ls</code>

<code>Received:  touch a.txt</code>

<code>Please </code><code>input</code> <code>command: ls</code>

<code>Received: </code>

<code>socket</code><code>-</code><code>client.py</code>

<code>socket</code><code>-</code><code>server.py</code>

<code>Please </code><code>input</code> <code>command: touch a.txt</code>

<code>Received:  </code><code>return</code> <code>null</code>

<code>a.txt</code>

<code>Please </code><code>input</code> <code>command:</code>

我想通過上面這個例子你已經大緻掌握了socket的通信過程。

再舉一個例子,通過socket擷取本機網卡IP:

<code>&gt;&gt;&gt; socket.gethostname()</code>

<code>'ubuntu'</code>

<code>&gt;&gt;&gt; socket.gethostbyname(socket.gethostname())</code>

<code>'127.0.1.1'</code>

<code>&gt;&gt;&gt; s </code><code>=</code> <code>socket.socket(socket.AF_INET, socket.SOCK_DGRAM)</code>

<code>&gt;&gt;&gt; s.connect((</code><code>'10.255.255.255'</code><code>, </code><code>0</code><code>))</code>

<code>&gt;&gt;&gt; s.getsockname()</code>

<code>(</code><code>'192.168.1.120'</code><code>, </code><code>35765</code><code>)</code>

<code>&gt;&gt;&gt; s.getsockname()[</code><code>0</code><code>]</code>

<code>'192.168.1.120'</code>

部落格位址:http://lizhenliang.blog.51cto.com

QQ群:323779636(Shell/Python運維開發群)

17.2 SocketServer

ScoketServer是Socket服務端庫,比socket庫更進階,實作了多線程和多線程,并發處理多個用戶端請求。

下面是幾個常用的類:

<code>SocketServer</code><code>.</code><code>TCPServer</code>(server_address, 

RequestHandlerClass, bind_and_activate=True)

伺服器類,TCP協定

<code>SocketServer</code><code>.</code><code>UDPServer</code>(server_address, 

伺服器類,UDP協定

<code>SocketServer</code><code>.</code><code>BaseServer</code>(server_address, 

RequestHandlerClass)

這個是所有伺服器對象的超類。它定義了接口,不提供大多數方法,在子類中進行。

<code>SocketServer</code><code>.</code><code>BaseRequestHandler</code>

這個是所有請求處理對象的超類。它定義了接口,一個具體的請求處理程式子類必須定義一個新的handle()方法。

SocketServer.StreamRequestHandler

流式socket,根據socket生成讀寫socket用的兩個檔案對象,調用rfile和wfile讀寫

SocketServer.DatagramRequestHandler

資料報socket,同樣生成rfile和wfile,但UDP不直接關聯socket。這裡rfile是由UDP中讀取的資料生成,wfile則是建立一個StringIO,用于寫資料

SocketServer.ForkingMixIn/ThreadingMixIn

多程序(分叉)/多線程實作異步。混合類,這個類不會直接執行個體化。用于實作處理多連接配接

<code>SocketServer</code><code>.</code><code>BaseServer()對象有以下方法:</code>

fileno()

傳回一個整數檔案描述符上伺服器監聽的套接字

handle_request()

處理一個請求

serve_forever(poll_interval=0.5)

處理,直至有明确要求shutdown()的請求。輪訓關機每poll_interval秒

shutdown()

告訴serve_forever()循環停止并等待

server_close()

清理伺服器

address_family

位址族

server_address

監聽的位址

RequestHandlerClass

使用者提供的請求處理類

socket

socket對象上的伺服器将監聽傳入的請求

allow_reuse_address

伺服器是否允許位址的重用。預設False

request_queue_size

請求隊列的大小。

socket_type

socket類型。socket.SOCK_STREAM或socket.SOCK_DGRAM

timeout

逾時時間,以秒為機關

finish_request()

實際處理通過執行個體請求RequestHandleClass并調用其handle()方法

get_request()

必須接受從socket的請求,并傳回

handle_error(request, client_address)

如果這個函數被條用handle()

process_request(request, client_address)

?

server_activate()

server_bind()

由伺服器構造函數調用的套接字綁定到所需的位址

verify_request(request, client_address)

傳回一個布爾值,如果該值是True,則該請求将被處理,如果是False,該請求将被拒絕。

建立一個伺服器需要幾個步驟:

1)建立類,繼承請求處理類(BaseRequestHandler),并重載其handle()方法,此方法将處理傳入的請求

2)執行個體化伺服器類之一,它傳遞伺服器的位址和請求處理程式類

3)調用handle_request()或serve_forever()伺服器對象的方法來處理一個或多個請求

4)調用server_close()關閉套接字

17.2.1 TCP程式設計

<code># -*- coding: utf-8 -*</code>

<code>import</code> <code>SocketServer</code>

<code>class</code> <code>MyTCPHandler(SocketServer.BaseRequestHandler):</code>

<code>    </code><code>"""</code>

<code>    </code><code>請求處理程式類。</code>

<code>    </code><code>每個連接配接到伺服器都要執行個體化一次,而且必須覆寫handle()方法來實作與用戶端通信</code>

<code>    </code><code>def</code> <code>handle(</code><code>self</code><code>):</code>

<code>        </code><code># self.request 接收用戶端資料</code>

<code>        </code><code>self</code><code>.data </code><code>=</code> <code>self</code><code>.request.recv(</code><code>1024</code><code>).strip()</code>

<code>        </code><code>print</code> <code>"%s wrote:"</code> <code>%</code> <code>(</code><code>self</code><code>.client_address[</code><code>0</code><code>])</code>

<code>        </code><code>print</code> <code>self</code><code>.data</code>

<code>        </code><code># 把接收的資料轉為大寫發給用戶端</code>

<code>        </code><code>self</code><code>.request.sendall(</code><code>self</code><code>.data.upper())</code>

<code>if</code> <code>__name__ </code><code>=</code><code>=</code> <code>"__main__"</code><code>:</code>

<code>    </code><code>HOST, PORT </code><code>=</code> <code>"localhost"</code><code>, </code><code>9999</code>

<code>    </code><code># 建立伺服器并綁定本地位址和端口</code>

<code>    </code><code>server </code><code>=</code> <code>SocketServer.TCPServer((HOST, PORT), MyTCPHandler)</code>

<code>    </code><code># 激活伺服器,會一直運作,直到Ctrl-C中斷</code>

<code>    </code><code>server.serve_forever()</code>

另一個請求處理程式類,利用流(類檔案對象簡化通信提供标準檔案接口):

<code>class</code> <code>MyTCPHandler(SocketServer.StreamRequestHandler):</code>

<code>        </code><code># self.rfile建立的是一個類檔案對象處理程式,就可以調用readline()而不是recv()</code>

<code>        </code><code>self</code><code>.data </code><code>=</code> <code>self</code><code>.rfile.readline().strip()</code>

<code>        </code><code># 同樣,self.wfile是一個類檔案對象,用于回複用戶端</code>

<code>        </code><code>self</code><code>.wfile.write(</code><code>self</code><code>.data.upper())</code>

<code>HOST, PORT </code><code>=</code> <code>"localhost"</code><code>, </code><code>9999</code>

<code>data </code><code>=</code> <code>" "</code><code>.join(sys.argv[</code><code>1</code><code>:])</code>

<code>sock </code><code>=</code> <code>socket.socket(socket.AF_INET, socket.SOCK_STREAM)</code>

<code>    </code><code>sock.connect((HOST, PORT))</code>

<code>    </code><code>sock.sendall(data </code><code>+</code> <code>"\n"</code><code>)</code>

<code>    </code><code>received </code><code>=</code> <code>sock.recv(</code><code>1024</code><code>)</code>

<code>finally</code><code>:</code>

<code>    </code><code>sock.close()</code>

<code>print</code> <code>"Sent: %s"</code> <code>%</code> <code>data</code>

<code>print</code> <code>"Received: %s"</code> <code>%</code> <code>received</code>

服務端結果:

<code># python TCPServer.py</code>

<code>127.0</code><code>.</code><code>0.1</code> <code>wrote:</code>

<code>hello</code>

<code>nice</code>

用戶端結果:

<code># python TCPClient.py hello</code>

<code>Sent: hello</code>

<code>Received: HELLO</code>

<code># python TCPClient.py nice</code>

<code>Sent: nice</code>

<code>Received: NICE</code>

17.2.2 UDP程式設計

<code>        </code><code>self</code><code>.data </code><code>=</code> <code>self</code><code>.request[</code><code>0</code><code>].strip()</code>

<code>        </code><code>self</code><code>.socket </code><code>=</code> <code>self</code><code>.request[</code><code>1</code><code>]</code>

<code>        </code><code>self</code><code>.socket.sendto(</code><code>self</code><code>.data.upper(), </code><code>self</code><code>.client_address)</code>

<code>    </code><code>server </code><code>=</code> <code>SocketServer.UDPServer((HOST, PORT), MyTCPHandler)</code>

<code>sock </code><code>=</code> <code>socket.socket(socket.AF_INET, socket.SOCK_DGRAM)</code>

<code>sock.sendto(data </code><code>+</code> <code>"\n"</code><code>, (HOST, PORT))</code>

<code>received </code><code>=</code> <code>sock.recv(</code><code>1024</code><code>)</code>

與TCP執行結果一樣。

17.2.3 異步混合

建立異步處理,使用ThreadingMixIn和ForkingMixIn類。

ThreadingMixIn類的一個例子:

<code>import</code> <code>threading</code>

<code>class</code> <code>ThreadedTCPRequestHandler(SocketServer.BaseRequestHandler):</code>

<code>        </code><code>data </code><code>=</code> <code>self</code><code>.request.recv(</code><code>1024</code><code>)</code>

<code>        </code><code>cur_thread </code><code>=</code> <code>threading.current_thread()</code>

<code>        </code><code>response </code><code>=</code> <code>"%s: %s"</code> <code>%</code> <code>(cur_thread.name, data)</code>

<code>        </code><code>self</code><code>.request.sendall(response)</code>

<code>class</code> <code>ThreadedTCPServer(SocketServer.ThreadingMixIn, SocketServer.TCPServer):</code>

<code>    </code><code>pass</code>

<code>def</code> <code>client(ip, port, message):</code>

<code>    </code><code>sock </code><code>=</code> <code>socket.socket(socket.AF_INET, socket.SOCK_STREAM)</code>

<code>    </code><code>sock.connect((ip, port))</code>

<code>    </code><code>try</code><code>:</code>

<code>        </code><code>sock.sendall(message)</code>

<code>        </code><code>response </code><code>=</code> <code>sock.recv(</code><code>1024</code><code>)</code>

<code>        </code><code>print</code> <code>"Received: %s"</code> <code>%</code> <code>response</code>

<code>    </code><code>finally</code><code>:</code>

<code>        </code><code>sock.close()</code>

<code>    </code><code># 端口0意味着随機使用一個未使用的端口</code>

<code>    </code><code>HOST, PORT </code><code>=</code> <code>"localhost"</code><code>, </code><code>0</code>

<code>    </code><code>server </code><code>=</code> <code>ThreadedTCPServer((HOST, PORT), ThreadedTCPRequestHandler)</code>

<code>    </code><code>ip, port </code><code>=</code> <code>server.server_address</code>

<code>    </code><code># 伺服器啟動一個線程,該線程将開始。每個線程處理每個請求</code>

<code>    </code><code>server_thread </code><code>=</code> <code>threading.Thread(target</code><code>=</code><code>server.serve_forever)</code>

<code>    </code><code># 作為守護線程</code>

<code>    </code><code>server_thread.daemon </code><code>=</code> <code>True</code>

<code>    </code><code>server_thread.start()</code>

<code>    </code><code>print</code> <code>"Server loop running in thread:"</code><code>, server_thread.name</code>

<code>    </code><code>client(ip, port, </code><code>"Hello World 1"</code><code>)</code>

<code>    </code><code>client(ip, port, </code><code>"Hello World 2"</code><code>)</code>

<code>    </code><code>client(ip, port, </code><code>"Hello World 3"</code><code>)</code>

<code>    </code><code>server.shutdown()</code>

<code>      </code><code>server.server_close()</code>

<code>Server loop running </code><code>in</code> <code>thread: Thread</code><code>-</code><code>1</code>

<code>Received: Thread</code><code>-</code><code>2</code><code>: Hello World </code><code>1</code>

<code>Received: Thread</code><code>-</code><code>3</code><code>: Hello World </code><code>2</code>

<code>Received: Thread</code><code>-</code><code>4</code><code>: Hello World </code><code>3</code>

<code></code>

本文轉自 李振良OK 51CTO部落格,原文連結:http://blog.51cto.com/lizhenliang/1879549,如需轉載請自行聯系原作者