目錄
寫在前面的話
概覽
環(huán)境
URL請求程序:
2. 系統(tǒng)時間查詢
服務端
T_TCPServer.py代碼
客戶端
T_TCPClient.py代碼
運行效果
3. 網(wǎng)絡文件傳輸
服務端
TF_TCPServer.py代碼
運行效果(后面加了遠程功能,效果圖暫時還在本地)
4. 網(wǎng)絡聊天室
服務端
UDPServer.py代碼
客戶端
UDPClient.py代碼
運行效果
開啟服務器,jennie客戶端和john客戶端陸續(xù)加入群聊
聊天(4欄:服務器,mike,? john, ?jennie)
mike回復消息同樣被轉(zhuǎn)發(fā)給全部用戶
用戶退出
問題及解決:
URL 請求程序
系統(tǒng)時間查詢
網(wǎng)絡文件傳輸
網(wǎng)絡聊天室
實驗心得體會
總結
寫在前面的話
這個危,開學初老師曾覺得這個編程實驗可能會勸退我這個小白。emmm熬過來了。上學期有老師說得先自學下Java搞這個,emmm寒假整c++一些個小系統(tǒng),Java純純學點點寫幾個基本小系統(tǒng)罷了覺得不太可。幸好我的班用python。前年學了點毛皮撿撿還能用,1周速成python,再1周速成個多人聊天室,爬過來就可。
概覽
? UDP與TCP套接字的區(qū)別
? UDP和TCP套接字編程方法
? 簡單網(wǎng)絡應用的編程思路
? 網(wǎng)絡編程相關的一些庫
1. URL 請求程序
2. 系統(tǒng)時間查詢
3. 網(wǎng)絡文件傳輸
4. 網(wǎng)絡聊天室
環(huán)境
? 具有Internet連接的主機, VScode(直接在其終端運行),PowerShell
? 編程語言: python
-
URL請求程序:
請求一個網(wǎng)頁,并存儲為html文件,計算所請求網(wǎng)頁的大小。打印所請求網(wǎng)頁的URL、存儲文件名、文件大小等信息。(圖1 2)
Figure 1 url請求程序
調(diào)用requests庫請求對應url并保存。將url切片獲得文件名,通過os庫獲得文件大小。
Figure 2 保存下來HTML網(wǎng)頁文件
2. 系統(tǒng)時間查詢
? 實現(xiàn)一個基于客戶/服務器的系統(tǒng)時間查詢程序。
? 傳輸層使用TCP。
? 交互過程
1) 客戶端向服務器端發(fā)送字符串Time。
2) 服務器端收到該字符串后,返回當前系統(tǒng)時間。
3) 客戶端向服務器端發(fā)送字符串Exit。
4) 服務器端返回Bye,然后結束TCP連接。
-
服務端
服務端創(chuàng)建TCP協(xié)議的socket套接字進程,端口號為12000,并保持監(jiān)聽狀態(tài)。一旦收到客戶端的連接就從socket套接字中獲取客戶端發(fā)送的消息。 如果收到無效信息回復提示。直到Exit才停止收發(fā)并斷開連接。并可以與另外的客戶端通信。
T_TCPServer.py代碼
from socket import *
import time#獲得系統(tǒng)時間
serverPort = 12000
serverSocket = socket(AF_INET, SOCK_STREAM)
serverSocket.bind(('', serverPort))
serverSocket.listen(1)
print('----------------------------------')
print('服務器已經(jīng)準備好連接了')
print('服務器地址為:',serverSocket.getsockname())
while True:
connectionSocket, addr = serverSocket.accept()
print('----------------------------------')
print('接收到一個新連接')
print('連接地址為:',serverSocket.getsockname())
print('客戶端地址為:',addr)
while True:
request = connectionSocket.recv(1024).decode()
if request == '':
continue
print('收到請求: ', request)
if request == 'Time':
# 格式化成2016-03-20 11:45:39形式
now = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())
print('發(fā)送響應:',now)
connectionSocket.send(('收到當前服務器上系統(tǒng)時間為:'+now).encode())
elif request == 'Exit':
print('發(fā)送響應:Bye')
connectionSocket.send('收到回復:Bye'.encode())
connectionSocket.close()#結束TCP連接
break#跳出循環(huán)
else:
print('發(fā)送響應:輸入有誤,請重新輸入')
connectionSocket.send('輸入有誤,請重新輸入'.encode())
-
客戶端
考慮遠程實現(xiàn),需要手動輸入服務端IP及端口號12000。通過ipconfig查詢,本機IP:192.168.0.107。
T_TCPClient.py代碼
from socket import *
clientSocket = socket(AF_INET, SOCK_STREAM)
print('1個客戶端正在運行')
serverName = input('請輸入要連接的服務端IP:')
serverPort = int(input('請輸入要連接的服務端的端口號:'))
clientSocket.connect((serverName, serverPort))
print('客戶端地址:',clientSocket.getsockname())
print('連接到',clientSocket.getpeername())
while True:
request = input('發(fā)送一條請求:')
clientSocket.send(request.encode())#請求發(fā)送
modifiedSentence = clientSocket.recv(1024)#回復報文
print(modifiedSentence.decode())#打印回復
if request == 'Exit':
clientSocket.close()
break
-
運行效果
先啟動服務端,再啟動客戶端A連接并交互,發(fā)送“Time”時服務端回復系統(tǒng)時間,發(fā)送“Exit”時回復“Bye”并斷開與客戶端的連接。發(fā)送命令時無效,并回復提示信息。斷開與客戶端A的連接后服務端還可以繼續(xù)與其他客戶端通信。(圖3)?
Figure 3 系統(tǒng)時間查詢
3. 網(wǎng)絡文件傳輸
? 實現(xiàn)一個基于客戶/服務器的網(wǎng)絡文件傳輸程序。
? 傳輸層使用TCP。
? 交互過程
1) 客戶端從用戶輸入獲得待請求的文件名。
2) 客戶端向服務器端發(fā)送文件名。
3) 服務器端收到文件名后,傳輸文件。
4) 客戶端接收文件,重命名并存儲在硬盤。
-
服務端
同樣服務端需要先創(chuàng)建TCP協(xié)議下的與固定端口號綁定的進程套接字socket,并保持監(jiān)聽狀態(tài)等待與客戶端連接。每次發(fā)送文件分組64字節(jié),直至發(fā)送完畢后關閉連接。引入os庫來查看文件是否存在,并返回提示。
TF_TCPServer.py代碼
from socket import *
clientSocket = socket(AF_INET, SOCK_STREAM)
print('-----------------------------')
print('客戶端正在運行')
serverName = input('請輸入要連接的服務端IP:')
serverPort = int(input('請輸入要連接的服務端的端口號:'))
clientSocket.connect((serverName, serverPort))
print('客戶端地址:',clientSocket.getsockname())
print('連接到',clientSocket.getpeername())
filename = input('請輸入所請求的文件名:')
clientSocket.send(filename.encode())#請求發(fā)送
print('已發(fā)送文件名 【', filename, '】 至服務器')
modifiedSentence = clientSocket.recv(1024).decode()#回復報文
print(modifiedSentence)#打印回復報文
if modifiedSentence == 'ok':
file_size = clientSocket.recv(1024).decode()#獲得文件大小
print('文件大小為:', file_size, ' 字節(jié)')#打印回復報文
print('-----------------------------')
f = open(filename, 'wb')#新建待寫入文件
while True:
r = clientSocket.recv(64)#每次接收64字節(jié)報文
if len(r)==0:
print('-----------------------------')
print('1個名為 【', filename, '】、大小為 ', file_size, ' 字節(jié)的文件已被保存')
print('接收完畢!')
print('-----------------------------')
break#發(fā)完退出
print('收到 ', len(r), ' 字節(jié)的數(shù)據(jù)')
f.write(r)#寫入文件
clientSocket.close()
-
運行效果(后面加了遠程功能,效果圖暫時還在本地)
先啟動好服務器并保持監(jiān)聽狀態(tài)等待連接,在啟動客戶端與服務器建立連接(TCP),發(fā)送所請求傳輸?shù)奈募?。服務器收到文件名后利?/span>os庫查詢是否存在文件。文件不存在返回提示信息并斷開連接。文件存在則開始傳輸每次64字節(jié)的文件分組,直至檢測到待傳輸文件分組為0字節(jié)時停止傳輸,并提示傳輸完畢信息,斷開連接??蛻舳嗽诿看问盏轿募纸M的時候?qū)⑵浯嬗诳蛻舳吮镜氐耐募V敝了盏椒纸M為0字節(jié)時確定接收完畢,打印提示信息并關閉連接。
4. 網(wǎng)絡聊天室
? 實現(xiàn)一個基于客戶/服務器的網(wǎng)絡聊天程序。
? 要求實現(xiàn)多個用戶的群聊。要求客戶端打印聊天消息,服務器打印系統(tǒng)信息。
? 傳輸層使用UDP。
? 不要求實現(xiàn)GUI界面。
-
服務端
服務器程序維護一個聊天室用戶字典列表userList來了解聊天室里有哪些用戶(客戶端),映射關系為IP:name。新用戶到達時加進來,舊用戶離開時刪除。每次用戶發(fā)來消息,通過知道聊天室里有哪些用戶(客戶端),確定該用戶是否是新用戶。
需要實現(xiàn)的群聊功能是一個用戶發(fā)消息,所有用戶都能收到。相應地,一個客戶端把聊天消息發(fā)給服務器,服務器再將收到的消息轉(zhuǎn)發(fā)給所有客戶端。所以在服務端需要有一個SendUser() 函數(shù)來實現(xiàn)每次收到信息轉(zhuǎn)發(fā)給用戶表內(nèi)全部用戶相同信息的功能。
服務端創(chuàng)建與固定端口號綁定的socket并開啟等待消息,消息有3類:新用戶加入的昵稱消息,‘quit’用戶退出群聊消息,一般聊天消息。
第一類為新用戶加入的昵稱消息。如果所收到客戶端的IP是用戶字典里不存在的,則可判斷為新用戶且發(fā)送過來的消息為他的昵稱?;貜蜌g迎并將新用戶加入消息轉(zhuǎn)發(fā)給全部用戶(用戶字典里的)。
第二類“quit”消息。當用戶發(fā)送“quit”時,用戶退出群聊,服務端斷開與其的連接并將該消息轉(zhuǎn)發(fā)給全部用戶。
第三類一般聊天消息。如果所接受消息并非上面2類,就可判斷其為一般聊天消息,服務器需要將其加上用戶名轉(zhuǎn)發(fā)給用戶字典里的全部用戶。用戶名可通過消息的IP地址在用戶字典里查詢到。
UDPServer.py代碼
from socket import *
userList = {} #創(chuàng)建userList(map ip:name)
def SendUsers(userList, data):#將數(shù)據(jù)發(fā)送給全部用戶
for user in userList:
serverSocket.sendto(data.encode(), user)
print('發(fā)送給 ', user, ' ---> ',data)
serverPort = 12000
serverSocket = socket(AF_INET, SOCK_DGRAM)
serverSocket.bind(('', serverPort))
print('服務器已經(jīng)準備好接收了')
while True:
message, clientAddress = serverSocket.recvfrom(2048)
m = message.decode()#解碼報文
print('收到來自 ', clientAddress, ' 的信息 ---> ', m)
#如果所接收用戶地址不在,則為新用戶,加入列表
if not clientAddress in userList:
userList[clientAddress] = m
SendUsers(userList, m + ' 已經(jīng)加入群聊!')
elif m == 'quit':#
SendUsers(userList, userList[clientAddress]+' 已經(jīng)離開群聊!')
del userList[clientAddress]
else:#聊天
SendUsers(userList, userList[clientAddress]+':'+m )
-
客戶端
客戶端為了保證收發(fā)同時,需要兩個線程實現(xiàn)的。一個線程Receive() 負責接收并顯示消息,另一個線程Send() 負責獲取輸入和發(fā)送消息。Python里threading庫可以實現(xiàn)多線程編程。
UDPClient.py代碼
from socket import *
import threading#多線程
import time
#發(fā)送函數(shù)線程
def Send(clientSocket, serverName, serverPort):
while True:
m = input()
clientSocket.sendto(m.encode(), (serverName, serverPort))
if m == 'quit':
time.sleep(1)#延時1秒再關閉連接,避免收發(fā)線程沖突
clientSocket.close()#發(fā)送退出則斷開連接并跳出循環(huán)
break
#接收函數(shù)線程
def Receive(clientSocket):
while True:
try: # 利用 close 作為退出標志
modifiedMessage, serverAddress = clientSocket.recvfrom(2048)
print(modifiedMessage.decode())
except:
break
serverName = 'localhost'
serverPort = 12000
clientSocket = socket(AF_INET, SOCK_DGRAM)#創(chuàng)建客戶端進程套接字
#發(fā)送昵稱給服務器
name = input('請輸入昵稱:')
clientSocket.sendto(name.encode(), (serverName, serverPort))
print('歡迎', name, ',輸入 <quit> 退出群聊!')
#創(chuàng)建接收進程并啟動
th_Send = threading.Thread(target=Send, args=(clientSocket, serverName, serverPort))
th_Receive = threading.Thread(target=Receive, args=(clientSocket,))
th_Send.start()
th_Receive.start()
-
運行效果
總體效果
-
開啟服務器,jennie客戶端和john客戶端陸續(xù)加入群聊
服務器先開啟,jennie客戶端先發(fā)送昵稱加入消息,服務器收到消息并將jennie加入用戶字典并向jennie回復歡迎信息,并將新用戶加入消息轉(zhuǎn)發(fā)給全部用戶(當前用戶字典僅有jennie)。接下來john加入,同樣的過程,這時全部用戶(jennie,john)同時收到轉(zhuǎn)發(fā)消息,后面還有Mike加入下圖未展示(左:服務器,中:john客戶端2,右:jennie客戶端1)
-
聊天(4欄:服務器,mike,? john, ?jennie)
?? ?jennie先發(fā)送消息“你們知道那時候可以會深大嗎”,服務器收到消息后通過查詢用戶字典知道jennie為老用戶,發(fā)送的是一般聊天信息,則會加上jennie昵稱把該消息轉(zhuǎn)發(fā)給全部用戶(用戶字典里:jennie, john, mike),全部用戶客戶端可以收到該條消息。
-
mike回復消息同樣被轉(zhuǎn)發(fā)給全部用戶
-
用戶退出
??? 用戶在發(fā)送‘quit’給服務器后,服務器會將信息該用戶退出信息轉(zhuǎn)發(fā)給全部用戶,而該用戶也會等待1s后(避免與receive線程沖突)在關閉客戶端連接。如圖中mike退出。
??? mike退出后其他用戶繼續(xù)聊天,服務器也繼續(xù)接收。
問題及解決:
-
URL 請求程序
- 在創(chuàng)建文件名名時可以直接通過接受的url字符串切片獲得。
- 可通過os庫的相關功能獲得文件大小
-
系統(tǒng)時間查詢
- 在“系統(tǒng)時間查詢”實驗部分中,我發(fā)現(xiàn)要實現(xiàn)遠程就必須添加服務器地址的輸入。也必須獲得本機IP。通過ipconfig查詢,本機IP:192.168.0.107
- 通過socket庫的getsockname() 函數(shù)可獲得本地進程IP地址和端口號,getpeername() 獲得所連接的遠程服務端的地址和端口號。
- 利用time庫獲得格式化系統(tǒng)時間表達。
-
網(wǎng)絡文件傳輸
- 通過文件分組的大小是否為0判斷是否文件發(fā)送完畢。
- 利用os庫判斷文件是否存在
-
網(wǎng)絡聊天室
- 顯示套接字不可以作為線程函數(shù)參數(shù),經(jīng)查閱資料發(fā)現(xiàn)線程參數(shù)接受的是元組,需要在參數(shù)參數(shù)后面加‘,’代表這是個元組。
- 某一個用戶退出時,服務器顯示某個遠程主機強制斷開連接報錯,而代碼里我在用戶發(fā)送‘quit’后就斷開該客戶端,可能會導致后續(xù)客戶端繼續(xù)收服務器信息的receive線程出現(xiàn)問題,所以為了避免沖突我在用戶發(fā)送‘quit’后等待1s再關閉客戶端連接,避免客戶端receive線程發(fā)生沖突,問題也解決了。
實驗心得體會
通過本次實驗,學會通過python的requests庫來完成url請求并獲得網(wǎng)頁文件和相關信息。同時可利用os庫獲取文件具體信息。
我加深關于TCP/UDP套接字的認識,了解了他們的區(qū)別,比如TCP套接字連接后服務端就可以保持監(jiān)聽狀態(tài),創(chuàng)建連接后的短期內(nèi)通信與同一客戶端無需再次連接。而UDP并無嚴格意義的連接過程,服務端也沒有監(jiān)聽的概念。
另外我通過該次試驗,初步學習了socket編程方法,通過套接字的創(chuàng)建實現(xiàn)服務端與客戶端基于TCP/UDP協(xié)議的進程通信,實現(xiàn)文件傳輸。
學會了基于此再利用多線程編程的知識即可實現(xiàn)多人聊天功能,比如在這里通過利用python的threading庫實現(xiàn)客戶端的收發(fā)雙線程并發(fā)通信。注意的是當客戶端準備關閉連接的時候,需等待一段時間避免接收線程由于管道的提前關閉而導致出錯。?
總結
我們可以通過python的requests庫來完成url請求并獲得網(wǎng)頁文件和相關信息。
通過套接字的創(chuàng)建實現(xiàn)服務端與客戶端基于TCP/UDP協(xié)議的進程通信,實現(xiàn)文件傳輸。文章來源:http://www.zghlxwxcb.cn/news/detail-430039.html
基于此再利用多線程編程的知識即可實現(xiàn)多人聊天功能,比如在這里通過利用python的threading庫實現(xiàn)客戶端的收發(fā)雙線程并發(fā)通信。注意的是當客戶端準備關閉連接的時候,需等待一段時間避免接收線程由于管道的提前關閉而導致出錯。文章來源地址http://www.zghlxwxcb.cn/news/detail-430039.html
到了這里,關于【計算機網(wǎng)絡】4 Socket網(wǎng)絡編程的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關文章,希望大家以后多多支持TOY模板網(wǎng)!