目錄:
一魁淳、TCP編程
二盆驹、UDP編程
Python 提供了兩個級別訪問的網(wǎng)絡(luò)服務(wù):
低級別的網(wǎng)絡(luò)服務(wù)支持基本的Socket
琅催,它提供了標準的 BSD Sockets API居凶,可以訪問底層操作系統(tǒng)Socket接口的全部方法。
高級別的網(wǎng)絡(luò)服務(wù)模塊SocketServer
藤抡, 它提供了服務(wù)器中心類侠碧,可以簡化網(wǎng)絡(luò)服務(wù)器的開發(fā)。SocketServer
其實是在Socket基礎(chǔ)上的一個多線程升級缠黍,作為入門教程弄兜,我們不做涉及。
我們在此將socket編程方式分為面向連接的方式(即TCP)和不面向連接的方式(即UDP)進行分別討論。
一替饿、TCP編程
TCP(Transmission Control Protocol 傳輸控制協(xié)議)是一種面向連接的语泽、可靠的、基于字節(jié)流的傳輸層通信協(xié)議,對應(yīng)到編程中视卢,它的特點就是需要先建立連接踱卵,再進行數(shù)據(jù)傳輸。
在此盜一張很棒的圖解方便大家理解:
然后据过,臨時為了演示需要惋砂,將一個日歷查看功能布在了服務(wù)端,可通過接受客戶端提交的請求绳锅,返回對應(yīng)的請求內(nèi)容西饵。當然,你完全可以根據(jù)自己的需要布置更加有意義的小程序替換這里鳞芙,如:nmap等罗标。
需要注意的是,socket在python3中的數(shù)據(jù)傳輸均使用bytes的方式积蜻,所以我們需要及時的進行編碼解碼操作。
其中socket的基本語法為socket.socket([family[, type[, proto]]])
family: 套接字家族可以使AF_UNIX或者AF_INET
type: 套接字類型可以根據(jù)是面向連接的還是非連接分為SOCK_STREAM或SOCK_DGRAM
protocol: 一般不填默認為0
作為入門系列我們不在這些概念上拓展彻消,具體用法參見以下完整實例:
#TCP服務(wù)端
import socket
import calendar
s=socket.socket(socket.AF_INET, socket.SOCK_STREAM)
ip_port=('127.0.0.1',11111) #定義綁定ip和端口
s.bind(ip_port) #綁定監(jiān)聽
s.listen(5) #最大連接數(shù)
while True:
print('正在等待客戶端連接……')
conn,addr=s.accept() #接收連接請求
print("接入地址: %s" % str(addr))
msg='連接成功!\n【歡迎來到史上最辣雞的日歷系統(tǒng)】\n輸入"exit"退出本系統(tǒng)'
conn.send(msg.encode()) #發(fā)送應(yīng)答信息
while True: #持續(xù)接收數(shù)據(jù)
data=conn.recv(1024)
if data==b'exit': #收到exit斷開連接
break
try:
conn.send(calendar.month(2019, int(str(data.decode()))).encode())
except IndexError: #異常處理機制竿拆,防止用戶亂輸造成服務(wù)端崩潰
pass
conn.send('請正確輸入月份:(1-12)'.encode())
conn.close() #斷開連接
#輸出
正在等待客戶端連接……
接入地址: ('127.0.0.1', 16942)
正在等待客戶端連接……
#TCP客戶端
import socket
c=socket.socket(socket.AF_INET, socket.SOCK_STREAM)
ip_port=('127.0.0.1',11111) #定義訪問ip和端口
c.connect(ip_port) #連接服務(wù)器
while True:
data = c.recv(1024) # 接收返回信息
print(data.decode())
msg=input('請輸入想要查看的月份: 例如:1\n>>>>>')
c.send(msg.encode())
if msg=='exit': #用戶輸入exit時退出
break
c.close() #斷開連接
#輸出:
連接成功!
【歡迎來到史上最辣雞的日歷系統(tǒng)】
輸入"exit"退出本系統(tǒng)
請輸入想要查看的月份: 例如:1
>>>>>1
January 2019
Mo Tu We Th Fr Sa Su
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
請輸入想要查看的月份: 例如:1
>>>>>2
February 2019
Mo Tu We Th Fr Sa Su
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
請輸入想要查看的月份: 例如:1
>>>>>1111
請正確輸入月份:(1-12)
請輸入想要查看的月份: 例如:1
>>>>>exit
Process finished with exit code 0
其中有一處類型轉(zhuǎn)換是使用的int(str())
,即bytes類型解碼轉(zhuǎn)字符串再轉(zhuǎn)數(shù)字的形式,暫時沒有找到更有效且穩(wěn)定的替換方案宾尚,在線求助各位大佬~
關(guān)于常用套接字函數(shù)在上面的例子中已通過注釋的形式進行了說明丙笋,在此進行統(tǒng)一整理:
#服務(wù)器端套接字
s.bind()
綁定地址(host,port)到套接字, 在AF_INET下,以元組(host,port)的形式表示地址煌贴。
s.listen()
開始TCP監(jiān)聽御板。backlog指定在拒絕連接之前,操作系統(tǒng)可以掛起的最大連接數(shù)量牛郑。該值至少為1怠肋,大部分應(yīng)用程序設(shè)為5就可以了。
s.accept()
被動接受TCP客戶端連接,(阻塞式)等待連接的到來
#客戶端套接字
s.connect()
主動初始化TCP服務(wù)器連接淹朋,笙各。一般address的格式為元組(hostname,port),如果連接出錯础芍,返回socket.error錯誤杈抢。
s.connect_ex()
connect()函數(shù)的擴展版本,出錯時返回出錯碼,而不是拋出異常
#公共用途的套接字函數(shù)
s.recv()
接收TCP數(shù)據(jù),數(shù)據(jù)以字符串形式返回仑性,bufsize指定要接收的最大數(shù)據(jù)量惶楼。flag提供有關(guān)消息的其他信息,通常可以忽略歼捐。
s.send()
發(fā)送TCP數(shù)據(jù)何陆,將string中的數(shù)據(jù)發(fā)送到連接的套接字。返回值是要發(fā)送的字節(jié)數(shù)量窥岩,該數(shù)量可能小于string的字節(jié)大小甲献。
s.sendall()
完整發(fā)送TCP數(shù)據(jù),完整發(fā)送TCP數(shù)據(jù)颂翼。將string中的數(shù)據(jù)發(fā)送到連接的套接字晃洒,但在返回之前會嘗試發(fā)送所有數(shù)據(jù)。成功返回None朦乏,失敗則拋出異常球及。
s.recvfrom()
接收UDP數(shù)據(jù),與recv()類似呻疹,但返回值是(data,address)吃引。其中data是包含接收數(shù)據(jù)的字符串,address是發(fā)送數(shù)據(jù)的套接字地址刽锤。
s.sendto()
發(fā)送UDP數(shù)據(jù)镊尺,將數(shù)據(jù)發(fā)送到套接字,address是形式為(ipaddr并思,port)的元組庐氮,指定遠程地址。返回值是發(fā)送的字節(jié)數(shù)宋彼。
s.close()
關(guān)閉套接字
s.getpeername()
返回連接套接字的遠程地址弄砍。返回值通常是元組(ipaddr,port)。
s.getsockname()
返回套接字自己的地址输涕。通常是一個元組(ipaddr,port)
s.setsockopt(level,optname,value)
設(shè)置給定套接字選項的值音婶。
s.getsockopt(level,optname[.buflen])
返回套接字選項的值。
s.settimeout(timeout)
設(shè)置套接字操作的超時期莱坎,timeout是一個浮點數(shù)衣式,單位是秒。值為None表示沒有超時期檐什。一般瞳收,超時期應(yīng)該在剛創(chuàng)建套接字時設(shè)置,因為它們可能用于連接的操作(如connect())
s.gettimeout()
返回當前超時期的值厢汹,單位是秒螟深,如果沒有設(shè)置超時期,則返回None烫葬。
s.fileno()
返回套接字的文件描述符界弧。
s.setblocking(flag)
如果flag為0凡蜻,則將套接字設(shè)為非阻塞模式,否則將套接字設(shè)為阻塞模式(默認值)垢箕。非阻塞模式下划栓,如果調(diào)用recv()沒有發(fā)現(xiàn)任何數(shù)據(jù),或send()調(diào)用無法立即發(fā)送數(shù)據(jù)条获,那么將引起socket.error異常忠荞。
s.makefile()
創(chuàng)建一個與該套接字相關(guān)連的文件
二、UDP編程
UDP 是User Datagram Protocol的簡稱帅掘, 中文名是用戶數(shù)據(jù)報協(xié)議委煤,是OSI(Open System Interconnection,開放式系統(tǒng)互聯(lián)) 參考模型中一種無連接的傳輸層協(xié)議修档,提供面向事務(wù)的簡單不可靠信息傳送服務(wù)碧绞。對應(yīng)到編程中,就是不需要連接的建立吱窝,直接進行數(shù)據(jù)傳輸讥邻。
我們直接改寫上面的例子進行對比講解:
#服務(wù)端
import socket
import calendar
s=socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
ip_port=('127.0.0.1',22222) #定義綁定ip和端口
s.bind(ip_port) #綁定監(jiān)聽
while True:
print('UDP服務(wù)端監(jiān)聽開啟……\n')
data,addr = s.recvfrom(1024) #recvfrom()獲取數(shù)據(jù)和地址
if data==b'exit':
break
print('Received from %s:%s.' % addr)
try: #異常處理機制,防止用戶亂輸造成服務(wù)端崩潰
s.sendto(calendar.month(2019, int(str(data.decode()))).encode(),addr)
except:
s.sendto('請正確輸入月份:(1-12)'.encode(),addr)
s.close()
#輸出:
UDP服務(wù)端監(jiān)聽開啟……
Received from 127.0.0.1:61716.
UDP服務(wù)端監(jiān)聽開啟……
Received from 127.0.0.1:61716.
UDP服務(wù)端監(jiān)聽開啟……
Received from 127.0.0.1:61716.
UDP服務(wù)端監(jiān)聽開啟……
#客戶端
import socket
c = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
ip_port = ('127.0.0.1', 22222) #定義訪問ip和端口
print('【歡迎來到史上最辣雞的日歷系統(tǒng)】\n輸入"exit"退出本系統(tǒng)')
while True:
msg=input('請輸入想要查看的月份: 例如:1\n>>>>>')
if msg=='exit':
break
c.sendto(msg.encode(), ip_port)
data=c.recv(1024) #接收服務(wù)器返回數(shù)據(jù)
print(data.decode())
c.close() #斷開連接
#輸出:
【歡迎來到史上最辣雞的日歷系統(tǒng)】
輸入"exit"退出本系統(tǒng)
請輸入想要查看的月份: 例如:1
>>>>>1
January 2019
Mo Tu We Th Fr Sa Su
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
請輸入想要查看的月份: 例如:1
>>>>>2
February 2019
Mo Tu We Th Fr Sa Su
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
請輸入想要查看的月份: 例如:1
>>>>>11111
請正確輸入月份:(1-12)
請輸入想要查看的月份: 例如:1
>>>>>exit
Process finished with exit code 0
簡單說下區(qū)別院峡,UDP編程兴使,服務(wù)端不需要創(chuàng)建連接等待,直接接收照激;也不需要listen設(shè)置最大連接數(shù)鲫惶。客戶端同樣不需要先創(chuàng)建連接实抡,也就是不需要進行connect()操作,直接發(fā)送數(shù)據(jù)即可欢策。數(shù)據(jù)發(fā)送方式改為sendto()吆寨,需要制定地址,畢竟UDP是非面向連接的踩寇。
TCP是建立可靠連接啄清,并且通信雙方都可以以流的形式發(fā)送數(shù)據(jù)。相對TCP俺孙,UDP則是面向無連接的協(xié)議辣卒。使用UDP協(xié)議時,不需要建立連接睛榄,只需要知道對方的IP地址和端口號荣茫,就可以直接發(fā)數(shù)據(jù)包。但是场靴,能不能到達就不知道了啡莉。雖然用UDP傳輸數(shù)據(jù)不可靠港准,但它的優(yōu)點是和TCP比,速度快咧欣,對于不要求可靠到達的數(shù)據(jù)浅缸,就可以使用UDP協(xié)議。