第十七章 Python网络编程
Socket簡介
在網(wǎng)絡上的兩個程序通過一個雙向的通信連接實現(xiàn)數(shù)據(jù)的交換,這個鏈接的一端稱為一個Socket(套接字),用于描述IP地址和端口。
建立網(wǎng)絡通信連接至少要一對端口號(Socket),Socket本質(zhì)是編程接口(API),對TCP/IP的封裝,提供了網(wǎng)絡通信能力。
每種服務都打開一個Socket,并綁定到端口上,不同的端口對應不同的服務,就像http對應80端口。
Socket是面向C/S(客戶端/服務器)模型設(shè)計,客戶端在本地隨機申請一個唯一的Socket號,服務器擁有公開的socket,任何客戶端都可以向它發(fā)送連接請求和信息請求。
比如:用手機打電話給10086客服,你的手機號就是客戶端,10086客服是服務端。必須在知道對方電話號碼前提下才能與對方通訊。
Socket數(shù)據(jù)處理流程如圖:
17.1 socket
在Python中提供此服務的模塊是socket和SocketServer,下面是socket常用的類、方法:
| 方法 | 描述 |
| socket.socket([family[, type[, proto]]]) | socket初始化函數(shù),(地址族,socket類型,協(xié)議編號)協(xié)議編號默認0 |
| socket.AF_INET | IPV4協(xié)議通信 |
| socket.AF_INET6 | IPV6協(xié)議通信 |
| socket.SOCK_STREAM | socket類型,TCP |
| socket.SOCK_DGRAM | socket類型,UDP |
| socket.SOCK_RAW | 原始socket,可以處理普通socker無法處理的報文,比如ICMP |
| socket.SOCK_RDM | 更可靠的UDP類型,保證對方收到數(shù)據(jù) |
| socket.SOCK_SEQPACKET | 可靠的連續(xù)數(shù)據(jù)包服務 |
socket.socket()對象有以下方法:
| accept() | 接受連接并返回(socket object, address info),address是客戶端地址 |
| bind(address) | 綁定socket到本地地址,address是一個雙元素元組(host,port) |
| listen(backlog) | 開始接收連接,backlog是最大連接數(shù),默認1 |
| connect(address) | 連接socket到遠程地址 |
| connect_ex(address) | 連接socket到遠程地址,成功返回0,錯誤返回error值 |
| getpeername() | 返回遠程端地址(hostaddr, port) |
| gettimeout() | 返回當前超時的值,單位秒,如果沒有設(shè)置返回none |
| recv(buffersize[, flags]) | 接收來自socket的數(shù)據(jù),buffersize是接收數(shù)據(jù)量 |
| send(data[, flags]) | 發(fā)送數(shù)據(jù)到socket,返回值是發(fā)送的字節(jié)數(shù) |
| sendall(data[, flags]) | 發(fā)送所有數(shù)據(jù)到socket,成功返回none,失敗拋出異常 |
| setblocking(flag) | 設(shè)置socket為阻塞(flag是true)或非阻塞(flag是flase) |
溫習下TCP與UDP區(qū)別:
TCP和UDP是OSI七層模型中傳輸層提供的協(xié)議,提供可靠端到端的傳輸服務。
TCP(Transmission Control Protocol,傳輸控制協(xié)議),面向連接協(xié)議,雙方先建立可靠的連接,再發(fā)送數(shù)據(jù)。適用于可靠性要求高的應用場景。
UDP(User Data Protocol,用戶數(shù)據(jù)報協(xié)議),面向非連接協(xié)議,不與對方建立連接,直接將數(shù)據(jù)包發(fā)送給對方,因此相對TCP傳輸速度快?。適用于可靠性要求低的應用場景。
17.1.1 TCP編程
下面創(chuàng)建一個服務端TCP協(xié)議的Socket演示下。
先寫一個服務端:
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | #!/usr/bin/python #?-*-?coding:?utf-8?-*- import?socket HOST?=?''?????????????????#?為空代表所有可用的網(wǎng)卡 PORT?=?50007??????????????#?任意非特權(quán)端口 s?=?socket.socket(socket.AF_INET,?socket.SOCK_STREAM) s.bind((HOST,?PORT)) s.listen(1)???#?最大連接數(shù) conn,?addr?=?s.accept()???#?返回客戶端地址 print?'Connected?by',?addr while?1: ????data?=?conn.recv(1024)???#?每次最大接收客戶端發(fā)來數(shù)據(jù)1024字節(jié) ????if?not?data:?break???????#?當沒有數(shù)據(jù)就退出死循環(huán)? ????print?"Received:?",?data?#?打印接收的數(shù)據(jù) ????conn.sendall(data)???????#?把接收的數(shù)據(jù)再發(fā)給客戶端 conn.close() |
再寫一個客戶端:
| 1 2 3 4 5 6 7 8 9 10 11 | #!/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')?#?發(fā)送數(shù)據(jù) data?=?s.recv(1024)???????#?接收服務端發(fā)來的數(shù)據(jù) s.close() print?'Received:?',?data |
寫好后,打開一個終端窗口執(zhí)行:
| 1 2 3 4 5 | #?python?socket-server.py 監(jiān)聽中... #?直到客戶端運行會接收到下面數(shù)據(jù)并退出 Connected?by?('192.168.1.120',?37548) Received:??Hello,?world |
再打開一個終端窗口執(zhí)行:
# 如果端口監(jiān)聽說明服務端運行正常
| 1 2 3 4 | #?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)監(jiān)聽進來的連接
4)接受連接
5)處理數(shù)據(jù)
17.1.2 UDP編程
服務端:
| 1 2 3 4 5 6 7 8 9 10 11 | 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() |
客戶端:
| 1 2 3 4 5 6 7 8 | 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協(xié)議時,服務端就少了listen()和accept(),不需要建立連接就直接接收客戶端的數(shù)據(jù),也是把數(shù)據(jù)直接發(fā)送給客戶端。
客戶端少了connect(),同樣直接通過sendto()給服務器發(fā)數(shù)據(jù)。
而TCP協(xié)議則前提先建立三次握手。
17.1.3 舉一個更直觀的socket通信例子
客戶端發(fā)送bash命令,服務端接收到并執(zhí)行,把返回結(jié)果回應給客戶端。
服務端:
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 | #!/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字節(jié) ????????data?=?conn.recv(1024) ????????if?not?data:?#?客戶端關(guān)閉服務端會收到一個空數(shù)據(jù) ????????????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() |
客戶端:
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | #!/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() |
查看運行效果,先運行服務端,再運行客戶端:
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | #?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: |
我想通過上面這個例子你已經(jīng)大致掌握了socket的通信過程。
再舉一個例子,通過socket獲取本機網(wǎng)卡IP:
| 1 2 3 4 5 6 7 8 9 10 | >>>?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運維開發(fā)群)
17.2 SocketServer
ScoketServer是Socket服務端庫,比socket庫更高級,實現(xiàn)了多線程和多線程,并發(fā)處理多個客戶端請求。
下面是幾個常用的類:
| SocketServer.TCPServer(server_address,? RequestHandlerClass,?bind_and_activate=True) | 服務器類,TCP協(xié)議 |
| SocketServer.UDPServer(server_address,? RequestHandlerClass,?bind_and_activate=True) | 服務器類,UDP協(xié)議 |
| SocketServer.BaseServer(server_address,? RequestHandlerClass) | 這個是所有服務器對象的超類。它定義了接口,不提供大多數(shù)方法,在子類中進行。 |
| SocketServer.BaseRequestHandler | 這個是所有請求處理對象的超類。它定義了接口,一個具體的請求處理程序子類必須定義一個新的handle()方法。 |
| SocketServer.StreamRequestHandler | 流式socket,根據(jù)socket生成讀寫socket用的兩個文件對象,調(diào)用rfile和wfile讀寫 |
| SocketServer.DatagramRequestHandler | 數(shù)據(jù)報socket,同樣生成rfile和wfile,但UDP不直接關(guān)聯(lián)socket。這里rfile是由UDP中讀取的數(shù)據(jù)生成,wfile則是新建一個StringIO,用于寫數(shù)據(jù) |
| SocketServer.ForkingMixIn/ThreadingMixIn | 多進程(分叉)/多線程實現(xiàn)異步。混合類,這個類不會直接實例化。用于實現(xiàn)處理多連接 |
SocketServer.BaseServer()對象有以下方法:
| fileno() | 返回一個整數(shù)文件描述符上服務器監(jiān)聽的套接字 |
| handle_request() | 處理一個請求 |
| serve_forever(poll_interval=0.5) | 處理,直至有明確要求shutdown()的請求。輪訓關(guān)機每poll_interval秒 |
| shutdown() | 告訴serve_forever()循環(huán)停止并等待 |
| server_close() | 清理服務器 |
| address_family | 地址族 |
| server_address | 監(jiān)聽的地址 |
| RequestHandlerClass | 用戶提供的請求處理類 |
| socket | socket對象上的服務器將監(jiān)聽傳入的請求 |
| allow_reuse_address | 服務器是否允許地址的重用。默認False |
| request_queue_size | 請求隊列的大小。 |
| socket_type | socket類型。socket.SOCK_STREAM或socket.SOCK_DGRAM |
| timeout | 超時時間,以秒為單位 |
| finish_request() | 實際處理通過實例請求RequestHandleClass并調(diào)用其handle()方法 |
| get_request() | 必須接受從socket的請求,并返回 |
| handle_error(request, client_address) | 如果這個函數(shù)被條用handle() |
| process_request(request, client_address) | ? |
| server_activate() | ? |
| server_bind() | 由服務器構(gòu)造函數(shù)調(diào)用的套接字綁定到所需的地址 |
| verify_request(request, client_address) | 返回一個布爾值,如果該值是True,則該請求將被處理,如果是False,該請求將被拒絕。 |
創(chuàng)建一個服務器需要幾個步驟:
1)創(chuàng)建類,繼承請求處理類(BaseRequestHandler),并重載其handle()方法,此方法將處理傳入的請求
2)實例化服務器類之一,它傳遞服務器的地址和請求處理程序類
3)調(diào)用handle_request()或serve_forever()服務器對象的方法來處理一個或多個請求
4)調(diào)用server_close()關(guān)閉套接字
17.2.1 TCP編程
服務端:
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | #!/usr/bin/python #?-*-?coding:?utf-8?-* import?SocketServer class?MyTCPHandler(SocketServer.BaseRequestHandler): ????""" ????請求處理程序類。 ????每個連接到服務器都要實例化一次,而且必須覆蓋handle()方法來實現(xiàn)與客戶端通信 ????""" ????def?handle(self): ????????#?self.request?接收客戶端數(shù)據(jù) ????????self.data?=?self.request.recv(1024).strip() ????????print?"%s?wrote:"?%?(self.client_address[0]) ????????print?self.data ????????#?把接收的數(shù)據(jù)轉(zhuǎn)為大寫發(fā)給客戶端 ????????self.request.sendall(self.data.upper()) if?__name__?==?"__main__": ????HOST,?PORT?=?"localhost",?9999 ????#?創(chuàng)建服務器并綁定本地地址和端口 ????server?=?SocketServer.TCPServer((HOST,?PORT),?MyTCPHandler) ????#?激活服務器,會一直運行,直到Ctrl-C中斷 ????server.serve_forever() |
另一個請求處理程序類,利用流(類文件對象簡化通信提供標準文件接口):
| 1 2 3 4 5 6 7 8 | class?MyTCPHandler(SocketServer.StreamRequestHandler): ????def?handle(self): ????????#?self.rfile創(chuàng)建的是一個類文件對象處理程序,就可以調(diào)用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()) |
客戶端:
| 1 2 3 4 5 6 7 8 9 10 11 12 13 | 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 |
服務端結(jié)果:
| 1 2 3 4 5 | #?python?TCPServer.py 127.0.0.1?wrote: hello 127.0.0.1?wrote: nice |
客戶端結(jié)果:
| 1 2 3 4 5 6 | #?python?TCPClient.py?hello Sent:?hello Received:?HELLO #?python?TCPClient.py?nice Sent:?nice Received:?NICE |
17.2.2 UDP編程
服務端:
| 1 2 3 4 5 6 7 8 9 10 11 12 | 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() |
客戶端:
| 1 2 3 4 5 6 7 8 9 | 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執(zhí)行結(jié)果一樣。
17.2.3 異步混合
創(chuàng)建異步處理,使用ThreadingMixIn和ForkingMixIn類。
ThreadingMixIn類的一個例子:
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 | #!/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() |
| 1 2 3 4 5 | #?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 |
本文轉(zhuǎn)自 李振良OK 51CTO博客,原文鏈接:http://blog.51cto.com/lizhenliang/1879549,如需轉(zhuǎn)載請自行聯(lián)系原作者
總結(jié)
以上是生活随笔為你收集整理的第十七章 Python网络编程的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: SQL的top 100 percent用
- 下一篇: C#基础面试题(学习总结)