Python之路--Python基礎9--Socket編程

1、socket介紹

  Socket是應用層與TCP/IP協議族通訊的中間軟件抽象層,它是一組接口。在設計模式中,Socket其實就是一個門面模式,它把複雜的TCP/IP協議族隱藏在Socket接口後面,對用戶來講,一組簡單的接口就是所有,讓Socket去組織數據,以符合指定的協議。因此,咱們無需深刻理解tcp/udp協議,socket已經爲咱們封裝好了,咱們只須要遵循socket的規定去編程,寫出的程序天然就是遵循tcp/udp標準的。linux

 

2、套接字工做流程

   一個生活中的場景。你要打電話給一個朋友,先撥號,朋友聽到電話鈴聲後提起電話,這時你和你的朋友就創建起了鏈接,就能夠講話了。等交流結束,掛斷電話結束這次交談。 生活中的場景就解釋了這工做原理。算法

 

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

 

3、Socket模塊函數的用法

服務端套接字函數:編程

  s.bind()綁定(主機,端口號)到套接字
  s.listen()開始TCP監聽
  s.accept()被動接受TCP客戶的鏈接,(阻塞式)等待鏈接的到來

客戶端套接字函數
  s.connect()主動初始化TCP服務器鏈接
  s.connect_ex()connect()函數的擴展版本,出錯時返回出錯碼,而不是拋出異常

公共用途的套接字函數
  s.recv() 接收TCP數據
  s.send() 發送TCP數據(send在待發送數據量大於己端緩存區剩餘空間時,數據丟失,不會發完)
  s.sendall() 發送完整的TCP數據(本質就是循環調用send,sendall在待發送數據量大於己端緩存區剩餘空間時,數據不丟失,循環調用send直到發完)
  s.recvfrom() 接收UDP數據
  s.sendto() 發送UDP數據
  s.getpeername() 鏈接到當前套接字的遠端的地址
  s.getsockname() 當前套接字的地址
  s.getsockopt() 返回指定套接字的參數
  s.setsockopt() 設置指定套接字的參數
  s.close() 關閉套接字

面向鎖的套接字方法
  s.setblocking() 設置套接字的阻塞與非阻塞模式
  s.settimeout() 設置阻塞套接字操做的超時時間
  s.gettimeout() 獲得阻塞套接字操做的超時時間

面向文件的套接字的函數
  s.fileno() 套接字的文件描述符
  s.makefile() 建立一個與該套接字相關的文件json

 

4、基於TCP的套接字

tcp是基於連接的,必須先啓動服務端,而後再啓動客戶端去連接服務端。
windows

#TCP服務端
 ss = socket()  #建立服務器套接字
ss.bind()      #把地址綁定到套接字
ss.listen()    #監聽連接
inf_loop:      #服務器無限循環
    cs = ss.accept()   #接受客戶端連接
    comm_loop:         #通信循環
        cs.recv()/cs.send() #對話(接收與發送)
    cs.close()    #關閉客戶端套接字
ss.close()        #關閉服務器套接字(可選)
#TCP客戶端
 cs = socket()     # 建立客戶套接字
cs.connect()      # 嘗試鏈接服務器
comm_loop:        # 通信循環
    cs.send()/cs.recv()    # 對話(發送/接收)
cs.close()                 # 關閉客戶套接字

 

栗子:socket通訊流程與打電話流程相似,咱們就以打電話爲例來實現一個low版的套接字通訊:設計模式

#服務端

#_*_coding:utf-8_*_
__author__ = 'YL'

import socket
ip_port
=('127.0.0.1',9000) #電話卡 BUFSIZE=1024 #收發消息的尺寸
s=socket.socket(socket.AF_INET,socket.SOCK_STREAM) #買手機 s.bind(ip_port) #手機插卡 s.listen(5) #手機待機 conn,addr=s.accept() #手機接電話 # print(conn) # print(addr) print('接到來自%s的電話' %addr[0]) msg=conn.recv(BUFSIZE) #聽消息,聽話 print(msg,type(msg)) conn.send(msg.upper()) #發消息,說話 conn.close() #掛電話 s.close() #手機關機
#客戶端

#_*_coding:utf-8_*_
__author__ = 'YL'

import socket ip_port=('127.0.0.1',9000) BUFSIZE=1024 s=socket.socket(socket.AF_INET,socket.SOCK_STREAM) s.connect_ex(ip_port) #撥電話
 s.send('LHF nb'.encode('utf-8'))                #發消息,說話(只能發送字節類型)
 feedback=s.recv(BUFSIZE)                        #收消息,聽話
print(feedback.decode('utf-8')) s.close() #掛電話

 

加上連接循環與通訊循環(持續通話)緩存

#服務端
import socket phone = socket.socket(socket.AF_INET, socket.SOCK_STREAM)   #買手機
phone.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) #就是它,在bind前加
phone.bind(("127.0.0.1", 8080))                             #插入手機卡
 phone.listen(5)                                             #開機

while True:      #連接循環
    conn, addr = phone.accept()                             #接電話
    print("client :", addr) while True:  #通信循環
        try: data = conn.recv(1024)                          #收消息
            if not data: break             #針對linux,客戶端斷開連接的異常處理
            print("from client msg: %s" % data) conn.send(data.upper()) #發消息
        except Exception: break conn.close() #掛電話
phone.close()                                               #關機
#客戶端

import socket client = socket.socket(socket.AF_INET, socket.SOCK_STREAM) client.connect(("127.0.0.1", 8080)) #撥通電話

while True: msg = input(">>>: ") client.send(msg.encode("utf-8")) data = client.recv(1024) print(data) client.close()

問題:有的同窗在重啓服務端時可能會遇到如下錯誤服務器

解決:多線程

phone=socket(AF_INET,SOCK_STREAM) phone.setsockopt(SOL_SOCKET,SO_REUSEADDR,1) #就是它,在bind前加
phone.bind(('127.0.0.1',8080))

 

5、基於UDP的套接字

udp是無連接的,先啓動哪一端都不會報錯。

#UDP服務端
 ss = socket()   #建立一個服務器的套接字
ss.bind()       #綁定服務器套接字
inf_loop:       #服務器無限循環
    cs = ss.recvfrom()/ss.sendto() # 對話(接收與發送)
ss.close()                         # 關閉服務器套接字
#UDP客戶端
 cs = socket()   # 建立客戶套接字
comm_loop:      # 通信循環
    cs.sendto()/cs.recvfrom()   # 對話(發送/接收)
cs.close()                      # 關閉客戶套接字

 

栗子:udp套接字簡單示例

#UDP服務端
#
_*_coding:utf-8_*_
import socket ip_port=('127.0.0.1',9000) BUFSIZE=1024 udp_server_client=socket.socket(socket.AF_INET,socket.SOCK_DGRAM) udp_server_client.bind(ip_port) while True: msg,addr=udp_server_client.recvfrom(BUFSIZE) print(msg,addr) udp_server_client.sendto(msg.upper(),addr)
#UDP客戶端

#_*_coding:utf-8_*_
__author__ = 'YL'

import socket ip_port=('127.0.0.1',9000) BUFSIZE=1024 udp_server_client=socket.socket(socket.AF_INET,socket.SOCK_DGRAM) while True: msg=input('>>: ').strip() if not msg:continue udp_server_client.sendto(msg.encode('utf-8'),ip_port) back_msg,addr=udp_server_client.recvfrom(BUFSIZE) print(back_msg.decode('utf-8'),addr)

 

栗子2:qq聊天(因爲udp無鏈接,因此能夠同時多個客戶端去跟服務端通訊)

bug:消息都是服務端接收到,而且服務端進行回覆的,至關於服務端一我的與衆客戶端在聊天

#QQ服務端

#_*_coding:utf-8_*_
__author__ = 'YL'
import socket ip_port=('127.0.0.1',8081) udp_server_sock=socket.socket(socket.AF_INET,socket.SOCK_DGRAM) #買手機
udp_server_sock.bind(ip_port) while True: qq_msg,addr=udp_server_sock.recvfrom(1024) print('來自[%s:%s]的一條消息:\033[1;44m%s\033[0m' %(addr[0],addr[1],qq_msg.decode('utf-8'))) back_msg=input('回覆消息: ').strip() udp_server_sock.sendto(back_msg.encode('utf-8'),addr)
#QQ客戶端1,2,3

#_*_coding:utf-8_*_
__author__ = 'YL'
import socket BUFSIZE=1024 udp_client_socket=socket.socket(socket.AF_INET,socket.SOCK_DGRAM) qq_name_dic={ 'alex':('127.0.0.1',8081), '二':('127.0.0.1',8081), '一棵樹':('127.0.0.1',8081), '武大郎':('127.0.0.1',8081), } while True: qq_name=input('請選擇聊天對象: ').strip() while True: msg=input('請輸入消息,回車發送: ').strip() if msg == 'quit':break
        if not msg or not qq_name or qq_name not in qq_name_dic:continue udp_client_socket.sendto(msg.encode('utf-8'),qq_name_dic[qq_name]) back_msg,addr=udp_client_socket.recvfrom(BUFSIZE) print('來自[%s:%s]的一條消息:\033[1;44m%s\033[0m' %(addr[0],addr[1],back_msg.decode('utf-8'))) udp_client_socket.close()

 

栗子3:時間服務器

#ntp服務端

#_*_coding:utf-8_*_
__author__ = 'YL'

from socket import *
from time import strftime ip_port=('127.0.0.1',9000) bufsize=1024 tcp_server=socket(AF_INET,SOCK_DGRAM) tcp_server.bind(ip_port) while True: msg,addr=tcp_server.recvfrom(bufsize) print('===>',msg) if not msg: time_fmt='%Y-%m-%d %X'
    else: time_fmt=msg.decode('utf-8') back_msg=strftime(time_fmt) tcp_server.sendto(back_msg.encode('utf-8'),addr) tcp_server.close()
#ntp客戶端

#_*_coding:utf-8_*_
__author__ = 'YL'
from socket import * ip_port=('127.0.0.1',9000) bufsize=1024 tcp_client=socket(AF_INET,SOCK_DGRAM) while True: msg=input('請輸入時間格式(例%Y %m %d)>>: ').strip() tcp_client.sendto(msg.encode('utf-8'),ip_port) data=tcp_client.recv(bufsize) print(data.decode('utf-8')) tcp_client.close()

 輸出:

 

 

6、粘包

一、粘包是啥

  只有TCP有粘包現象,UDP永遠不會粘包

  發送端能夠是一K一K地發送數據,而接收端的應用程序能夠兩K兩K地提走數據,固然也有可能一次提走3K或6K數據,或者一次只提走幾個字節的數據,也就是說,應用程序所看到的數據是一個總體,或說是一個流(stream),一條消息有多少字節對應用程序是不可見的,所以TCP協議是面向流的協議,這也是容易出現粘包問題的緣由。

  而UDP是面向消息的協議,每一個UDP段都是一條消息,應用程序必須以消息爲單位提取數據,不能一次提取任意字節的數據,這一點和TCP是很不一樣的。怎樣定義消息呢?能夠認爲對方一次性write/send的數據爲一個消息,須要明白的是當對方send一條信息的時候,不管底層怎樣分段分片,TCP協議層會把構成整條消息的數據段排序完成後才呈如今內核緩衝區。

  例如基於tcp的套接字客戶端往服務端上傳文件,發送時文件內容是按照一段一段的字節流發送的,在接收方看了,根本不知道該文件的字節流從何處開始,在何處結束。

  所謂粘包問題主要仍是由於接收方不知道消息之間的界限,不知道一次性提取多少字節的數據所形成的。(玩過單片機串口通訊的都深有感觸)

  此外,發送方引發的粘包是由TCP協議自己形成的,TCP爲提升傳輸效率,發送方每每要收集到足夠多的數據後才發送一個TCP段。若連續幾回須要send的數據都不多,一般TCP會根據優化算法把這些數據合成一個TCP段後一次發送出去,這樣接收方就收到了粘包數據。

  

  TCP(transport control protocol,傳輸控制協議)是面向鏈接的,面向流的,提供高可靠性服務。收發兩端(客戶端和服務器端)都要有一一成對的socket,所以,發送端爲了將多個發往接收端的包,更有效的發到對方,使用了優化方法(Nagle算法),將屢次間隔較小且數據量小的數據,合併成一個大的數據塊,而後進行封包。這樣,接收端,就難於分辨出來了,必須提供科學的拆包機制。 即面向流的通訊是無消息保護邊界的。

  UDP(user datagram protocol,用戶數據報協議)是無鏈接的,面向消息的,提供高效率服務。不會使用塊的合併優化算法,, 因爲UDP支持的是一對多的模式,因此接收端的skbuff(套接字緩衝區)採用了鏈式結構來記錄每個到達的UDP包,在每一個UDP包中就有了消息頭(消息來源地址,端口等信息),這樣,對於接收端來講,就容易進行區分處理了。 即面向消息的通訊是有消息保護邊界的。

  tcp是基於數據流的,因而收發的消息不能爲空,這就須要在客戶端和服務端都添加空消息的處理機制,防止程序卡住,而udp是基於數據報的,即使是你輸入的是空內容(直接回車),那也不是空消息,udp協議會幫你封裝上消息頭。

 

  udp的recvfrom是阻塞的,一個recvfrom(x)必須對惟一一個sendinto(y),收完了x個字節的數據就算完成,如果y>x數據就丟失,這意味着udp根本不會粘包,可是會丟數據,不可靠。

  tcp的協議數據不會丟,沒有收完包,下次接收,會繼續上次繼續接收,己端老是在收到ack時纔會清除緩衝區內容。數據是可靠的,可是會粘包。

 

二、兩種狀況下會發生粘包

a、發送端須要等緩衝區滿才發送出去,形成粘包(發送數據時間間隔很短,數據塊很小,會合到一塊兒,產生粘包)

#服務端

#_*_coding:utf-8_*_
__author__ = 'YL'
from socket import * ip_port=('127.0.0.1',8080) tcp_socket_server=socket(AF_INET,SOCK_STREAM) tcp_socket_server.bind(ip_port) tcp_socket_server.listen(5) conn,addr=tcp_socket_server.accept() data1=conn.recv(10) data2=conn.recv(10) print('----->',data1.decode('utf-8')) print('----->',data2.decode('utf-8')) conn.close()
#客戶端

#_*_coding:utf-8_*_
__author__ = 'YL'
import socket BUFSIZE=1024 ip_port=('127.0.0.1',8080) s=socket.socket(socket.AF_INET,socket.SOCK_STREAM) res=s.connect_ex(ip_port) s.send('hello'.encode('utf-8')) s.send('feng'.encode('utf-8'))

輸出:

 

 

b、接收方不及時接收緩衝區的包,形成多個包接收(客戶端發送了一段數據,服務端只收了一小部分,服務端下次再收的時候仍是從緩衝區拿上次遺留的數據,產生粘包) 

#_*_coding:utf-8_*_
__author__ = 'YL'
from socket import * ip_port=('127.0.0.1',8080) tcp_socket_server=socket(AF_INET,SOCK_STREAM) tcp_socket_server.bind(ip_port) tcp_socket_server.listen(5) conn,addr=tcp_socket_server.accept() data1=conn.recv(2) #一次沒有收完整
data2=conn.recv(10)#下次收的時候,會先取舊的數據,而後取新的

print('----->',data1.decode('utf-8')) print('----->',data2.decode('utf-8')) conn.close()
#_*_coding:utf-8_*_
__author__ = 'YL'
import socket BUFSIZE=1024 ip_port=('127.0.0.1',8080) s=socket.socket(socket.AF_INET,socket.SOCK_STREAM) res=s.connect_ex(ip_port) s.send('hello feng'.encode('utf-8'))

輸出:

 

三、解決辦法

  爲字節流加上自定義固定長度報頭,報頭中包含字節流長度,而後一次send到對端,對端在接收時,先從緩存中取出定長的報頭,而後再取真實數據

  struct模塊:該模塊能夠把一個類型,如數字,轉成固定長度的bytes

  咱們能夠把報頭作成字典,字典裏包含將要發送的真實數據的詳細信息,而後json序列化,而後用struck將序列化後的數據長度打包成4個字節(4個本身足夠用了)

  發送時:

  先發報頭長度

  再編碼報頭內容而後發送

  最後發真實內容

 

  接收時:

  先收報頭長度,用struct取出來

  根據取出的長度收取報頭內容,而後解碼,反序列化

  從反序列化的結果中取出待取數據的詳細信息,而後去取真實的數據內容

#服務端

import socket import subprocess import struct import json phone = socket.socket(socket.AF_INET, socket.SOCK_STREAM)    # 買手機
phone.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)  # 就是它,在bind前加
phone.bind(("127.0.0.1", 8080))                              # 插入手機卡
 phone.listen(5)  # 開機

while True:      # 連接循環
    conn, addr = phone.accept()    # 接電話
    print("client :", addr) while True:                    # 通信循環
        try: cmd = conn.recv(1024)  # 收消息
            if not cmd: break              # 針對linux,客戶端斷開連接的異常處理
            print("from client msg: %s" % cmd) res = subprocess.Popen(cmd.decode("utf-8"), shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE) err = res.stderr.read() if err: back_msg = err else: back_msg = res.stdout.read() # 第一階段:製做報頭
            head_dic = { "data_size": len(back_msg) } head_json = json.dumps(head_dic) head_bytes = head_json.encode("utf-8") # 第二階段:發送包頭長度
            conn.send(struct.pack("i", len(head_bytes))) # 第三階段:發報頭
 conn.send(head_bytes) # 第四階段:發送真實數據
 conn.send(back_msg) except Exception: break conn.close() # 掛電話
phone.close()     # 關機
#客戶端

import socket import struct import json client = socket.socket(socket.AF_INET, socket.SOCK_STREAM) client.connect(("127.0.0.1", 8080))  # 撥通電話

while True: cmd = input(">>>: ").strip() if not cmd: continue client.send(cmd.encode("utf-8")) # 收報頭的長度
    head = client.recv(4) head_size = struct.unpack("i", head)[0] # 根據報頭長度接收報頭
    head_bytes = client.recv(head_size) head_json = head_bytes.decode("GBK") head_dic = json.loads(head_json) data_size = head_dic["data_size"]  # 取出真實數據長度

    # 接收真實的數據
    recv_size = 0 recv_bytes = b""
    while recv_size < data_size: res = client.recv(1024) recv_bytes += res recv_size += len(res) print(res.decode("GBK"))

注意注意注意:

res=subprocess.Popen(cmd.decode('utf-8'),
shell=True,
stderr=subprocess.PIPE,
stdout=subprocess.PIPE)

的結果的編碼是以當前所在的系統爲準的,若是是windows,那麼res.stdout.read()讀出的就是GBK編碼的,在接收端需要用GBK解碼

且只能從管道里讀一次結果

 

7、FTP上傳下載

#服務端

import socket import struct import json import subprocess import os class MYTCPServer: address_family = socket.AF_INET socket_type = socket.SOCK_STREAM allow_reuse_address = False max_packet_size = 8192 coding='utf-8' request_queue_size = 5 server_dir='file_upload'

    def __init__(self, server_address, bind_and_activate=True): """Constructor. May be extended, do not override.""" self.server_address=server_address self.socket = socket.socket(self.address_family, self.socket_type) if bind_and_activate: try: self.server_bind() self.server_activate() except: self.server_close() raise

    def server_bind(self): """Called by constructor to bind the socket. """
        if self.allow_reuse_address: self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) self.socket.bind(self.server_address) self.server_address = self.socket.getsockname() def server_activate(self): """Called by constructor to activate the server. """ self.socket.listen(self.request_queue_size) def server_close(self): """Called to clean-up the server. """ self.socket.close() def get_request(self): """Get the request and client address from the socket. """
        return self.socket.accept() def close_request(self, request): """Called to clean up an individual request.""" request.close() def run(self): while True: self.conn,self.client_addr=self.get_request() print('from client ',self.client_addr) while True: try: head_struct = self.conn.recv(4) if not head_struct:break head_len = struct.unpack('i', head_struct)[0] head_json = self.conn.recv(head_len).decode(self.coding) head_dic = json.loads(head_json) print(head_dic) #head_dic={'cmd':'put','filename':'a.txt','filesize':123123}
                    cmd=head_dic['cmd'] if hasattr(self,cmd): func=getattr(self,cmd) func(head_dic) except Exception: break

    def put(self,args): file_path=os.path.normpath(os.path.join( self.server_dir, args['filename'] )) filesize=args['filesize'] recv_size=0 print('----->',file_path) with open(file_path,'wb') as f: while recv_size < filesize: recv_data=self.conn.recv(self.max_packet_size) f.write(recv_data) recv_size+=len(recv_data) print('recvsize:%s filesize:%s' %(recv_size,filesize)) tcpserver1=MYTCPServer(('127.0.0.1',8080)) tcpserver1.run()
#客戶端

import
socket import struct import json import os class MYTCPClient: address_family = socket.AF_INET socket_type = socket.SOCK_STREAM allow_reuse_address = False max_packet_size = 8192 coding='utf-8' request_queue_size = 5 def __init__(self, server_address, connect=True): self.server_address=server_address self.socket = socket.socket(self.address_family, self.socket_type) if connect: try: self.client_connect() except: self.client_close() raise def client_connect(self): self.socket.connect(self.server_address) def client_close(self): self.socket.close() def run(self): while True: inp=input(">>: ").strip() if not inp:continue l=inp.split() cmd=l[0] if hasattr(self,cmd): func=getattr(self,cmd) func(l) def put(self,args): cmd=args[0] filename=args[1] if not os.path.isfile(filename): print('file:%s is not exists' %filename) return else: filesize=os.path.getsize(filename) head_dic={'cmd':cmd,'filename':os.path.basename(filename),'filesize':filesize} print(head_dic) head_json=json.dumps(head_dic) head_json_bytes=bytes(head_json,encoding=self.coding) head_struct=struct.pack('i',len(head_json_bytes)) self.socket.send(head_struct) self.socket.send(head_json_bytes) send_size=0 with open(filename,'rb') as f: for line in f: self.socket.send(line) send_size+=len(line) print(send_size) else: print('upload successful') client=MYTCPClient(('127.0.0.1',8080)) client.run()

 

8、認證客戶端的連接的合法性

  若是你想在分佈式系統中實現一個簡單的客戶端連接認證功能,又不像SSL那麼複雜,那麼利用hmac+加鹽的方式來實現

#服務端

#_*_coding:utf-8_*_
__author__ = 'YL'
from socket import *
import hmac,os secret_key=b'linhaifeng bang bang bang'
def conn_auth(conn): ''' 認證客戶端連接 :param conn: :return: '''
    print('開始驗證新連接的合法性') msg=os.urandom(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)
#合法客戶端

#_*_coding:utf-8_*_
__author__ = 'Linhaifeng'
from socket import *
import hmac,os secret_key=b'linhaifeng bang bang bang'
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)

服務端輸出:

 

#非法客戶端,不知道加密方式

#_*_coding:utf-8_*_
__author__ = 'YL'
from socket import *

def client_handler(ip_port,bufsize=1024): tcp_socket_client=socket(AF_INET,SOCK_STREAM) tcp_socket_client.connect(ip_port) 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)

服務端輸出:

不合法客戶端輸出:

 

#非法客戶端,不知道secret_key

#_*_coding:utf-8_*_
__author__ = 'YL'
from socket import *
import hmac,os secret_key=b'linhaifeng bang bang bang1111'
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)

服務端輸出:

不合法客戶端輸出:

 

9、socketserver實現併發

  SocketServer內部使用 IO多路複用 以及 「多線程」 和 「多進程」 ,從而實現併發處理多個客戶端請求的Socket服務端。即:每一個客戶端請求鏈接到服務器時,Socket服務端都會在服務器是建立一個「線程」或者「進程」 專門負責處理當前客戶端的全部請求。

栗子:

#服務端

import socketserver class FtpServer(socketserver.BaseRequestHandler): def handle(self): print(self.request)     #conn
        print(self.client_address) while True: data = self.request.recv(1024) self.request.send(data.upper()) if __name__ == "__main__": s = socketserver.ThreadingTCPServer(("127.0.0.1", 8080), FtpServer) s.serve_forever() #連接循環有了
#客戶端1/2/3

from socket import * client = socket(AF_INET, SOCK_STREAM) client.connect(("127.0.0.1", 8080)) while True: msg = input(">>>: ") client.send(msg.encode("utf-8")) data = client.recv(1024) print(data)
相關文章
相關標籤/搜索