1. 程式人生 > >python網絡編程--socketserver 和 ftp功能簡單說明

python網絡編程--socketserver 和 ftp功能簡單說明

時間服務器 內存溢出 code backlog clas 服務端 密碼 bsp compare

1. socketserver

我們之前寫的tcp協議的socket是不是一次只能和一個客戶端通信,如果用socketserver可以實現和多個客戶端通信。它是在socket的基礎上進行了一層封裝,也就是說底層還是調用的socket,在py2.7裏面叫做SocketServer也就是大寫了兩個S,在py3裏面就小寫了。後面我們要寫的FTP作業,需要用它來實現並發,也就是同時可以和多個客戶端進行通信,多個人可以同時進行上傳下載等。

我們舉一個使用socketserver的例子

import socketserver                              #1、引入模塊
class MyServer(socketserver.BaseRequestHandler): #2、自己寫一個類,類名自己隨便定義,然後繼承socketserver這個模塊裏面的BaseRequestHandler這個類

    def handle(self):                            #3、寫一個handle方法,必須叫這個名字
        #self.request                            #6、self.request 相當於一個conn

        self.request.recv(1024)                  #7、收消息
        msg = ‘這是發送的消息‘
        self.request.send(bytes(msg,encoding=‘utf-8‘)) #8、發消息

        self.request.close()                     #9、關閉連接

        # 拿到了我們對每個客戶端的管道,那麽我們自己在這個方法裏面的就寫我們接收消息發送消息的邏輯就可以了
        pass
if __name__ == ‘__mian__‘:
    #thread 線程,現在只需要簡單理解線程,別著急,後面很快就會講到啦,看下面的圖
    server = socketserver.ThreadingTCPServer((‘127.0.0.1‘,8090),MyServer)#4、使用socketserver的ThreadingTCPServer這個類,將IP和端口的元祖傳進去,還需要將上面咱們自己定義的類傳進去,得到一個對象,相當於我們通過它進行了bind、listen
    server.serve_forever()                       #5、使用我們上面這個類的對象來執行serve_forever()方法,他的作用就是說,我的服務一直開啟著,就像京東一樣,不能關閉網站,對吧,並且serve_forever()幫我們進行了accept


#註意:
#有socketserver 那麽有socketclient的嗎?
#當然不會有,我要作為客戶去訪問京東的時候,京東幫我也客戶端了嗎,客戶端是不是在我們自己的電腦啊,並且socketserver對客戶端沒有太高的要求,只需要自己寫一些socket就行了。
 

來看下完整的客戶端與服務端代碼

服務端代碼:

import socketserver

class Myserver(socketserver.BaseRequestHandler):

    def handle(self):
        while 1:
            from_client_msg = self.request.recv(1024)  # self.request = conn
            print(from_client_msg.decode(‘utf-8‘))
            msg = input(‘服務端說:‘)
            self.request.send(msg.encode(‘utf-8‘))


if __name__ == ‘__main__‘:

    ip_port = (‘127.0.0.1‘,8001)
    
    # 設置allow_reuse_address允許服務器重用地址
    socketserver.TCPServer.allow_reuse_address = True

    #server = socketserver.TCPServer((HOST, PORT),Myserver)  
    server = socketserver.ThreadingTCPServer(ip_port,Myserver)
    
     # 讓server永遠運行下去,除非強制停止程序
    server.serve_forever() 

客戶端:

import socket
client = socket.socket()
client.connect((‘127.0.0.1‘,8001))

while 1:
    msg = input(‘客戶端說>>>‘)
    client.send(msg.encode(‘utf-8‘))
    from_server_msg = client.recv(1024)
    print(from_server_msg.decode(‘utf-8‘))

  

2.驗證客戶端的鏈接合法性(加密)

  首先,我們來探討一下,什麽叫驗證合法性, 舉個例子:有一天,我開了一個socket服務端,只想讓咱們這個班的同學使用,但是有一天,隔壁班的同學過來問了一下我開的這個服務端的ip和端口,然後他是不是就可以去連接我了啊,那怎麽辦,我是不是不想讓他連接我啊,我需要驗證一下你的身份,這就是驗證連接的合法性,再舉個例子,就像我們上面說的你的windows系統是不是連接微軟的時間服務器來獲取時間的啊,你的mac能到人家微軟去獲取時間嗎,你願意,人家微軟還不願意呢,對吧,那這時候,你每次連接我來獲取時間的時候,我是不是就要驗證你的身份啊,也就是你要帶著你的系統信息,我要判斷你是不是我微軟的windows,對吧,如果是mac,我是不是不讓你連啊,這就是連接合法性。如果驗證你的連接是合法的,那麽如果我還要對你的身份進行驗證的需求,也就是要驗證用戶名和密碼,那麽我們還需要進行身份認證。連接認證>>身份認證>>ok你可以玩了。

就用到兩個方法

1. os.urandom(n)

  其中os.urandom(n) 是一種bytes類型的隨機生成n個字節字符串的方法,而且每次生成的值都不相同。再加上md5等加密的處理,就能夠成內容不同長度相同的字符串了。

官方解釋為:

os.urandom(n)函數在python官方文檔中做出了這樣的解釋函數定位:

  Return a string of n random bytes suitable for cryptographic use. 意思就是,返回一個有n個byte那麽長的一個string,然後很適合用於加密。然後這個函數,在文檔中,被歸結於os這個庫的Miscellaneous Functions,意思是不同種類的函數(也可以說是混種函數)

  原因是: This function returns random bytes from an OS-specific randomness source. (函數返回的隨機字節是根據不同的操作系統特定的隨機函數資源。即,這個函數是調用OS內部自帶的隨機函數的。有特異性)

2. hamc

Python自帶的hmac模塊實現了標準的Hmac算法,我們首先需要準備待計算的原始消息message,隨機key,哈希算法,這裏采用MD5,使用hmac的代碼如下:

import hmac
message = b‘Hello world‘
key = b‘secret‘
h = hmac.new(key,message,digestmod=‘MD5‘)
print(h.hexdigest())
比較兩個密文是否相同,可以用hmac.compare_digest(密文、密文),然會True或者False。 

可見使用hmac和普通hash算法非常類似。hmac輸出的長度和原始哈希算法的長度一致。需要註意傳入的key和message都是bytes類型,str類型需要首先編碼為bytes

def hmac_md5(key, s):
    return hmac.new(key.encode(‘utf-8‘), s.encode(‘utf-8‘), ‘MD5‘).hexdigest()

class User(object):
    def __init__(self, username, password):
        self.username = username
        self.key = ‘‘.join([chr(random.randint(48, 122)) for i in range(20)])
        self.password = hmac_md5(self.key, password)

  

如果你想在分布式系統中實現一個簡單的客戶端鏈接認證功能,又不像SSL那麽復雜,那麽利用hmac+加鹽的方式來實現,看代碼

sendall()與send()沒有什麽區別 ,可視為send()

server端

from socket import *
import hmac,os

secret_key=b‘Jedan has a big key!‘
def conn_auth(conn):
    ‘‘‘
    認證客戶端鏈接
    :param conn:
    :return:
    ‘‘‘
    print(‘開始驗證新鏈接的合法性‘)
    msg=os.urandom(32)#生成一個32字節的隨機字符串
    conn.sendall(msg)
    h=hmac.new(secret_key,msg)
    digest=h.digest()
    respone=conn.recv(len(digest))
    return hmac.compare_digest(respone,digest)

def data_handler(conn,bufsize=1024):
    if not conn_auth(conn):
        print(‘該鏈接不合法,關閉‘)
        conn.close()
        return
    print(‘鏈接合法,開始通信‘)
    while True:
        data=conn.recv(bufsize)
        if not data:break
        conn.sendall(data.upper())

def server_handler(ip_port,bufsize,backlog=5):
    ‘‘‘
    只處理鏈接
    :param ip_port:
    :return:
    ‘‘‘
    tcp_socket_server=socket(AF_INET,SOCK_STREAM)
    tcp_socket_server.bind(ip_port)
    tcp_socket_server.listen(backlog)
    while True:
        conn,addr=tcp_socket_server.accept()
        print(‘新連接[%s:%s]‘ %(addr[0],addr[1]))
        data_handler(conn,bufsize)

if __name__ == ‘__main__‘:
    ip_port=(‘127.0.0.1‘,9999)
    bufsize=1024
    server_handler(ip_port,bufsize)

client端

from socket import *
import hmac,os

secret_key=b‘Jedan has a big key!‘
def conn_auth(conn):
    ‘‘‘
    驗證客戶端到服務器的鏈接
    :param conn:
    :return:
    ‘‘‘
    msg=conn.recv(32)
    h=hmac.new(secret_key,msg)
    digest=h.digest()
    conn.sendall(digest)

def client_handler(ip_port,bufsize=1024):
    tcp_socket_client=socket(AF_INET,SOCK_STREAM)
    tcp_socket_client.connect(ip_port)

    conn_auth(tcp_socket_client)

    while True:
        data=input(‘>>: ‘).strip()
        if not data:continue
        if data == ‘quit‘:break

        tcp_socket_client.sendall(data.encode(‘utf-8‘))
        respone=tcp_socket_client.recv(bufsize)
        print(respone.decode(‘utf-8‘))
    tcp_socket_client.close()

if __name__ == ‘__main__‘:
    ip_port=(‘127.0.0.1‘,9999)
    bufsize=1024
    client_handler(ip_port,bufsize)

 

3.實現ftp簡單上傳的功能

流程和思路:

 我們可以把報頭做成字典,字典裏包含將要發送的真實數據的描述信息(大小啊之類的),然後json序列化,然後用struck將序列化後的數據長度打包成4個字節。
我們在網絡上傳輸的所有數據 都叫做數據包,數據包裏的所有數據都叫做報文,報文裏面不止有你的數據,還有ip地址、mac地址、端口號等等,其實所有的報文都有報頭,這個報頭是協議規定的,看一下

發送時:
1.先發報頭長度
2.再編碼報頭內容然後發送,使用json,不要用eval,因為eval容易造成內存溢出
3.最後發真實內容

接收時:
1.先手報頭長度,用struct取出來
2.根據取出的長度收取報頭內容,然後解碼,反序列化
3.從反序列化的結果中取出待取數據的描述信息,然後去取真實的數據內容

服務端server

import json
import socket
import struct

server = socket.socket()
server.bind((‘127.0.0.1‘,8001))
server.listen()
conn,addr = server.accept()

#首先接收文件的描述信息的長度
struct_data_len = conn.recv(4)
data_len = struct.unpack(‘i‘,struct_data_len)[0]

# 通過文件信息的長度將文件的描述信息全部接收
print(‘data_len>>>‘,data_len)
file_info_bytes = conn.recv(data_len)
#將文件描述信息轉換為字典類型,以便操作
file_info_json = file_info_bytes.decode(‘utf-8‘)
file_info_dict = json.loads(file_info_json) #{‘file_name‘: ‘aaa.mp4‘, ‘file_size‘: 24409470}

print(file_info_dict)

#統計每次接收的累計長度
recv_sum = 0

#根據文件描述信息,指定文件路徑和文件名稱
file_path = ‘D:\s18\jj‘ + ‘\\‘ + file_info_dict[‘file_name‘]

#接收文件的真實數據
with open(file_path,‘wb‘) as f:
    #循環接收,循環結束的依據是文件描述信息中文件的大小,也是通過一個初始值為0的變量來統計
    while recv_sum < file_info_dict[‘file_size‘]:
        every_recv_data = conn.recv(1024)
        recv_sum += len(every_recv_data)
        f.write(every_recv_data)

客戶端client:

import os
import socket
import json
import struct
client = socket.socket()
client.connect((‘127.0.0.1‘,8001))

#統計文件大小
file_size = os.path.getsize(r‘D:\python_workspace_s18\day029\aaa.mp4‘)


#統計文件描述信息,給服務端,服務端按照我的文件描述信息來保存文件,命名文件等等,現在放到一個字典裏面了
file_info = {
    ‘file_name‘:‘aaa.mp4‘,
    ‘file_size‘:file_size,
}

#由於字典無法直接轉換成bytes類型的數據,所以需要json來將字典轉換為json字符串.在把字符串轉換為字節類型的數據進行發送
#json.dumps是將字典轉換為json字符串的方法
file_info_json = json.dumps(file_info)

#將字符串轉換成bytes類型的數據
file_info_byte = file_info_json.encode(‘utf-8‘)

#為了防止黏包現象,將文件描述信息的長度打包後和文件的描述信息的數據一起發送過去
data_len = len(file_info_byte)
data_len_struct = struct.pack(‘i‘,data_len)

#發送文件描述信息
client.send(data_len_struct + file_info_byte)

#定義一個變量,=0,作為每次讀取文件的長度的累計值
sum = 0
#打開的aaa.mp4文件,rb的形式,
with open(‘aaa.mp4‘,‘rb‘) as f:
    #循環讀取文件內容
    while sum < file_size:
        #每次讀取的文件內容,每次讀取1024B大小的數據
        every_read_data = f.read(1024)
        #將sum累加,統計長度
        sum += len(every_read_data)
        #將每次讀取的文件的真實數據返送給服務端
        client.send(every_read_data)

python網絡編程--socketserver 和 ftp功能簡單說明