1. 程式人生 > >socketserver和socket的補充(驗證客戶端合法性)

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

hello ram cli digest hand 連接 close his imp

技術分享圖片
一、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 = bxiaoming # 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 = bxiaoming # 因為鹽是程序員自己設置的,那麽程序員寫的客戶端肯定知道自己的鹽是什麽 sk = socket.socket() sk.connect((127.0.0.1,8080)) auth(sk) sk.send(bconnect success) # 進行其他正常的和server端的溝通 sk.close() 二、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(bhelloworld) 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(bhiworld) 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 = rE:\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 = rF:\電影\電影\荒野生存.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()
技術分享圖片

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