socketserver和socket的補充(驗證客戶端合法性)

1、socket的補充
1、參數
socket.socket(family=AF_INET,type=SOCK_STREAM,proto=0,fileno=None)
參數說明:
family
地址系列應爲AF_INET(默認值ipv4),AF_INET6(ipv6),AF_UNIX,AF_CAN或AF_RDS。
(AF_UNIX 域其實是使用本地 socket 文件來通訊)
type
套接字類型應爲SOCK_STREAM(默認值,tcp協議),SOCK_DGRAM(udp協議),SOCK_RAW或其餘SOCK_常量之一。
SOCK_STREAM 是基於TCP的,有保障的(即能保證數據正確傳送到對方)面向鏈接的SOCKET,多用於資料傳送。 
SOCK_DGRAM 是基於UDP的,無保障的面向消息的socket,多用於在網絡上發廣播信息。
proto
協議號一般爲零,能夠省略,或者在地址族爲AF_CAN的狀況下,協議應爲CAN_RAW或CAN_BCM之一。
fileno
若是指定了fileno,則其餘參數將被忽略,致使帶有指定文件描述符的套接字返回。
與socket.fromfd()不一樣,fileno將返回相同的套接字,而不是重複的。
這可能有助於使用socket.close()關閉一個獨立的插座。



2、socket更多方法介紹
服務端套接字函數
s.bind()    綁定(主機,端口號)到套接字
s.listen() 開始TCP監聽
s.accept() 被動接受TCP客戶的鏈接,(阻塞式)等待鏈接的到來
客戶端套接字函數
s.connect()  主動初始化TCP服務器鏈接
s.connect_ex() connect()函數的擴展版本,出錯時返回出錯碼,而不是拋出異常
公共用途的套接字函數
s.recv()  接收TCP數據
s.recvfrom() 接收UDP數據
 
s.send()  發送TCP數據
s.sendall() 發送TCP數據
s.sendto()  發送UDP數據
 
s.getpeername() 鏈接到當前套接字的遠端的地址(client地址)
s.getsockname()  當前套接字的地址(server地址)
 
s.setsockopt() 設置指定套接字的參數(端口複用)
s.getsockopt()  返回指定套接字的參數
 
s.close() 關閉套接字
面向鎖的套接字方法
s.setblocking() 設置套接字的阻塞(True)與非阻塞模式(False)  *****
s.settimeout() 設置阻塞套接字操做的超時時間  accept()的等待時間
s.gettimeout()  獲得阻塞套接字操做的超時時間
   
面向文件的套接字的函數
s.fileno()   套接字的文件描述符
s.makefile() 建立一個與該套接字相關的文件
 
 
官方文檔對socket模塊下的socket.send()和socket.sendall()解釋以下:
socket.send(string[, flags])
Send data to the socket. The socket must be connected to a remote socket. The optional flags argument has the same meaning as for recv() above. 
Returns the number of bytes sent. Applications are responsible for checking that all data has been sent; if only some of the data was transmitted, 
the application needs to attempt delivery of the remaining data.
send()的返回值是發送的字節數量,這個數量值可能小於要發送的string的字節數,也就是說可能沒法發送string中全部的數據。若是有錯誤則會拋出異常。



socket.sendall(string[, flags])
Send data to the socket. The socket must be connected to a remote socket. The optional flags argument has the same meaning as for recv() above. 
Unlike send(), this method continues to send data from string until either all data has been sent or an error occurs. None is returned on success. 
On error, an exception is raised, and there is no way to determine how much data, if any, was successfully sent.
嘗試發送string的全部數據,成功則返回None,失敗則拋出異常。



故,下面兩段代碼是等價的:
sock.sendall('Hello world\n')

buffer = 'Hello world\n'
while buffer:
    bytes = sock.send(buffer)
    buffer = buffer[bytes:]
send和sendall
 
 

 

3、驗證客戶端合法性
場景:若是別人知道了你的服務器的IP,那麼他就能夠使用掃端口的方式去鏈接上你的服務器,由於咱們都知道,端口的範圍是0-65535,
那麼別人知道了你的服務器IP後,就能夠循環掃這些端口,就能夠鏈接上你的服務,你服務器所進行的一些操做,好比一些數據的傳輸,
就會被別人所獲取,因此這個時候就須要驗證客戶端的合法性。


代碼 服務端:
import os import hmac import socket def auth(conn): msg = os.urandom(32) # 生成一個32位的隨機的字節碼(urandom生成的字節碼就是bytes類型的) conn.send(msg) # 把這個隨機的字節碼發送到client端 # hmac接收兩個參數,第一個參數至關於hashlib的鹽,第二個參數是咱們隨機生成的字節碼,兩個參數都是bytes類型 result = hmac.new(secret_key,msg) # 處理這個隨機字節碼,socket_key是鹽 res = result.hexdigest() # 獲得結果(字符串) client_digest = conn.recv(1024) # 接收client端處理的結果 if res == client_digest.decode('utf-8'): print('合法的鏈接') # 對比成功能夠繼續通訊 return True else: print('不合法鏈接') # 不成功 return False secret_key = b'xiaoming' # hmac的鹽 sk = socket.socket() sk.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1) sk.bind(('127.0.0.1',8080)) sk.listen() conn,addr = sk.accept() if auth(conn): msg = conn.recv(1024) # True正常的和client端進行溝通 print(msg.decode('utf-8')) conn.close() else: conn.close() # False 直接關閉和這個客戶端的鏈接 sk.close() 客戶端: import hmac import socket def auth(sk): msg = sk.recv(32) # 接收服務端傳來的隨機字節碼 result = hmac.new(secret_key,msg) # 處理接收到的隨機字節碼 res = result.hexdigest() # 獲得結果 sk.send(res.encode('utf-8')) # 把結果發回給服務端,讓服務端進行驗證 secret_key = b'xiaoming' # 由於鹽是程序員本身設置的,那麼程序員寫的客戶端確定知道本身的鹽是什麼 sk = socket.socket() sk.connect(('127.0.0.1',8080)) auth(sk) sk.send(b'connect success') # 進行其餘正常的和server端的溝通 sk.close() 2、socketserver 正常服務端的socket,每一次只能鏈接一個客戶端,只有跟當前客戶端斷開鏈接後才能和下一個客戶端鏈接, 而用socketserver能夠跟多個客戶端同時鏈接(併發)。 服務端: import socketserver # tcp協議的server端就不須要導入socket class Myserver(socketserver.BaseRequestHandler): # 繼承socketserver.BaseRequestHandler這個類 def handle(self): # 必須繼承handle方法並重寫 conn = self.request # self.request就是客戶端的對象 while True: # 和客戶端進行交互 conn.send(b'helloworld') print(conn.recv(1024).decode('utf-8')) # 設置allow_reuse_address容許服務器重用地址 socketserver.TCPServer.allow_reuse_address = True # 建立一個對象server,綁定ip和端口,至關於sk = socket.socket() sk.bind(('127.0.0.1',8888))這兩步的結合 server = socketserver.ThreadingTCPServer(('127.0.0.1',8888),Myserver) # 讓server一直運行下去,除非強制中止程序 server.serve_forever() 客戶端: import socket sk = socket.socket() sk.connect(('127.0.0.1',8888)) while True: ret = sk.recv(1024) print(ret.decode('utf-8')) sk.send(b'hiworld') sk.close() 解釋爲何必定要重寫handler方法: Myserver這個類沒有__init__方法,那麼它就會去繼承使用父類BaseRequestHandler的__init__方法 看看BaseRequestHandler源碼: class BaseRequestHandler: def __init__(self, request, client_address, server): self.request = request # 獲取客戶端的鏈接(對象),設置爲本身的屬性 self.client_address = client_address # 客戶端的地址 self.server = server self.setup() try: self.handle() # 初識化對象的時候執行handler方法 finally: self.finish() def setup(self): pass def handle(self): pass def finish(self): pass 總結: 也就是說,子類繼承了父類的__init__方法,這個方法裏面已經取到了客戶端的對象conn,和地址addr, 而且初始化的時候調用了handler方法,可是父類的handler方法並無實現任何功能,因此子類應該重寫handler方法便於與客戶端交互。 實例:上傳文件 server.py import json import struct import socketserver import operate_handler class MyFTP(socketserver.BaseRequestHandler): def handle(self): conn = self.request length = conn.recv(4) length = struct.unpack('i',length)[0] operate = (conn.recv(length)).decode('utf-8') operate_dic = json.loads(operate) opt = operate_dic['operate'] usr = operate_dic['user'] print(opt,usr) getattr(operate_handler,opt)(conn,usr) socketserver.TCPServer.allow_reuse_address = True server = socketserver.ThreadingTCPServer(('127.0.0.1',9000),MyFTP) server.serve_forever() operate_handler.py import os import json import struct base_path = r'E:\PythonProject\ftp\server\root' def upload(conn,usr): fileinfo_len = conn.recv(4) fileinfo_len = struct.unpack('i',fileinfo_len)[0] fileinfo = (conn.recv(fileinfo_len)).decode('utf-8') fileinfo = json.loads(fileinfo) file_path = os.path.join(base_path,usr,fileinfo['filename']) file_path = os.path.abspath(file_path) with open(file_path,'wb') as f: while fileinfo['filesize']: content = conn.recv(20480) fileinfo['filesize'] -= len(content) f.write(content) print('接收完畢') client.py import os import json import struct import socket # 發送信息 def my_send(sk,operate_info): b_optinfo = (json.dumps(operate_info)).encode('utf-8') num = struct.pack('i',len(b_optinfo)) sk.send(num) sk.send(b_optinfo) sk = socket.socket() sk.connect(('127.0.0.1',9000)) # [登陸,註冊,退出] # 要進行的操做 operate_info = {'operate':'upload','user':'xiaoming'} my_send(sk,operate_info) # 選擇一個文發送到server端 file_path = r'F:\電影\電影\荒野生存.mp4' # 發送文件信息 file_name = os.path.basename(file_path) file_size = os.path.getsize(file_path) file_info = {'filename':file_name,'filesize':file_size} my_send(sk,file_info) # server端接收寫入 with open(file_path,'rb') as f: while file_size: content = f.read(20480) file_size -= len(content) sk.send(content) print('上傳完畢') sk.close()
相關文章
相關標籤/搜索