Socket Socket是什麼? 下麵來看一下網路的傳輸過程: 上面圖片顯示了網路傳輸的基本過程,傳輸是通過底層實現的,有很多底層,我們寫傳輸過程的時候,要知道所有的過程那就太複雜了,socket為我們封裝了底層的傳輸流程,讓我們直接可以在socket上直接實現數據交換。 socket本質:對底層 ...
Socket
Socket是什麼?
下麵來看一下網路的傳輸過程:
上面圖片顯示了網路傳輸的基本過程,傳輸是通過底層實現的,有很多底層,我們寫傳輸過程的時候,要知道所有的過程那就太複雜了,socket為我們封裝了底層的傳輸流程,讓我們直接可以在socket上直接實現數據交換。
socket本質:對底層網路協議的封裝。
socket實現數據的發送和接收,通過什麼建立連接呢?下麵看一幅簡單的圖片:
在電腦上,我們運行了很多線程,我們如何實現數據的定向交換呢?如何實現客戶端和伺服器的連接呢?連接我們可以通過IP地址進行連接,連接上之後發送給那個程式呢?這時候我們就要通過port(埠號)進行指明,因為實現連接的過程是通過IP+埠號(port)進行連接。
客戶端
import socket while True: s = socket.socket() '''生成一個socket連接''' s.connect(("localhost",6970)) #建立連接,連接本地,埠號是6969埠 message = input("請輸入您要發送的信息:").encode('utf-8') if message == "quit": break s.sendall(message) #python3上只能傳輸二進位位元組 data = s.recv(1024) #接收伺服器端傳來的內容 print(data) s.close() #關閉連接
上面代碼就是客戶端,通過客戶端發送數據到伺服器,實現交互,客戶端加上了一個while迴圈,能夠實現多次交互,我們知道,正常情況下,交互一次就退出了,通過While迴圈,讓客戶端不停的產生新的連接,就能不斷與客戶端進行數據交換。
下麵是客戶算發送的數據,只能以字元的形式進行發送,所以發送的是字元,漢字看不到,進行轉換了。而且不知道為什麼,輸入空之後,客戶端沒有響應,靜止不動了:
請輸入您要發送的信息:dasfda b'dasfda' 請輸入您要發送的信息:不能發送漢字嗎 b'\xe4\xb8\x8d\xe8\x83\xbd\xe5\x8f\x91\xe9\x80\x81\xe6\xb1\x89\xe5\xad\x97\xe5\x90\x97' 請輸入您要發送的信息:放大法是否對 b'\xe6\x94\xbe\xe5\xa4\xa7\xe6\xb3\x95\xe6\x98\xaf\xe5\x90\xa6\xe5\xaf\xb9' 請輸入您要發送的信息:dfafdas b'dfafdas' 請輸入您要發送的信息:dfasdfa b'dfasdfa' 請輸入您要發送的信息:dfasfd b'dfasfd' 請輸入您要發送的信息:afdasdfas fb'afdasdfas' 請輸入您要發送的信息:dasfda b'fdasfda' 請輸入您要發送的信息:dfa fdab'dfa' 請輸入您要發送的信息: b'fda' 請輸入您要發送的信息:afasfda b'afasfda' 請輸入您要發送的信息:afdasfd b'afdasfd' 請輸入您要發送的信息:afdasdfa b'afdasdfa' 請輸入您要發送的信息:
伺服器
import socket '''生成socket實例''' s = socket.socket() s.bind(("localhost",6970)) #綁定本地IP和6969埠號 s.listen(10) #監聽客戶端發送的信息,一旦有客戶端發送過來連接,就接收,現在是等待狀態,防止堵塞 print("連接建立完畢,正在等待數據.......") while True: conn,addr = s.accept() #接收數據,accept()會接收兩個數據,一個是連接conn,一個是地址addr(IP和埠號) print("Addr:",addr) data = conn.recv(1024) #通過連接接收數據 print(data) conn.send(data) #發送數據,把接收到的信息發送 conn.close() s.close()
上面是伺服器的代碼,我們也使用了一個迴圈,讓伺服器一直掛著,等待客戶端發送數據,不停的接收:
下麵是伺服器接收的數據:
連接建立完畢,正在等待數據....... Addr: ('127.0.0.1', 51924) b'dasfda' Addr: ('127.0.0.1', 51926) b'\xe4\xb8\x8d\xe8\x83\xbd\xe5\x8f\x91\xe9\x80\x81\xe6\xb1\x89\xe5\xad\x97\xe5\x90\x97' Addr: ('127.0.0.1', 51928) b'\xe6\x94\xbe\xe5\xa4\xa7\xe6\xb3\x95\xe6\x98\xaf\xe5\x90\xa6\xe5\xaf\xb9' Addr: ('127.0.0.1', 51930) b'dfafdas' Addr: ('127.0.0.1', 51932) b'dfasdfa' Addr: ('127.0.0.1', 51934) b'dfasfd' Addr: ('127.0.0.1', 51936) b'afdasdfas' Addr: ('127.0.0.1', 51938) b'fdasfda' Addr: ('127.0.0.1', 51940) b'dfa' Addr: ('127.0.0.1', 51942) b'fda' Addr: ('127.0.0.1', 51944) b'afasfda' Addr: ('127.0.0.1', 51946) b'afdasfd' Addr: ('127.0.0.1', 51948) b'afdasdfa' Addr: ('127.0.0.1', 51950)
可以看出,數據進行了多次的交互,並且接收了數據,是以字元編碼形式進行接收的。
客戶端 import socket client = socket.socket() #聲明socket類型,同時生成socket連接對象 client.connect(("localhost",6969)) client.send("我要下載A片".encode("utf-8")) #在python3中只能發送位元組 data = client.recv(1024) #預設接收數據的大小,定義1024個位元組 print("recv:",data.decode()) client.close() #關閉連接 伺服器 #伺服器端 import socket server = socket.socket() server.bind(("localhost",6969)) #綁定要監聽埠 server.listen() #監聽 print("我要開始等電話了") conn,addr = server.accept() #等待接收,conn對方的連接,addr對方的地址,接收會接收到一個IP編號和地址 #conn就是客戶端連過來而在伺服器端為其生成的一個連接實例 print(conn,addr) print("電話來了") data = conn.recv(1024) #接收的數據大小,不能直接調用server接收,打電話新的進來,可以切換 print("recv:",data) conn.send(data.upper()) server.close()
下麵來看一個迴圈的代碼:
#伺服器端 import socket server = socket.socket() server.bind(("localhost",6969)) #綁定要監聽埠 server.listen() #監聽 print("我要開始等電話了") while True: conn,addr = server.accept() #等待接收,conn對方的連接,addr對方的地址,接收會接收到一個IP編號和地址 #conn就是客戶端連過來而在伺服器端為其生成的一個連接實例 print(conn,addr) print("電話來了") data = conn.recv(1024) #接收的數據大小,不能直接調用server接收,打電話新的進來,可以切換 print("recv:",data) conn.send(data.upper()) server.close()
首先啟動伺服器端,等待客戶端發送數據:
客戶端:
import socket client = socket.socket() #聲明socket類型,同時生成socket連接對象 client.connect(("localhost",6969)) while True: msg = input(">>:").strip() #輸入空會卡主,我們知道,QQ也是不允許用戶輸入空的,會提示輸入為空 client.send(msg.encode("utf-8")) #在python3中只能發送位元組 data = client.recv(1024) #預設接收數據的大小,定義1024個位元組 print("recv:",data.decode()) client.close() #關閉連接
客戶端輸入:
>>:我
recv: 我
>>:要
上面可以看出,客戶端輸入第一次正常,第二次卡住了,只能進行一次通訊,說明客戶端的實現一次通訊之後終止了。
服務端接收:
我要開始等電話了
<socket.socket fd=4, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('127.0.0.1', 6969), raddr= ('127.0.0.1', 58460)> ('127.0.0.1', 58460)
電話來了
recv: b'\xe6\x88\x91'
可以看出,伺服器端第一次接收數據之後,也卡住了,伺服器端卡主是由於沒有新的連接進來,連接並沒有終端,而只是客戶端自己斷開,找不到新的連接掛在哪裡。
下麵來修改伺服器代碼:
#伺服器端 import socket server = socket.socket() server.bind(("localhost",7071)) #綁定要監聽埠 server.listen() #監聽 print("我要開始等電話了") conn, addr = server.accept() # 等待接收,conn對方的連接,addr對方的地址,接收會接收到一個IP編號和地址 # conn就是客戶端連過來而在伺服器端為其生成的一個連接實例 print(conn, addr) print("電話來了") while True: data = conn.recv(1024) #接收的數據大小,不能直接調用server接收,打電話新的進來,可以切換 # if not data: # break print("recv:",data) conn.send(data.upper()) server.close()
現在重新啟動伺服器,然後啟動客戶端,在客戶端輸入交互內容,如下:
客戶端輸入呢絨: >>:wo recv: WO >>:yao recv: YAO >>:zixue recv: ZIXUE 伺服器端接收內容: 我要開始等電話了 <socket.socket fd=4, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('127.0.0.1', 7071), raddr=('127.0.0.1', 51508)> ('127.0.0.1', 51508) 電話來了 recv: b'wo' recv: b'yao' recv: b'zixue
現在顯示一切都是那麼完美,現在來斷開客戶端,看會怎樣,如下:
ecv: b''
recv: b''
recv: b''
recv: b''
recv: b''
......
客戶端斷開之後,伺服器端並沒有斷開,由於伺服器端在迴圈接收消息,由於沒有客戶端發送消息,因為接收到的消息一直是空的,並且伺服器端server = socket.accept()並沒有執行接收過程,因為一直在運行迴圈。
下麵我們來驗證伺服器端的情況,看客戶端斷了之後,伺服器端是如何執行的,為此我們需要簡單修改一下伺服器端代碼,如下:
#伺服器端 import socket server = socket.socket() server.bind(("localhost",7071)) #綁定要監聽埠 server.listen() #監聽 print("我要開始等電話了") conn, addr = server.accept() # 等待接收,conn對方的連接,addr對方的地址,接收會接收到一個IP編號和地址 # conn就是客戶端連過來而在伺服器端為其生成的一個連接實例 print(conn, addr) print("電話來了") count = 0 while True: data = conn.recv(1024) #接收的數據大小,不能直接調用server接收,打電話新的進來,可以切換 # if not data: # break print("recv:",data) conn.send(data.upper()) count += 1 if count >= 10: break server.close()
運行結果如下:
我要開始等電話了
<socket.socket fd=4, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('127.0.0.1', 7071), raddr= ('127.0.0.1', 51532)> ('127.0.0.1', 51532)
電話來了
recv: b'wo'
recv: b'yao'
recv: b'\xe8\x87\xaa\xe5\xad\xa6'
recv: b'\xe5\xbe\x80'
recv: b''
recv: b''
recv: b''
recv: b''
recv: b''
recv: b''
從上面代碼可以看出,當客戶端埠之後,伺服器端並沒有斷開,由於連接斷開了,伺服器一直在迴圈接收數據。(在Windows上,如果客戶端斷開,伺服器端也會斷開)
下麵來修改一下代碼,讓客戶端斷開,伺服器端也斷開。
如下:
#伺服器端 import socket server = socket.socket() server.bind(("localhost",7071)) #綁定要監聽埠 server.listen() #監聽 print("我要開始等電話了") conn, addr = server.accept() # 等待接收,conn對方的連接,addr對方的地址,接收會接收到一個IP編號和地址 # conn就是客戶端連過來而在伺服器端為其生成的一個連接實例 print(conn, addr) print("電話來了") while True: data = conn.recv(1024) #接收的數據大小,不能直接調用server接收,打電話新的進來,可以切換 if not data: #加入一個判斷,如果接收為空,則斷開伺服器 break print("recv:",data) conn.send(data.upper()) server.close()
上面代碼中,對伺服器端代碼進行了簡單完善,讓客戶端斷開的時候,伺服器端也斷開,如何實現呢?只需要判斷客戶端接收到空的時候終止既可以。然後啟動伺服器,啟動客戶端進行數據傳輸如下:
客戶端輸入: /usr/bin/python3.5 /home/zhuzhu/第七天/socket_client.py >>:woyao recv: WOYAO >>:自學 recv: 自學 >>:Traceback (most recent call last): File "/home/zhuzhu/第七天/socket_client.py", line 7, in <module> msg = input(">>:").strip() #輸入空會卡主,我們知道,QQ也是不允許用戶輸入空的,會提示輸入為空 KeyboardInterrupt 伺服器端接收: 我要開始等電話了 <socket.socket fd=4, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('127.0.0.1', 7071), raddr=('127.0.0.1', 51548)> ('127.0.0.1', 51548) 電話來了 recv: b'woyao' recv: b'\xe8\x87\xaa\xe5\xad\xa6'
從上面結果可以看出,客戶端斷開時候,伺服器端也斷開了,那麼如何實現客戶端斷開,伺服器端接收新的客戶端的消息,只斷開當前連接,重新生成一個新的連接。如下:
伺服器端
#伺服器端 import socket server = socket.socket() server.bind(("localhost",7071)) #綁定要監聽埠 server.listen() #監聽 while True: print("我要開始等電話了") conn, addr = server.accept() # 等待接收,conn對方的連接,addr對方的地址,接收會接收到一個IP編號和地址 # conn就是客戶端連過來而在伺服器端為其生成的一個連接實例 print(conn, addr) print("電話來了") while True: data = conn.recv(1024) #接收的數據大小,不能直接調用server接收,打電話新的進來,可以切換 if not data: break print("recv:",data) conn.send(data.upper()) server.close()
上面代碼進行了兩層嵌套,內層嵌套是如果接收消息為空,則斷開本次連接,外層迴圈是記憶體連接斷開之後,重新生成一個新的連接,因此我們首先啟動伺服器端,現在同時打開三個客戶端如下:
啟動伺服器:
我要開始等電話了
<socket.socket fd=4, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('127.0.0.1', 7071), raddr= ('127.0.0.1', 51568)> ('127.0.0.1', 51568)
電話來了
客戶端1:
/usr/bin/python3.5 /home/zhuzhu/第七天/socket_client.py
>>:
客戶端2:
/usr/bin/python3.5 /home/zhuzhu/第七天/socket_client.py
>>:
客戶端3:
/usr/bin/python3.5 /home/zhuzhu/第七天/socket_client.py
>>:
首先在客戶端1發送消息,如下:
>>:11
recv: 11
伺服器:
/usr/bin/python3.5 /home/zhuzhu/第七天/socket_server.py
我要開始等電話了
<socket.socket fd=4, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('127.0.0.1', 7071), raddr= ('127.0.0.1', 51568)> ('127.0.0.1', 51568)
電話來了
recv: b'11'
可以看到,伺服器接收到了客戶端1發送的消息,在客戶端2和客戶端3發送消息,如下:
客戶端2:
>>:shibushi
客戶端3:
/usr/bin/python3.5 /home/zhuzhu/第七天/socket_client.py
>>:接收不到嗎?
伺服器端:
/usr/bin/python3.5 /home/zhuzhu/第七天/socket_server.py
我要開始等電話了
<socket.socket fd=4, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('127.0.0.1', 7071), raddr= ('127.0.0.1', 51568)> ('127.0.0.1', 51568)
電話來了
recv: b'11'
可以看到,伺服器並沒有接收到客戶端2和客戶端3的信息,現在終端客戶端1,如下:
客戶端1:
>>:11
>>:Traceback (most recent call last):
File "/home/zhuzhu/第七天/socket_client.py", line 7, in <module>
msg = input(">>:").strip() #輸入空會卡主,我們知道,QQ也是不允許用戶輸入空的,會提示輸入為空
KeyboardInterrupt
伺服器:
我要開始等電話了
<socket.socket fd=4, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('127.0.0.1', 7071), raddr= ('127.0.0.1', 51568)> ('127.0.0.1', 51568)
電話來了
recv: b'11'
我要開始等電話了
<socket.socket fd=5, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('127.0.0.1', 7071), raddr= ('127.0.0.1', 51570)> ('127.0.0.1', 51570)
電話來了
recv: b'shibushi'
從上面可以看出,客戶端1斷開後,伺服器與客戶端1的連接也斷開了,生成了一個新的連接,連接客戶端2,並接收客戶端2發送過來的消息。
因此我們得出結論,上面代碼實現了客戶端斷開後,伺服器端重新掛起,等待新的連接的任務。
當所有客戶端中斷後,如下:
/usr/bin/python3.5 /home/zhuzhu/第七天/socket_server.py 我要開始等電話了 <socket.socket fd=4, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('127.0.0.1', 7071), raddr=('127.0.0.1', 51568)> ('127.0.0.1', 51568) 電話來了 recv: b'11' 我要開始等電話了 <socket.socket fd=5, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('127.0.0.1', 7071), raddr=('127.0.0.1', 51570)> ('127.0.0.1', 51570) 電話來了 recv: b'shibushi' 我要開始等電話了 <socket.socket fd=4, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('127.0.0.1', 7071), raddr=('127.0.0.1', 51572)> ('127.0.0.1', 51572) 電話來了 recv: b'\xe6\x8e\xa5\xe6\x94\xb6\xe4\xb8\x8d\xe5\x88\xb0\xe5\x90\x97\xef\xbc\x9f' recv: b'shibush' recv: b'duankaiyixai' recv: b'exit' 我要開始等電話了
可以看出,當客戶端所有連接斷開後,伺服器端還在等待消息。等待新的連接介入。
上面客戶端代碼是有缺陷的,因為send()是不能發送空的,我們發送空就會讓客戶端卡主,如下:
客戶端輸入:
>>:eoyoa
recv: EOYOA
>>:dfasfd
recv: DFASFD
>>: #發送空,卡住了
從上面客戶端執行代碼可以看出,確實當前卡主了,現在來完善一下客戶端,讓當用戶輸入為空的時候,跳過本次過程,告訴用戶不能發送空,重新輸入:
import socket client = socket.socket() #聲明socket類型,同時生成socket連接對象 client.connect(("localhost",7071)) while True: msg = input(">>:").strip() #輸入空會卡主,我們知道,QQ也是不允許用戶輸入空的,會提示輸入為空 if not msg: print("對不起,發送消息不能為空!") continue client.send(msg.encode("utf-8")) #在python3中只能發送位元組,send()不能發送空 data = client.recv(1024) #預設接收數據的大小,定義1024個位元組 print("recv:",data.decode()) client.close() #關閉連接
上面代碼中,客戶端加入了一個判斷,當用戶輸入發送消息為空時,告訴用戶不能為空,並跳過本次迴圈(我們知道,QQ聊天也是不能發送空消息的),運行如下:
客戶端輸入:
/usr/bin/python3.5 /home/zhuzhu/第七天/socket_client.py
>>:dasfd
arecv: DASFD
>>:dfasfd
recv: ADFASFD
>>:
對不起,發送消息不能為空!
>>:發順豐達
recv: 發順豐達 #不小心打了一個廣告
>>:
對不起,發送消息不能為空!
>>:
可以看出,上述代碼實現了不能發送為空的功能,如果想要空的時候退出,可以把continue改成break,當然這是非主流做法了。任何聊天工具不會讓用戶輸入空就終止聊天的。