您好,登錄后才能下訂單哦!
客戶端/服務器架構
所謂服務器就是一系列硬件或軟件,為一個或多個客戶端(服務的用戶)提供所需要的'服務'。它存在的唯一目的就是等待客戶端的請求,并響應它們(提供服務),然后等待更多的請求。另一方面,客戶端因特定的請求而聯系服務器,并發送必要的數據,然后等待服務器的回應,最后完成請求或給出故障信息。服務器無限地運行下去,并不斷地處理請求;而客戶端會進行一次性請求,然后接收該服務,最后結束他們之間的事務。
目前最常見的客戶端/服務器架構如圖所示,其中描繪了多個客戶端通過互聯網從一臺服務器上檢索信息。
套接字(socket)是計算機網絡數據結構,它體現了'通信端點'的概念。在沒有任何類型的通信開始之前,網絡應用程序必須創建套接字。可以將它們比作電話插孔,沒有它將無法通信。
socket起源于Unix,而Unix/Linux基本哲學之一就是“一切皆文件”,對于文件用打開、讀寫、關閉模式來操作。socket就是該模式的一個實現,socket即是一種特殊的文件,一些socket函數就是對其進行的操作(讀/寫IO、打開、關閉)。套接字通常有兩種類型:基于文件的和面向網絡的。
第一種類型是基于文件的AF_UNIX,其主要用于同一計算機中的兩個進程間通信;第二種是面向網絡的AF_INET,主要用于互聯網上計算機之間的通信。
通常網絡套接字分為面向連接的套接字與無連接的套接字。面向連接的通信提供序列化的、可靠的和不重復的數據交付,而沒有記錄邊界。實現這種連接類型的主要協議是傳輸控制協議(TCP)。為了創建TCP套接字,必須使用SOCK_STREAM作為套接字類型。
與傳輸控制協議形成對比的是數據報類型的套接字,它是一種無連接的套接字。這意味著,在通信開始之前并不需要建立連接。此時,在數據傳輸過程并無法保證它的順序性、可靠性或者重復性。然而,數據報保存了記錄邊界,這意味著消息是以整體發送的,而并非分成多個片段。實現這種連接類型的主要協議是用戶數據報(UDP)。為了創建UDP套接字,必須使用SOCK_DGRAM作為套接字類型。
Python中的網絡編程
s=socket(family,type, proto=0)
參數一:地址簇 socket.AF_INET IPv4(默認) socket.AF_UNIX 只能夠用于單一的Unix系統進程間通信 參數二:連接類型 socket.SOCK_STREAM 流式socket , for TCP (默認) socket.SOCK_RAW 原始套接字,普通的套接字無法處理ICMP、IGMP等網絡報文,而SOCK_RAW可以 ;其次,SOCK_RAW也可以處理特殊的IPv4報文;利用原始套接字,可以通過IP_HDRINCL套接字選項由用戶構造IP頭。 參數三:協議 0 (默認)與特定的地址家族相關的協議,如果是0,則系統就會根據地址格式和套接類別,自動選擇一個合適的協議 |
服務器套接字方法
s.bind(address) | 將套接字綁定到地址。address地址的格式取決于地址族。在AF_INET下,以元組 (host,port)的形式表示地址。 |
s.listen(backlog) | 開始監聽傳入連接。backlog指定在拒絕連接之前,可以掛起的最大連接數量。 |
s.accept() | 接受連接并返回(conn,address),其中conn是新的套接字對象,可以用來接收和發送數據。address是連接客戶端的地址。接收TCP客戶的連接(阻塞式)等待連接的到來 |
客戶端套接字方法
s.connect(address) | 連接到address處的套接字。address的格式為元組(hostname,port),如果連接出錯,返回socket.error錯誤。 |
s.connect_ex(address) | 同上,只不過會有返回值,連接成功時返回0,連接失敗時候返回編碼,而不是拋出一個異常。 |
普通的套接字方法
s.recv(bufsize[,flag]) | 接收套接字的數據。數據以字符串形式返回,bufsize指定最多可以接收的數量。flag提供有關消息的其他信息,通常可以忽略。 |
s.recv_into() | 接收TCP消息到指定的緩存區。 |
sk.recvfrom(bufsize[.flag]) | 與recv()類似,但返回值是(data,address)。其中data是包含接收數據的字符串,address是發送數據的套接字地址。 |
s.send(string[,flag]) | 將string中的數據發送到連接的套接字。返回值是要發送的字節數量,該數量可能小于string的字節大小。即:可能未將指定內容全部發送 。 |
s.sendall(string[,flag]) | 將string中的數據發送到連接的套接字,但在返回之前會嘗試發送所有數據。成功返回None,失敗則拋出異常。內部通過遞歸調用 send,將所有內容發送出去。 |
s.sendto(string[,flag],address) | 將數據發送到套接字,address是形式為(ipaddr,port)的元組,指定遠程地址。返回值是發送的字節數。該函數主要用于UDP協議。 |
s.close() | 關閉套接字 |
s.getpeername() | 返回連接套接字的遠程地址。返回值通常是元組(ipaddr,port) |
s.getsockname() | 返回套接字自己的地址。通常是一個元組(ipaddr,port) |
面向阻塞的套接字方法
s.setblocking(bool) | 是否阻塞(默認True),如果設置False,那么accept和recv時一旦無數據,則報錯。 |
s.settimeout(timeout) | 設置套接字操作的超時期,timeout是一個浮點數,單位是秒。值為 None表示沒有超時期。一般,超時期應該在剛創建套接字時設置,因為它們可能用于連接的操作(如 client 連接最多等待5s) |
s.gettimeout() | 獲取阻塞套接字操作的超時時間 |
面向文件的套接字方法
s.fileno() | 套接字的文件描述符 |
s.makefile() | 創建套接字關聯的文件對象 |
創建TCP服務器與客戶端
服務器端:
# !/usr/bin/env python # -*- coding:utf-8 -*- ''' 這個腳本創建TCP服務器,它接收來自客戶端的消息并打印在終端,返回當前時間戳 ''' from socket import * from time import ctime HOST = '' PORT = 12345 BUFSIZ = 1024 ADDR = (HOST,PORT) tcpSerSock = socket(AF_INET,SOCK_STREAM) tcpSerSock.bind(ADDR) tcpSerSock.listen(5) while True: print('waiting for connection...') tcpCliSock,addr=tcpSerSock.accept() print('...connected from:',addr) while True: data=tcpCliSock.recv(BUFSIZ) print(str(data,'utf-8')) if not data: break tcpCliSock.send(bytes(ctime(),'utf-8')) tcpCliSock.close() tcpSerSock.close()
客戶端:
# !/usr/bin/env python # -*- coding:utf-8 -*- from socket import * HOST = '127.0.0.1' PORT = 12345 BUFSIZ = 1024 ADDR = (HOST,PORT) tcpCliSock = socket(AF_INET,SOCK_STREAM) tcpCliSock.connect(ADDR) while True: data = input('> ') if not data: break tcpCliSock.send(bytes(data,'utf-8')) data = tcpCliSock.recv(BUFSIZ) if not data: break print(str(data,'utf-8')) tcpCliSock.close()
運行結果:
服務器端:
waiting for connection... ...connected from: ('127.0.0.1', 62735) 123 1234 3453 45 567
客戶端:
> 123 Sat May 6 21:31:06 2017 > 1234 Sat May 6 21:32:44 2017 > 3453 Sat May 6 21:32:46 2017 > 45 Sat May 6 21:32:47 2017 > 567 Sat May 6 21:32:48 2017 >
創建UDP客戶端與服務器
服務器端:
#!/usr/bin/env python # -*- coding:utf-8 -*- from socket import * from time import ctime ''' 這個腳本創建UDP服務器,接收來自客戶端的消息并打印在終端,返回當前時間戳 ''' HOST = '' PORT = 12345 BUFSIZ = 1024 ADDR = (HOST,PORT) udpSerSock = socket(AF_INET,SOCK_DGRAM) udpSerSock.bind(ADDR) while True: print('waiting for message... ') data,addr = udpSerSock.recvfrom(BUFSIZ) print(str(data,'utf-8')) udpSerSock.sendto(bytes(ctime(),'utf-8'),addr) print('...received form and return to:',addr) udpSerSock.close()
客戶端:
#!/usr/bin/env python # -*- coding:utf-8 -*- from socket import * HOST = 'localhost' PORT = 12345 BUFSIZ = 1024 ADDR = (HOST,PORT) udpCliSock = socket(AF_INET,SOCK_DGRAM) while True: data=input('>') if not data: break udpCliSock.sendto(bytes(data,'utf-8'),ADDR) data,ADDR=udpCliSock.recvfrom(BUFSIZ) if not data: break print(str(data,'utf-8')) udpCliSock.close()
輸出結果:
服務器端:
waiting for message... 123 ...received form and return to: ('127.0.0.1', 58765) waiting for message... 12334 ...received form and return to: ('127.0.0.1', 58765) waiting for message... 123 ...received form and return to: ('127.0.0.1', 58765) waiting for message... 14 ...received form and return to: ('127.0.0.1', 58765) waiting for message... 234 ...received form and return to: ('127.0.0.1', 58765) waiting for message... 5 ...received form and return to: ('127.0.0.1', 58765) waiting for message...
客戶端:
>123 Sun May 7 11:59:46 2017 >12334 Sun May 7 12:02:32 2017 >123 1Sun May 7 12:02:33 2017 >4 2Sun May 7 12:02:34 2017 >34 Sun May 7 12:02:34 2017 >5 Sun May 7 12:02:35 2017 >
SocketServer模塊
SocketServer是標準庫中的一個高級模塊,它的目的是簡化很多樣板代碼,它們是創建網絡客戶端和服務器必須的代碼。這個模塊中有各種各樣的類,這些類幫助程序員隱藏具體的實現細節,除此之外以面相對象的方式處理事務有助于組織數據,以及邏輯性地將功能放在正確的地方。在python3中SocketServer改名為socketserver。
各個類之間的繼承關系:
| BaseServer | | v | TCPServer |------->| UnixStreamServer | | v | UDPServer |------->| UnixDatagramServer |
類 | 描述 |
BaseServer | 包含核心服務器功能和mix-in類的鉤子,僅用于推導,這樣不會創建這個類的實例:可以用TCPServer或UDPServer創建類的實例 |
TCPServer/UDPServer | 基于的網絡同步TCP/UDP服務器 |
UnixStreamServer / UnixDatagramServer | 基于文件的基礎同步TCP/UDP服務器 |
上面四個服務類都是同步處理請求的,即一個請求沒處理完不能處理下一個請求。當要想支持異步模型,可以利用多繼承讓server類繼承ForkingMixIn或ThreadingMixIn類。
類 | 描述 |
ForkingMixIn/ ThreadingMixIn | 核心派出或線程功能:只作用mix-in類域一個服務器類配合實現一些異步性;不能直接實例化。 |
ForkingTCPServer/ ForkingUDPServer | ForkingMixIn和TCPServer/UDPServer的組合,利用多進程實現異步 |
ThreadingTCPServer/ ThreadingUDPServer | ThreadingMixIn和TCPServer/UDPServer的組合,利用多線程實現異步 |
在SocketServer模塊中,服務器處理請求主要通過BaseRequestHandler類來實現,在這個類中有多個處理函數可以使用。
setup()
在handle()方法之前調用執行請求的初始化動作,默認不執行任何操作。
handle()
此函數必須完成對服務器所有的請求工作,有些實例屬性對其可獲得,
請求通過self.request函數,
客戶端地址通過self.client_address函數,
服務器實例通過self.server函數,
self.request對于流服務self.request是套接字對象,對于數據報文self.request是一系列字符串和套接字。
finish()
在handle()函數后調用此方法,執行請求后的清理操作,默認不執行任何操作,如果setup()出現異常,此函數不會被調用。
服務器端實例:
#!/usr/bin/env python # -*- coding:utf-8 -*- ''' 通過socketserver類、TCPServer和StreamRequestHandler,該腳本創建時間戳TCP服務器 ''' from socketserver import (TCPServer as TCP, StreamRequestHandler as SRH) from time import ctime HOST = '' PORT = 12345 ADDR = (HOST,PORT) class MyRequestHandler(SRH): def setup(self): print('處理請求前!!!') def handle(self): conn=self.request print('...connected from:',self.client_address) print(str(conn.recv(1024),'utf-8')) conn.send(bytes(ctime(),'utf-8')) def finish(self): print('處理請求后!!!') print('----------------------') tcpServ = TCP(ADDR,MyRequestHandler) print('waiting for connection...') tcpServ.serve_forever()
客戶端實例:
#!/usr/bin/env python # -*- coding:utf-8 -*- from socket import * HOST = 'localhost' PORT = 12345 BUFSIZ = 1024 ADDR = (HOST,PORT) while True: tcpCliSock = socket(AF_INET,SOCK_STREAM) tcpCliSock.connect(ADDR) data=input('>') if not data: break tcpCliSock.send(bytes(data,'utf-8')) data=tcpCliSock.recv(BUFSIZ) if not data: break print(str(data,'utf-8')) tcpCliSock.close()
運行結果:
服務器端:
waiting for connection... 處理請求前!!! ...connected from: ('127.0.0.1', 62042) 1223 處理請求后!!! ---------------------- 處理請求前!!! ...connected from: ('127.0.0.1', 62051) 123 處理請求后!!! ---------------------- 處理請求前!!! ...connected from: ('127.0.0.1', 62052) 5435 處理請求后!!! ---------------------- 處理請求前!!! ...connected from: ('127.0.0.1', 62053) 574 處理請求后!!! ---------------------- 處理請求前!!! ...connected from: ('127.0.0.1', 62054) 68 處理請求后!!! ----------------------
客戶端:
>1223 Sun May 7 13:01:45 2017 >123 Sun May 7 13:01:46 2017 >5435 Sun May 7 13:01:47 2017 >574 Sun May 7 13:01:48 2017 >68 Sun May 7 13:01:49 2017 >
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。