進擊のpython
網絡編程——socket
socket的中文意思叫做套接字,socket方法其實也叫套接字方法
我們研究過TCP/UDP協議,但是要是讓我們自己搭建,就十分困難了
而這時候,socket就出來了
socket他是存在在應用層和傳輸層之間的一堆接口
他把復雜的協議都封裝好了,你想用的時候,直接調用就行
就跟我們寫模塊一樣,把麻煩的代碼寫進去,等到用的時候只需要調用就行了
那你想想,咱們在研究模塊的時候,是不是學會方法怎么用就行??
那咱們學套接字方法,也是學會方法就行
我要是想給你打電話的話應該是怎么個過程?
# 首先我得有電話
# 輸入想打的電話
# 等待你接聽
# 跟你bb兩句
# 聽你bb兩句
那你接電話呢?
# 首先你得有電話
# 你還得有電話號碼
# 然后你得有電話線
# 等着接電話
# 鈴響接電話
# 聽你bb兩句
沒問題吧!
那我要是想寫成socket通信呢?
首先我得先創建兩個py文件是吧,通信嘛,當然要兩個:客戶端和服務端
然后要在兩個文件都調用socket模塊是吧
接下來就繼續吧!
解釋一下啊,socket里面的兩個屬性,第一個是套接字的類型,我們這個是基於網絡的嘛,所以是AF_INET
第二個是你數據的傳輸方式,SOCK_STREAM是流式傳輸,也就是TCP協議
在這我們要學習一個方法 bind 綁定,因為是手機綁定,所以是phone.bind
里面要添加一個元組參數 IP 和 端口
其中ip 我設置的是127.0.0.1,代表着本機的意思,我這不是要在自己電腦上操作嘛
端口最大到65535 其中,1-1024 是系統的端口,1024之后的就是應用的了,你隨便選個就行
這里就是又一個方法了 listen 監聽
里面要傳一個參數,這個5是什么意思呢?
我這個電話接通的時候,別人又給我打電話,我也不能掛了啊
那這種狀態可以維持幾個手機同時給我打電話呢?5個
那最后,我就開始等電話了是吧
那客戶端呢?是不是要買手機啊我用綁定ip嘛?
換句話說我用手機綁定手機卡,說只能用這個手機的這張卡才能給你打電話???
這顯然不對啊,所以,客戶端不用綁定

客戶端的撥號,用到了connet方法,里面放一個元組參數,元組由服務端的ip和端口構成
那我要是運行,是不是應該先運行服務端,再運行客戶端.這個能理解吧
那我們執行一下
看到了吧,這就算是連接上了,那我們看看我們拿的是什么東西
(<socket.socket fd=484, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('127.0.0.1', 8080), raddr=('127.0.0.1', 52621)>, ('127.0.0.1', 52621))
首先可以肯定這是個元組是吧,第一個元素是個什么東西?是不是套接字對象啊
第二個元素呢?是不是客戶端的ip以及端口信息啊
那我們是不是可以通過解構的方式把這兩個信息拿到啊
connet, client_addr = phone.accept()
那你說我這是不是就算是拿到了管道啊,那我有了管道了,我是不是就可以進行傳輸了啊
那我就可以收信息了,怎么收?又用到一個方法recv
connet.recv(1024)
它里面要傳個參數,傳接受的最大字節數
你給我傳一個,我就收一個,你要是給我傳1025個,我也收1024個
至於為什么是1024呢?我們后面說,你現在就先這么寫
那我要是發消息呢?send方法
connet.send()
里面傳發送的東西
打完電話就該掛電話了對吧
connet.close()
掛完電話是不是就關機了啊
phone.close()
那服務端完事了,客戶端是不是也應該寫點什么了
服務端是接收-發送
那我客戶端是不是應該是發送-接收
客戶端發送怎么寫?send
phone.send("hello".encode("utf-8"))
別忘了把編碼綴上
接收呢??recv
k = phone.recv(1024)
print(k)
然后呢?關機!
phone.close()
行啦!我們來看看寫完的長什么樣
執行一下吧(別忘了先執行服務端,再執行客戶端)
這樣,就完成了一個簡單的C/S結構!
話不多說,自己多敲幾遍
正常人誰那么搞啊,不都是有來言有去語的嗎
那我們是不是就應該用循環來搞這個需求
循環放在哪呢?我們不是要循環收發這個過程嘛
那我們就應該循環收發信息這一塊的代碼啊
# 服務端
import socket
# 買手機
phone = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 綁定手機卡
phone.bind(("127.0.0.1", 8080))
# 開機
phone.listen(5)
# 等電話
connet, client_addr = phone.accept()
# 收發消息
while 1:
k = connet.recv(1024)
print(f'從客戶端接收的消息:{k}')
connet.send(k.upper())
# 掛電話
connet.close()
# 關機
phone.close()
# 客戶端
import socket
# 買手機
phone = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 撥號
phone.connect(("127.0.0.1", 8080))
# 發收信息
while 1:
msg = input(">>>")
phone.send(msg.encode("utf-8"))
k = phone.recv(1024)
print(f"從服務端接收的消息:{k}")
# 關閉
phone.close()
# 服務端
從客戶端接收的消息:b'as'
從客戶端接收的消息:b'ds'
從客戶端接收的消息:b'fa'
# 客戶端
>>>as
從服務端接收的消息:b'AS'
>>>ds
從服務端接收的消息:b'DS'
>>>fa
從服務端接收的消息:b'FA'
>>>
還有一點就是想說,你們在調試的時候沒遇到這個報錯嗎?
OSError:[Errno 48] Address already in use
這個問題就是說你剛關那個程序,然后系統還沒有把你定義的端口給回收,你就又用了,就會出這個錯誤
怎么解決呢?
一種辦法是修改客戶端和服務端的端口,但是你不覺得太傻逼了嗎?
所以還有另一種方法,就是在程序服務端綁定ip和端口下面添加一段這個代碼
phone.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
這樣就沒事了,這個就是如果我發現這個端口被占用了,我就重新啟用他
不是,那你們沒有遇到bug嘛?
比如我在服務端直接敲回車,結果是什么???
你會發現你的程序執行不了了
那是誰的問題呢?是沒發過去還是沒接過來呢?
我們在send的下面寫一個打印,發現可以被打印,那就說明我發過去了
而當我在接收的下面寫一個打印,發現沒有被打印,說明我沒有接受到
而且服務端也沒有執行原來有的print
那就說明是我發過去了,但是他什么都沒有接收,所以她始終都在接受的這個位置等我傳
也就是說空字符串是沒辦法單獨傳過去的,怎么解決這個問題呢?加個判斷就好了啊
if not msg:continue
這樣bug就沒了
還有問題!你在斷開客戶端的時候,你會發現,服務端報錯了!
ConnectionResetError: [WinError 10054] 遠程主機強迫關閉了一個現有的連接。
這是我們不能忍的啊,我不跟你鏈接了,你就報錯?這不符合常識
所以我們就可以用異常捕獲來處理
try:
k = connet.recv(1024)
print(f'從客戶端接收的消息:{k}')
connet.send(k.upper())
except ConnectionResetError:
break
這樣就沒問題了!
這里多說一句啊,我這個代碼是在windows里運行的,如果你是linux運行的
服務端是不會報錯的,他會不停的接收一個空,一直循環不報錯
這樣就會使程序變成死循環!,這是我們所不能接受的!
所以,在服務端加個判斷
if not k:break
這樣就萬無一失了!
畢竟正常人不會發送個空過來!