Socket簡介python
在網絡上的兩個程序經過一個雙向的通訊鏈接實現數據的交換,這個連接的一端稱爲一個Socket(套接字),用於描述IP地址和端口。shell
創建網絡通訊鏈接至少要一對端口號(Socket),Socket本質是編程接口(API),對TCP/IP的封裝,提供了網絡通訊能力。編程
每種服務都打開一個Socket,並綁定到端口上,不一樣的端口對應不一樣的服務,就像http對應80端口。ubuntu
Socket是面向C/S(客戶端/服務器)模型設計,客戶端在本地隨機申請一個惟一的Socket號,服務器擁有公開的socket,任何客戶端均可以向它發送鏈接請求和信息請求。bash
好比:用手機打電話給10086客服,你的手機號就是客戶端,10086客服是服務端。必須在知道對方電話號碼前提下才能與對方通信。服務器
Socket數據處理流程如圖:網絡
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庫更高級,實現了多線程和多線程,併發處理多個客戶端請求。
下面是幾個經常使用的類:
RequestHandlerClass, bind_and_activate=True) |
服務器類,TCP協議 |
RequestHandlerClass, bind_and_activate=True) |
服務器類,UDP協議 |
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