<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
先從伺服器端說起。伺服器端先初始化Socket,然後與埠繫結(bind),對埠進行監聽(listen),呼叫accept阻塞,等待使用者端連線。在這時如果有個使用者端初始化一個Socket,然後連線伺服器(connect),如果連線成功,這時使用者端與伺服器端的連線就建立了。使用者端傳送資料請求,伺服器端接收請求並處理請求,然後把迴應資料傳送給使用者端,使用者端讀取資料,最後關閉連線,一次互動結束,使用以下Python程式碼實現:
import socket # socket_family 可以是 AF_UNIX 或 AF_INET。socket_type 可以是 SOCK_STREAM 或 SOCK_DGRAM。protocol 一般不填,預設值為 0 socket.socket(socket_family, socket_type, protocal=0) # 獲取tcp/ip通訊端 tcpSock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # 獲取udp/ip通訊端 udpSock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
可以通過netstat -an | findstr 8080
檢視通訊端狀態
import socket # 1、買手機 phone = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # tcp稱為流式協定,udp稱為資料包協定SOCK_DGRAM # print(phone) # 2、插入/繫結手機卡 # phone.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1) phone.bind(('127.0.0.1', 8080)) # 3、開機 phone.listen(5) # 半連線池,限制的是請求數 # 4、等待電話連線 print('start....') while True: # 連線迴圈 conn, client_addr = phone.accept() # (三次握手建立的雙向連線,(使用者端的ip,埠)) # print(conn) print('已經有一個連線建立成功', client_addr) # 5、通訊:收發訊息 while True: # 通訊迴圈 try: print('伺服器端正在收資料...') data = conn.recv(1024) # 最大接收的位元組數,沒有資料會在原地一直等待收,即傳送者傳送的資料量必須>0bytes # print('===>') if len(data) == 0: break # 在使用者端單方面斷開連線,伺服器端才會出現收空資料的情況 print('來自使用者端的資料', data) conn.send(data.upper()) except ConnectionResetError: break # 6、掛掉電話連線 conn.close() # 7、關機 phone.close() # start.... # 已經有一個連線建立成功 ('127.0.0.1', 4065) # 伺服器端正在收資料... # 來自使用者端的資料 b'xad' # 伺服器端正在收資料...
import socket # 1、買手機 phone = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # print(phone) # 2、撥電話 phone.connect(('127.0.0.1', 8080)) # 指定伺服器端ip和埠 # 3、通訊:發收訊息 while True: # 通訊迴圈 msg = input('>>: ').strip() # msg='' if len(msg) == 0: continue phone.send(msg.encode('utf-8')) # print('has send----->') data = phone.recv(1024) # print('has recv----->') print(data) # 4、關閉 phone.close() # >>: 啊 # b'a' # >>: 啊啊 # b'xb0xa1xb0xa1' # >>:
這個是由於你的伺服器端仍然存在四次揮手的time_wait狀態在佔用地址(如果不懂,請深入研究1.tcp三次握手,四次揮手 2.syn洪水攻擊 3.伺服器高並行情況下會有大量的time_wait狀態的優化方法)
# phone=socket(AF_INET,SOCK_STREAM) phone.setsockopt(SOL_SOCKET,SO_REUSEADDR,1) #就是它,在bind前加 phone.bind(('127.0.0.1',8080))
發現系統存在大量TIME_WAIT狀態的連線,通過調整linux核心引數解決, vi /etc/sysctl.conf 編輯檔案,加入以下內容: net.ipv4.tcp_syncookies = 1 net.ipv4.tcp_tw_reuse = 1 net.ipv4.tcp_tw_recycle = 1 net.ipv4.tcp_fin_timeout = 30 然後執行 /sbin/sysctl -p 讓引數生效。 net.ipv4.tcp_syncookies = 1 表示開啟SYN Cookies。當出現SYN等待佇列溢位時,啟用cookies來處理,可防範少量SYN攻擊,預設為0,表示關閉; net.ipv4.tcp_tw_reuse = 1 表示開啟重用。允許將TIME-WAIT sockets重新用於新的TCP連線,預設為0,表示關閉; net.ipv4.tcp_tw_recycle = 1 表示開啟TCP連線中TIME-WAIT sockets的快速回收,預設為0,表示關閉。 net.ipv4.tcp_fin_timeout 修改系統預設的 TIMEOUT 時間
伺服器端通過subprocess執行該命令,然後返回命令的結果。
伺服器端:
from socket import * import subprocess server = socket(AF_INET, SOCK_STREAM) server.bind(('127.0.0.1', 8000)) server.listen(5) print('start...') while True: conn, client_addr = server.accept() while True: print('from client:', client_addr) cmd = conn.recv(1024) if len(cmd) == 0: break print('cmd:', cmd) obj = subprocess.Popen(cmd.decode('utf8'), # 輸入的cmd命令 shell=True, # 通過shell執行 stderr=subprocess.PIPE, # 把錯誤輸出放入管道,以便列印 stdout=subprocess.PIPE) # 把正確輸出放入管道,以便列印 stdout = obj.stdout.read() # 列印正確輸出 stderr = obj.stderr.read() # 列印錯誤輸出 conn.send(stdout) conn.send(stderr) conn.close() server.close()
使用者端
import socket client = socket.socket(socket.AF_INET, socket.SOCK_STREAM) client.connect(('127.0.0.1', 8000)) while True: data = input('please enter your data') client.send(data.encode('utf8')) data = client.recv(1024) print('from server:', data) client.close()
輸入dir
命令,由於伺服器端傳送位元組少於1024位元組,使用者端可以接受。
輸入tasklist
命令,由於伺服器端傳送位元組多於1024位元組,使用者端只接受部分資料,並且當你再次輸入dir
命令的時候,使用者端會接收dir
命令的結果,但是會列印上一次的剩餘未傳送完的資料,這就是粘包問題。
傳送資料時間間隔很短,資料量很小,會合到一起,產生粘包。
伺服器端
# _*_coding:utf-8_*_ 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_*_ 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('world'.encode('utf-8')) # 伺服器端一起收到b'helloworld'
使用者端傳送了一段資料,伺服器端只收了一小部分,伺服器端下次再收的時候還是從緩衝區拿上次遺留的資料,產生粘包。
伺服器端
# _*_coding:utf-8_*_ 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_*_ 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'))
問題的根源在於,接收端不知道傳送端將要傳送的位元組流的長度,所以解決粘包的方法就是圍繞,如何讓傳送端在傳送資料前,把自己將要傳送的位元組流總大小讓接收端知曉,然後接收端來一個死迴圈接收完所有資料。
為何low:程式的執行速度遠快於網路傳輸速度,所以在傳送一段位元組前,先用send去傳送該位元組流長度,這種方式會放大網路延遲帶來的效能損耗。
伺服器端:
import socket, subprocess server = socket.socket(socket.AF_INET, socket.SOCK_STREAM) server.bind(('127.0.0.1', 8000)) server.listen(5) while True: conn, addr = server.accept() print('start...') while True: cmd = conn.recv(1024) print('cmd:', cmd) obj = subprocess.Popen(cmd.decode('utf8'), shell=True, stderr=subprocess.PIPE, stdout=subprocess.PIPE) stdout = obj.stdout.read() if stdout: ret = stdout else: stderr = obj.stderr.read() ret = stderr ret_len = len(ret) conn.send(str(ret_len).encode('utf8')) data = conn.recv(1024).decode('utf8') if data == 'recv_ready': conn.sendall(ret) conn.close() server.close()
使用者端:
import socket client = socket.socket(socket.AF_INET, socket.SOCK_STREAM) client.connect(('127.0.0.1', 8000)) while True: msg = input('please enter your cmd you want>>>').strip() if len(msg) == 0: continue client.send(msg.encode('utf8')) length = int(client.recv(1024)) client.send('recv_ready'.encode('utf8')) send_size = 0 recv_size = 0 data = b'' while recv_size < length: data = client.recv(1024) recv_size += len(data) print(data.decode('utf8'))
struct模組解析
import struct import json # 'i'是格式 try: obj = struct.pack('i', 1222222222223) except Exception as e: print(e) obj = struct.pack('i', 1222) print(obj, len(obj)) # 'i' format requires -2147483648 <= number <= 2147483647 # b'xc6x04x00x00' 4 res = struct.unpack('i', obj) print(res[0]) # 1222
解決粘包問題的核心就是:為位元組流加上自定義固定長度報頭,報頭中包含位元組流長度,然後一次send到對端,對端在接收時,先從快取中取出定長的報頭,然後再取真實資料。
1、 使用struct模組建立報頭:
import json import struct header_dic = { 'filename': 'a.txt', 'total_size':111111111111111111111111111111111222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222223131232, 'hash': 'asdf123123x123213x' } header_json = json.dumps(header_dic) header_bytes = header_json.encode('utf-8') print(len(header_bytes))# 223 # 'i'是格式 obj = struct.pack('i', len(header_bytes)) print(obj, len(obj)) # b'xdfx00x00x00' 4 res = struct.unpack('i', obj) print(res[0]) # 223
2、伺服器端:
from socket import * import subprocess import struct import json server = socket(AF_INET, SOCK_STREAM) server.bind(('127.0.0.1', 8000)) server.listen(5) print('start...') while True: conn, client_addr = server.accept() print(conn, client_addr) while True: cmd = conn.recv(1024) obj = subprocess.Popen(cmd.decode('utf8'), shell=True, stderr=subprocess.PIPE, stdout=subprocess.PIPE) stderr = obj.stderr.read() stdout = obj.stdout.read() # 製作報頭 header_dict = { 'filename': 'a.txt', 'total_size': len(stdout) + len(stderr), 'hash': 'xasf123213123' } header_json = json.dumps(header_dict) header_bytes = header_json.encode('utf8') # 1. 先把報頭的長度len(header_bytes)打包成4個bytes,然後傳送 conn.send(struct.pack('i', len(header_bytes))) # 2. 傳送報頭 conn.send(header_bytes) # 3. 傳送真實的資料 conn.send(stdout) conn.send(stderr) conn.close() server.close()
3、 使用者端:
from socket import * import json import struct client = socket(AF_INET, SOCK_STREAM) client.connect(('127.0.0.1', 8000)) while True: cmd = input('please enter your cmd you want>>>') if len(cmd) == 0: continue client.send(cmd.encode('utf8')) # 1. 先收4個位元組,這4個位元組中包含報頭的長度 header_len = struct.unpack('i', client.recv(4))[0] # 2. 再接收報頭 header_bytes = client.recv(header_len) # 3. 從包頭中解析出想要的東西 header_json = header_bytes.decode('utf8') header_dict = json.loads(header_json) total_size = header_dict['total_size'] # 4. 再收真實的資料 recv_size = 0 res = b'' while recv_size < total_size: data = client.recv(1024) res += data recv_size += len(data) print(res.decode('utf8')) client.close()
UDP是無連結的,先啟動哪一端都不會報錯,並且可以同時多個使用者端去跟伺服器端通訊
UDP協定是資料包協定,發空的時候也會自帶報頭,因此使用者端輸入空,伺服器端也能收到。
UPD協定一般不用於傳輸巨量資料。
UPD通訊端無粘包問題,但是不能替代TCP通訊端,因為UPD協定有一個缺陷:如果資料傳送的途中,資料丟失,則資料就丟失了,而TCP協定則不會有這種缺陷,因此一般UPD通訊端使用者無關緊要的資料傳送,例如qq聊天。
import socket server = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) # 資料包協定-》UDP server.bind(('127.0.0.1', 8080)) while True: data, client_addr = server.recvfrom(1024) print('===>', data, client_addr) server.sendto(data.upper(), client_addr) server.close()
import socket client = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) # 資料包協定-》UDP while True: msg = input('>>: ').strip() # msg='' client.sendto(msg.encode('utf-8'), ('127.0.0.1', 8080)) data, server_addr = client.recvfrom(1024) print(data) client.close()
基於tcp的通訊端,關鍵就是兩個迴圈,一個連結迴圈,一個通訊迴圈
socketserver模組中分兩大類:server類(解決連結問題)和request類(解決通訊問題)。
基於tcp的socketserver我們自己定義的類中的。
self.server即通訊端物件
self.client_address即使用者端地址
import socketserver class MyHandler(socketserver.BaseRequestHandler): def handle(self): # 通訊迴圈 while True: # print(self.client_address) # print(self.request) #self.request=conn try: data = self.request.recv(1024) if len(data) == 0: break self.request.send(data.upper()) except ConnectionResetError: break if __name__ == '__main__': s = socketserver.ThreadingTCPServer(('127.0.0.1', 8080), MyHandler, bind_and_activate=True) s.serve_forever() # 代表連線迴圈 # 迴圈建立連線,每建立一個連線就會啟動一個執行緒(服務員)+呼叫Myhanlder類產生一個物件,呼叫該物件下的handle方法,專門與剛剛建立好的連線做通訊迴圈
import socket phone = socket.socket(socket.AF_INET, socket.SOCK_STREAM) phone.connect(('127.0.0.1', 8080)) # 指定伺服器端ip和埠 while True: # msg=input('>>: ').strip() #msg='' msg = 'client33333' # msg='' if len(msg) == 0: continue phone.send(msg.encode('utf-8')) data = phone.recv(1024) print(data) phone.close()
基於udp的socketserver我們自己定義的類中的
import socketserver class MyHandler(socketserver.BaseRequestHandler): def handle(self): # 通訊迴圈 print(self.client_address) print(self.request) data = self.request[0] print('客戶訊息', data) self.request[1].sendto(data.upper(), self.client_address) if __name__ == '__main__': s = socketserver.ThreadingUDPServer(('127.0.0.1', 8080), MyHandler) s.serve_forever()
import socket client = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) # 資料包協定-》udp while True: # msg=input('>>: ').strip() #msg='' msg = 'client1111' client.sendto(msg.encode('utf-8'), ('127.0.0.1', 8080)) data, server_addr = client.recvfrom(1024) print(data) client.close()
以下列出了 Python 網路程式設計的一些重要模組:
協定 | 功能用處 | 埠號 | Python 模組 |
---|---|---|---|
HTTP | 網頁存取 | 80 | httplib, urllib, xmlrpclib |
NNTP | 閱讀和張貼新聞文章,俗稱為"貼文" | 119 | nntplib |
FTP | 檔案傳輸 | 20 | ftplib, urllib |
SMTP | 傳送郵件 | 25 | smtplib |
POP3 | 接收郵件 | 110 | poplib |
IMAP4 | 獲取郵件 | 143 | imaplib |
Telnet | 命令列 | 23 | telnetlib |
Gopher | 資訊查詢 | 70 | gopherlib, urllib |
到此這篇關於Python網路程式設計之socket與socketserver的文章就介紹到這了。希望對大家的學習有所幫助,也希望大家多多支援it145.com。
相關文章
<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
综合看Anker超能充系列的性价比很高,并且与不仅和iPhone12/苹果<em>Mac</em>Book很配,而且适合多设备充电需求的日常使用或差旅场景,不管是安卓还是Switch同样也能用得上它,希望这次分享能给准备购入充电器的小伙伴们有所
2021-06-01 09:31:42
除了L4WUDU与吴亦凡已经多次共事,成为了明面上的厂牌成员,吴亦凡还曾带领20XXCLUB全队参加2020年的一场音乐节,这也是20XXCLUB首次全员合照,王嗣尧Turbo、陈彦希Regi、<em>Mac</em> Ova Seas、林渝植等人全部出场。然而让
2021-06-01 09:31:34
目前应用IPFS的机构:1 谷歌<em>浏览器</em>支持IPFS分布式协议 2 万维网 (历史档案博物馆)数据库 3 火狐<em>浏览器</em>支持 IPFS分布式协议 4 EOS 等数字货币数据存储 5 美国国会图书馆,历史资料永久保存在 IPFS 6 加
2021-06-01 09:31:24
开拓者的车机是兼容苹果和<em>安卓</em>,虽然我不怎么用,但确实兼顾了我家人的很多需求:副驾的门板还配有解锁开关,有的时候老婆开车,下车的时候偶尔会忘记解锁,我在副驾驶可以自己开门:第二排设计很好,不仅配置了一个很大的
2021-06-01 09:30:48
不仅是<em>安卓</em>手机,苹果手机的降价力度也是前所未有了,iPhone12也“跳水价”了,发布价是6799元,如今已经跌至5308元,降价幅度超过1400元,最新定价确认了。iPhone12是苹果首款5G手机,同时也是全球首款5nm芯片的智能机,它
2021-06-01 09:30:45