第十七章 Python網絡編程

Socket簡介python

在網絡上的兩個程序經過一個雙向的通訊鏈接實現數據的交換,這個連接的一端稱爲一個Socket(套接字),用於描述IP地址和端口。shell

創建網絡通訊鏈接至少要一對端口號(Socket),Socket本質是編程接口(API),對TCP/IP的封裝,提供了網絡通訊能力。編程

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

Socket是面向C/S(客戶端/服務器)模型設計,客戶端在本地隨機申請一個惟一的Socket號,服務器擁有公開的socket,任何客戶端均可以向它發送鏈接請求和信息請求。bash

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

Socket數據處理流程如圖:網絡

wKiom1hFB3ihlqXzAAD9zEGLSeA858.png

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演示下。

先寫一個服務端:

#!/usr/bin/python
# -*- coding: utf-8 -*-
import socket
HOST = ''                 # 爲空表明全部可用的網卡
PORT = 50007              # 任意非特權端口
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.bind((HOST, PORT))
s.listen(1)   # 最大鏈接數
conn, addr = s.accept()   # 返回客戶端地址
print 'Connected by', addr
while 1:
    data = conn.recv(1024)   # 每次最大接收客戶端發來數據1024字節
    if not data: break       # 當沒有數據就退出死循環 
    print "Received: ", data # 打印接收的數據
    conn.sendall(data)       # 把接收的數據再發給客戶端
conn.close()

再寫一個客戶端:

#!/usr/bin/python
# -*- coding: utf-8 -*-
import socket
HOST = '192.168.1.120'    # 遠程主機IP
PORT = 50007              # 遠程主機端口
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((HOST, PORT))
s.sendall('Hello, world') # 發送數據
data = s.recv(1024)       # 接收服務端發來的數據
s.close()
print 'Received: ', data

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

# python socket-server.py
監聽中...
# 直到客戶端運行會接收到下面數據並退出
Connected by ('192.168.1.120', 37548)
Received:  Hello, world

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

# 若是端口監據說明服務端運行正常

# netstat -antp |grep 50007
tcp        0      0 0.0.0.0:50007           0.0.0.0:*               LISTEN      72878/python
# python socket-client.py
Received: Hello, world

經過實驗瞭解搭到Socket服務端工做有如下幾個步驟:

1)打開socket

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

3)監聽進來的鏈接

4)接受鏈接

5)處理數據

17.1.2 UDP編程

服務端:

import socket
HOST = ''               
PORT = 50007             
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
s.bind((HOST, PORT))
while 1:
    data, addr = s.recvfrom(1024)
    print 'Connected by', addr
    print "Received: ", data
    s.sendto("Hello %s"% repr(addr), addr)
conn.close()

客戶端:

import socket
HOST = '192.168.1.99'                 
PORT = 50007             
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
s.sendto(data, (HOST, PORT))
data = s.recv(1024)
s.close()
print 'Received: ', data

運行方式與TCP編程同樣。

使用UDP協議時,服務端就少了listen()和accept(),不須要創建鏈接就直接接收客戶端的數據,也是把數據直接發送給客戶端。

客戶端少了connect(),一樣直接經過sendto()給服務器發數據。

而TCP協議則前提先創建三次握手。

17.1.3 舉一個更直觀的socket通訊例子

客戶端發送bash命令,服務端接收到並執行,把返回結果迴應給客戶端。

服務端:

#!/usr/bin/python
# -*- coding: utf-8 -*-
import sys
import subprocess
import socket
HOST = ''               
PORT = 50007             
try:
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    s.bind((HOST, PORT))
    s.listen(1)
except socket.error as e:
    s.close()
    print e
    sys.exit(1)
while 1:
    conn, addr = s.accept()
    print 'Connected by', addr
    while 1:
        # 每次讀取1024字節
        data = conn.recv(1024)
        if not data: # 客戶端關閉服務端會收到一個空數據
            print repr(addr) + " close."
            conn.close()
            break     
        print "Received: ", data
        cmd = subprocess.Popen(data, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True)
        result_tuple = cmd.communicate()
        if cmd.returncode != 0 or cmd.returncode == None:
            result = result_tuple[1]
            # result = cmd.stderr.read()
        else:
            result = result_tuple[0]
            # result = cmd.stdout.read()  # 讀不到標準輸出,不知道爲啥,因此不用
        if result:
            conn.sendall(result)
        else:
            conn.sendall("return null")
s.close()

客戶端:

#!/usr/bin/python
# -*- coding: utf-8 -*-
import sys
import socket
HOST = '192.168.1.120'   
PORT = 50007             
try:
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    s.connect((HOST, PORT))
except socket.error as e:
    s.close()
    print e
    sys.exit(1)
while 1:
    cmd = raw_input("Please input command: ")
    if not cmd: continue
    s.sendall(cmd)
    recv_data = s.recv(1024)
    print 'Received: ', recv_data
s.close()

查看運行效果,先運行服務端,再運行客戶端:

# python socket-server.py
Connected by ('192.168.1.120', 45620)
Received:  ls
Received:  touch a.txt
Received:  ls

# python socket-client.py
Please input command: ls
Received: 
socket-client.py
socket-server.py
Please input command: touch a.txt
Received:  return null
Please input command: ls
Received: 
a.txt
socket-client.py
socket-server.py
Please input command:

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

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

>>> socket.gethostname()
'ubuntu'
>>> socket.gethostbyname(socket.gethostname())
'127.0.1.1'
>>> s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
>>> s.connect(('10.255.255.255', 0))
>>> s.getsockname()
('192.168.1.120', 35765)
>>> s.getsockname()[0]
'192.168.1.120'


博客地址:http://lizhenliang.blog.51cto.com

QQ羣:323779636(Shell/Python運維開發羣


17.2 SocketServer

ScoketServer是Socket服務端庫,比socket庫更高級,實現了多線程和多線程,併發處理多個客戶端請求。

下面是幾個經常使用的類:

SocketServer.TCPServer(server_address

RequestHandlerClassbind_and_activate=True)

服務器類,TCP協議

SocketServer.UDPServer(server_address

RequestHandlerClassbind_and_activate=True)

服務器類,UDP協議

SocketServer.BaseServer(server_address

RequestHandlerClass)

這個是全部服務器對象的超類。它定義了接口,不提供大多數方法,在子類中進行。
SocketServer.BaseRequestHandler 這個是全部請求處理對象的超類。它定義了接口,一個具體的請求處理程序子類必須定義一個新的handle()方法。
SocketServer.StreamRequestHandler 流式socket,根據socket生成讀寫socket用的兩個文件對象,調用rfile和wfile讀寫
SocketServer.DatagramRequestHandler 數據報socket,一樣生成rfile和wfile,但UDP不直接關聯socket。這裏rfile是由UDP中讀取的數據生成,wfile則是新建一個StringIO,用於寫數據
SocketServer.ForkingMixIn/ThreadingMixIn 多進程(分叉)/多線程實現異步。混合類,這個類不會直接實例化。用於實現處理多鏈接

SocketServer.BaseServer()對象有如下方法:

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編程

服務端:

#!/usr/bin/python
# -*- coding: utf-8 -*
import SocketServer
class MyTCPHandler(SocketServer.BaseRequestHandler):
    """
    請求處理程序類。
    每一個鏈接到服務器都要實例化一次,並且必須覆蓋handle()方法來實現與客戶端通訊
    """
    def handle(self):
        # self.request 接收客戶端數據
        self.data = self.request.recv(1024).strip()
        print "%s wrote:" % (self.client_address[0])
        print self.data
        # 把接收的數據轉爲大寫發給客戶端
        self.request.sendall(self.data.upper())
if __name__ == "__main__":
    HOST, PORT = "localhost", 9999
    # 建立服務器並綁定本地地址和端口
    server = SocketServer.TCPServer((HOST, PORT), MyTCPHandler)
    # 激活服務器,會一直運行,直到Ctrl-C中斷
    server.serve_forever()

另外一個請求處理程序類,利用流(類文件對象簡化通訊提供標準文件接口):

class MyTCPHandler(SocketServer.StreamRequestHandler):
    def handle(self):
        # self.rfile建立的是一個類文件對象處理程序,就能夠調用readline()而不是recv()
        self.data = self.rfile.readline().strip()
        print "%s wrote:" % (self.client_address[0])
        print self.data
        # 一樣,self.wfile是一個類文件對象,用於回覆客戶端
        self.wfile.write(self.data.upper())

客戶端:

import socket
import sys
HOST, PORT = "localhost", 9999
data = " ".join(sys.argv[1:])
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
try:
    sock.connect((HOST, PORT))
    sock.sendall(data + "\n")
    received = sock.recv(1024)
finally:
    sock.close()
print "Sent: %s" % data
print "Received: %s" % received

服務端結果:

# python TCPServer.py
127.0.0.1 wrote:
hello
127.0.0.1 wrote:
nice

客戶端結果:

# python TCPClient.py hello
Sent: hello
Received: HELLO
# python TCPClient.py nice
Sent: nice
Received: NICE

17.2.2 UDP編程

服務端:

import SocketServer
class MyTCPHandler(SocketServer.BaseRequestHandler):
    def handle(self):
        self.data = self.request[0].strip()
        self.socket = self.request[1]
        print "%s wrote:" % (self.client_address[0])
        print self.data
        self.socket.sendto(self.data.upper(), self.client_address)
if __name__ == "__main__":
    HOST, PORT = "localhost", 9999
    server = SocketServer.UDPServer((HOST, PORT), MyTCPHandler)
    server.serve_forever()

客戶端:

import socket
import sys
HOST, PORT = "localhost", 9999
data = " ".join(sys.argv[1:])
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sock.sendto(data + "\n", (HOST, PORT))
received = sock.recv(1024)
print "Sent: %s" % data
print "Received: %s" % received

與TCP執行結果同樣。

17.2.3 異步混合

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

ThreadingMixIn類的一個例子:

#!/usr/bin/python
# -*- coding: utf-8 -*
import socket
import threading
import SocketServer
class ThreadedTCPRequestHandler(SocketServer.BaseRequestHandler):
    def handle(self):
        data = self.request.recv(1024)
        cur_thread = threading.current_thread()
        response = "%s: %s" % (cur_thread.name, data)
        self.request.sendall(response)
class ThreadedTCPServer(SocketServer.ThreadingMixIn, SocketServer.TCPServer):
    pass
def client(ip, port, message):
    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    sock.connect((ip, port))
    try:
        sock.sendall(message)
        response = sock.recv(1024)
        print "Received: %s" % response
    finally:
        sock.close()
if __name__ == "__main__":
    # 端口0意味着隨機使用一個未使用的端口
    HOST, PORT = "localhost", 0
    server = ThreadedTCPServer((HOST, PORT), ThreadedTCPRequestHandler)
    ip, port = server.server_address
    # 服務器啓動一個線程,該線程將開始。每一個線程處理每一個請求
    server_thread = threading.Thread(target=server.serve_forever)
    # 做爲守護線程
    server_thread.daemon = True
    server_thread.start()
    print "Server loop running in thread:", server_thread.name
    client(ip, port, "Hello World 1")
    client(ip, port, "Hello World 2")
    client(ip, port, "Hello World 3")
    server.shutdown()
      server.server_close()

# python socket-server.py
Server loop running in thread: Thread-1
Received: Thread-2: Hello World 1
Received: Thread-3: Hello World 2
Received: Thread-4: Hello World 3
相關文章
相關標籤/搜索