[python学习] 专题七.网络编程之套接字Socket、TCP和UDP通信实例
? ? ? ? 很早以前研究過(guò)C#和C++的網(wǎng)絡(luò)通信,參考我的文章:?
? ? ? ? ? ? ? ??C#網(wǎng)絡(luò)編程之Tcp實(shí)現(xiàn)客戶端和服務(wù)器聊天
? ? ? ? ? ? ? ??C#網(wǎng)絡(luò)編程之套接字編程基礎(chǔ)知識(shí)
? ? ? ? ? ? ? ??C#網(wǎng)絡(luò)編程之使用Socket類Send、Receive方法的同步通訊? ? ? ?
? ? ? ? Python網(wǎng)絡(luò)編程也類似。同時(shí)最近找工作筆試面試考察Socket套接字、TCP\UDP區(qū)別比較多,所以這篇文章主要精簡(jiǎn)了《Python核心編程(第二版)》第16章內(nèi)容。內(nèi)容包括:服務(wù)器和客戶端架構(gòu)、套接字Socket、TCP\UDP通信實(shí)例和常見(jiàn)筆試考題。最后希望文章對(duì)你有所幫助,如果有不足之處,還請(qǐng)海涵~? ? ? ??
?
一. 服務(wù)器和客戶端架構(gòu)
1.什么是客戶端/服務(wù)區(qū)架構(gòu)?
? ? ? ? 書(shū)中的定義是服務(wù)器是一個(gè)軟件或硬件,用于向一個(gè)或多個(gè)客戶端(客戶)提供所需要的“服務(wù)”。服務(wù)器存在的唯一目的就是等待客戶的請(qǐng)求,給這些客戶服務(wù),然后再等待其他的請(qǐng)求。而客戶連接上(預(yù)先已知的)服務(wù)器,提出自己的請(qǐng)求,發(fā)送必要的數(shù)據(jù),然后等待服務(wù)器完成請(qǐng)求或說(shuō)明失敗原因的反饋。
? ? ? ? 服務(wù)器不停的處理外來(lái)的請(qǐng)求,而客戶一次只能提出一個(gè)服務(wù)的請(qǐng)求,等待結(jié)果。再結(jié)束這個(gè)事務(wù)。客戶之后可以再提出其他的請(qǐng)求,只是這個(gè)請(qǐng)求會(huì)被視為另一個(gè)不同的事務(wù)了。
2.硬件客戶端/服務(wù)器架構(gòu)和軟件客戶端/服務(wù)器架構(gòu)
? ? ? ? 硬件的客戶端/服務(wù)器架構(gòu),例如打印服務(wù)器、文件服務(wù)器(客戶可以遠(yuǎn)程把服務(wù)器的磁盤(pán)映射到自己本體并使用);軟件客戶端/服務(wù)器架構(gòu)主要是程序的運(yùn)行、數(shù)據(jù)收發(fā)、升級(jí)等,最常見(jiàn)的是Web服務(wù)器、數(shù)據(jù)庫(kù)服務(wù)器。如一臺(tái)機(jī)器存放一些網(wǎng)頁(yè)或Web應(yīng)用程序,然后啟動(dòng)服務(wù)。其服務(wù)器的任務(wù)就是接受客戶端的請(qǐng)求,把網(wǎng)頁(yè)發(fā)給客戶端(如用戶計(jì)算機(jī)上的瀏覽器),然后再等待下一個(gè)客戶端請(qǐng)求。
3.客戶端/服務(wù)器網(wǎng)絡(luò)編程
? ? ? ? 在完成服務(wù)之前,服務(wù)器必須要先完成一些設(shè)置。先要先創(chuàng)建一個(gè)通訊端點(diǎn),讓服務(wù)器能“監(jiān)聽(tīng)”請(qǐng)求。你可以把我們服務(wù)器比作一個(gè)公司的接待員或回答公司總線電話的話務(wù)員,一旦電話和設(shè)備安裝完成,話務(wù)員也就到位后,服務(wù)就開(kāi)始了。
? ? ? ? 同樣一旦通信端點(diǎn)創(chuàng)建好之后,我們?cè)凇氨O(jiān)聽(tīng)”的服務(wù)器就可以進(jìn)入它那等待和處理客戶請(qǐng)求的無(wú)限循環(huán)中了。服務(wù)器準(zhǔn)備好之后,也要通知潛在的客戶,讓它們知道服務(wù)器已經(jīng)準(zhǔn)備好處理服務(wù)了,否則沒(méi)人會(huì)提請(qǐng)求的。所以需要把公司電話公開(kāi)給客戶。
? ? ? ? 而客戶端只要?jiǎng)?chuàng)建一個(gè)通信端點(diǎn),建立到服務(wù)器的連接,然后客戶端就可以提出請(qǐng)求了。請(qǐng)求中也可以包含必要的數(shù)據(jù)交互。一旦請(qǐng)求處理完成,客戶端收到了結(jié)果,通信就結(jié)束了。這就是客戶端和服務(wù)器的簡(jiǎn)單網(wǎng)絡(luò)通信。
?
二. 套接字Socket
1.什么是套接字
? ? ? ? 套接字是一種具有之前所說(shuō)的“通信端點(diǎn)”概念的計(jì)算網(wǎng)絡(luò)數(shù)據(jù)結(jié)構(gòu)。相當(dāng)于電話插口,沒(méi)它無(wú)法通信,這個(gè)比喻非常形象。
? ? ? ? 套接字起源于20世紀(jì)70年代加州伯克利分校版本的Unix,即BSD Unix。又稱為“伯克利套接字”或“BSD套接字”。最初套接字被設(shè)計(jì)用在同一臺(tái)主機(jī)上多個(gè)應(yīng)用程序之間的通訊,這被稱為進(jìn)程間通訊或IPC。
? ? ? ? 套接字分兩種:基于文件型和基于網(wǎng)絡(luò)的
? ? ? ? 第一個(gè)套接字家族為AF_UNIX,表示“地址家族:UNIX”。包括Python在內(nèi)的大多數(shù)流行平臺(tái)上都使用術(shù)語(yǔ)“地址家族”及其縮寫(xiě)AF。由于兩個(gè)進(jìn)程都運(yùn)行在同一臺(tái)機(jī)器上,而且這些套接字是基于文件的,所以它們的底層結(jié)構(gòu)是由文件系統(tǒng)來(lái)支持的。可以理解為同一臺(tái)電腦上,文件系統(tǒng)確實(shí)是不同的進(jìn)程都能進(jìn)行訪問(wèn)的。
? ? ? ? 第二個(gè)套接字家族為AF_INET,表示”地址家族:Internet“。還有一種地址家族AF_INET6被用于網(wǎng)際協(xié)議IPv6尋址。Python 2.5中加入了一種Linux套接字的支持:AF_NETLINK(無(wú)連接)套接字家族,讓用戶代碼與內(nèi)核代碼之間的IPC可以使用標(biāo)準(zhǔn)BSD套接字接口,這種方法更為精巧和安全。
? ? ? ? Python只支持AF_UNIX、AF_NETLINK和AF_INET家族。網(wǎng)絡(luò)編程關(guān)注AF_INET。
? ? ? ? 如果把套接字比作電話的查看——即通信的最底層結(jié)構(gòu),那主機(jī)與端口就相當(dāng)于區(qū)號(hào)和電話號(hào)碼的一對(duì)組合。一個(gè)因特網(wǎng)地址由網(wǎng)絡(luò)通信必須的主機(jī)與端口組成。
? ? ? ? 而且另一端一定要有人接聽(tīng)才行,否則會(huì)提示”對(duì)不起,您撥打的電話是空號(hào),請(qǐng)查詢后再撥“。同樣你也可能會(huì)遇到如”不能連接該服務(wù)器、服務(wù)器無(wú)法響應(yīng)“等。合法的端口范圍是0~65535,其中小于1024端口號(hào)為系統(tǒng)保留端口。
2.面向連接與無(wú)連接
? ? ? ? 面向連接:通信之前一定要建立一條連接,這種通信方式也被成為”虛電路“或”流套接字“。面向連接的通信方式提供了順序的、可靠地、不會(huì)重復(fù)的數(shù)據(jù)傳輸,而且也不會(huì)被加上數(shù)據(jù)邊界。這意味著,每發(fā)送一份信息,可能會(huì)被拆分成多份,每份都會(huì)不多不少地正確到達(dá)目的地,然后重新按順序拼裝起來(lái),傳給正等待的應(yīng)用程序。
? ? ? ? 實(shí)現(xiàn)這種連接的主要協(xié)議就是傳輸控制協(xié)議TCP。要?jiǎng)?chuàng)建TCP套接字就得創(chuàng)建時(shí)指定套接字類型為SOCK_STREAM。TCP套接字這個(gè)類型表示它作為流套接字的特點(diǎn)。由于這些套接字使用網(wǎng)際協(xié)議IP來(lái)查找網(wǎng)絡(luò)中的主機(jī),所以這樣形成的整個(gè)系統(tǒng),一般會(huì)由這兩個(gè)協(xié)議(TCP和IP)組合描述,即TCP/IP。
? ? ? ? 無(wú)連接:無(wú)需建立連接就可以通訊。但此時(shí),數(shù)據(jù)到達(dá)的順序、可靠性及不重復(fù)性就無(wú)法保障了。數(shù)據(jù)報(bào)會(huì)保留數(shù)據(jù)邊界,這就表示數(shù)據(jù)是整個(gè)發(fā)送的,不會(huì)像面向連接的協(xié)議先拆分成小塊。它就相當(dāng)于郵政服務(wù)一樣,郵件和包裹不一定按照發(fā)送順序達(dá)到,有的甚至可能根本到達(dá)不到。而且網(wǎng)絡(luò)中的報(bào)文可能會(huì)重復(fù)發(fā)送。
? ? ? ? 那么這么多缺點(diǎn),為什么還要使用它呢?由于面向連接套接字要提供一些保證,需要維護(hù)虛電路連接,這都是嚴(yán)重的額外負(fù)擔(dān)。數(shù)據(jù)報(bào)沒(méi)有這些負(fù)擔(dān),所有它會(huì)更”便宜“,通常能提供更好的性能,更適合某些場(chǎng)合,如現(xiàn)場(chǎng)直播要求的實(shí)時(shí)數(shù)據(jù)講究快等。
? ? ? ? 實(shí)現(xiàn)這種連接的主要協(xié)議是用戶數(shù)據(jù)報(bào)協(xié)議UDP。要?jiǎng)?chuàng)建UDP套接字就得創(chuàng)建時(shí)指定套接字類型為SOCK_DGRAM。這個(gè)名字源于datagram(數(shù)據(jù)報(bào)),這些套接字使用網(wǎng)際協(xié)議來(lái)查找網(wǎng)絡(luò)主機(jī),整個(gè)系統(tǒng)叫UDP/IP。? ? ?
3.socket()模塊函數(shù)
? ? ? ? 使用socket模塊的socket()函數(shù)來(lái)創(chuàng)建套接字。語(yǔ)法如下:
? ? ? ? ? ? socket(socket_family, socket_type, protocol=0)
? ? ? ? 其中socket_family不是AF_VNIX就是AF_INET,socket_type可以是SOCK_STREAM或者SOCK_DGRAM,protocol一般不填,默認(rèn)值是0。
? ? ? ? 創(chuàng)建一個(gè)TCP/IP套接字的語(yǔ)法如下:
? ? ? ? ? ? ?tcpSock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
? ? ? ? 同樣創(chuàng)建一個(gè)UDP/IP套接字的語(yǔ)法如下:
? ? ? ? ? ? ?udpSock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
? ? ? ? 由于socket模塊中有太多屬性,所以使用"from socket import *"語(yǔ)句,把socket模塊里面的所有屬性都帶到命名空間中,大幅縮短代碼。調(diào)用如下:
? ? ? ? ? ? ?tcpSock = socket(AF_INET, SOCK_STREAM)
4.套接字對(duì)象方法
? ? ? ?下面是最常用的套接字對(duì)象方法:
? ? ? ?服務(wù)器端套接字函數(shù)
| socket類型 | 描述 |
| s.bind() | 綁定地址(主機(jī)號(hào) 端口號(hào)對(duì))到套接字 |
| s.listen() | 開(kāi)始TCP監(jiān)聽(tīng) |
| s.accept() | 被動(dòng)接受TCP客戶端連接,(阻塞式)等待連續(xù)的到來(lái) |
? ? ? ?客戶端套接字函數(shù)
| socket類型 | 描述 |
| s.connect() | 主動(dòng)初始化TCP服務(wù)器連接 |
| s.connect_ex() | connect()函數(shù)擴(kuò)展版本,出錯(cuò)時(shí)返回出錯(cuò)碼而不是跑出異常 |
? ? ? ?公共用途的套接字函數(shù)
?
| socket類型 | 描述 |
| s.recv() | 接受TCP數(shù)據(jù) |
| s.send() | 發(fā)送TCP數(shù)據(jù) |
| s.sendall() | 完整發(fā)送TCP數(shù)據(jù) |
| s.recvfrom() | 接受UDP數(shù)據(jù) |
| s.sendto() | 發(fā)送UDP數(shù)據(jù) |
| s.getpeername() | 連接到當(dāng)前套接字的遠(yuǎn)端地址(TCP連接) |
| s.getsockname() | 獲取當(dāng)前套接字的地址 |
| s.getsockopt() | 返回指定套接字的參數(shù) |
| s.setsockopt() | 設(shè)置指定套接字的參數(shù) |
| s.close() | 關(guān)閉套接字 |
? ? ? ??面模塊的套接字函數(shù)
| socket類型 | 描述 |
| s.setblocking() | 設(shè)置套接字的阻塞與非阻塞模式 |
| s.settimeout() | 設(shè)置阻塞套接字操作的超時(shí)時(shí)間 |
| s.gettimeout() | 得到阻塞套接字操作的超時(shí)時(shí)間 |
面文件的接字函數(shù)
| socket類型 | 描述 |
| s.fileno() | 套接字的文件描述符 |
| s.makefile() | 創(chuàng)建一個(gè)與套接字關(guān)聯(lián)的文件對(duì)象 |
? ? ? ? 提示:在運(yùn)行網(wǎng)絡(luò)應(yīng)用程序時(shí),如果能夠使用在不同的電腦上運(yùn)行服務(wù)器和客戶端最好不過(guò),它能讓你更好理解通信過(guò)程,而更多的是方位localhost或127.0.0.1.
?
三. TCP通信實(shí)例
1.服務(wù)器 tcpSerSock.py
? ? ? ? 核心操作如下:
? ? ? ss = socket() ? ? ? ? ? ? ? ?# 創(chuàng)建服務(wù)器套接字
? ? ? ss.bind() ? ? ? ? ? ? ? ?# 地址綁定到套接字上
? ? ? ss.listen() ? ? ? ? ? ? ? ? ? ? ?# 監(jiān)聽(tīng)連接
? ? ? ? ? ? ?inf_loop: ? ? ? ? ? ? ? ? ? ? ? # 服務(wù)器無(wú)限循環(huán)
? ? ? ? ? ? ? ? ? cs = ss.accept() ? ? ? # 接受客戶端連接 阻塞式:程序連接之前處于掛起狀態(tài)
? ? ? ? ? ? ?comm_loop: ? ? ? ? ? ? ? ? # 通信循環(huán)
? ? ? ? ? ? ? ? ? cs.recv()/cs.send() ? # 對(duì)話 接受與發(fā)送數(shù)據(jù)
? ? ? ? ? ? ?cs.close() ? ? ? ? ? ? ? ? ? ? ?# 關(guān)閉客戶端套接字?
? ? ? ? ? ? ?ss.close() ? ? ? ? ? ? ? ? ? ? ?# 關(guān)閉服務(wù)器套接字 (可選)
2.客戶端 tcpCliSock.py
? ? ? ? 核心操作如下:
? ? ? cs = socket() ? ? ? ? ? ? ? ??# 創(chuàng)建客戶端套接字
? ? ? ? ? ? ?cs.connect() ? ? ? ? ? ? ? ? ?# 嘗試連接服務(wù)器
? ? ? ? ? ? ?comm_loop: ? ? ? ? ? ? ? ? # 通訊循環(huán)
? ? ? ? ? ? ? ? ? cs.send()/cs.recv() ? ?# 對(duì)話 發(fā)送接受數(shù)據(jù)
? ? ? ? ? ? ?cs.close() ? ? ? ? ? ? ? ? ? ? ? # 關(guān)閉客戶端套接字
3.運(yùn)行結(jié)果及注意
? ? ? ? 由于服務(wù)器被動(dòng)地?zé)o限循環(huán)等待連接,所以需要先運(yùn)行服務(wù)器,再開(kāi)客戶端。又因?yàn)槲业腜ython總會(huì)無(wú)法響應(yīng),所以采用cmd運(yùn)行服務(wù)器Server程序,Python IDLE運(yùn)行客戶端進(jìn)行通信。運(yùn)行結(jié)果如下圖所示:
? ? ? ? 如果出現(xiàn)錯(cuò)誤[Error] Bad file descriptor表示服務(wù)器關(guān)閉客戶端連接了,刪除即可
?
?
? ? ? ? 建議:創(chuàng)建線程來(lái)處理客戶端請(qǐng)求。SocketServer模塊是一個(gè)基于socket模塊的高級(jí)別的套接字通信模塊,支持新的線程或進(jìn)處理戶端請(qǐng)求。同時(shí)建議在退出和調(diào)用服務(wù)器close()函數(shù)時(shí)使用try-except語(yǔ)句
四. UDP通信實(shí)例
1.服務(wù)器 udpSerSock.py
? ? ? ? 核心操作如下:
? ? ? ss = socket() ? ? ? ? ? ? ? ?# 創(chuàng)建服務(wù)器套接字
? ? ? ss.bind() ? ? ? ? ? ? ? ?# 綁定服務(wù)器套接字
? ? ? ? ? ? ?inf_loop: ? ? ? ? ? ? ? ? ? ? ??# 服務(wù)器無(wú)限循環(huán)
? ? ? ? ? ? ? ? ? cs = ss.recvfrom()/ss.sendto() ? ? ????
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ??# 對(duì)話 接受與發(fā)送數(shù)據(jù)
? ? ? ? ? ? ?ss.close() ? ? ? ? ? ? ? ? ? ? ?# 關(guān)閉服務(wù)器套接字?
2.客戶端?udpCliSock.py
? ? ? ? 核心操作如下:
? ? ? cs = socket() ? ? ? ? ? ? ? ? ? ? ? ? ? ?# 創(chuàng)建客戶端套接字
? ? ? ? ? ? ?inf_loop: ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?# 服務(wù)器無(wú)限循環(huán)
? ? ? ? ? ? ? ? ? cs.sendto()/cs.recvfrom()? ?# 對(duì)話 接受與發(fā)送數(shù)據(jù) ??? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?
? ? ? ? ? ? ?cs.close() ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ??# 關(guān)閉客戶端套接字?
?
# -*- coding: utf-8 -*- from socket import *HOST = 'localhost' #主機(jī)名 PORT = 21567 #端口號(hào) 與服務(wù)器一致 BUFSIZE = 1024 #緩沖區(qū)大小1K ADDR = (HOST,PORT)udpCliSock = socket(AF_INET, SOCK_DGRAM)while True: #無(wú)限循環(huán)等待連接到來(lái)try:data = raw_input('>')if not data:breakudpCliSock.sendto(data, ADDR) #發(fā)送數(shù)據(jù)data,ADDR = udpCliSock.recvfrom(BUFSIZE) #接受數(shù)據(jù)if not data:breakprint 'Server : ', dataexcept Exception,e:print 'Error: ',eudpCliSock.close() #關(guān)閉客戶端3.運(yùn)行結(jié)果及注意
? ? ? ? UDP服務(wù)器不是面向連接的,所以不需要設(shè)置什么東西,直接等待連接就好。同時(shí)由于數(shù)據(jù)報(bào)套接字是無(wú)連接的,所以無(wú)法把客戶端連接交給另外的套接字進(jìn)行后續(xù)的通訊,這些服務(wù)器只是接受消息,需要的話加時(shí)間錯(cuò)后返回一個(gè)收到的結(jié)果給客戶端。
? ? ? ? UDP客戶端與TCP客戶端唯一區(qū)別就是不用去UDP服務(wù)器建立連接,而是直接把消息發(fā)送出去,然后等待服務(wù)器回復(fù)即可。
? ? ? ? 運(yùn)行結(jié)果如下圖所示:白色為客戶端輸入消息,黑色為服務(wù)器收到消息并回復(fù)。當(dāng)Client輸入"Hello, I am Client"時(shí),服務(wù)器顯示該消息并返回時(shí)間戳和收到的信息給客戶端。
總結(jié):
? ? ? ? 后面大家自己可以閱讀下SocketServer模塊,它是標(biāo)準(zhǔn)庫(kù)中一個(gè)高級(jí)別的模塊,用于簡(jiǎn)化實(shí)現(xiàn)網(wǎng)絡(luò)客戶端和服務(wù)器所需的大量樣板代碼。該模塊中已經(jīng)實(shí)現(xiàn)了一些可供使用的類直接調(diào)用幾塊。
? ? ? ? Twisted框架是一個(gè)完全事件驅(qū)動(dòng)的網(wǎng)絡(luò)框架。它允許你使用和開(kāi)發(fā)完全異步的網(wǎng)絡(luò)應(yīng)用程序和協(xié)議。
? ? ? ? 這些東西我更傾向于分享原理和底層的一些東西吧!同時(shí)最近考到的筆試題包括:TCP和UDP的區(qū)別、socket其中的參數(shù)含義、TCP三次握手及傳遞的參數(shù)、寫(xiě)個(gè)socket通訊偽代碼。
? ? ? ?總之,希望文章對(duì)你有所幫助~
? ? ? ?(By:Eastmount 2015-10-5 早上8點(diǎn)?http://blog.csdn.net/eastmount/)
?
總結(jié)
以上是生活随笔為你收集整理的[python学习] 专题七.网络编程之套接字Socket、TCP和UDP通信实例的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: [笔试题目] 腾讯2015年9月基础研究
- 下一篇: [Python爬虫] scrapy爬虫系