1.1? udp網(wǎng)絡(luò)程序-發(fā)送數(shù)據(jù)
Socket函數(shù)
mySocket = socket(family, type)
函數(shù)socket()的參數(shù)family用于設(shè)置網(wǎng)絡(luò)通信的域,函數(shù)socket()根據(jù)這個參數(shù)選擇通信協(xié)議的族。通信協(xié)議族在文件sys/socket.h中定義。

函數(shù)socket()的參數(shù)type用于設(shè)置套接字通信的類型,主要有SOCKET_STREAM(流式套接字)、SOCK——DGRAM(數(shù)據(jù)包套接字)等。
并不是所有的協(xié)議族都實現(xiàn)了這些協(xié)議類型,例如,AF_INET協(xié)議族就沒有實現(xiàn)SOCK_SEQPACKET協(xié)議類型。
創(chuàng)建一個udp客戶端程序的流程是簡單,具體步驟如下:
1.創(chuàng)建客戶端套接字
2.發(fā)送/接收數(shù)據(jù)
3.關(guān)閉套接字
代碼如下:
from socket import *
#1.創(chuàng)建套接字
udpSocket = socket(AF_INET, SOCK_DGRAM)
#2.準(zhǔn)備接收方的地址
sendAddr = ('192.168.11.74', 7788)
#3.從鍵盤獲取數(shù)據(jù)
sendData = input("請輸入要發(fā)送的數(shù)據(jù):")
#4.發(fā)送數(shù)據(jù)到指定的電腦上
udpSocket.sendto(sendData.encode('gbk'), sendAddr)
#5.關(guān)閉套接字
udpSocket.close()
1.2 udp網(wǎng)絡(luò)程序-發(fā)送、接收數(shù)據(jù)
#coding=utf-8
fromsocketimport*
#1.創(chuàng)建套接字
udpSocket = socket(AF_INET, SOCK_DGRAM)
#2.準(zhǔn)備接收方的地址
sendAddr = ('192.168.1.103',8080)
#3.從鍵盤獲取數(shù)據(jù)
sendData = input("請輸入要發(fā)送的數(shù)據(jù):")
#4.發(fā)送數(shù)據(jù)到指定的電腦上
udpSocket.sendto(sendData, sendAddr)
#5.等待接收對方發(fā)送的數(shù)據(jù)
recvData = udpSocket.recvfrom(1024)# 1024表示本次接收的最大字節(jié)數(shù)
#6.顯示對方發(fā)送的數(shù)據(jù)
print(recvData)
#7.關(guān)閉套接字
udpSocket.close()
1.3 udp網(wǎng)絡(luò)程序-端口問題
會變的端口號
重新運行多次腳本,然后在“網(wǎng)絡(luò)調(diào)試助手”中,看到的現(xiàn)象如下:

說明:
·每重新運行一次網(wǎng)絡(luò)程序,上圖中紅圈中的數(shù)字,不一樣的原因在于,這個數(shù)字標(biāo)識這個網(wǎng)絡(luò)程序,當(dāng)重新運行時,如果沒有確定到底用哪個,系統(tǒng)默認(rèn)會隨機分配
·記住一點:這個網(wǎng)絡(luò)程序在運行的過程中,這個就唯一標(biāo)識這個程序,所以如果其他電腦上的網(wǎng)絡(luò)程序如果想要向此程序發(fā)送數(shù)據(jù),那么就需要向這個數(shù)字(即端口)標(biāo)識的程序發(fā)送即可
1.4 udp 綁定信息
1.綁定信息
還記得在上一節(jié)課中,如果一個網(wǎng)絡(luò)程序在每次運行的時候端口是隨機變化的么?
一般情況下,在一天電腦上運行的網(wǎng)絡(luò)程序有很多,而各自用的端口號很多情況下不知道,為了不與其他的網(wǎng)絡(luò)程序占用同一個端口號,往往在編程中,udp的端口號一般不綁定
但是如果需要做成一個服務(wù)器端的程序的話,是需要綁定的,想想看這又是為什么呢?
如果報警電話每天都在變,想必世界就會亂了,所以一般服務(wù)性的程序,往往需要一個固定的端口號,這就是所謂的端口綁定
2.綁定示例
#coding=utf-8
fromsocketimport*
#1.創(chuàng)建套接字
udpSocket = socket(AF_INET, SOCK_DGRAM)
#2.綁定本地的相關(guān)信息,如果一個網(wǎng)絡(luò)程序不綁定,則系統(tǒng)會隨機分配
bindAddr = ('',7788)# ip地址和端口號,ip一般不用寫,表示本機的任何一個ip
udpSocket.bind(bindAddr)
#3.等待接收對方發(fā)送的數(shù)據(jù)
recvData = udpSocket.recvfrom(1024)# 1024表示本次接收的最大字節(jié)數(shù)
#4.顯示接收到的數(shù)據(jù)
print(recvData)
#5.關(guān)閉套接字
udpSocket.close()
總結(jié)
·一個udp網(wǎng)絡(luò)程序,可以不綁定,此時操作系統(tǒng)會隨機進行分配一個端口,如果重新運行次程序端口可能會發(fā)生變化
·一個udp網(wǎng)絡(luò)程序,也可以綁定信息(ip地址,端口號),如果綁定成功,那么操作系統(tǒng)用這個端口號來進行區(qū)別收到的網(wǎng)絡(luò)數(shù)據(jù)是否是此進程的
1.5 udp網(wǎng)絡(luò)通信過程

1.6 udp應(yīng)用:echo服務(wù)器
參考代碼
#coding=utf-8
fromsocketimport*
#1.創(chuàng)建套接字
udpSocket = socket(AF_INET, SOCK_DGRAM)
#2.綁定本地的相關(guān)信息
bindAddr = ('',7788)# ip地址和端口號,ip一般不用寫,表示本機的任何一個ip
udpSocket.bind(bindAddr)
num =1
whileTrue:
#3.等待接收對方發(fā)送的數(shù)據(jù)
recvData = udpSocket.recvfrom(1024)# 1024表示本次接收的最大字節(jié)數(shù)
#4.將接收到的數(shù)據(jù)再發(fā)送給對方
udpSocket.sendto(recvData[0], recvData[1])
#5.統(tǒng)計信息
print('已經(jīng)將接收到的第%d個數(shù)據(jù)返回給對方,內(nèi)容為:%s'%(num,recvData[0]))
num+=1
#5.關(guān)閉套接字
udpSocket.close()
1.7 udp應(yīng)用:聊天室
參考代碼
fromsocketimport*
fromtimeimportctime
#1.創(chuàng)建套接字
udpSocket = socket(AF_INET, SOCK_DGRAM)
#2.綁定本地的相關(guān)信息
bindAddr = ('',7788)# ip地址和端口號,ip一般不用寫,表示本機的任何一個ip
udpSocket.bind(bindAddr)
whileTrue:
#3.等待接收對方發(fā)送的數(shù)據(jù)
recvData = udpSocket.recvfrom(1024)# 1024表示本次接收的最大字節(jié)數(shù)
#4.打印信息
print('【%s】%s:%s'%(ctime(),recvData[1][0],recvData[0]))
#5.關(guān)閉套接字
udpSocket.close()
1.8 udp廣播
網(wǎng)絡(luò)編程中的廣播
importsocket, sys
dest = ('',7788)
#創(chuàng)建udp套接字
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
#對這個需要發(fā)送廣播數(shù)據(jù)的套接字進行修改設(shè)置,否則不能發(fā)送廣播數(shù)據(jù)
s.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST,1)
#以廣播的形式發(fā)送數(shù)據(jù)到本網(wǎng)絡(luò)的所有電腦中
s.sendto("Hi", dest)
print("等待對方回復(fù)(按ctrl+c退出)")
whileTrue:
(buf, address) = s.recvfrom(2048)
print("Received from %s: %s"% (address, buf))
1.9 udp總結(jié)
1. udp是TCP/IP協(xié)議族中的一種協(xié)議能夠完成不同機器上的程序間的數(shù)據(jù)通信
2. udp服務(wù)器、客戶端
·udp的服務(wù)器和客戶端的區(qū)分:往往是通過請求服務(wù)和提供服務(wù)來進行區(qū)分
·請求服務(wù)的一方稱為:客戶端
·提供服務(wù)的一方稱為:服務(wù)器
3. udp綁定問題
·一般情況下,服務(wù)器端,需要綁定端口,目的是為了讓其他的客戶端能夠正確發(fā)送到此進程
·客戶端,一般不需要綁定,而是讓操作系統(tǒng)隨機分配,這樣就不會因為需要綁定的端口被占用而導(dǎo)致程序無法運行的情況
1.10 udp模擬qq
1.任務(wù)要求:
·使用多線程完成一個全雙工的QQ聊天程序
單工、半雙工、全雙工
單工數(shù)據(jù)傳輸只支持?jǐn)?shù)據(jù)在一個方向上傳輸;
半雙工數(shù)據(jù)傳輸允許數(shù)據(jù)在兩個方向上傳輸,但是,在某一時刻,只允許數(shù)據(jù)在一個方向上傳輸,它實際上是一種切換方向的單工通信;
全雙工數(shù)據(jù)通信允許數(shù)據(jù)同時在兩個方向上傳輸,因此,全雙工通信是兩個單工通信方式的結(jié)合,它要求發(fā)送設(shè)備和接收設(shè)備都有獨立的接收和發(fā)送能力.
2.參考代碼
from threading import Thread
from socket import *
# 1.收數(shù)據(jù),然后打印
def recvData():
while True:
recvInfo = udpSocket.recvfrom(1024)
print(">>%s:%s" % (str(recvInfo[1]), recvInfo[0]))
# 2.檢測鍵盤,發(fā)數(shù)據(jù)
def sendData():
while True:
sendInfo = input("<<")
udpSocket.sendto(sendInfo.encode("gb2312"), (destIp, destPort))
udpSocket = None
destIp = ""
destPort = 0
def main():
global udpSocket
global destIp
global destPort
destIp = input("對方的ip:")
destPort = int(input("對方的ip:"))
udpSocket = socket(AF_INET, SOCK_DGRAM)
udpSocket.bind(("", 4567))
tr = Thread(target=recvData)
ts = Thread(target=sendData)
tr.start()
ts.start()
tr.join()
ts.join()
if __name__ == "__main__":
main()
1.11 TFTP客戶端
1. TFTP協(xié)議介紹
TFTP(Trivial File Transfer Protocol,簡單文件傳輸協(xié)議)
是TCP/IP協(xié)議族中的一個用來在客戶端與服務(wù)器之間進行簡單文件傳輸?shù)膮f(xié)議
特點:
·簡單
·占用資源小
·適合傳遞小文件
·適合在局域網(wǎng)進行傳遞
·端口號為69
·基于UDP實現(xiàn)
2. TFTP下載過程
TFTP服務(wù)器默認(rèn)監(jiān)聽69號端口
當(dāng)客戶端發(fā)送“下載”請求(即讀請求)時,需要向服務(wù)器的69端口發(fā)送
服務(wù)器若批準(zhǔn)此請求,則使用一個新的、臨時的 端口進行數(shù)據(jù)傳輸

當(dāng)服務(wù)器找到需要現(xiàn)在的文件后,會立刻打開文件,把文件中的數(shù)據(jù)通過TFTP協(xié)議發(fā)送給客戶端
如果文件的總大小較大(比如3M),那么服務(wù)器分多次發(fā)送,每次會從文件中讀取512個字節(jié)的數(shù)據(jù)發(fā)送過來
因為發(fā)送的次數(shù)有可能會很多,所以為了讓客戶端對接收到的數(shù)據(jù)進行排序,所以在服務(wù)器發(fā)送那512個字節(jié)數(shù)據(jù)的時候,會多發(fā)2個字節(jié)的數(shù)據(jù),用來存放序號,并且放在512個字節(jié)數(shù)據(jù)的前面,序號是從1開始的
因為需要從服務(wù)器上下載文件時,文件可能不存在,那么此時服務(wù)器就會發(fā)送一個錯誤的信息過來,為了區(qū)分服務(wù)發(fā)送的是文件內(nèi)容還是錯誤的提示信息,所以又用了2個字節(jié) 來表示這個數(shù)據(jù)包的功能(稱為操作碼),并且在序號的前面

因為udp的數(shù)據(jù)包不安全,即發(fā)送方發(fā)送是否成功不能確定,所以TFTP協(xié)議中規(guī)定,為了讓服務(wù)器知道客戶端已經(jīng)接收到了剛剛發(fā)送的那個數(shù)據(jù)包,所以當(dāng)客戶端接收到一個數(shù)據(jù)包的時候需要向服務(wù)器進行發(fā)送確認(rèn)信息,即發(fā)送收到了,這樣的包成為ACK(應(yīng)答包)
為了標(biāo)記數(shù)據(jù)已經(jīng)發(fā)送完畢,所以規(guī)定,當(dāng)客戶端接收到的數(shù)據(jù)小于516(2字節(jié)操作碼+2個字節(jié)的序號+512字節(jié)數(shù)據(jù))時,就意味著服務(wù)器發(fā)送完畢了
TFTP數(shù)據(jù)包的格式如下:

?O4;3?m