客户端/服务器架构

  • Post author:
  • Post category:其他


基于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)







版权声明:本文为Henry_Lv原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。