socket 的原意是「插座」,在計算機通訊領域,socket 被翻譯爲「套接字」,它是計算機之間進行通訊的一種約定或一種方式。經過 socket 這種約定,一臺計算機能夠接收其餘計算機的數據,也能夠向其餘計算機發送數據。python
這個世界上有不少種套接字(socket),好比 DARPA Internet 地址(Internet 套接字)、本地節點的路徑名(Unix套接字)、CCITT X.25地址(X.25 套接字)等。react
Internet 套接字分紅兩種類型:編程
流格式套接字(Stream Sockets)也叫「面向鏈接的套接字」,在代碼中使用 SOCK_STREAM 表示。數組
數據報格式套接字(Datagram Sockets)也叫「無鏈接的套接字」,在代碼中使用 SOCK_DGRAM 表示。安全
數據報格式套接字(Datagram Sockets)也叫「無鏈接的套接字」,在代碼中使用 SOCK_DGRAM 表示。
計算機只管傳輸數據,不做數據校驗,若是數據在傳輸中損壞,或者沒有到達另外一臺計算機,是沒有辦法補救的。也就是說,數據錯了就錯了,沒法重傳。
由於數據報套接字所作的校驗工做少,因此在傳輸效率方面比流格式套接字要高。
能夠將 SOCK_DGRAM 比喻成高速移動的摩托車快遞,它有如下特徵:服務器
SOCK_STREAM 是一種可靠的、雙向的通訊數據流,數據能夠準確無誤地到達另外一臺計算機,若是損壞或丟失,能夠從新發送。網絡
SOCK_STREAM 有如下幾個特徵:框架
爲何流格式套接字能夠達到高質量的數據傳輸呢?這是由於它使用了 TCP 協議(The Transmission Control Protocol,傳輸控制協議),TCP 協議會控制你的數據按照順序到達而且沒有錯誤。異步
你也許見過 TCP,是由於你常常據說「TCP/IP」。TCP 用來確保數據的正確性,IP(Internet Protocol,網絡協議)用來控制數據如何從源頭到達目的地,也就是常說的「路由」。socket
能夠將 SOCK_STREAM 比喻成一條傳送帶,只要傳送帶自己沒有問題(不會斷網),就能保證數據不丟失;同時,較晚傳送的數據不會先到達,較早傳送的數據不會晚到達,這就保證了數據是按照順序傳遞的。
那麼,「數據的發送和接收不一樣步」該如何理解呢?
假設傳送帶傳送的是水果,接收者須要湊齊 100 個後才能裝袋,可是傳送帶可能把這 100 個水果分批傳送,好比第一批傳送 20 個,第二批傳送 50 個,第三批傳送 30 個。接收者不須要和傳送帶保持同步,只要根據本身的節奏來裝袋便可,不用管傳送帶傳送了幾批,也不用每到一批就裝袋一次,能夠等到湊夠了 100 個水果再裝袋。
流格式套接字的內部有一個緩衝區(也就是字符數組),經過 socket 傳輸的數據將保存到這個緩衝區。接收端在收到數據後並不必定當即讀取,只要數據不超過緩衝區的容量,接收端有可能在緩衝區被填滿之後一次性地讀取,也可能分紅好幾回讀取。
也就是說,無論數據分幾回傳送過來,接收端只須要根據本身的要求讀取,不用非得在數據到達時當即讀取。傳送端有本身的節奏,接收端也有本身的節奏,它們是不一致的。
面向鏈接的套接字通訊工做流程
(1)服務器先用socket函數來創建一個套接字,用這個套接字完成通訊的監聽
(2)用bind函數來綁定一個端口號和IP地址。由於本地計算機可能有多個IP,每個IP有多個端口號,須要指定一個IP和端口進行監聽
(3)服務器調用listen函數,使服務器的這個端口和IP出於監聽狀態,等待客戶機的鏈接
(4)客戶機用socket創建一個套接字
(5)客戶機調用connect函數,經過遠程IP和端口號鏈接遠程計算機指定的端口
(6)服務器用accept函數來接收遠程計算機的鏈接,創建起與客戶端之間的通訊
(7)創建鏈接之後,客戶機用write函數向socket中寫入數據。也可用read函數讀取服務器發送來的數據
(8)服務器用read函數讀取客戶機發送來的數據,也可用write函數發送數據
(9)完成通訊之後,用close函數關閉socket鏈接
要建立套接字,必須使用socket.socket()函數。
form socket import * tcpsock = socket(AF_INTE, SOCK_STREMA)
常見的套接字對象方法和屬性
名 稱 |
描 述 |
服務器套接字方法 |
|
s.bind() |
將地址(主機名、端口號對)綁定到套接字上 |
s.listen() |
設置並啓動 TCP 監聽器 |
s.accept() |
被動接受 TCP 客戶端鏈接,一直等待直到鏈接到達(阻塞) |
客戶端套接字方法 |
|
s.connect() |
主動發起 TCP 服務器鏈接 |
s.connect_ex() |
connect()的擴展版本,此時會以錯誤碼的形式返回問題,而不是拋出一個異常 |
普通的套接字方法 |
|
s.recv() |
接收 TCP 消息 |
s.recv_into()① |
接收 TCP 消息到指定的緩衝區 |
s.send() |
發送 TCP 消息 |
s.sendall() |
完整地發送 TCP 消息 |
s.recvfrom() |
接收 UDP 消息 |
s.recvfrom_into()① |
接收 UDP 消息到指定的緩衝區 |
s.sendto() |
發送 UDP 消息 |
s.getpeername() |
鏈接到套接字(TCP)的遠程地址 |
s.getsockname() |
當前套接字的地址 |
s.getsockopt() |
返回給定套接字選項的值 |
s.setsockopt() |
設置給定套接字選項的值 |
s.shutdown() |
關閉鏈接 |
s.close() |
關閉套接字 |
s.detach()② |
在未關閉文件描述符的狀況下關閉套接字,返回文件描述符 |
s.ioctl()③ |
控制套接字的模式(僅支持 Windows) |
面向阻塞的套接字方法 |
|
s.setblocking() |
設置套接字的阻塞或非阻塞模式 |
s.settimeout()④ |
設置阻塞套接字操做的超時時間 |
s.gettimeout()④ |
獲取阻塞套接字操做的超時時間 |
面向文件的套接字方法 |
|
s.fileno() |
套接字的文件描述符 |
s.makefile() |
建立與套接字關聯的文件對象 |
數據屬性 |
|
s.family① |
套接字家族 |
s.type① |
套接字類型 |
s.proto① |
套接字協議 |
服務器:
#!/use/bin/env python from socket import * import time HOST = '' PORT = 21567 BUFSIZE = 1024 ADDR = (HOST, PORT) tcpSerSock = socket(AF_INET) tcpSerSock.bind(ADDR) tcpSerSock.listen(5) while True: print 'waiting to connection...' tcpCliSock, addr = tcpSerSock.accept() print '....connected from:',addr while True: data = tcpCliSock.recv(BUFSIZE) if not data: break lotime = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()) tcpCliSock.send('[%s] %s' % (lotime, data)) tcpCliSock.close() tcpSerSock.close()
客戶端:
#!/use/bin/env python from socket import * HOST = 'localhost' PORT = 21567 BUFSIZE = 1024 ADDR = (HOST, PORT) tcpCliSock = socket(AF_INET) tcpCliSock.connect(ADDR) while True: data = raw_input('> ') if not data: break tcpCliSock.send(data) data = tcpCliSock.recv(BUFSIZE) if not data: break print data tcpCliSock.close()
服務器:
#!/use/bin/env python from socket import * import time HOST = '' PORT = 21567 BUFSIZE = 1024 ADDR = (HOST, PORT) udpSerSock = socket(AF_INET, SOCK_DGRAM) udpSerSock.bind(ADDR) while True: print('waittinng for meaasge...') data, addr = udpSerSock.recvfrom(BUFSIZE) lotime = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()) udpSerSock.sendto(b'[%s] %s' %(lotime, data), addr) print('...received from and returned to: ', addr) udpSerSock.close()
客戶端:
#!/use/bin/env python from socket import * HOST = 'localhost' PORT = 21567 BUFSIZE = 1024 ADDR = (HOST, PORT) udpCliSock = socket(AF_INET, SOCK_DGRAM) while True: data = raw_input('> ') if not data: break udpCliSock.sendto(data, ADDR) data, addr = udpCliSock.recvfrom(BUFSIZE) if not data: break print(data.decode('utf-8')) udpCliSock.close()
除了屬性的socket.socket()函數外,socket()模塊還提供下面常見屬性:
socket 模塊屬性
屬 性 名 稱 |
描 述 |
數據屬性 |
|
AF_UNIX、AF_INET、AF_INET6①、AF_NETLINK②、AF_TIPC③ |
Python 中支持的套接字地址家族 |
SO_STREAM、SO_DGRAM |
套接字類型(TCP=流,UDP=數據報) |
has_ipv6④ |
指示是否支持 IPv6 的布爾標記 |
異常 |
|
error |
套接字相關錯誤 |
herror① |
主機和地址相關錯誤 |
gaierror① |
地址相關錯誤 |
timeout |
超時時間 |
函數 |
|
socket() |
以給定的地址家族、套接字類型和協議類型(可選)建立一個套接字對象 |
socketpair()⑤ |
以給定的地址家族、套接字類型和協議類型(可選)建立一對套接字對象 |
create_connection() |
常規函數,它接收一個地址(主機名,端口號)對,返回套接字對象 |
fromfd() |
以一個打開的文件描述符建立一個套接字對象 |
ssl() |
經過套接字啓動一個安全套接字層鏈接;不執行證書驗證 |
getaddrinfo()① |
獲取一個五元組序列形式的地址信息 |
getnameinfo() |
給定一個套接字地址,返回(主機名,端口號)二元組 |
getfqdn()⑥ |
返回完整的域名 |
gethostname() |
返回當前主機名 |
gethostbyname() |
將一個主機名映射到它的 IP 地址 |
gethostbyname_ex() |
gethostbyname()的擴展版本,它返回主機名、別名主機集合和 IP 地址列表 |
gethostbyaddr() |
將一個 IP 地址映射到 DNS 信息;返回與 gethostbyname_ex()相同的 3 元組 |
getprotobyname() |
將一個協議名(如‘tcp’)映射到一個數字 |
getservbyname()/getservbyport() |
將一個服務名映射到一個端口號,或者反過來;對於任何一個函數來講,協議名都是可選的 |
ntohl()/ntohs() |
未來自網絡的整數轉換爲主機字節順序 |
htonl()/htons() |
未來自主機的整數轉換爲網絡字節順序 |
inet_aton()/inet_ntoa() |
將 IP 地址八進制字符串轉換成 32 位的包格式,或者反過來(僅用於 IPv4 地址) |
inet_pton()/inet_ntop() |
將IP 地址字符串轉換成打包的二進制格式,或者反過來(同時適用於 IPv4 和IPv6 地址) |
getdefaulttimeout()/setdefaulttimeout() |
以秒(浮點數)爲單位返回默認套接字超時時間;以秒(浮點數)爲單位設置默認套接字超時時間 |
雖然說用Python編寫簡單的網絡程序很方便,但複雜一點的網絡程序仍是用現成的框架比較 好。這樣就能夠專心事務邏輯,而不是套接字的各類細節。SocketServer模塊簡化了編寫網絡服務程序的任務。同時SocketServer模塊也 是Python標準庫中不少服務器框架的基礎。
socketserver在python2中爲SocketServer,在python3種取消了首字母大寫,更名爲socketserver。
socketserver中包含了兩種類,一種爲服務類(server class),一種爲請求處理類(request handle class)。前者提供了許多方法:像綁定,監聽,運行…… (也就是創建鏈接的過程) 後者則專一於如何處理用戶所發送的數據(也就是事務邏輯)。
**通常狀況下,全部的服務,都是先創建鏈接,也就是創建一個服務類的實例,而後開始處理用戶請求,也就是創建一個請求處理類的實例。
SocketServer 模塊類
類 |
描 述 |
BaseServer |
包含核心服務器功能和mix-in 類的鉤子;僅用於推導,這樣不會建立這個類的實例;能夠用 TCPServer 或 UDPServer 建立類的實例 |
TCPServer/UDPServer |
基礎的網絡同步 TCP/UDP 服務器 |
UnixStreamServer/UnixDatagramServer |
基於文件的基礎同步 TCP/UDP 服務器 |
ForkingMixIn/ThreadingMixIn |
核心派出或線程功能;只用做 mix-in 類與一個服務器類配合實現一些異步性;不能直接實例化這個類 |
ForkingTCPServer/ForkingUDPServer |
ForkingMixIn 和 TCPServer/UDPServer 的組合 |
ThreadingTCPServer/ThreadingUDPServer |
ThreadingMixIn 和 TCPServer/UDPServer 的組合 |
BaseRequestHandler |
包含處理服務請求的核心功能;僅僅用於推導,這樣沒法建立這個類的實例; 可使用StreamRequestHandler 或 DatagramRequestHandler 建立類的實例 |
StreamRequestHandler/DatagramRequestHandler |
實現 TCP/UDP 服務器的服務處理器 |
服務端:
#!/use/bin/env python # -*- coding: utf-8 -*- from SocketServer import (TCPServer as TCP, StreamRequestHandler as SRH) import time HOST = '' PORT = 21567 BUFSIZE = 1024 ADDR = (HOST, PORT) #重寫SocketServer的子類StreamRequestHandler的handle方法,該方法默認沒有任何行爲 class MyRequestHandler(SRH): def handle(self): print '...connected from:', self.client_address lotime = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()) #readline()來獲取客戶端消息,write()將字符串發回客戶端 self.wfile.write('[%s] %s' % (lotime, self.rfile.readline())) #建立TCP服務器,並沒有限循環的等待客戶端請求 tcpServ = TCP(ADDR, MyRequestHandler) print 'waiting for conntion....' tcpServ.serve_forever()
客戶端:
#!/use/bin/env python # -*- coding: utf-8 -*- from socket import * HOST = 'localhost' PORT = 21567 BUFSIZE = 1024 ADDR = (HOST, PORT) while True: tcpCliSock = socket(AF_INET) tcpCliSock.connect(ADDR) data = raw_input('> ') if not data: break tcpCliSock.send('%s\r\n' % data) data = tcpCliSock.recv(BUFSIZE) if not data: break print data.strip() tcpCliSock.close()
twisted是一個用python語言寫的事件驅動的網絡框架,他支持不少種協議,包括UDP,TCP,TLS和其餘應用層協議,好比HTTP,SMTP,NNTM,IRC,XMPP/Jabber。 很是好的一點是twisted實現和不少應用層的協議,開發人員能夠直接只用這些協議的實現。其實要修改Twisted的SSH服務器端實現很是簡單。不少時候,開發人員須要實現protocol類。
一個Twisted程序由reactor發起的主循環和一些回調函數組成。當事件發生了,好比一個client鏈接到了server,這時候服務器端的事件會被觸發執行。
安裝方法:
進入連接https://pypi.org/simple/twisted/下載安裝包進行安裝。
方法二:
sudo apt-get install python-setuptools sudo apt-get install python-dev sudo easy_install twisted
服務端:#!/use/bin/env python
# -*- coding: utf-8 -*- from twisted.internet import protocol, reactor import time PORT = 21567
#得到protocol類併爲時間戳服務器調用TSServProtocol,而後重寫了connetctionMade()和dataReceived()方法
class TSServProtocol(protocol.Protocol):
#當客戶端鏈接到服務器時就執行connectionMade() def connectionMade(self): clnt =self.clnt = self.transport.getPeer().host print '...connected from:', clnt
#當服務器接收到客戶端請求時執行dataReceived() def dataReceived(self, data): lotime = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()) self.transport.write('[%s] %s' % (lotime, data)) factory = protocol.Factory() factory.protocol = TSServProtocol print 'waiting for connection ...' reactor.listenTCP(PORT, factory) reactor.run()
客戶端:
#!/use/bin/env python # -*- coding: utf-8 -*- from twisted.internet import protocol, reactor HOST = 'localhost' PORT = 21567 class TSClntProtocol(protocol.Protocol): def sendData(self): data = raw_input('> ') if data: print '...sending %s...' % data self.transport.write(data) else: self.transport.loseConnection() def connectionMade(self): self.sendData() def dataReceived(self, data): print data self.sendData() class TSClntFactory(protocol.ClientFactory): protocol = TSClntProtocol clientConnectionLost = clientConnectionFalied = lambda self, connector, reason: reactor.stop() reactor.connectTCP(HOST, PORT, TSClntFactory()) reactor.run()