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()
client
# -*- 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()  # 關閉伺服器端
server

上面的程式碼的有一個問題, 就是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()
客戶端
 
Tags: