python-網絡編程

 一:socket和套接字

1.1.什麼是socket 

  socket 的原意是「插座」,在計算機通訊領域,socket 被翻譯爲「套接字」,它是計算機之間進行通訊的一種約定或一種方式。經過 socket 這種約定,一臺計算機能夠接收其餘計算機的數據,也能夠向其餘計算機發送數據。python

1.2.套接字分類

       這個世界上有不少種套接字(socket),好比 DARPA Internet 地址(Internet 套接字)、本地節點的路徑名(Unix套接字)、CCITT X.25地址(X.25 套接字)等。react

1.3.Internet 套接分類

  Internet 套接字分紅兩種類型:編程

  流格式套接字(Stream Sockets)也叫「面向鏈接的套接字」,在代碼中使用 SOCK_STREAM 表示。數組

    數據報格式套接字(Datagram Sockets)也叫「無鏈接的套接字」,在代碼中使用 SOCK_DGRAM 表示。安全

1.4.無鏈接套接字

數據報格式套接字(Datagram Sockets)也叫「無鏈接的套接字」,在代碼中使用 SOCK_DGRAM 表示。

計算機只管傳輸數據,不做數據校驗,若是數據在傳輸中損壞,或者沒有到達另外一臺計算機,是沒有辦法補救的。也就是說,數據錯了就錯了,沒法重傳。

由於數據報套接字所作的校驗工做少,因此在傳輸效率方面比流格式套接字要高。

能夠將 SOCK_DGRAM 比喻成高速移動的摩托車快遞,它有如下特徵:服務器

    • 強調快速傳輸而非傳輸順序;
    • 傳輸的數據可能丟失也可能損毀;
    • 限制每次傳輸的數據大小;
    • 數據的發送和接收是同步的(有的教程也稱「存在數據邊界」)。
    • 衆所周知,速度是快遞行業的生命。用摩托車發往同一地點的兩件包裹無需保證順序,只要以最快的速度交給客戶就行。這種方式存在損壞或丟失的風險,並且包裹大小有必定限制。所以,想要傳遞大量包裹,就得分配發送。

      將無鏈接套接字比喻成摩托車快遞

      另外,用兩輛摩托車分別發送兩件包裹,那麼接收者也須要分兩次接收,因此「數據的發送和接收是同步的」;換句話說,接收次數應該和發送次數相同。

      總之,數據報套接字是一種不可靠的、不按順序傳遞的、以追求速度爲目的的套接字。

      數據報套接字也使用 IP 協議做路由,可是它不使用 TCP 協議,而是使用 UDP 協議(User Datagram Protocol,用戶數據報協議)。

      QQ 視頻聊天和語音聊天就使用 SOCK_DGRAM 來傳輸數據,由於首先要保證通訊的效率,儘可能減少延遲,而數據的正確性是次要的,即便丟失很小的一部分數據,視頻和音頻也能夠正常解析,最多出現噪點或雜音,不會對通訊質量有實質的影響。

1.5.有鏈接套接字

  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鏈接

二:python中的網絡編程

2.1.socket()模塊函數

  要建立套接字,必須使用socket.socket()函數。

form socket import *

tcpsock = socket(AF_INTE, SOCK_STREMA)

2.2.套接字對象(內置)方法

常見的套接字對象方法和屬性

名 稱

描 述

服務器套接字方法

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①

套接字協議

 

 2.3執行TCP服務器和客戶端

 服務器:

#!/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()

  

2.4.UDP服務器和客戶端

服務器:

#!/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()

  

 2.5.socket模塊屬性

除了屬性的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()

以秒(浮點數)爲單位返回默認套接字超時時間;以秒(浮點數)爲單位設置默認套接字超時時間

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

2.6.socketServer模塊

雖然說用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()

  

2.7.Twisted框架

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()
相關文章
相關標籤/搜索