Socket
1.不同的協議在同一條網線上傳遞(傳遞的都是數據流)
1.send 發
2.receive 收
2.OSI七層協議
應用層
表示層
會話層
傳輸層
網路層 ip
數據鏈路層 mac
物理層
傳輸層:數據通訊需要規則:
TCP/IP : 三次握手 、四次斷開 syn syn+ack ack
UDP
3. 一台電腦上可開闢的埠數為:65535 port
nginx:80 mysql:3306
4.模擬通訊
發送端:
import socket : 因為socket封裝了所有的上層協議,所以在使用時,需要指明要使用的協議
socket.TCP/IP (聲明協議類型)
connect(a.ip, a.port) 建立鏈接,鏈接對方的ip以及埠號
socket.send(hello) 發送數據
rece() 接收數據
socket.close() 關閉鏈接
接收端: 注意,先要有接收端(服務端),再有發送端
import socket
socket.TCP/IP (聲明協議類型)
listen(0.0.0.0, 3306) 監聽(術語):因為一台機器有多個網卡,所以要先聲明自己的網卡(0.0.0.0表示選擇所有網卡),再聲明自己的埠號
waiting() 等待接收數據
reve() 接收數據
send() 發送數據
伺服器端不需要關閉,等待其他程式來連

簡單的伺服器-客戶端

# -*- coding:utf8 -*- # 客戶端 import socket client = socket.socket() # 聲明socket協議類型,同時生成socket鏈接對象 client.connect(("localhost", 6996)) # 連接伺服器的ip和埠號 client.send("hello你好".encode(encoding="utf8")) # 在python3中,發送的數據都是二進位數據流,所以需要將字元串轉化為二進位 data = client.recv(1024) # 接收伺服器端發送過來的數據,大小上線為1024位元組 print(data.decode()) client.close()

# -*- coding:utf8 -*- # 伺服器端 import socket server = socket.socket() # 聲明socket協議類型,同時生成socket鏈接對象 server.bind(("localhost", 6996)) # 綁定需要監聽的 網卡ip 和 埠號 server.listen() # 監聽 print("我要開始等電話了") """ 等待電話打進來 conn:就是客戶端連接進來,伺服器端為其生成的一個鏈接實例 addr:客戶端ip """ conn, addr = server.accept() print(conn, addr) print("電話來了") data = conn.recv(1024) # 1024為接收客戶端發送過來數據的上限為1024位元組 print("reve", data.decode()) conn.send(data.upper()) # 發送數據給客戶端 server.close() # 關閉伺服器端
上面的程式碼的有一個問題, 就是SocketServer.py運行起來後, 接收了一次客戶端的data就退出了。。。, 但實際場景中,一個連接建立起來後,可能要進行多次往返的通訊。

優化後的伺服器-客戶端
光只是簡單的發消息、收消息沒意思,干點正事,可以做一個極簡版的ssh,就是客戶端連接上伺服器後,讓伺服器執行命令,並返回結果給客戶端。

import os import socket server = socket.socket() server.bind(("localhost", 6996)) server.listen(5) # 設置客戶端連接上限 while True: print("等待連接") conn, addr = server.accept() while True: data_1 = conn.recv(1024) if not data_1: print("客戶端斷開了...", conn.getpeername()) break # 這裡斷開就會再次回到第一次外層的loop data_2 = os.popen(data_1.decode()).read() conn.send(str((len(data_2.encode()))).encode()) # 提醒客戶端將會發送數據的長度 conn.send(data_2.encode()) print("已將消息發送給:", conn.getpeername()) server.close()

import socket client = socket.socket() client.connect(("localhost", 6996)) while True: str_input = input("請輸入命令").strip() if len(str_input) == 0: continue client.send(str_input.encode()) data_len = client.recv(1024) pd_len = 0 data_1 = b"" while pd_len < int(data_len): data = client.recv(1024) data_1 += data pd_len += len(data) print(pd_len, data_len) print(data_1.decode()) client.close()
粘包:
- 如果出現粘包問題(通知客戶端長度的數據和需要發送給客戶端的數據,被緩衝池統一處理髮送給了客戶端),只需要在發送這兩個數據中間,在進行一次交互就可以了(例如:客戶端:發送我準備好接收數據了,伺服器端:接收到回饋後,給客戶端發送數據)
Socket中的方法:
sk.bind(address) 必會
s.bind(address) 將套接字綁定到地址。address地址的格式取決於地址族。在AF_INET下,以元組(host,port)的形式表示地址。
sk.listen(backlog) 必會
開始監聽傳入連接。backlog指定在拒絕連接之前,可以掛起的最大連接數量。
backlog等於5,表示內核已經接到了連接請求,但伺服器還沒有調用accept進行處理的連接個數最大為5
這個值不能無限大,因為要在內核中維護連接隊列
sk.setblocking(bool) 必會
是否阻塞(默認True),如果設置False,那麼accept和recv時一旦無數據,則報錯。
sk.accept() 必會
接受連接並返回(conn,address),其中conn是新的套接字對象,可以用來接收和發送數據。address是連接客戶端的地址。
接收TCP 客戶的連接(阻塞式)等待連接的到來
sk.connect(address) 必會
連接到address處的套接字。一般,address的格式為元組(hostname,port),如果連接出錯,返回socket.error錯誤。
sk.connect_ex(address)
同上,只不過會有返回值,連接成功時返回 0 ,連接失敗時候返回編碼,例如:10061
sk.close() 必會
關閉套接字
sk.recv(bufsize[,flag]) 必會
接受套接字的數據。數據以字元串形式返回,bufsize指定最多可以接收的數量。flag提供有關消息的其他資訊,通常可以忽略。
sk.recvfrom(bufsize[.flag])
與recv()類似,但返回值是(data,address)。其中data是包含接收數據的字元串,address是發送數據的套接字地址。
sk.send(string[,flag]) 必會
將string中的數據發送到連接的套接字。返回值是要發送的位元組數量,該數量可能小於string的位元組大小。即:可能未將指定內容全部發送。
sk.sendall(string[,flag]) 必會
將string中的數據發送到連接的套接字,但在返回之前會嘗試發送所有數據。成功返回None,失敗則拋出異常。
內部通過遞歸調用send,將所有內容發送出去。
sk.sendto(string[,flag],address)
將數據發送到套接字,address是形式為(ipaddr,port)的元組,指定遠程地址。返回值是發送的位元組數。該函數主要用於UDP協議。
sk.settimeout(timeout) 必會
設置套接字操作的超時期,timeout是一個浮點數,單位是秒。值為None表示沒有超時期。一般,超時期應該在剛創建套接字時設置,因為它們可能用於連接的操作(如 client 連接最多等待5s )
sk.getpeername() 必會
返回連接套接字的遠程地址。返回值通常是元組(ipaddr,port)。
sk.getsockname()
返回套接字自己的地址。通常是一個元組(ipaddr,port)
SocketServer實現並發:
第一步: 你必須自己創建一個請求處理類,並且這個類要進程BaseRequestHandler,並且還要重寫父類中的Handler()方法
第二步: 你必須實例化TCPServer,並且傳遞server ip 和 你上面創建的請求處理類 給這個TCPServer
第三步: server.randler_reques()只處理一個請求(所以一般不用)
server.serve_forever()處理多個一個請求,永遠執行
第四步: 關閉伺服器 server.close()

# -*- coding:UTF-8 -*- import socketserver # 創建請求類 class MySocketServer(socketserver.BaseRequestHandler): # 與客戶端所有的交互都重寫在這個方法中 def handle(self) : while True: try: # 在Python3中客戶端斷開鏈接,伺服器端會拋出異常 self.data = self.request.recv(1024).strip() print("客戶端地址為:{}".format(self.client_address[0])) print(self.data.decode()) self.request.send(self.data.upper()) except ConnectionResetError as reason: print(reason) break if __name__ == "__main__": HOST, PORK = "localhost", 999 # 這句程式碼可以實現伺服器與客戶端的一對一,但是不能一對多 #server = socketserver.TCPServer((HOST, PORK), MySocketServer) # 把程式碼改為下面的程式碼,則可以實現,一對多 server = socketserver.ThreadingTCPServer((HOST, PORK), MySocketServer) server.serve_forever() server.server_close()

# # -*- coding:utf8 -*- # 客戶端 import socket client = socket.socket() # 聲明socket協議類型,同時生成socket鏈接對象 client.connect(("localhost", 999)) # 連接伺服器的ip和埠號 while True: a = input("請輸入") client.send(a.encode(encoding="utf8")) # 在python3中,發送的數據都是二進位數據流,所以需要將字元串轉化為二進位 data = client.recv(1024) # 接收伺服器端發送過來的數據,大小上線為1024位元組 print(data.decode()) client.close()
