客戶端/服務器架構

基於TCP套接字

先從服務器端說起。服務器端先初始化Socket,然後與端口綁定(bind),對端口進行監聽(listen),調用accept阻塞,等待客戶端連接。在這時如果有個客戶端初始化一個Socket,然後連接服務器(connect),如果連接成功,這時客戶端與服務器端的連接就建立了。客戶端發送數據請求,服務器端接收請求並處理請求,然後把迴應數據發送給客戶端,客戶端讀取數據,最後關閉連接,一次交互結束

tcp是基於鏈接的,必須先啓動服務端,然後再啓動客戶端去鏈接服務端

鏈接過程:三次握手————通信——————四次揮手

TCP服務端

from socket import *
ip_port=('218.197.223.92',800)#本機服務器的地址與端口
back_log=5                       #半鏈池大小
buffer_size=1024                 #接收字節數

tcp_serve=socket(AF_INET,SOCK_STREAM)#初始化socket,AF_INET是基於網絡模型的套接字家族,SOCK_STREAM是數據流模式
tcp_serve.bind(ip_port)              #綁定IP與端口
tcp_serve.listen(back_log)           #開始監聽

#可以接收多個客戶端
while True:
    print('開始連接...')
    con,addr=tcp_serve.accept()      #連接成功後會返回鏈接與客戶端地址
    #print(con,addr)
    print('連接成功,客戶端地址爲%s'%(addr[0]))
    #與一個客戶端持續通信
    while True:
        try:
            msg=con.recv(buffer_size)     #recv()讀取客戶端發送的信息,參數爲接收的字節數
            print('接收的信息爲',msg.decode('utf-8'))
            send_msg=input('>>:').strip()
            if not send_msg:continue    #當發送的信息爲空時無法傳輸,造成客戶端無法讀取,導致阻塞
            con.send(send_msg.encode('utf-8')) #send()發送信息,必須爲二進制
        except Exception:                #當客戶端突然斷開時會發送錯誤,用異常處理捕捉錯誤,讓服務器繼續運行
            print('客戶端%s連接斷開'%addr[0])
            break
tcp_serve.close() #關閉服務器
TCP客戶端
from socket import *
ip_port=('218.197.223.92',800)  #要連接的服務器的IP與端口
buffer_size=1024                   #要讀取的字節數

tcp_client=socket(AF_INET,SOCK_STREAM)
tcp_client.connect(ip_port)        #連接服務器

#與服務器持續對話
while True:
    msg=input('>>:').strip()
    if not msg:continue   #當msg爲空時,數據不會發送出去,服務器也就收不到信息,會導致程序阻塞
    tcp_client.send(msg.encode('utf-8'))
    msg=tcp_client.recv(buffer_size)
    print('收到的信息爲',msg.decode('utf-8'))
tcp_client.close()         #關閉客戶端

遠程執行命令的TCP服務端

from socket import *
import subprocess
'''遠程執行命令'''
ip_port=('218.197.223.92',800)
back_log=5
buffer_size=1024
tcp_shell_serve=socket(AF_INET,SOCK_STREAM)
tcp_shell_serve.bind(ip_port)
tcp_shell_serve.listen(back_log)

while True:
    print('開始連接...')
    conn,addr=tcp_shell_serve.accept()
    print('連接到客戶端:%s'%addr[0])
    while True:
        try:
            cmd=conn.recv(buffer_size)
            if not cmd:break         #當客戶端正常斷開時傳入爲空
            res=subprocess.Popen(cmd.decode('utf-8'),shell=True, #運行命令提示符並執行指令 ,並把輸出,異常和輸入存在subprocess的管道(PIPE)中
                                 stdout=subprocess.PIPE,  #輸出
                                 stdin=subprocess.PIPE,   #輸入
                                 stderr=subprocess.PIPE)  #異常
            err=res.stderr.read()  #由管道讀出異常 
            if err:                #若存在異常則返回異常
                res_cmd=err
            else:
                res_cmd=res.stdout.read()   #否則返回輸出結果(輸出結果爲二進制,編碼方式由系統決定,Windowns爲gbk編碼)
            conn.send(res_cmd)               #把結果發送給客戶端
        except Exception:                   #當客戶端非正常斷開時會觸發異常
            print('客戶端%s斷開連接'%addr[0])
            break

tcp_shell_serve.close()



基於UDP套接字

udp是無鏈接的,先啓動哪一端都不會報錯(由於udp無連接,所以可以同時多個客戶端去跟服務端通信)

UDP服務端
from socket import *
ip_port=('218.197.223.92',800)
buffer_size=1024

udp_serve=socket(AF_INET,SOCK_DGRAM)#創建基於UDP的服務器套接字,SOCK_DGRAM爲數據報模式
udp_serve.bind(ip_port)             #綁定主機地址與端口
#因爲UDP是無連接的,所以不需要TCP中的監聽(listen)與鏈接(accpet)

#與客戶端持續通信
while True:
    data,addr=udp_serve.recvfrom(buffer_size)  #recvfrom()讀取客戶端發送的信息,返回信息與客戶端地址
    print('來着客戶端地址%s的信息:%s'%(addr,data.decode('utf-8')))
    msg=input('>>:').strip()                  #UDP空字符不會導致阻塞
    udp_serve.sendto(msg.encode('utf-8'),addr)#sendto()向客戶端發送信息,包括要發送的信息與客戶端地址

udp_serve.close()#關閉服務器
UDP客戶端
from socket import *
ip_port=('218.197.223.92',800)
buffer_size=1024
udp_client=socket(AF_INET,SOCK_DGRAM)

while True:
    msg=input('>>:').strip()
    udp_client.sendto(msg.encode('utf-8'),ip_port)
    data,addr=udp_client.recvfrom(buffer_size)
    print('來着服務端的消息:%s'%(data.decode('utf-8')))

udp_client.close()

模擬一個向服務器發送請求返回時間的UDP服務器

from socket import *
import time
ip_port=('218.197.223.92',800)
buffer_size=1024

udp_serve=socket(AF_INET,SOCK_DGRAM)#創建基於UDP的服務器套接字,SOCK_DGRAM爲數據報模式
udp_serve.bind(ip_port)             #綁定主機地址與端口
#因爲UDP是無連接的,所以不需要TCP中的監聽(listen)與鏈接(accpet)

#與客戶端持續通信
while True:
    data,addr=udp_serve.recvfrom(buffer_size)  #recvfrom()讀取客戶端發送的信息,返回信息與客戶端地址
    if not data:
        fmt='%Y-%m-%d %X'
    else:
        fmt=data.decode('utf-8')
    udp_serve.sendto(time.strftime(fmt).encode('utf-8'),addr)#sendto()向客戶端發送信息,包括要發送的信息與客戶端地址

udp_serve.close()#關閉服務器

鏈接合法驗證

服務端

from socket import *
import os,hmac
ip_port=('10.11.165.103',800)
secret_key=b'hello world'     #鹽
def conn_auth(conn,secret_key):
    print('開始驗證鏈接的合法性')
    data=os.urandom(32)  #產生隨機的32位二進制
    conn.sendall(data)
    h=hmac.new(secret_key,data)  #進行哈希運算(加鹽)
    digest=h.digest()
    rec=conn.recv(len(digest))
    res=hmac.compare_digest(digest,rec)
    if res:
        conn.send('1'.encode('utf-8'))
    else:
        conn.send('0'.encode('utf-8'))
    return res

def requst_handler(conn,buffer_size=1024):
    if not conn_auth(conn,secret_key):
        print('該鏈接不合法,關閉鏈接')
        conn.close()
        return
    print('鏈接驗證成功,開始通信')
    while True:
        data=conn.recv(buffer_size)
        print('收到的消息爲:',data.decode('utf-8'))
        conn.send(data.upper())

def serve_handler(ip_port,back_log=5):
    s=socket(AF_INET,SOCK_STREAM)
    s.bind(ip_port)
    s.listen(back_log)
    conn,addr=s.accept()
    return requst_handler(conn)

if __name__=='__main__':
    serve_handler(ip_port)
客戶端
from socket import *
import hmac
ip_port=('10.11.165.103',800)
secret_key=b'hello world'

def auth_conn(conn):
    print('開始驗證客戶端到服務端的鏈接')
    data=conn.recv(32)
    h=hmac.new(secret_key,data)
    digest=h.digest()
    conn.sendall(digest)
    res=conn.recv(32)
    auth=bool(int(res.decode('utf-8')))
    return auth

def data_handler(ip_port,buffer_size=1024):
    s=socket(AF_INET,SOCK_STREAM)
    s.connect(ip_port)
    if not auth_conn(s):
        print('驗證失敗')
        return
    print('客戶端鏈接到服務端成功')
    while True:
        msg=input('>>:').strip()
        if not msg:continue
        s.send(msg.encode('utf-8'))
        data=s.recv(buffer_size)
        print('收到的消息是',data.decode('utf-8'))

if __name__=='__main__':
    data_handler(ip_port)



發佈了51 篇原創文章 · 獲贊 2 · 訪問量 1萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章