本地的進程間通訊(IPC)有不少種方式,但能夠總結爲下面4類:php
但這些都不是本文的主題!咱們要討論的是網絡中進程之間如何通訊?首要解決的問題是如何惟一標識一個進程,不然通訊無從談起!在本地能夠經過進程PID來惟一標識一個進程,可是在網絡中這是行不通的。其實TCP/IP協議族已經幫咱們解決了這個問題,網絡層的「ip地址」能夠惟一標識網絡中的主機,而傳輸層的「協議+端口」能夠惟一標識主機中的應用程序(進程)。這樣利用三元組(ip地址,協議,端口)就能夠標識網絡的進程了,網絡中的進程通訊就能夠利用這個標誌與其它進程進行交互。html
使用TCP/IP協議的應用程序一般採用應用編程接口:UNIX BSD的套接字(socket)和UNIX System V的TLI(已經被淘汰),來實現網絡進程之間的通訊。就目前而言,幾乎全部的應用程序都是採用socket,而如今又是網絡時代,網絡中進程通訊是無處不在,這就是我爲何說「一切皆socket」。python
網絡編程對全部開發語言都是同樣的,Python也不例外。用Python進行網絡編程,就是在Python程序自己這個進程內,鏈接別的服務器進程的通訊端口進行通訊。linux
(1) IP、TCP和UDP
當您編寫socket應用程序的時候,您能夠在使用TCP仍是使用UDP之間作出選擇。它們都有各自的優勢和缺點。
TCP是流協議,而UDP是數據報協議。換句話說,TCP在客戶機和服務器之間創建持續的開放鏈接,在該鏈接的生命期內,字節能夠經過該鏈接寫出(而且保證順序正確)。然而,經過 TCP 寫出的字節沒有內置的結構,因此須要高層協議在被傳輸的字節流內部分隔數據記錄和字段。
另外一方面,UDP不須要在客戶機和服務器之間創建鏈接,它只是在地址之間傳輸報文。UDP的一個很好特性在於它的包是自分隔的(self-delimiting),也就是一個數據報都準確地指出它的開始和結束位置。然而,UDP的一個可能的缺點在於,它不保證包將會按順序到達,甚至根本就不保證。固然,創建在UDP之上的高層協議可能會提供握手和確認功能。
對於理解TCP和UDP之間的區別來講,一個有用的類比就是電話呼叫和郵寄信件之間的區別。在呼叫者用鈴聲通知接收者,而且接收者拿起聽筒以前,電話呼叫不是活動的。只要沒有一方掛斷,該電話信道就保持活動,可是在通話期間,他們能夠自由地想說多少就說多少。來自任何一方的談話都按臨時的順序發生。另外一方面,當你發一封信的時候,郵局在投遞時既不對接收方是否存在做任何保證,也不對信件投遞將花多長時間作出有力保證。接收方可能按與信件的發送順序不一樣的順序接收不一樣的信件,而且發送方也可能在他們發送信件是交替地接收郵件。與(理想的)郵政服務不一樣,沒法送達的信件老是被送到死信辦公室處理,而再也不返回給發送。
編程
(2)對等方、端口、名稱和地址
除了TCP和UDP協議之外,通訊一方(客戶機或者服務器)還須要知道的關於與之通訊的對方機器的兩件事情:IP地址或者端口。IP地址是一個32位的數據值,爲了人們好記,通常用圓點分開的4組數字的形式來表示,好比:64.41.64.172。端口是一個16位的數據值,一般被簡單地表示爲一個小於65536的數字。大多數狀況下,該值介於10到100的範圍內。一個IP地址獲取送到某臺機器的一個數據包,而一個端口讓機器決定將該數據包交給哪一個進程/服務(若是有的話)。這種解釋略顯簡單,但基本思路是正確的。
上面的描述幾乎都是正確的,但它也遺漏了一些東西。大多數時候,當人們考慮Internet主機(對等方)時,咱們都不會記憶諸如64.41.64.172這樣的數字,而是記憶諸如gnosis.cx這樣的名稱。爲了找到與某個特定主機名稱相關聯的IP地址,通常都使用域名服務器(DNS),可是有時會首先使用本地查找(常常是經過/etc/hosts的內容)。對於本教程,咱們將通常地假設有一個IP地址可用,不過下面討論編寫名稱查找代碼。
windows
(3)主機名稱解析
命令行實用程序nslookup能夠被用來根據符號名稱查找主機IP地址。實際上,許多常見的實用程序,好比ping或者網絡配置工具,也會順便作一樣的事情。可是以編程方式作這樣的事情很簡單。設計模式
======================TCP/IP======================
應用層: 它只負責產生相應格式的數據 ssh ftp nfs cifs dns http smtp pop3
-----------------------------------
傳輸層: 定義數據傳輸的兩種模式:
TCP(傳輸控制協議:面向鏈接,可靠的,效率相對不高)
UDP(用戶數據報協議:非面向鏈接,不可靠的,但效率高)
-----------------------------------
網絡層: 鏈接不一樣的網絡如以太網、令牌環網
IP (路由,分片) 、ICMP、 IGMP
ARP ( 地址解析協議,做用是將IP解析成MAC )
-----------------------------------
數據鏈路層: 以太網傳輸
-----------------------------------
物理層: 主要任務是規定各類傳輸介質和接口與傳輸信號相關的一些特性
-----------------------------------
數組
TCP/IP(Transmission Control Protocol/Internet Protocol)即傳輸控制協議/網間協議,是一個工業標準的協議集,它是爲廣域網(WANs)設計的。瀏覽器
TCP socket 因爲在通向前須要創建鏈接,因此其模式較 UDP socket 負責些。緩存
UDP(User Data Protocol,用戶數據報協議)是與TCP相對應的協議。它是屬於TCP/IP協議族中的一種。如圖:
UDP Socket圖:
UDP socket server 端代碼在進行bind後,無需調用listen方法。
TCP/IP協議族包括運輸層、網絡層、鏈路層,
而socket所在位置如圖,Socket是應用層與TCP/IP協議族通訊的中間軟件抽象層。
socket起源於Unix,而Unix/Linux基本哲學之一就是「一切皆文件」,均可以用「打開open –> 讀寫write/read –> 關閉close」模式來操做。Socket就是該模式的一個實現,socket便是一種特殊的文件,一些socket函數就是對其進行的操做(讀/寫IO、打開、關閉).
說白了Socket是應用層與TCP/IP協議族通訊的中間軟件抽象層,它是一組接口。在設計模式中,Socket其實就是一個門面模式,它把複雜的TCP/IP協議族隱藏在Socket接口後面,對用戶來講,一組簡單的接口就是所有,讓Socket去組織數據,以符合指定的協議。
注意:其實socket也沒有層的概念,它只是一個facade設計模式的應用,讓編程變的更簡單。是一個軟件抽象層。在網絡編程中,咱們大量用的都是經過socket實現的。
Socket是網絡編程的一個抽象概念。一般咱們用一個Socket表示「打開了一個網絡連接」,而打開一個Socket須要知道目標計算機的IP地址和端口號,再指定協議類型便可。
咱們知道tcp創建鏈接要進行「三次握手」,即交換三個分組。大體流程以下:
只有就完了三次握手,可是這個三次握手發生在socket的那幾個函數中呢?請看下圖:
圖一、socket中發送的TCP三次握手
從圖中能夠看出,當客戶端調用connect時,觸發了鏈接請求,向服務器發送了SYN J包,這時connect進入阻塞狀態;服務器監聽到鏈接請求,即收到SYN J包,調用accept函數接收請求向客戶端發送SYN K ,ACK J+1,這時accept進入阻塞狀態;客戶端收到服務器的SYN K ,ACK J+1以後,這時connect返回,並對SYN K進行確認;服務器收到ACK K+1時,accept返回,至此三次握手完畢,鏈接創建。
總結:客戶端的connect在三次握手的第二個次返回,而服務器端的accept在三次握手的第三次返回。
上面介紹了socket中TCP的三次握手創建過程,及其涉及的socket函數。如今咱們介紹socket中的四次握手釋放鏈接的過程,請看下圖:
圖二、socket中發送的TCP四次握手
圖示過程以下:
這樣每一個方向上都有一個FIN和ACK。
Python 提供了兩個級別訪問的網絡服務。:
Socket又稱"套接字",應用程序一般經過"套接字"向網絡發出請求或者應答網絡請求,使主機間或者一臺計算機上的進程間能夠通信。
socket和file的區別:
服務器端先初始化Socket,而後與端口綁定(bind),對端口進行監聽(listen),調用accept阻塞,等待客戶端鏈接。在這時若是有個客戶端初始化一個Socket,而後鏈接服務器(connect),若是鏈接成功,這時客戶端與服務器端的鏈接就創建了。客戶端發送數據請求,服務器端接收請求並處理請求,而後把迴應數據發送給客戶端,客戶端讀取數據,最後關閉鏈接,一次交互結束。
Python 中,咱們用 socket()函數來建立套接字,語法格式以下:
socket.socket([family[, type[, proto]]])
SOCK_STREAM
或SOCK_DGRAM
咱們使用 socket 模塊的 socket 函數來建立一個 socket 對象。socket 對象能夠經過調用其餘函數來設置一個 socket 服務。
如今咱們能夠經過調用 bind(hostname, port) 函數來指定服務的 port(端口)。
接着,咱們調用 socket 對象的 accept 方法。該方法等待客戶端的鏈接,並返回 connection 對象,表示已鏈接到客戶端。
完整代碼以下:
#!/usr/bin/python3 # 文件名:server.py # 導入 socket、sys 模塊 import socket import sys # 建立 socket 對象 serversocket = socket.socket( socket.AF_INET, socket.SOCK_STREAM) # 獲取本地主機名 host = socket.gethostname() port = 9999 # 綁定端口 serversocket.bind((host, port)) # 設置最大鏈接數,超事後排隊 serversocket.listen(5) while True: # 創建客戶端鏈接 clientsocket,addr = serversocket.accept() print("鏈接地址: %s" % str(addr)) msg='歡迎訪問python教程!'+ "\r\n" clientsocket.send(msg.encode('utf-8')) clientsocket.close()
接下來咱們寫一個簡單的客戶端實例鏈接到以上建立的服務。端口號爲 12345。
socket.connect(hosname, port ) 方法打開一個 TCP 鏈接到主機爲 hostname 端口爲 port 的服務商。鏈接後咱們就能夠從服務端後期數據,記住,操做完成後須要關閉鏈接。
完整代碼以下:
#!/usr/bin/python3 # 文件名:client.py # 導入 socket、sys 模塊 import socket import sys # 建立 socket 對象 s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # 獲取本地主機名 host = socket.gethostname() # 設置端口好 port = 9999 # 鏈接服務,指定主機和端口 s.connect((host, port)) # 接收小於 1024 字節的數據 msg = s.recv(1024) s.close() print (msg.decode('utf-8'))
先執行server端,而後打開client端就能看到結果
大多數鏈接都是可靠的TCP鏈接。建立TCP鏈接時,主動發起鏈接的叫客戶端,被動響應鏈接的叫服務器。
舉個例子,當咱們在瀏覽器中訪問新浪時,咱們本身的計算機就是客戶端,瀏覽器會主動向新浪的服務器發起鏈接。若是一切順利,新浪的服務器接受了咱們的鏈接,一個TCP鏈接就創建起來的,後面的通訊就是發送網頁內容了。
因此,咱們要建立一個基於TCP鏈接的Socket,能夠這樣作:
# 導入socket庫: import socket # 建立一個socket: s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # 創建鏈接: s.connect(('www.sina.com.cn', 80))
建立Socket
時,AF_INET
指定使用IPv4協議,若是要用更先進的IPv6,就指定爲AF_INET6
。SOCK_STREAM
指定使用面向流的TCP協議,這樣,一個Socket
對象就建立成功,可是尚未創建鏈接。
客戶端要主動發起TCP鏈接,必須知道服務器的IP地址和端口號。新浪網站的IP地址能夠用域名www.sina.com.cn
自動轉換到IP地址,可是怎麼知道新浪服務器的端口號呢?
答案是做爲服務器,提供什麼樣的服務,端口號就必須固定下來。因爲咱們想要訪問網頁,所以新浪提供網頁服務的服務器必須把端口號固定在80
端口,由於80
端口是Web服務的標準端口。其餘服務都有對應的標準端口號,例如SMTP服務是25
端口,FTP服務是21
端口,等等。端口號小於1024的是Internet標準服務的端口,端口號大於1024的,能夠任意使用。
所以,咱們鏈接新浪服務器的代碼以下:
s.connect(('www.sina.com.cn', 80))
注意參數是一個tuple
,包含地址和端口號。
創建TCP鏈接後,咱們就能夠向新浪服務器發送請求,要求返回首頁的內容:
# 發送數據: s.send(b'GET / HTTP/1.1\r\nHost: www.sina.com.cn\r\nConnection: close\r\n\r\n')
TCP鏈接建立的是雙向通道,雙方均可以同時給對方發數據。可是誰先發誰後發,怎麼協調,要根據具體的協議來決定。例如,HTTP協議規定客戶端必須先發請求給服務器,服務器收到後才發數據給客戶端。
發送的文本格式必須符合HTTP標準,若是格式沒問題,接下來就能夠接收新浪服務器返回的數據了:
接收數據時,調用recv(max)
方法,一次最多接收指定的字節數,所以,在一個while循環中反覆接收,直到recv()
返回空數據,表示接收完畢,退出循環。
當咱們接收完數據後,調用close()
方法關閉Socket,這樣,一次完整的網絡通訊就結束了:
# 關閉鏈接: s.close()
接收到的數據包括HTTP頭和網頁自己,咱們只須要把HTTP頭和網頁分離一下,把HTTP頭打印出來,網頁內容保存到文件:
header, html = data.split(b'\r\n\r\n', 1) print(header.decode('utf-8')) # 把接收的數據寫入文件: with open('sina.html', 'wb') as f: f.write(html)
如今,只須要在瀏覽器中打開這個sina.html
文件,就能夠看到新浪的首頁了。
和客戶端編程相比,服務器編程就要複雜一些。
服務器進程首先要綁定一個端口並監聽來自其餘客戶端的鏈接。若是某個客戶端鏈接過來了,服務器就與該客戶端創建Socket鏈接,隨後的通訊就靠這個Socket鏈接了。
因此,服務器會打開固定端口(好比80)監聽,每來一個客戶端鏈接,就建立該Socket鏈接。因爲服務器會有大量來自客戶端的鏈接,因此,服務器要可以區分一個Socket鏈接是和哪一個客戶端綁定的。一個Socket依賴4項:服務器地址、服務器端口、客戶端地址、客戶端端口來惟一肯定一個Socket。
可是服務器還須要同時響應多個客戶端的請求,因此,每一個鏈接都須要一個新的進程或者新的線程來處理,不然,服務器一次就只能服務一個客戶端了。
咱們來編寫一個簡單的服務器程序,它接收客戶端鏈接,把客戶端發過來的字符串加上Hello
再發回去。
首先,建立一個基於IPv4和TCP協議的Socket:
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
而後,咱們要綁定監聽的地址和端口。服務器可能有多塊網卡,能夠綁定到某一塊網卡的IP地址上,也能夠用0.0.0.0
綁定到全部的網絡地址,還能夠用127.0.0.1
綁定到本機地址。127.0.0.1
是一個特殊的IP地址,表示本機地址,若是綁定到這個地址,客戶端必須同時在本機運行才能鏈接,也就是說,外部的計算機沒法鏈接進來。
端口號須要預先指定。由於咱們寫的這個服務不是標準服務,因此用9999
這個端口號。請注意,小於1024
的端口號必需要有管理員權限才能綁定:
# 監聽端口: s.bind(('127.0.0.1', 9999))
緊接着,調用listen()
方法開始監聽端口,傳入的參數指定等待鏈接的最大數量:
s.listen(5) print('Waiting for connection...')
接下來,服務器程序經過一個永久循環來接受來自客戶端的鏈接,accept()
會等待並返回一個客戶端的鏈接:
while True: # 接受一個新鏈接: sock, addr = s.accept() # 建立新線程來處理TCP鏈接: t = threading.Thread(target=tcplink, args=(sock, addr)) t.start()
每一個鏈接都必須建立新線程(或進程)來處理,不然,單線程在處理鏈接的過程當中,沒法接受其餘客戶端的鏈接:
def tcplink(sock, addr): print('Accept new connection from %s:%s...' % addr) sock.send(b'Welcome!') while True: data = sock.recv(1024) time.sleep(1) if not data or data.decode('utf-8') == 'exit': break sock.send(('Hello, %s!' % data.decode('utf-8')).encode('utf-8')) sock.close() print('Connection from %s:%s closed.' % addr)
鏈接創建後,服務器首先發一條歡迎消息,而後等待客戶端數據,並加上Hello
再發送給客戶端。若是客戶端發送了exit
字符串,就直接關閉鏈接。
要測試這個服務器程序,咱們還須要編寫一個客戶端程序:
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # 創建鏈接: s.connect(('127.0.0.1', 9999)) # 接收歡迎消息: print(s.recv(1024).decode('utf-8')) for data in [b'Michael', b'Tracy', b'Sarah']: # 發送數據: s.send(data) print(s.recv(1024).decode('utf-8')) s.send(b'exit') s.close()
咱們須要打開兩個命令行窗口,一個運行服務器程序,另外一個運行客戶端程序,就能夠看到效果了:
TCP是創建可靠鏈接,而且通訊雙方均可以以流的形式發送數據。相對TCP,UDP則是面向無鏈接的協議。
使用UDP協議時,不須要創建鏈接,只須要知道對方的IP地址和端口號,就能夠直接發數據包。可是,能不能到達就不知道了。
雖然用UDP傳輸數據不可靠,但它的優勢是和TCP比,速度快,對於不要求可靠到達的數據,就可使用UDP協議。
咱們來看看如何經過UDP協議傳輸數據。和TCP相似,使用UDP的通訊雙方也分爲客戶端和服務器。服務器首先須要綁定端口:
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) # 綁定端口: s.bind(('127.0.0.1', 9999))
建立Socket時,SOCK_DGRAM
指定了這個Socket的類型是UDP。綁定端口和TCP同樣,可是不須要調用listen()
方法,而是直接接收來自任何客戶端的數據:
print 'Bind UDP on 9999...' while True: # 接收數據: data, addr = s.recvfrom(1024) print 'Received from %s:%s.' % addr s.sendto('Hello, %s!' % data, addr)
recvfrom()
方法返回數據和客戶端的地址與端口,這樣,服務器收到數據後,直接調用sendto()
就能夠把數據用UDP發給客戶端。
注意這裏省掉了多線程,由於這個例子很簡單。
客戶端使用UDP時,首先仍然建立基於UDP的Socket,而後,不須要調用connect()
,直接經過sendto()
給服務器發數據:
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) for data in ['Michael', 'Tracy', 'Sarah']: # 發送數據: s.sendto(data, ('127.0.0.1', 9999)) # 接收數據: print s.recv(1024) s.close()
從服務器接收數據仍然調用recv()
方法。
UDP的使用與TCP相似,可是不須要創建鏈接。此外,服務器綁定UDP端口和TCP端口互不衝突,也就是說,UDP的9999端口與TCP的9999端口能夠各自綁定。
Python 提供了兩個級別訪問的網絡服務。:
函數 | 描述 |
---|---|
服務器端套接字 | |
s.bind() | 綁定地址(host,port)到套接字, 在AF_INET下,以元組(host,port)的形式表示地址。 |
s.listen() | 開始TCP監聽。backlog指定在拒絕鏈接以前,操做系統能夠掛起的最大鏈接數量。該值至少爲1,大部分應用程序設爲5就能夠了。 |
s.accept() | 被動接受TCP客戶端鏈接,(阻塞式)等待鏈接的到來 |
客戶端套接字 | |
s.connect() | 主動初始化TCP服務器鏈接,。通常address的格式爲元組(hostname,port),若是鏈接出錯,返回socket.error錯誤。 |
s.connect_ex() | connect()函數的擴展版本,出錯時返回出錯碼,而不是拋出異常 |
公共用途的套接字函數 | |
s.recv() | 接收TCP數據,數據以字符串形式返回,bufsize指定要接收的最大數據量。flag提供有關消息的其餘信息,一般能夠忽略。 |
s.send() | 發送TCP數據,將string中的數據發送到鏈接的套接字。返回值是要發送的字節數量,該數量可能小於string的字節大小。 |
s.sendall() | 完整發送TCP數據,完整發送TCP數據。將string中的數據發送到鏈接的套接字,但在返回以前會嘗試發送全部數據。成功返回None,失敗則拋出異常。 |
s.recvform() | 接收UDP數據,與recv()相似,但返回值是(data,address)。其中data是包含接收數據的字符串,address是發送數據的套接字地址。 |
s.sendto() | 發送UDP數據,將數據發送到套接字,address是形式爲(ipaddr,port)的元組,指定遠程地址。返回值是發送的字節數。 |
s.close() | 關閉套接字 |
s.getpeername() | 返回鏈接套接字的遠程地址。返回值一般是元組(ipaddr,port)。 |
s.getsockname() | 返回套接字本身的地址。一般是一個元組(ipaddr,port) |
s.setsockopt(level,optname,value) | 設置給定套接字選項的值。 |
s.getsockopt(level,optname[.buflen]) | 返回套接字選項的值。 |
s.settimeout(timeout) | 設置套接字操做的超時期,timeout是一個浮點數,單位是秒。值爲None表示沒有超時期。通常,超時期應該在剛建立套接字時設置,由於它們可能用於鏈接的操做(如connect()) |
s.gettimeout() | 返回當前超時期的值,單位是秒,若是沒有設置超時期,則返回None。 |
s.fileno() | 返回套接字的文件描述符。 |
s.setblocking(flag) | 若是flag爲0,則將套接字設爲非阻塞模式,不然將套接字設爲阻塞模式(默認值)。非阻塞模式下,若是調用recv()沒有發現任何數據,或send()調用沒法當即發送數據,那麼將引發socket.error異常。 |
s.makefile() | 建立一個與該套接字相關連的文件 |
server端:
#!/usr/bin/env python # -*- coding:utf-8 -*- import socket ip_port = ('127.0.0.1',9999) sk = socket.socket() sk.bind(ip_port) sk.listen(5) while True: print 'server waiting...' conn,addr = sk.accept() client_data = conn.recv(1024) print client_data conn.sendall('不要回答,不要回答,不要回答') conn.close() socket server
client:
#!/usr/bin/env python # -*- coding:utf-8 -*- import socket ip_port = ('127.0.0.1',9999) sk = socket.socket() sk.connect(ip_port) sk.sendall('請求佔領地球') server_reply = sk.recv(1024) print server_reply sk.close() socket client
WEB服務應用:
#!/usr/bin/env python #coding:utf-8 import socket def handle_request(client): buf = client.recv(1024) client.send("HTTP/1.1 200 OK\r\n\r\n") client.send("Hello, World") def main(): sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) sock.bind(('localhost',8080)) sock.listen(5) while True: connection, address = sock.accept() handle_request(connection) connection.close() if __name__ == '__main__': main()
Socket 對象(內建)方法:
服務器端 s.bind() # 綁定地址(host,port)到套接字, 在AF_INET下,以元組(host,port)的形式表示地址。 s.listen() # 開始TCP監聽。backlog指定在拒絕鏈接以前,操做系統能夠掛起的最大鏈接數量。該值至少爲1,大部分應用程序設爲5就能夠了。 s.accept() # 被動接受TCP客戶端鏈接,(阻塞式)等待鏈接的到來 客戶端 s.connect() # 主動初始化TCP服務器鏈接,。通常address的格式爲元組(hostname,port),若是鏈接出錯,返回socket.error錯誤。 s.connect_ex() # connect()函數的擴展版本,出錯時返回出錯碼,而不是拋出異常 公共用途的函數 s.recv() # 接收TCP數據,數據以字符串形式返回,bufsize指定要接收的最大數據量。flag提供有關消息的其餘信息,一般能夠忽略。 s.send() # 發送TCP數據,將string中的數據發送到鏈接的套接字。返回值是要發送的字節數量,該數量可能小於string的字節大小。 s.sendall() # 完整發送TCP數據,完整發送TCP數據。將string中的數據發送到鏈接的套接字,但在返回以前會嘗試發送全部數據。成功返回None,失敗則拋出異常。 s.close() # 關閉套接字 s.recvform() # 接收UDP數據,與recv()相似,但返回值是(data,address)。其中data是包含接收數據的字符串,address是發送數據的套接字地址。 s.sendto() # 發送UDP數據,將數據發送到套接字,address是形式爲(ipaddr,port)的元組,指定遠程地址。返回值是發送的字節數。 s.getpeername() # 返回鏈接套接字的遠程地址。返回值一般是元組(ipaddr,port)。 s.getsockname() # 返回套接字本身的地址。一般是一個元組(ipaddr,port) s.setsockopt(level,optname,value) # 設置給定套接字選項的值。 s.getsockopt(level,optname[.buflen]) # 返回套接字選項的值。 s.settimeout(timeout) # 設置套接字操做的超時期,timeout是一個浮點數,單位是秒。值爲None表示沒有超時期。通常,超時期應該在剛建立套接字時設置,由於它們可能用於鏈接的操做(如connect()) s.gettimeout() # 返回當前超時期的值,單位是秒,若是沒有設置超時期,則返回None。 s.fileno() # 返回套接字的文件描述符。 s.setblocking(flag) # 若是flag爲0,則將套接字設爲非阻塞模式,不然將套接字設爲阻塞模式(默認值)。非阻塞模式下,若是調用recv()沒有發現任何數據,或send()調用沒法當即發送數據,那麼將引發socket.error異常。 s.makefile() # 建立一個與該套接字相關連的文件
更多功能
sk = socket.socket(socket.AF_INET,socket.SOCK_STREAM,0)
參數一:地址簇
socket.AF_INET IPv4(默認)
socket.AF_INET6 IPv6
socket.AF_UNIX 只可以用於單一的Unix系統進程間通訊
參數二:類型
socket.SOCK_STREAM 流式socket , for TCP (默認)
socket.SOCK_DGRAM 數據報式socket , for UDP
socket.SOCK_RAW 原始套接字,普通的套接字沒法處理ICMP、IGMP等網絡報文,而SOCK_RAW能夠;其次,SOCK_RAW也能夠處理特殊的IPv4報文;此外,利用原始套接字,能夠經過IP_HDRINCL套接字選項由用戶構造IP頭。
socket.SOCK_RDM 是一種可靠的UDP形式,即保證交付數據報但不保證順序。SOCK_RAM用來提供對原始協議的低級訪問,在須要執行某些特殊操做時使用,如發送ICMP報文。SOCK_RAM一般僅限於高級用戶或管理員運行的程序使用。
socket.SOCK_SEQPACKET 可靠的連續數據包服務
參數三:協議
0 (默認)與特定的地址家族相關的協議,若是是 0 ,則系統就會根據地址格式和套接類別,自動選擇一個合適的協議
UDP Demo
import socket ip_port = ('127.0.0.1',9999) sk = socket.socket(socket.AF_INET,socket.SOCK_DGRAM,0) sk.bind(ip_port) while True: data = sk.recv(1024) print data import socket ip_port = ('127.0.0.1',9999) sk = socket.socket(socket.AF_INET,socket.SOCK_DGRAM,0) while True: inp = raw_input('數據:').strip() if inp == 'exit': break sk.sendto(inp,ip_port) sk.close() UDP Demo
sk.bind(address)
s.bind(address) 將套接字綁定到地址。address地址的格式取決於地址族。在AF_INET下,以元組(host,port)的形式表示地址。
sk.listen(backlog)
開始監聽傳入鏈接。backlog指定在拒絕鏈接以前,能夠掛起的最大鏈接數量。
backlog等於5,表示內核已經接到了鏈接請求,但服務器尚未調用accept進行處理的鏈接個數最大爲5
這個值不能無限大,由於要在內核中維護鏈接隊列
sk.setblocking(bool)
是否阻塞(默認True),若是設置False,那麼accept和recv時一旦無數據,則報錯。
sk.accept()
接受鏈接並返回(conn,address),其中conn是新的套接字對象,能夠用來接收和發送數據。address是鏈接客戶端的地址。
接收TCP 客戶的鏈接(阻塞式)等待鏈接的到來
sk.connect(address)
鏈接到address處的套接字。通常,address的格式爲元組(hostname,port),若是鏈接出錯,返回socket.error錯誤。
sk.connect_ex(address)
同上,只不過會有返回值,鏈接成功時返回 0 ,鏈接失敗時候返回編碼,例如:10061
sk.close()
關閉套接字
sk.recv(bufsize[,flag])
接受套接字的數據。數據以字符串形式返回,bufsize指定最多能夠接收的數量。flag提供有關消息的其餘信息,一般能夠忽略。
sk.recvfrom(bufsize[.flag])
與recv()相似,但返回值是(data,address)。其中data是包含接收數據的字符串,address是發送數據的套接字地址。
sk.send(string[,flag])
將string中的數據發送到鏈接的套接字。返回值是要發送的字節數量,該數量可能小於string的字節大小。即:可能未將指定內容所有發送。
sk.sendall(string[,flag])
將string中的數據發送到鏈接的套接字,但在返回以前會嘗試發送全部數據。成功返回None,失敗則拋出異常。
內部經過遞歸調用send,將全部內容發送出去。
sk.sendto(string[,flag],address)
將數據發送到套接字,address是形式爲(ipaddr,port)的元組,指定遠程地址。返回值是發送的字節數。該函數主要用於UDP協議。
sk.settimeout(timeout)
設置套接字操做的超時期,timeout是一個浮點數,單位是秒。值爲None表示沒有超時期。通常,超時期應該在剛建立套接字時設置,由於它們可能用於鏈接的操做(如 client 鏈接最多等待5s )
sk.getpeername()
返回鏈接套接字的遠程地址。返回值一般是元組(ipaddr,port)。
sk.getsockname()
返回套接字本身的地址。一般是一個元組(ipaddr,port)
sk.fileno()
套接字的文件描述符
# 服務端 import socket ip_port = ('127.0.0.1',9999) sk = socket.socket(socket.AF_INET,socket.SOCK_DGRAM,0) sk.bind(ip_port) while True: data,(host,port) = sk.recvfrom(1024) print(data,host,port) sk.sendto(bytes('ok', encoding='utf-8'), (host,port)) #客戶端 import socket ip_port = ('127.0.0.1',9999) sk = socket.socket(socket.AF_INET,socket.SOCK_DGRAM,0) while True: inp = input('數據:').strip() if inp == 'exit': break sk.sendto(bytes(inp, encoding='utf-8'),ip_port) data = sk.recvfrom(1024) print(data) sk.close() UDP
實例:智能機器人
服務端:
#!/usr/bin/env python # -*- coding:utf-8 -*- import socket ip_port = ('127.0.0.1',8888) sk = socket.socket() sk.bind(ip_port) sk.listen(5) while True: conn,address = sk.accept() conn.sendall('歡迎致電 10086,請輸入1xxx,0轉人工服務.') Flag = True while Flag: data = conn.recv(1024) if data == 'exit': Flag = False elif data == '0': conn.sendall('經過可能會被錄音.balabala一大推') else: conn.sendall('請從新輸入.') conn.close() 服務端
客戶端:
#!/usr/bin/env python # -*- coding:utf-8 -*- import socket ip_port = ('127.0.0.1',8005) sk = socket.socket() sk.connect(ip_port) sk.settimeout(5) while True: data = sk.recv(1024) print 'receive:',data inp = raw_input('please input:') sk.sendall(inp) if inp == 'exit': break sk.close() 客戶端
I/O多路複用指:經過一種機制,能夠監視多個描述符,一旦某個描述符就緒(通常是讀就緒或者寫就緒),可以通知程序進行相應的讀寫操做。
Linux
Linux中的 select,poll,epoll 都是IO多路複用的機制。
select select最先於1983年出如今4.2BSD中,它經過一個select()系統調用來監視多個文件描述符的數組,當select()返回後,該數組中就緒的文件描述符便會被內核修改標誌位,使得進程能夠得到這些文件描述符從而進行後續的讀寫操做。 select目前幾乎在全部的平臺上支持,其良好跨平臺支持也是它的一個優勢,事實上從如今看來,這也是它所剩很少的優勢之一。 select的一個缺點在於單個進程可以監視的文件描述符的數量存在最大限制,在Linux上通常爲1024,不過能夠經過修改宏定義甚至從新編譯內核的方式提高這一限制。 另外,select()所維護的存儲大量文件描述符的數據結構,隨着文件描述符數量的增大,其複製的開銷也線性增加。同時,因爲網絡響應時間的延遲使得大量TCP鏈接處於非活躍狀態,但調用select()會對全部socket進行一次線性掃描,因此這也浪費了必定的開銷。 poll poll在1986年誕生於System V Release 3,它和select在本質上沒有多大差異,可是poll沒有最大文件描述符數量的限制。 poll和select一樣存在一個缺點就是,包含大量文件描述符的數組被總體複製於用戶態和內核的地址空間之間,而不論這些文件描述符是否就緒,它的開銷隨着文件描述符數量的增長而線性增大。 另外,select()和poll()將就緒的文件描述符告訴進程後,若是進程沒有對其進行IO操做,那麼下次調用select()和poll()的時候將再次報告這些文件描述符,因此它們通常不會丟失就緒的消息,這種方式稱爲水平觸發(Level Triggered)。 epoll 直到Linux2.6纔出現了由內核直接支持的實現方法,那就是epoll,它幾乎具有了以前所說的一切優勢,被公認爲Linux2.6下性能最好的多路I/O就緒通知方法。 epoll能夠同時支持水平觸發和邊緣觸發(Edge Triggered,只告訴進程哪些文件描述符剛剛變爲就緒狀態,它只說一遍,若是咱們沒有采起行動,那麼它將不會再次告知,這種方式稱爲邊緣觸發),理論上邊緣觸發的性能要更高一些,可是代碼實現至關複雜。 epoll一樣只告知那些就緒的文件描述符,並且當咱們調用epoll_wait()得到就緒文件描述符時,返回的不是實際的描述符,而是一個表明就緒描述符數量的值,你只須要去epoll指定的一個數組中依次取得相應數量的文件描述符便可,這裏也使用了內存映射(mmap)技術,這樣便完全省掉了這些文件描述符在系統調用時複製的開銷。 另外一個本質的改進在於epoll採用基於事件的就緒通知方式。在select/poll中,進程只有在調用必定的方法後,內核纔對全部監視的文件描述符進行掃描,而epoll事先經過epoll_ctl()來註冊一個文件描述符,一旦基於某個文件描述符就緒時,內核會採用相似callback的回調機制,迅速激活這個文件描述符,當進程調用epoll_wait()時便獲得通知。
Python
Python中有一個select模塊,其中提供了:select、poll、epoll三個方法,分別調用系統的 select,poll,epoll 從而實現IO多路複用。
Windows Python: 提供: select Mac Python: 提供: select Linux Python: 提供: select、poll、epoll
注意:網絡操做、文件操做、終端操做等均屬於IO操做,對於windows只支持Socket操做,其餘系統支持其餘IO操做,可是沒法檢測 普通文件操做 自動上次讀取是否已經變化。
對於select方法:
句柄列表11, 句柄列表22, 句柄列表33 = select.select(句柄序列1, 句柄序列2, 句柄序列3, 超時時間) 參數: 可接受四個參數(前三個必須) 返回值:三個列表 select方法用來監視文件句柄,若是句柄發生變化,則獲取該句柄。 一、當 參數1 序列中的句柄發生可讀時(accetp和read),則獲取發生變化的句柄並添加到 返回值1 序列中 二、當 參數2 序列中含有句柄時,則將該序列中全部的句柄添加到 返回值2 序列中 三、當 參數3 序列中的句柄發生錯誤時,則將該發生錯誤的句柄添加到 返回值3 序列中 四、當 超時時間 未設置,則select會一直阻塞,直到監聽的句柄發生變化 當 超時時間 = 1時,那麼若是監聽的句柄均無任何變化,則select會阻塞 1 秒,以後返回三個空列表,若是監聽的句柄有變化,則直接執行。
利用select監聽終端操做實例:
#!/usr/bin/env python # -*- coding:utf-8 -*- import select import threading import sys while True: readable, writeable, error = select.select([sys.stdin,],[],[],1) if sys.stdin in readable: print 'select get stdin',sys.stdin.readline() 利用select監聽終端操做實例
利用select實現僞同時處理多個socket客戶端請求:服務端
#!/usr/bin/env python # -*- coding:utf-8 -*- import socket import select sk1 = socket.socket(socket.AF_INET, socket.SOCK_STREAM) sk1.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) sk1.bind(('127.0.0.1',8002)) sk1.listen(5) sk1.setblocking(0) inputs = [sk1,] while True: readable_list, writeable_list, error_list = select.select(inputs, [], inputs, 1) for r in readable_list: # 當客戶端第一次鏈接服務端時 if sk1 == r: print 'accept' request, address = r.accept() request.setblocking(0) inputs.append(request) # 當客戶端鏈接上服務端以後,再次發送數據時 else: received = r.recv(1024) # 當正常接收客戶端發送的數據時 if received: print 'received data:', received # 當客戶端關閉程序時 else: inputs.remove(r) sk1.close() 利用select實現僞同時處理多個Socket客戶端請求:服務端
利用select實現僞同時處理多個socket客戶端請求:客戶端
#!/usr/bin/env python # -*- coding:utf-8 -*- import socket ip_port = ('127.0.0.1',8002) sk = socket.socket() sk.connect(ip_port) while True: inp = raw_input('please input:') sk.sendall(inp) sk.close() 利用select實現僞同時處理多個Socket客戶端請求:客戶端
此處的Socket服務端相比與原生的Socket,他支持當某一個請求再也不發送數據時,服務器端不會等待而是能夠去處理其餘請求的數據。可是,若是每一個請求的耗時比較長時,select版本的服務器端也沒法完成同時操做。
基於select實現socket服務端
#!/usr/bin/env python #coding:utf8 ''' 服務器的實現 採用select的方式 ''' import select import socket import sys import Queue #建立套接字並設置該套接字爲非阻塞模式 server = socket.socket(socket.AF_INET,socket.SOCK_STREAM) server.setblocking(0) #綁定套接字 server_address = ('localhost',10000) print >>sys.stderr,'starting up on %s port %s'% server_address server.bind(server_address) #將該socket變成服務模式 #backlog等於5,表示內核已經接到了鏈接請求,但服務器尚未調用accept進行處理的鏈接個數最大爲5 #這個值不能無限大,由於要在內核中維護鏈接隊列 server.listen(5) #初始化讀取數據的監聽列表,最開始時但願從server這個套接字上讀取數據 inputs = [server] #初始化寫入數據的監聽列表,最開始並無客戶端鏈接進來,因此列表爲空 outputs = [] #要發往客戶端的數據 message_queues = {} while inputs: print >>sys.stderr,'waiting for the next event' #調用select監聽全部監聽列表中的套接字,並將準備好的套接字加入到對應的列表中 readable,writable,exceptional = select.select(inputs,outputs,inputs)#列表中的socket 套接字 若是是文件呢? #監控文件句柄有某一處發生了變化 可寫 可讀 異常屬於Linux中的網絡編程 #屬於同步I/O操做,屬於I/O複用模型的一種 #rlist--等待到準備好讀 #wlist--等待到準備好寫 #xlist--等待到一種異常 #處理可讀取的套接字 ''' 若是server這個套接字可讀,則說明有新連接到來 此時在server套接字上調用accept,生成一個與客戶端通信的套接字 並將與客戶端通信的套接字加入inputs列表,下一次能夠經過select檢查鏈接是否可讀 而後在發往客戶端的緩衝中加入一項,鍵名爲:與客戶端通信的套接字,鍵值爲空隊列 select系統調用是用來讓咱們的程序監視多個文件句柄(file descrīptor)的狀態變化的。程序會停在select這裏等待, 直到被監視的文件句柄有某一個或多個發生了狀態改變 ''' ''' 若可讀的套接字不是server套接字,有兩種狀況:一種是有數據到來,另外一種是連接斷開 若是有數據到來,先接收數據,而後將收到的數據填入往客戶端的緩存區中的對應位置,最後 將於客戶端通信的套接字加入到寫數據的監聽列表: 若是套接字可讀.但沒有接收到數據,則說明客戶端已經斷開。這時須要關閉與客戶端鏈接的套接字 進行資源清理 ''' for s in readable: if s is server: connection,client_address = s.accept() print >>sys.stderr,'connection from',client_address connection.setblocking(0)#設置非阻塞 inputs.append(connection) message_queues[connection] = Queue.Queue() else: data = s.recv(1024) if data: print >>sys.stderr,'received "%s" from %s'% \ (data,s.getpeername()) message_queues[s].put(data) if s not in outputs: outputs.append(s) else: print >>sys.stderr,'closing',client_address if s in outputs: outputs.remove(s) inputs.remove(s) s.close() del message_queues[s] #處理可寫的套接字 ''' 在發送緩衝區中取出響應的數據,發往客戶端。 若是沒有數據須要寫,則將套接字從發送隊列中移除,select中再也不監視 ''' for s in writable: try: next_msg = message_queues[s].get_nowait() except Queue.Empty: print >>sys.stderr,' ',s,getpeername(),'queue empty' outputs.remove(s) else: print >>sys.stderr,'sending "%s" to %s'% \ (next_msg,s.getpeername()) s.send(next_msg) #處理異常狀況 for s in exceptional: for s in exceptional: print >>sys.stderr,'exception condition on',s.getpeername() inputs.remove(s) if s in outputs: outputs.remove(s) s.close() del message_queues[s] 基於select實現socket服務端
SocketServer內部使用 IO多路複用 以及 「多線程」 和 「多進程」 ,從而實現併發處理多個客戶端請求的Socket服務端。即:每一個客戶端請求鏈接到服務器時,Socket服務端都會在服務器是建立一個「線程」或者「進程」 專門負責處理當前客戶端的全部請求。
ThreadingTCPServer
ThreadingTCPServer實現的Soket服務器內部會爲每一個client建立一個 「線程」,該線程用來和客戶端進行交互。
一、ThreadingTCPServer基礎
使用ThreadingTCPServer:
socketserver實現服務器
#!/usr/bin/env python # -*- coding:utf-8 -*- import SocketServer class MyServer(SocketServer.BaseRequestHandler): def handle(self): # print self.request,self.client_address,self.server conn = self.request conn.sendall('歡迎致電 10086,請輸入1xxx,0轉人工服務.') Flag = True while Flag: data = conn.recv(1024) if data == 'exit': Flag = False elif data == '0': conn.sendall('經過可能會被錄音.balabala一大推') else: conn.sendall('請從新輸入.') if __name__ == '__main__': server = SocketServer.ThreadingTCPServer(('127.0.0.1',8009),MyServer) server.serve_forever() SocketServer實現服務器
客戶端:
#!/usr/bin/env python # -*- coding:utf-8 -*- import socket ip_port = ('127.0.0.1',8009) sk = socket.socket() sk.connect(ip_port) sk.settimeout(5) while True: data = sk.recv(1024) print 'receive:',data inp = raw_input('please input:') sk.sendall(inp) if inp == 'exit': break sk.close() 客戶端
ThreadingTCPServer源碼剖析
ThreadingTCPServer的類圖關係以下:
內部調用流程爲:
ThreadingTCPServer相關源碼:
BaseServer
class BaseServer: """Base class for server classes. Methods for the caller: - __init__(server_address, RequestHandlerClass) - serve_forever(poll_interval=0.5) - shutdown() - handle_request() # if you do not use serve_forever() - fileno() -> int # for select() Methods that may be overridden: - server_bind() - server_activate() - get_request() -> request, client_address - handle_timeout() - verify_request(request, client_address) - server_close() - process_request(request, client_address) - shutdown_request(request) - close_request(request) - handle_error() Methods for derived classes: - finish_request(request, client_address) Class variables that may be overridden by derived classes or instances: - timeout - address_family - socket_type - allow_reuse_address Instance variables: - RequestHandlerClass - socket """ timeout = None def __init__(self, server_address, RequestHandlerClass): """Constructor. May be extended, do not override.""" self.server_address = server_address self.RequestHandlerClass = RequestHandlerClass self.__is_shut_down = threading.Event() self.__shutdown_request = False def server_activate(self): """Called by constructor to activate the server. May be overridden. """ pass def serve_forever(self, poll_interval=0.5): """Handle one request at a time until shutdown. Polls for shutdown every poll_interval seconds. Ignores self.timeout. If you need to do periodic tasks, do them in another thread. """ self.__is_shut_down.clear() try: while not self.__shutdown_request: # XXX: Consider using another file descriptor or # connecting to the socket to wake this up instead of # polling. Polling reduces our responsiveness to a # shutdown request and wastes cpu at all other times. r, w, e = _eintr_retry(select.select, [self], [], [], poll_interval) if self in r: self._handle_request_noblock() finally: self.__shutdown_request = False self.__is_shut_down.set() def shutdown(self): """Stops the serve_forever loop. Blocks until the loop has finished. This must be called while serve_forever() is running in another thread, or it will deadlock. """ self.__shutdown_request = True self.__is_shut_down.wait() # The distinction between handling, getting, processing and # finishing a request is fairly arbitrary. Remember: # # - handle_request() is the top-level call. It calls # select, get_request(), verify_request() and process_request() # - get_request() is different for stream or datagram sockets # - process_request() is the place that may fork a new process # or create a new thread to finish the request # - finish_request() instantiates the request handler class; # this constructor will handle the request all by itself def handle_request(self): """Handle one request, possibly blocking. Respects self.timeout. """ # Support people who used socket.settimeout() to escape # handle_request before self.timeout was available. timeout = self.socket.gettimeout() if timeout is None: timeout = self.timeout elif self.timeout is not None: timeout = min(timeout, self.timeout) fd_sets = _eintr_retry(select.select, [self], [], [], timeout) if not fd_sets[0]: self.handle_timeout() return self._handle_request_noblock() def _handle_request_noblock(self): """Handle one request, without blocking. I assume that select.select has returned that the socket is readable before this function was called, so there should be no risk of blocking in get_request(). """ try: request, client_address = self.get_request() except socket.error: return if self.verify_request(request, client_address): try: self.process_request(request, client_address) except: self.handle_error(request, client_address) self.shutdown_request(request) def handle_timeout(self): """Called if no new request arrives within self.timeout. Overridden by ForkingMixIn. """ pass def verify_request(self, request, client_address): """Verify the request. May be overridden. Return True if we should proceed with this request. """ return True def process_request(self, request, client_address): """Call finish_request. Overridden by ForkingMixIn and ThreadingMixIn. """ self.finish_request(request, client_address) self.shutdown_request(request) def server_close(self): """Called to clean-up the server. May be overridden. """ pass def finish_request(self, request, client_address): """Finish one request by instantiating RequestHandlerClass.""" self.RequestHandlerClass(request, client_address, self) def shutdown_request(self, request): """Called to shutdown and close an individual request.""" self.close_request(request) def close_request(self, request): """Called to clean up an individual request.""" pass def handle_error(self, request, client_address): """Handle an error gracefully. May be overridden. The default is to print a traceback and continue. """ print '-'*40 print 'Exception happened during processing of request from', print client_address import traceback traceback.print_exc() # XXX But this goes to stderr! print '-'*40 BaseServer
Tcpserver
class TCPServer(BaseServer): """Base class for various socket-based server classes. Defaults to synchronous IP stream (i.e., TCP). Methods for the caller: - __init__(server_address, RequestHandlerClass, bind_and_activate=True) - serve_forever(poll_interval=0.5) - shutdown() - handle_request() # if you don't use serve_forever() - fileno() -> int # for select() Methods that may be overridden: - server_bind() - server_activate() - get_request() -> request, client_address - handle_timeout() - verify_request(request, client_address) - process_request(request, client_address) - shutdown_request(request) - close_request(request) - handle_error() Methods for derived classes: - finish_request(request, client_address) Class variables that may be overridden by derived classes or instances: - timeout - address_family - socket_type - request_queue_size (only for stream sockets) - allow_reuse_address Instance variables: - server_address - RequestHandlerClass - socket """ address_family = socket.AF_INET socket_type = socket.SOCK_STREAM request_queue_size = 5 allow_reuse_address = False def __init__(self, server_address, RequestHandlerClass, bind_and_activate=True): """Constructor. May be extended, do not override.""" BaseServer.__init__(self, server_address, RequestHandlerClass) self.socket = socket.socket(self.address_family, self.socket_type) if bind_and_activate: try: self.server_bind() self.server_activate() except: self.server_close() raise def server_bind(self): """Called by constructor to bind the socket. May be overridden. """ if self.allow_reuse_address: self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) self.socket.bind(self.server_address) self.server_address = self.socket.getsockname() def server_activate(self): """Called by constructor to activate the server. May be overridden. """ self.socket.listen(self.request_queue_size) def server_close(self): """Called to clean-up the server. May be overridden. """ self.socket.close() def fileno(self): """Return socket file number. Interface required by select(). """ return self.socket.fileno() def get_request(self): """Get the request and client address from the socket. May be overridden. """ return self.socket.accept() def shutdown_request(self, request): """Called to shutdown and close an individual request.""" try: #explicitly shutdown. socket.close() merely releases #the socket and waits for GC to perform the actual close. request.shutdown(socket.SHUT_WR) except socket.error: pass #some platforms may raise ENOTCONN here self.close_request(request) def close_request(self, request): """Called to clean up an individual request.""" request.close() TCPServer
ThreadingMixIn
class ThreadingMixIn: """Mix-in class to handle each request in a new thread.""" # Decides how threads will act upon termination of the # main process daemon_threads = False def process_request_thread(self, request, client_address): """Same as in BaseServer but as a thread. In addition, exception handling is done here. """ try: self.finish_request(request, client_address) self.shutdown_request(request) except: self.handle_error(request, client_address) self.shutdown_request(request) def process_request(self, request, client_address): """Start a new thread to process the request.""" t = threading.Thread(target = self.process_request_thread, args = (request, client_address)) t.daemon = self.daemon_threads t.start() ThreadingMixIn
ThreadingTCPServer
class ThreadingTCPServer(ThreadingMixIn, TCPServer): pass
Socket套接字的概念
Socket(翻譯爲套接字, 我以爲很挫)
,是操做系統內核中的一個數據結構,它是網絡中的節點進行相互通訊的門戶
。它是網絡進程的ID。網絡通訊,歸根到底仍是進程間的通訊(不一樣計算機上的進程間通訊, 又稱進程間通訊, IP協議進行的主要是端到端通訊)。在網絡中,每個節點(計算機或路由)都有一個網絡地址,也就是IP地址。兩個進程通訊時,首先要肯定各自所在的網絡節點的網絡地址。可是,網絡地址只能肯定進程所在的計算機,而一臺計算機上極可能同時運行着多個進程,因此僅憑網絡地址還不能肯定究竟是和網絡中的哪個進程進行通訊,所以套接口中還須要包括其餘的信息,也就是端口號(PORT)。在一臺計算機中,一個端口號一次只能分配給一個進程,也就是說,在一臺計算機中,端口號和進程之間是一一對應關係。
因此,使用端口號和網絡地址的組合能夠惟一的肯定整個網絡中的一個網絡進程.
端口號的範圍從0~65535,一類是由互聯網指派名字和號碼公司ICANN負責分配給一些經常使用的應用程序固定使用的「周知的端口」,其值通常爲0~1023, 用戶自定義端口號通常大於等於1024, 我比較喜歡用8888
每個socket都用一個半相關描述{協議、本地地址、本地端口}來表示;一個完整的套接字則用一個相關描述{協議、本地地址、本地端口、遠程地址、遠程端口}來表示。socket也有一個相似於打開文件的函數調用,該函數返回一個整型的socket描述符,隨後的鏈接創建、數據傳輸等操做都是經過socket來實現的。
流程描述:
# 流程描述: # # 1 服務器根據地址類型(ipv4,ipv6)、socket類型、協議建立socket # # 2 服務器爲socket綁定ip地址和端口號 # # 3 服務器socket監聽端口號請求,隨時準備接收客戶端發來的鏈接,這時候服務器的socket並無被打開 # # 4 客戶端建立socket # # 5 客戶端打開socket,根據服務器ip地址和端口號試圖鏈接服務器socket # # 6 服務器socket接收到客戶端socket請求,被動打開,開始接收客戶端請求,直到客戶端返回鏈接信息。這時候socket進入阻塞狀態, # 所謂阻塞即accept()方法一直等到客戶端返回鏈接信息後才返回,開始接收下一個客戶端鏈接請求 # # 7 客戶端鏈接成功,向服務器發送鏈接狀態信息 # # 8 服務器accept方法返回,鏈接成功 # # 9 客戶端向socket寫入信息(或服務端向socket寫入信息) # # 10 服務器讀取信息(客戶端讀取信息) # # 11 客戶端關閉 # # 12 服務器端關閉
套接字格式:
socket(family,type[,protocal]) 使用給定的地址族、套接字類型、協議編號(默認爲0)來建立套接字。
socket類型 |
描述 |
socket.AF_UNIX |
只可以用於單一的Unix系統進程間通訊 |
socket.AF_INET |
服務器之間網絡通訊 |
socket.AF_INET6 |
IPv6 |
socket.SOCK_STREAM |
流式socket , for TCP |
socket.SOCK_DGRAM |
數據報式socket , for UDP |
socket.SOCK_RAW |
原始套接字,普通的套接字沒法處理ICMP、IGMP等網絡報文,而SOCK_RAW能夠;其次,SOCK_RAW也能夠處理特殊的IPv4報文;此外,利用原始套接字,能夠經過IP_HDRINCL套接字選項由用戶構造IP頭。 |
socket.SOCK_SEQPACKET |
可靠的連續數據包服務 |
建立TCP Socket: |
|
建立UDP Socket: |
s=socket.socket(socket.AF_INET,socket.SOCK_DGRAM) |
socket類型在Liunx和Python是同樣的, 只是Python中的類型都定義在
socket模塊
中, 調用方式socket.SOCK_XXXX
用於TCP通訊
流式套接字提供可靠的、面向鏈接的通訊流;它使用TCP協議,從而保證了數據傳輸的正確性和順序性
用於UDP通訊
數據報套接字定義了一種無鏈接的服務,數據經過相互獨立的報文進行傳輸,是無序的,而且不保證是可靠、無差錯的。它使用數據報協議UDP
用於新的網絡協議實現的測試等
原始套接字,普通的套接字沒法處理ICMP、IGMP等網絡報文,而SOCK_RAW能夠, 其次,SOCK_RAW也能夠處理特殊的IPv4報文;此外,利用原始套接字,能夠經過IP_HDRINCL套接字選項由用戶構造IP頭。
TCP通訊的基本步驟以下:
服務端:socket---bind---listen---while(True){---accept---recv---send----}---close
客戶端:socket----------------------------------connect---send---recv-------close
socket函數
使用給定的地址族、套接字類型、協議編號(默認爲0)來建立套接字
#Linux int socket(int domain, int type, int protocol); domain:AF_INET:Ipv4網絡協議 AF_INET6:IPv6網絡協議 type : tcp:SOCK_STREAM udp:SOCK_DGRAM protocol : 指定socket所使用的傳輸協議編號。一般爲0. 返回值:成功則返回套接口描述符,失敗返回-1。 #python socket.socket([family[, type[, proto]]]) family : AF_INET (默認ipv4), AF_INET6(ipv6) or AF_UNIX(Unix系統進程間通訊). type : SOCK_STREAM (TCP), SOCK_DGRAM(UDP) . protocol : 通常爲0或者默認
若是socket建立失敗會拋出一個socket.error異常
bind函數
將套接字綁定到地址, python下,以元組(host,port)的形式表示地址, Linux下使用sockaddr_in
結構體指針
#Linux int bind(int sockfd, struct sockaddr * my_addr, int addrlen); sockfd : 前面socket()的返回值 my_addr : 結構體指針變量 ##### struct sockaddr_in //經常使用的結構體 { unsigned short int sin_family; //即爲sa_family AF_INET uint16_t sin_port; //爲使用的port編號 struct in_addr sin_addr; //爲IP地址 unsigned char sin_zero[8]; //未使用 }; struct in_addr { uint32_t s_addr; }; #### addrlen : sockaddr的結構體長度。一般是計算sizeof(struct sockaddr); 返回值:成功則返回0,失敗返回-1 #python s.bind(address) s爲socket.socket()返回的套接字對象 address爲元組(host,port) host: ip地址, 爲一個字符串 post: 自定義主機號, 爲整型
listen函數
使服務器的這個端口和IP處於監聽狀態,等待網絡中某一客戶機的鏈接請求。若是客戶端有鏈接請求,端口就會接受這個鏈接
#Linux int listen(int sockfd,int backlog); sockfd : 爲前面socket的返回值. backlog : 指定同時能處理的最大鏈接要求,一般爲10或者5。最大值可設至128 返回值:成功則返回0,失敗返回-1 #python s.listen(backlog) s爲socket.socket()返回的套接字對象 backlog : 操做系統能夠掛起的最大鏈接數量。該值至少爲1,大部分應用程序設爲5就能夠了
accept函數
接受遠程計算機的鏈接請求,創建起與客戶機之間的通訊鏈接。服務器處於監聽狀態時,若是某時刻得到客戶機的鏈接請求,此時並非當即處理這個請求,而是將這個請求放在等待隊列中,當系統空閒時再處理客戶機的鏈接請求。
#Linux int accept(int s,struct sockaddr * addr,int * addrlen); sockfd : 爲前面socket的返回值. addr : 爲結構體指針變量,和bind的結構體是同種類型的,系統會把遠程主機的信息(遠程主機的地址和端口號信息)保存到這個指針所指的結構體中。 addrlen : 表示結構體的長度,爲整型指針 返回值:成功則返回新的socket處理代碼new_fd,失敗返回-1 #python s.accept() s爲socket.socket()返回的套接字對象 返回(conn,address),其中conn是新的套接字對象,能夠用來接收和發送數據。address是鏈接客戶端的地址
connect函數
用來請求鏈接遠程服務器
#Linux int connect (int sockfd,struct sockaddr * serv_addr,int addrlen); sockfd : 爲前面socket的返回值. serv_addr : 爲結構體指針變量,存儲着遠程服務器的IP與端口號信息 addrlen : 表示結構體變量的長度 返回值:成功則返回0,失敗返回-1 #python s.connect(address) s爲socket.socket()返回的套接字對象 address : 格式爲元組(hostname,port),若是鏈接出錯,返回socket.error錯誤
接收遠端主機傳來的數據
recv函數
#Linux int recv(int sockfd,void *buf,int len,unsigned int flags); sockfd : 爲前面accept的返回值.也就是新的套接字。 buf : 表示緩衝區 len : 表示緩衝區的長度 flags : 一般爲0 返回值:成功則返回實際接收到的字符數,可能會少於你所指定的接收長度。失敗返回-1 #python s.recv(bufsize[,flag]) s爲socket.socket()返回的套接字對象 bufsize : 指定要接收的數據大小 flag : 提供有關消息的其餘信息,一般能夠忽略 返回值爲數據以字符串形式
send函數
發送數據給指定的遠端主機
#Linux int send(int s,const void * msg,int len,unsigned int flags); sockfd : 爲前面socket的返回值. msg : 通常爲常量字符串 len : 表示長度 flags : 一般爲0 返回值:成功則返回實際傳送出去的字符數,可能會少於你所指定的發送長度。失敗返回-1 #python s.send(string[,flag]) s爲socket.socket()返回的套接字對象 string : 要發送的字符串數據 flag : 提供有關消息的其餘信息,一般能夠忽略 返回值是要發送的字節數量,該數量可能小於string的字節大小。 s.sendall(string[,flag]) #完整發送TCP數據。將string中的數據發送到鏈接的套接字,但在返回以前會嘗試發送全部數據。 返回值 : 成功返回None,失敗則拋出異常。
close函數
關閉套接字
#Linux int close(int fd); fd : 爲前面的sockfd 返回值:若文件順利關閉則返回0,發生錯誤時返回-1 #python s.close() s爲socket.socket()返回的套接字對象
一個簡單的回顯服務器和客戶端模型, 客戶端發出的數據, 服務器會回顯到客戶端的終端上(只是一個簡單的模型, 沒考慮錯誤處理等問題)
#服務器端 #!/usr/bin/env python # -*- coding:utf-8 -*- import socket #socket模塊 import commands #執行系統命令模塊 BUF_SIZE = 1024 #設置緩衝區大小 server_addr = ('127.0.0.1', 8888) #IP和端口構成表示地址 server = socket.socket(socket.AF_INET, socket.SOCK_STREAM) #生成一個新的socket對象 server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) #設置地址複用 server.bind(server_addr) #綁定地址 server.listen(5) #監聽, 最大監聽數爲5 while True: client, client_addr = server.accept() #接收TCP鏈接, 並返回新的套接字和地址 print 'Connected by', client_addr while True : data = client.recv(BUF_SIZE) #從客戶端接收數據 print data client.sendall(data) #發送數據到客戶端 server.close() #客戶端 #!/usr/bin/env python # -*- coding:utf-8 -*- import socket BUF_SIZE = 1024 #設置緩衝區的大小 server_addr = ('127.0.0.1', 8888) #IP和端口構成表示地址 client = socket.socket(socket.AF_INET, socket.SOCK_STREAM) #返回新的socket對象 client.connect(server_addr) #要鏈接的服務器地址 while True: data = raw_input("Please input some string > ") client.sendall(data) #發送數據到服務器 data = client.recv(BUF_SIZE) #從服務器端接收數據 print data client.close()
在進行網絡編程時, 最好使用大量的錯誤處理, 可以儘可能的發現錯誤, 也可以使代碼顯得更加嚴謹
#服務器端 #!/usr/bin/env python # -*- coding:utf-8 -*- import sys import socket #socket模塊 BUF_SIZE = 1024 #設置緩衝區大小 server_addr = ('127.0.0.1', 8888) #IP和端口構成表示地址 try : server = socket.socket(socket.AF_INET, socket.SOCK_STREAM) #生成一個新的socket對象 except socket.error, msg : print "Creating Socket Failure. Error Code : " + str(msg[0]) + " Message : " + msg[1] sys.exit() print "Socket Created!" server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) #設置地址複用 try : server.bind(server_addr) #綁定地址 except socket.error, msg : print "Binding Failure. Error Code : " + str(msg[0]) + " Message : " + msg[1] sys.exit() print "Socket Bind!" server.listen(5) #監聽, 最大監聽數爲5 print "Socket listening" while True: client, client_addr = server.accept() #接收TCP鏈接, 並返回新的套接字和地址, 阻塞函數 print 'Connected by', client_addr while True : data = client.recv(BUF_SIZE) #從客戶端接收數據 print data client.sendall(data) #發送數據到客戶端 server.close() #客戶端 #!/usr/bin/env python # -*- coding:utf-8 -*- import sys import socket BUF_SIZE = 1024 #設置緩衝區的大小 server_addr = ('127.0.0.1', 8888) #IP和端口構成表示地址 try : client = socket.socket(socket.AF_INET, socket.SOCK_STREAM) #返回新的socket對象 except socket.error, msg : print "Creating Socket Failure. Error Code : " + str(msg[0]) + " Message : " + msg[1] sys.exit() client.connect(server_addr) #要鏈接的服務器地址 while True: data = raw_input("Please input some string > ") if not data : print "input can't empty, Please input again.." continue client.sendall(data) #發送數據到服務器 data = client.recv(BUF_SIZE) #從服務器端接收數據 print data client.close()
UDP通訊流程圖以下:
服務端:socket---bind---recvfrom---sendto---close
客戶端:socket----------sendto---recvfrom---close
sendto()函數
發送UDP數據, 將數據發送到套接字
#Linux int sendto(int sockfd, const void *msg,int len,unsigned int flags,const struct sockaddr *to, int tolen); sockfd : 爲前面socket的返回值. msg : 通常爲常量字符串 len : 表示長度 flags : 一般爲0 to : 表示目地機的IP地址和端口號信息, 表示地址的結構體 tolen : 經常被賦值爲sizeof (struct sockaddr) 返回值 : 返回實際發送的數據字節長度或在出現發送錯誤時返回-1。 #Python s.sendto(string[,flag],address) s爲socket.socket()返回的套接字對象 address : 指定遠程地址, 形式爲(ipaddr,port)的元組 flag : 提供有關消息的其餘信息,一般能夠忽略 返回值 : 發送的字節數。
recvfrom()函數
接受UDP套接字的數據, 與recv()相似
#Linux int recvfrom(int sockfd,void *buf,int len,unsigned int flags,struct sockaddr *from,int *fromlen); sockfd : 爲前面socket的返回值. msg : 通常爲常量字符串 len : 表示長度 flags : 一般爲0 from :是一個struct sockaddr類型的變量,該變量保存鏈接機的IP地址及端口號 fromlen : 常置爲sizeof (struct sockaddr)。 返回值 : 返回接收到的字節數或當出現錯誤時返回-1,並置相應的errno。 #Python s.recvfrom(bufsize[.flag]) 返回值 : (data,address)元組, 其中data是包含接收數據的字符串,address是發送數據的套接字地址 bufsize : 指定要接收的數據大小 flag : 提供有關消息的其餘信息,一般能夠忽略
#服務器端 #!/usr/bin/env python # -*- coding:utf-8 -*- import socket BUF_SIZE = 1024 #設置緩衝區大小 server_addr = ('127.0.0.1', 8888) #IP和端口構成表示地址 server = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) #生成新的套接字對象 server.bind(server_addr) #套接字綁定IP和端口 while True : print "waitting for data" data, client_addr = server.recvfrom(BUF_SIZE) #從客戶端接收數據 print 'Connected by', client_addr, ' Receive Data : ', data server.sendto(data, client_addr) #發送數據給客戶端 server.close() #客戶端 #!/usr/bin/env python # -*- coding:utf-8 -*- import socket import struct BUF_SIZE = 1024 #設置緩衝區 server_addr = ('127.0.0.1', 8888) #IP和端口構成表示地址 client = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) #生成新的套接字對象 while True : data = raw_input('Please Input data > ') client.sendto(data, server_addr) #向服務器發送數據 data, addr = client.recvfrom(BUF_SIZE) #從服務器接收數據 print "Data : ", data client.close()
s.getpeername() #返回鏈接套接字的遠程地址。返回值一般是元組(ipaddr,port)。 s.getsockname() #返回套接字本身的地址。一般是一個元組(ipaddr,port) s.setsockopt(level,optname,value) #設置給定套接字選項的值。 s.getsockopt(level,optname[.buflen]) #返回套接字選項的值。 s.settimeout(timeout) #設置套接字操做的超時期,timeout是一個浮點數,單位是秒。值爲None表示沒有超時期。通常,超時期應該在剛建立套接字時設置,由於它們可能用於鏈接的操做(如connect()) s.gettimeout() #返回當前超時期的值,單位是秒,若是沒有設置超時期,則返回None。 s.fileno() #返回套接字的文件描述符。 s.setblocking(flag) #若是flag爲0,則將套接字設爲非阻塞模式,不然將套接字設爲阻塞模式(默認值)。非阻塞模式下,若是調用recv()沒有發現任何數據,或send()調用沒法當即發送數據,那麼將引發socket.error異常。 s.makefile() #建立一個與該套接字相關連的文件