網(wǎng)絡編程
一.楔子
你現(xiàn)在已經學會了寫python代碼睡互,假如你寫了兩個python文件a.py和b.py根竿,分別去運行,你就會發(fā)現(xiàn)就珠,這兩個python的文件分別運行的很好寇壳。但是如果這兩個程序之間想要傳遞一個數(shù)據(jù),你要怎么做呢妻怎?
這個問題以你現(xiàn)在的知識就可以解決了壳炎,我們可以創(chuàng)建一個文件,把a.py想要傳遞的內容寫到文件中逼侦,然后b.py從這個文件中讀取內容就可以了匿辩。
但是當你的a.py和b.py分別在不同電腦上的時候,你要怎么辦呢榛丢?
類似的機制有計算機網(wǎng)盤铲球,qq等等。我們可以在我們的電腦上和別人聊天晰赞,可以在自己的電腦上向網(wǎng)盤中上傳稼病、下載內容。這些都是兩個程序在通信掖鱼。
二.軟件開發(fā)的架構
我們了解的涉及到兩個程序之間通訊的應用大致可以分為兩種:
第一種是應用類:qq然走、微信、網(wǎng)盤锨用、優(yōu)酷這一類是屬于需要安裝的桌面應用
第二種是web類:比如百度丰刊、知乎、博客園等使用瀏覽器訪問就可以直接使用的應用
這些應用的本質其實都是兩個程序之間的通訊增拥。而這兩個分類又對應了兩個軟件開發(fā)的架構~
1.C/S架構
C/S即:Client與Server 啄巧,中文意思:客戶端與服務器端架構,這種架構也是從用戶層面(也可以是物理層面)來劃分的掌栅。
這里的客戶端一般泛指客戶端應用程序EXE秩仆,程序需要先安裝后,才能運行在用戶的電腦上猾封,對用戶的電腦操作系統(tǒng)環(huán)境依賴較大澄耍。
2.B/S架構
B/S即:Browser與Server,中文意思:瀏覽器端與服務器端架構,這種架構是從用戶層面來劃分的。
Browser瀏覽器齐莲,其實也是一種Client客戶端痢站,只是這個客戶端不需要大家去安裝什么應用程序,只需在瀏覽器上通過HTTP請求服務器端相關的資源(網(wǎng)頁資源)选酗,客戶端Browser瀏覽器就能進行增刪改查阵难。
三.網(wǎng)絡基礎
1.一個程序如何在網(wǎng)絡上找到另一個程序?
首先芒填,程序必須要啟動呜叫,其次,必須有這臺機器的地址殿衰,我們都知道我們人的地址大概就是國家\省\市\(zhòng)區(qū)\街道\樓\門牌號這樣字朱庆。那么每一臺聯(lián)網(wǎng)的機器在網(wǎng)絡上也有自己的地址,它的地址是怎么表示的呢闷祥?
就是使用一串數(shù)字來表示的娱颊,例如:100.4.5.6
IP地址是指互聯(lián)網(wǎng)協(xié)議地址(英語:Internet Protocol Address,又譯為網(wǎng)際協(xié)議地址)凯砍,是IP Address的縮寫维蒙。IP地址是IP協(xié)議提供的一種統(tǒng)一的地址格式,它為互聯(lián)網(wǎng)上的每一個網(wǎng)絡和每一臺主機分配一個邏輯地址果覆,以此來屏蔽物理地址的差異。
IP地址是一個32位的二進制數(shù)殖熟,通常被分割為4個“8位二進制數(shù)”(也就是4個字節(jié))局待。IP地址通常用“點分十進制”表示成(a.b.c.d)的形式,其中菱属,a,b,c,d都是0~255之間的十進制整數(shù)钳榨。例:點分十進IP地址(100.4.5.6),實際上是32位二進制數(shù)(01100100.00000100.00000101.00000110)纽门。
"端口"是英文port的意譯薛耻,可以認為是設備與外界通訊交流的出口。
因此ip地址精確到具體的一臺電腦赏陵,而端口精確到具體的程序饼齿。
2.osi七層模型
引子
須知一個完整的計算機系統(tǒng)是由硬件、操作系統(tǒng)蝙搔、應用軟件三者組成,具備了這三個條件缕溉,一臺計算機系統(tǒng)就可以自己跟自己玩了(打個單機游戲,玩?zhèn)€掃雷啥的)
如果你要跟別人一起玩吃型,那你就需要上網(wǎng)了证鸥,什么是互聯(lián)網(wǎng)?
互聯(lián)網(wǎng)的核心就是由一堆協(xié)議組成,協(xié)議就是標準枉层,比如全世界人通信的標準是英語泉褐,如果把計算機比作人,互聯(lián)網(wǎng)協(xié)議就是計算機界的英語鸟蜡。所有的計算機都學會了互聯(lián)網(wǎng)協(xié)議膜赃,那所有的計算機都就可以按照統(tǒng)一的標準去收發(fā)信息從而完成通信了。
osi七層模型
人們按照分工不同把互聯(lián)網(wǎng)協(xié)議從邏輯上劃分了層級:
3.socket概念
socket層
理解socket
Socket是應用層與TCP/IP協(xié)議族通信的中間軟件抽象層矩欠,它是一組接口财剖。在設計模式中,Socket其實就是一個門面模式癌淮,它把復雜的TCP/IP協(xié)議族隱藏在Socket接口后面躺坟,對用戶來說,一組簡單的接口就是全部乳蓄,讓Socket去組織數(shù)據(jù)咪橙,以符合指定的協(xié)議。
其實站在你的角度上看虚倒,socket就是一個模塊美侦。我們通過調用模塊中已經實現(xiàn)的方法建立兩個進程之間的連接和通信。
也有人將socket說成ip+port魂奥,因為ip是用來標識互聯(lián)網(wǎng)中的一臺主機的位置菠剩,而port是用來標識這臺機器上的一個應用程序。
所以我們只要確立了ip和port就能找到一個應用程序耻煤,并且使用socket模塊來與之通信具壮。
3.套接字(socket)的發(fā)展史
套接字起源于 20 世紀 70 年代加利福尼亞大學伯克利分校版本的 Unix,即人們所說的 BSD Unix。 因此,有時人們也把套接字稱為“伯克利套接字”或“BSD 套接字”哈蝇。一開始,套接字被設計用在同 一臺主機上多個應用程序之間的通訊棺妓。這也被稱進程間通訊,或 IPC。套接字有兩種(或者稱為有兩個種族),分別是基于文件型的和基于網(wǎng)絡型的炮赦。?
基于文件類型的套接字家族
套接字家族的名字:AF_UNIX
unix一切皆文件怜跑,基于文件的套接字調用的就是底層的文件系統(tǒng)來取數(shù)據(jù),兩個套接字進程運行在同一機器吠勘,可以通過訪問同一個文件系統(tǒng)間接完成通信
基于網(wǎng)絡類型的套接字家族
套接字家族的名字:AF_INET
(還有AF_INET6被用于ipv6性芬,還有一些其他的地址家族,不過剧防,他們要么是只用于某個平臺批旺,要么就是已經被廢棄,或者是很少被使用诵姜,或者是根本沒有實現(xiàn)汽煮,所有地址家族中搏熄,AF_INET是使用最廣泛的一個聪全,python支持很多種地址家族敢订,但是由于我們只關心網(wǎng)絡編程,所以大部分時候我么只使用AF_INET)
4.tcp協(xié)議和udp協(xié)議
TCP(Transmission Control Protocol)可靠的供搀、面向連接的協(xié)議(eg:打電話)鞋囊、傳輸效率低全雙工通信(發(fā)送緩存&接收緩存)止后、面向字節(jié)流。使用TCP的應用:Web瀏覽器溜腐;電子郵件译株、文件傳輸程序。
UDP(User Datagram Protocol)不可靠的挺益、無連接的服務歉糜,傳輸效率高(發(fā)送前時延小)望众,一對一匪补、一對多、多對一烂翰、多對多夯缺、面向報文,盡最大努力服務甘耿,無擁塞控制踊兜。使用UDP的應用:域名系統(tǒng)?(DNS);視頻流佳恬;IP語音(VoIP)润文。
我知道說這些你們也不懂,直接上圖殿怜。
四.套接字(socket)初使用
基于TCP協(xié)議的socket
tcp是基于鏈接的,必須先啟動服務端曙砂,然后再啟動客戶端去鏈接服務端
server端
import socket
sk = socket.socket()
sk.bind(('127.0.0.1',8898))? #把地址綁定到套接字sk.listen()? ? ? ? ? #監(jiān)聽鏈接conn,addr = sk.accept() #接受客戶端鏈接ret = conn.recv(1024)? #接收客戶端信息print(ret)? ? ? #打印客戶端信息conn.send(b'hi')? ? ? ? #向客戶端發(fā)送信息conn.close()? ? ? #關閉客戶端套接字sk.close()? ? ? ? #關閉服務器套接字(可選)
client端
import socket
sk = socket.socket()? ? ? ? ? # 創(chuàng)建客戶套接字sk.connect(('127.0.0.1',8898))? ? # 嘗試連接服務器sk.send(b'hello!')
ret = sk.recv(1024)? ? ? ? # 對話(發(fā)送/接收)print(ret)
sk.close()? ? ? ? ? ? # 關閉客戶套接字
問題:有的同學在重啟服務端時可能會遇到
解決方法:
#加入一條socket配置头谜,重用ip和端口import socketfrom socket import SOL_SOCKET,SO_REUSEADDR
sk = socket.socket()sk.setsockopt(SOL_SOCKET,SO_REUSEADDR,1) #就是它,在bind前加sk.bind(('127.0.0.1',8898))? #把地址綁定到套接字sk.listen()? ? ? ? ? #監(jiān)聽鏈接conn,addr = sk.accept() #接受客戶端鏈接ret = conn.recv(1024)? #接收客戶端信息print(ret)? ? ? ? ? ? ? #打印客戶端信息conn.send(b'hi')? ? ? ? #向客戶端發(fā)送信息conn.close()? ? ? #關閉客戶端套接字sk.close()? ? ? ? #關閉服務器套接字(可選)
基于UDP協(xié)議的socket
udp是無鏈接的鸠澈,啟動服務之后可以直接接受消息柱告,不需要提前建立鏈接
簡單使用
server端
import socket
udp_sk = socket.socket(type=socket.SOCK_DGRAM)? #創(chuàng)建一個服務器的套接字udp_sk.bind(('127.0.0.1',9000))? ? ? ? #綁定服務器套接字msg,addr = udp_sk.recvfrom(1024)print(msg)
udp_sk.sendto(b'hi',addr)? ? ? ? ? ? ? ? # 對話(接收與發(fā)送)udp_sk.close()? ? ? ? ? ? ? ? ? ? ? ? # 關閉服務器套接字
client端
import socket
ip_port=('127.0.0.1',9000)
udp_sk=socket.socket(type=socket.SOCK_DGRAM)
udp_sk.sendto(b'hello',ip_port)
back_msg,addr=udp_sk.recvfrom(1024)print(back_msg.decode('utf-8'),addr)
socket參數(shù)的詳解
socket.socket(family=AF_INET,type=SOCK_STREAM,proto=0,fileno=None)
創(chuàng)建socket對象的參數(shù)說明:
family地址系列應為AF_INET(默認值),AF_INET6,AF_UNIX,AF_CAN或AF_RDS。
(AF_UNIX 域實際上是使用本地 socket 文件來通信)
type套接字類型應為SOCK_STREAM(默認值),SOCK_DGRAM,SOCK_RAW或其他SOCK_常量之一笑陈。
SOCK_STREAM?是基于TCP的际度,有保障的(即能保證數(shù)據(jù)正確傳送到對方)面向連接的SOCKET,多用于資料傳送涵妥。?
SOCK_DGRAM?是基于UDP的乖菱,無保障的面向消息的socket,多用于在網(wǎng)絡上發(fā)廣播信息。
proto協(xié)議號通常為零,可以省略,或者在地址族為AF_CAN的情況下,協(xié)議應為CAN_RAW或CAN_BCM之一窒所。
fileno如果指定了fileno,則其他參數(shù)將被忽略,導致帶有指定文件描述符的套接字返回鹉勒。
與socket.fromfd()不同,fileno將返回相同的套接字,而不是重復的。
這可能有助于使用socket.close()關閉一個獨立的插座吵取。
SO_REUSEADDR
當socket關閉后禽额,本地端用于該socket的端口號立刻就可以被重用。通常來說皮官,只有經過系統(tǒng)定義一段時間后脯倒,才能被重用。
布爾型整數(shù)
?qq聊天
#_*_coding:utf-8_*_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)
#_*_coding:utf-8_*_import socket
BUFSIZE=1024
udp_client_socket=socket.socket(socket.AF_INET,socket.SOCK_DGRAM)
qq_name_dic={
? ? '金老板':('127.0.0.1',8081),
? ? '哪吒':('127.0.0.1',8081),
? ? 'egg':('127.0.0.1',8081),
? ? 'yuan':('127.0.0.1',8081),
}while True:
? ? qq_name=input('請選擇聊天對象: ').strip()
? ? while True:
? ? ? ? msg=input('請輸入消息,回車發(fā)送,輸入q結束和他的聊天: ').strip()
? ? ? ? if msg == 'q':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()
時間服務器
S.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)?這里value設置為1捺氢,表示將SO_REUSEADDR標記為TRUE藻丢,操作系統(tǒng)會在服務器socket被關閉或服務器進程終止后馬上釋放該服務器的端口,否則操作系統(tǒng)會保留幾分鐘該端口讯沈。
# _*_coding:utf-8_*_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.setsockopt(SOL_SOCKET,SO_REUSEADDR,1)
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()
#_*_coding:utf-8_*_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)
五.黏包
黏包現(xiàn)象
subprocess.Popen 參數(shù)
stdin, stdout and stderr 分別代表子程序的標準輸入郁岩,標準輸出,標準錯誤輸出的文件句柄缺狠,
????????????有效值可以是一個存在的文件對象问慎, ? 或者一個文件描述符,或者?PIPE(一個正整數(shù))或者是None挤茄。
????????????若賦值為PIPE 如叼,就會為子程序創(chuàng)建新管道pipe , 若為None ,就不會出現(xiàn)從定向穷劈,
????????????子程序繼承父程序的文件句柄笼恰。另外,stderr 可以是STDOUT, 這表明子程序的錯誤數(shù)據(jù)可以被獲得
????????????并發(fā)送到stdout輸出.
讓我們基于tcp先制作一個遠程執(zhí)行命令的程序(命令ls -l ; lllllll ; pwd)
res=subprocess.Popen(cmd.decode('utf-8'),
shell=True,
stderr=subprocess.PIPE,
stdout=subprocess.PIPE)
的結果的編碼是以當前所在的系統(tǒng)為準的歇终,如果是windows社证,那么res.stdout.read()讀出的就是GBK編碼的,在接收端需要用GBK解碼
且只能從管道里讀一次結果
同時執(zhí)行多條命令之后评凝,得到的結果很可能只有一部分追葡,在執(zhí)行其他命令的時候又接收到之前執(zhí)行的另外一部分結果,這種顯現(xiàn)就是黏包奕短。
基于tcp協(xié)議實現(xiàn)的黏包
#_*_coding:utf-8_*_from socket import *import subprocess
ip_port=('127.0.0.1',8888)
BUFSIZE=1024
tcp_socket_server=socket(AF_INET,SOCK_STREAM)
tcp_socket_server.setsockopt(SOL_SOCKET,SO_REUSEADDR,1)
tcp_socket_server.bind(ip_port)
tcp_socket_server.listen(5)while True:
? ? conn,addr=tcp_socket_server.accept()
? ? print('客戶端',addr)
? ? while True:
? ? ? ? cmd=conn.recv(BUFSIZE)
? ? ? ? if len(cmd) == 0:break? ? ? ? res=subprocess.Popen(cmd.decode('utf-8'),shell=True,
? ? ? ? ? ? ? ? ? ? ? ? stdout=subprocess.PIPE,
? ? ? ? ? ? ? ? ? ? ? ? stdin=subprocess.PIPE,
? ? ? ? ? ? ? ? ? ? ? ? stderr=subprocess.PIPE)
? ? ? ? stderr=res.stderr.read()
? ? ? ? stdout=res.stdout.read()
? ? ? ? conn.send(stderr)
? ? ? ? conn.send(stdout)
#_*_coding:utf-8_*_import socket
BUFSIZE=1024
ip_port=('127.0.0.1',8888)
s=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
res=s.connect_ex(ip_port)while True:
? ? msg=input('>>: ').strip()
? ? if len(msg) == 0:continue? ? if msg == 'quit':break? ? s.send(msg.encode('utf-8'))
? ? act_res=s.recv(BUFSIZE)
? ? print(act_res.decode('utf-8'),end='')
基于udp協(xié)議實現(xiàn)的黏包
#_*_coding:utf-8_*_from socket import *import subprocess
ip_port=('127.0.0.1',9000)
bufsize=1024
udp_server=socket(AF_INET,SOCK_DGRAM)
udp_server.setsockopt(SOL_SOCKET,SO_REUSEADDR,1)
udp_server.bind(ip_port)while True:
? ? #收消息? ? cmd,addr=udp_server.recvfrom(bufsize)
? ? print('用戶命令----->',cmd)
? ? #邏輯處理? ? res=subprocess.Popen(cmd.decode('utf-8'),shell=True,stderr=subprocess.PIPE,stdin=subprocess.PIPE,stdout=subprocess.PIPE)
? ? stderr=res.stderr.read()
? ? stdout=res.stdout.read()
? ? #發(fā)消息? ? udp_server.sendto(stderr,addr)
? ? udp_server.sendto(stdout,addr)
udp_server.close()
from socket import *
ip_port=('127.0.0.1',9000)
bufsize=1024
udp_client=socket(AF_INET,SOCK_DGRAM)while True:
? ? msg=input('>>: ').strip()
? ? udp_client.sendto(msg.encode('utf-8'),ip_port)
? ? err,addr=udp_client.recvfrom(bufsize)
? ? out,addr=udp_client.recvfrom(bufsize)
? ? if err:
? ? ? ? print('error : %s'%err.decode('utf-8'),end='')
? ? if out:
? ? ? ? print(out.decode('utf-8'), end='')
注意:只有TCP有粘包現(xiàn)象宜肉,UDP永遠不會粘包
黏包成因
TCP協(xié)議中的數(shù)據(jù)傳遞
tcp協(xié)議的拆包機制
當發(fā)送端緩沖區(qū)的長度大于網(wǎng)卡的MTU時,tcp會將這次發(fā)送的數(shù)據(jù)拆成幾個數(shù)據(jù)包發(fā)送出去翎碑。
MTU是Maximum Transmission Unit的縮寫谬返。意思是網(wǎng)絡上傳送的最大數(shù)據(jù)包。MTU的單位是字節(jié)日杈。 大部分網(wǎng)絡設備的MTU都是1500遣铝。如果本機的MTU比網(wǎng)關的MTU大佑刷,大的數(shù)據(jù)包就會被拆開來傳送,這樣會產生很多數(shù)據(jù)包碎片翰蠢,增加丟包率项乒,降低網(wǎng)絡速度。
面向流的通信特點和Nagle算法
TCP(transport control protocol梁沧,傳輸控制協(xié)議)是面向連接的檀何,面向流的,提供高可靠性服務廷支。
收發(fā)兩端(客戶端和服務器端)都要有一一成對的socket频鉴,因此,發(fā)送端為了將多個發(fā)往接收端的包恋拍,更有效的發(fā)到對方垛孔,使用了優(yōu)化方法(Nagle算法),將多次間隔較小且數(shù)據(jù)量小的數(shù)據(jù)施敢,合并成一個大的數(shù)據(jù)塊周荐,然后進行封包。
這樣僵娃,接收端概作,就難于分辨出來了,必須提供科學的拆包機制默怨。 即面向流的通信是無消息保護邊界的讯榕。
對于空消息:tcp是基于數(shù)據(jù)流的,于是收發(fā)的消息不能為空匙睹,這就需要在客戶端和服務端都添加空消息的處理機制愚屁,防止程序卡住,而udp是基于數(shù)據(jù)報的痕檬,即便是你輸入的是空內容(直接回車)霎槐,也可以被發(fā)送,udp協(xié)議會幫你封裝上消息頭發(fā)送過去梦谜。
可靠黏包的tcp協(xié)議:tcp的協(xié)議數(shù)據(jù)不會丟丘跌,沒有收完包,下次接收改淑,會繼續(xù)上次繼續(xù)接收,己端總是在收到ack時才會清除緩沖區(qū)內容浴讯。數(shù)據(jù)是可靠的朵夏,但是會粘包。
?基于tcp協(xié)議特點的黏包現(xiàn)象成因?
發(fā)送端可以是一K一K地發(fā)送數(shù)據(jù)榆纽,而接收端的應用程序可以兩K兩K地提走數(shù)據(jù)仰猖,當然也有可能一次提走3K或6K數(shù)據(jù)捏肢,或者一次只提走幾個字節(jié)的數(shù)據(jù)。
也就是說饥侵,應用程序所看到的數(shù)據(jù)是一個整體鸵赫,或說是一個流(stream),一條消息有多少字節(jié)對應用程序是不可見的躏升,因此TCP協(xié)議是面向流的協(xié)議辩棒,這也是容易出現(xiàn)粘包問題的原因。
而UDP是面向消息的協(xié)議膨疏,每個UDP段都是一條消息一睁,應用程序必須以消息為單位提取數(shù)據(jù),不能一次提取任意字節(jié)的數(shù)據(jù)佃却,這一點和TCP是很不同的者吁。
怎樣定義消息呢?可以認為對方一次性write/send的數(shù)據(jù)為一個消息饲帅,需要明白的是當對方send一條信息的時候复凳,無論底層怎樣分段分片,TCP協(xié)議層會把構成整條消息的數(shù)據(jù)段排序完成后才呈現(xiàn)在內核緩沖區(qū)灶泵。
例如基于tcp的套接字客戶端往服務端上傳文件育八,發(fā)送時文件內容是按照一段一段的字節(jié)流發(fā)送的,在接收方看了丘逸,根本不知道該文件的字節(jié)流從何處開始单鹿,在何處結束
此外,發(fā)送方引起的粘包是由TCP協(xié)議本身造成的深纲,TCP為提高傳輸效率仲锄,發(fā)送方往往要收集到足夠多的數(shù)據(jù)后才發(fā)送一個TCP段。若連續(xù)幾次需要send的數(shù)據(jù)都很少湃鹊,通常TCP會根據(jù)優(yōu)化算法把這些數(shù)據(jù)合成一個TCP段后一次發(fā)送出去儒喊,這樣接收方就收到了粘包數(shù)據(jù)。
UDP不會發(fā)生黏包
UDP(user datagram protocol币呵,用戶數(shù)據(jù)報協(xié)議)是無連接的怀愧,面向消息的,提供高效率服務余赢。
不會使用塊的合并優(yōu)化算法芯义,, 由于UDP支持的是一對多的模式,所以接收端的skbuff(套接字緩沖區(qū))采用了鏈式結構來記錄每一個到達的UDP包妻柒,在每個UDP包中就有了消息頭(消息來源地址扛拨,端口等信息),這樣举塔,對于接收端來說绑警,就容易進行區(qū)分處理了求泰。 即面向消息的通信是有消息保護邊界的。
對于空消息:tcp是基于數(shù)據(jù)流的计盒,于是收發(fā)的消息不能為空渴频,這就需要在客戶端和服務端都添加空消息的處理機制,防止程序卡住北启,而udp是基于數(shù)據(jù)報的卜朗,即便是你輸入的是空內容(直接回車),也可以被發(fā)送暖庄,udp協(xié)議會幫你封裝上消息頭發(fā)送過去聊替。
不可靠不黏包的udp協(xié)議:udp的recvfrom是阻塞的,一個recvfrom(x)必須對唯一一個sendinto(y),收完了x個字節(jié)的數(shù)據(jù)就算完成,若是y;x數(shù)據(jù)就丟失培廓,這意味著udp根本不會粘包惹悄,但是會丟數(shù)據(jù),不可靠肩钠。
補充說明:
? ? 用UDP協(xié)議發(fā)送時泣港,用sendto函數(shù)最大能發(fā)送數(shù)據(jù)的長度為:65535- IP頭(20) – UDP頭(8)=65507字節(jié)。用sendto函數(shù)發(fā)送數(shù)據(jù)時价匠,如果發(fā)送數(shù)據(jù)長度大于該值当纱,則函數(shù)會返回錯誤。(丟棄這個包踩窖,不進行發(fā)送)
? ? 用TCP協(xié)議發(fā)送時坡氯,由于TCP是數(shù)據(jù)流協(xié)議,因此不存在包大小的限制(暫不考慮緩沖區(qū)的大醒笕)箫柳,這是指在用send函數(shù)時,數(shù)據(jù)長度參數(shù)不受限制啥供。而實際上悯恍,所指定的這段數(shù)據(jù)并不一定會一次性發(fā)送出去,如果這段數(shù)據(jù)比較長伙狐,會被分段發(fā)送涮毫,如果比較短,可能會等待和下一次數(shù)據(jù)一起發(fā)送贷屎。
會發(fā)生黏包的兩種情況
情況一 發(fā)送方的緩存機制
發(fā)送端需要等緩沖區(qū)滿才發(fā)送出去罢防,造成粘包(發(fā)送數(shù)據(jù)時間間隔很短,數(shù)據(jù)了很小唉侄,會合到一起咒吐,產生粘包)
#_*_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('egg'.encode('utf-8'))
情況二 接收方的緩存機制
接收方不及時接收緩沖區(qū)的包,造成多個包接收(客戶端發(fā)送了一段數(shù)據(jù),服務端只收了一小部分渤滞,服務端下次再收的時候還是從緩沖區(qū)拿上次遺留的數(shù)據(jù),產生粘包)?
#_*_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)#下次收的時候,會先取舊的數(shù)據(jù),然后取新的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 egg'.encode('utf-8'))
總結
黏包現(xiàn)象只發(fā)生在tcp協(xié)議中:
1.從表面上看榴嗅,黏包問題主要是因為發(fā)送方和接收方的緩存機制妄呕、tcp協(xié)議面向流通信的特點。
2.實際上嗽测,主要還是因為接收方不知道消息之間的界限绪励,不知道一次性提取多少字節(jié)的數(shù)據(jù)所造成的
黏包的解決方案
解決方案一
問題的根源在于,接收端不知道發(fā)送端將要傳送的字節(jié)流的長度唠粥,所以解決粘包的方法就是圍繞疏魏,如何讓發(fā)送端在發(fā)送數(shù)據(jù)前,把自己將要發(fā)送的字節(jié)流總大小讓接收端知曉晤愧,然后接收端來一個死循環(huán)接收完所有數(shù)據(jù)大莫。
#_*_coding:utf-8_*_import socket,subprocess
ip_port=('127.0.0.1',8080)
s=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
s.bind(ip_port)
s.listen(5)while True:
? ? conn,addr=s.accept()
? ? print('客戶端',addr)
? ? while True:
? ? ? ? msg=conn.recv(1024)
? ? ? ? if not msg:break? ? ? ? res=subprocess.Popen(msg.decode('utf-8'),shell=True,\
? ? ? ? ? ? ? ? ? ? ? ? ? ? stdin=subprocess.PIPE,\
? ? ? ? ? ? ? ? ? ? ? ? stderr=subprocess.PIPE,\
? ? ? ? ? ? ? ? ? ? ? ? stdout=subprocess.PIPE)
? ? ? ? err=res.stderr.read()
? ? ? ? if err:
? ? ? ? ? ? ret=err
? ? ? ? else:
? ? ? ? ? ? ret=res.stdout.read()
? ? ? ? data_length=len(ret)
? ? ? ? conn.send(str(data_length).encode('utf-8'))
? ? ? ? data=conn.recv(1024).decode('utf-8')
? ? ? ? if data == 'recv_ready':
? ? ? ? ? ? conn.sendall(ret)
? ? conn.close()
#_*_coding:utf-8_*_import socket,time
s=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
res=s.connect_ex(('127.0.0.1',8080))while True:
? ? msg=input('>>: ').strip()
? ? if len(msg) == 0:continue? ? if msg == 'quit':break? ? s.send(msg.encode('utf-8'))
? ? length=int(s.recv(1024).decode('utf-8'))
? ? s.send('recv_ready'.encode('utf-8'))
? ? send_size=0
? ? recv_size=0
? ? data=b''? ? while recv_size < length:
? ? ? ? data+=s.recv(1024)
? ? ? ? recv_size+=len(data)
? ? print(data.decode('utf-8'))
存在的問題:
程序的運行速度遠快于網(wǎng)絡傳輸速度,所以在發(fā)送一段字節(jié)前官份,先用send去發(fā)送該字節(jié)流長度只厘,這種方式會放大網(wǎng)絡延遲帶來的性能損耗
解決方案進階
剛剛的方法,問題在于我們我們在發(fā)送
我們可以借助一個模塊舅巷,這個模塊可以把要發(fā)送的數(shù)據(jù)長度轉換成固定長度的字節(jié)羔味。這樣客戶端每次接收消息之前只要先接受這個固定長度字節(jié)的內容看一看接下來要接收的信息大小,那么最終接受的數(shù)據(jù)只要達到這個值就停止钠右,就能剛好不多不少的接收完整的數(shù)據(jù)了赋元。
struct模塊
該模塊可以把一個類型,如數(shù)字飒房,轉成固定長度的bytes
>>> struct.pack('i',1111111111111)
struct.error: 'i' format requires -2147483648 <= number <= 2147483647 #這個是范圍
import json,struct#假設通過客戶端上傳1T:1073741824000的文件a.txt#為避免粘包,必須自定制報頭header={'file_size':1073741824000,'file_name':'/a/b/c/d/e/a.txt','md5':'8f6fbf8347faa4924a76856701edb0f3'} #1T數(shù)據(jù),文件路徑和md5值#為了該報頭能傳送,需要序列化并且轉為byteshead_bytes=bytes(json.dumps(header),encoding='utf-8') #序列化并轉成bytes,用于傳輸#為了讓客戶端知道報頭的長度,用struck將報頭長度這個數(shù)字轉成固定長度:4個字節(jié)head_len_bytes=struct.pack('i',len(head_bytes)) #這4個字節(jié)里只包含了一個數(shù)字,該數(shù)字是報頭的長度#客戶端開始發(fā)送conn.send(head_len_bytes) #先發(fā)報頭的長度,4個bytesconn.send(head_bytes) #再發(fā)報頭的字節(jié)格式conn.sendall(文件內容) #然后發(fā)真實內容的字節(jié)格式#服務端開始接收head_len_bytes=s.recv(4) #先收報頭4個bytes,得到報頭長度的字節(jié)格式x=struct.unpack('i',head_len_bytes)[0] #提取報頭的長度head_bytes=s.recv(x) #按照報頭長度x,收取報頭的bytes格式header=json.loads(json.dumps(header)) #提取報頭#最后根據(jù)報頭的內容提取真實的數(shù)據(jù),比如real_data_len=s.recv(header['file_size'])
s.recv(real_data_len)
關于struct的詳細用法
#_*_coding:utf-8_*_
#http://www.cnblogs.com/coser/archive/2011/12/17/2291160.html__author__ = 'Linhaifeng'import structimport binasciiimport ctypes
values1 = (1, 'abc'.encode('utf-8'), 2.7)
values2 = ('defg'.encode('utf-8'),101)
s1 = struct.Struct('I3sf')
s2 = struct.Struct('4sI')print(s1.size,s2.size)
prebuffer=ctypes.create_string_buffer(s1.size+s2.size)print('Before : ',binascii.hexlify(prebuffer))# t=binascii.hexlify('asdfaf'.encode('utf-8'))
# print(t)s1.pack_into(prebuffer,0,*values1)
s2.pack_into(prebuffer,s1.size,*values2)print('After pack',binascii.hexlify(prebuffer))print(s1.unpack_from(prebuffer,0))print(s2.unpack_from(prebuffer,s1.size))
s3=struct.Struct('ii')
s3.pack_into(prebuffer,0,123,123)print('After pack',binascii.hexlify(prebuffer))print(s3.unpack_from(prebuffer,0))
使用struct解決黏包?
借助struct模塊搁凸,我們知道長度數(shù)字可以被轉換成一個標準大小的4字節(jié)數(shù)字。因此可以利用這個特點來預先發(fā)送數(shù)據(jù)長度情屹。
發(fā)送時接收時
先發(fā)送struct轉換好的數(shù)據(jù)長度4字節(jié)先接受4個字節(jié)使用struct轉換成數(shù)字來獲取要接收的數(shù)據(jù)長度
再發(fā)送數(shù)據(jù)再按照長度接收數(shù)據(jù)
服務端(自定制報頭)
import socket,struct,jsonimport subprocess
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()
? ? while True:
? ? ? ? cmd=conn.recv(1024)
? ? ? ? if not cmd:break? ? ? ? print('cmd: %s' %cmd)
? ? ? ? res=subprocess.Popen(cmd.decode('utf-8'),
? ? ? ? ? ? ? ? ? ? ? ? ? ? shell=True,
? ? ? ? ? ? ? ? ? ? ? ? ? ? stdout=subprocess.PIPE,
? ? ? ? ? ? ? ? ? ? ? ? ? ? stderr=subprocess.PIPE)
? ? ? ? err=res.stderr.read()
? ? ? ? print(err)
? ? ? ? if err:
? ? ? ? ? ? back_msg=err
? ? ? ? else:
? ? ? ? ? ? back_msg=res.stdout.read()
? ? ? ? conn.send(struct.pack('i',len(back_msg))) #先發(fā)back_msg的長度? ? ? ? conn.sendall(back_msg) #在發(fā)真實的內容? ? conn.close()
客戶端(自定制報頭)
#_*_coding:utf-8_*_import socket,time,struct
s=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
res=s.connect_ex(('127.0.0.1',8080))while True:
? ? msg=input('>>: ').strip()
? ? if len(msg) == 0:continue? ? if msg == 'quit':break? ? s.send(msg.encode('utf-8'))
? ? l=s.recv(4)
? ? x=struct.unpack('i',l)[0]
? ? print(type(x),x)
? ? # print(struct.unpack('I',l))? ? r_s=0
? ? data=b''? ? while r_s < x:
? ? ? ? r_d=s.recv(1024)
? ? ? ? data+=r_d
? ? ? ? r_s+=len(r_d)
? ? # print(data.decode('utf-8'))? ? print(data.decode('gbk')) #windows默認gbk編碼
我們還可以把報頭做成字典,字典里包含將要發(fā)送的真實數(shù)據(jù)的詳細信息垃你,然后json序列化椅文,然后用struck將序列化后的數(shù)據(jù)長度打包成4個字節(jié)(4個自己足夠用了)
發(fā)送時接收時
先發(fā)報頭長度先收報頭長度,用struct取出來
再編碼報頭內容然后發(fā)送根據(jù)取出的長度收取報頭內容惜颇,然后解碼皆刺,反序列化
最后發(fā)真實內容從反序列化的結果中取出待取數(shù)據(jù)的詳細信息,然后去取真實的數(shù)據(jù)內容
服務端:定制稍微復雜一點的報頭
import socket,struct,jsonimport subprocess
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()
? ? while True:
? ? ? ? cmd=conn.recv(1024)
? ? ? ? if not cmd:break? ? ? ? print('cmd: %s' %cmd)
? ? ? ? res=subprocess.Popen(cmd.decode('utf-8'),
? ? ? ? ? ? ? ? ? ? ? ? ? ? shell=True,
? ? ? ? ? ? ? ? ? ? ? ? ? ? stdout=subprocess.PIPE,
? ? ? ? ? ? ? ? ? ? ? ? ? ? stderr=subprocess.PIPE)
? ? ? ? err=res.stderr.read()
? ? ? ? print(err)
? ? ? ? if err:
? ? ? ? ? ? back_msg=err
? ? ? ? else:
? ? ? ? ? ? back_msg=res.stdout.read()
? ? ? ? headers={'data_size':len(back_msg)}
? ? ? ? head_json=json.dumps(headers)
? ? ? ? head_json_bytes=bytes(head_json,encoding='utf-8')
? ? ? ? conn.send(struct.pack('i',len(head_json_bytes))) #先發(fā)報頭的長度? ? ? ? conn.send(head_json_bytes) #再發(fā)報頭? ? ? ? conn.sendall(back_msg) #在發(fā)真實的內容? ? conn.close()
客戶端
from socket import *import struct,json
ip_port=('127.0.0.1',8080)
client=socket(AF_INET,SOCK_STREAM)
client.connect(ip_port)while True:
? ? cmd=input('>>: ')
? ? if not cmd:continue? ? client.send(bytes(cmd,encoding='utf-8'))
? ? head=client.recv(4)
? ? head_json_len=struct.unpack('i',head)[0]
? ? head_json=json.loads(client.recv(head_json_len).decode('utf-8'))
? ? data_len=head_json['data_size']
? ? recv_size=0
? ? recv_data=b''? ? while recv_size < data_len:
? ? ? ? recv_data+=client.recv(1024)
? ? ? ? recv_size+=len(recv_data)
? ? print(recv_data.decode('utf-8'))
? ? #print(recv_data.decode('gbk')) #windows默認gbk編碼
FTP作業(yè):上傳下載文件
服務端
import socketimport structimport jsonimport subprocessimport osclass 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()#下列代碼與本題無關class MYUDPServer:
? ? """UDP server class."""? ? address_family = socket.AF_INET
? ? socket_type = socket.SOCK_DGRAM
? ? allow_reuse_address = False
? ? max_packet_size = 8192
? ? coding='utf-8'? ? def get_request(self):
? ? ? ? data, client_addr = self.socket.recvfrom(self.max_packet_size)
? ? ? ? return (data, self.socket), client_addr
? ? def server_activate(self):
? ? ? ? # No need to call listen() for UDP.? ? ? ? pass? ? def shutdown_request(self, request):
? ? ? ? # No need to shutdown anything.? ? ? ? self.close_request(request)
? ? def close_request(self, request):
? ? ? ? # No need to close anything.? ? ? ? pass
客戶端
import socketimport structimport jsonimport osclass 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()
六.socket的更多方法介紹
服務端套接字函數(shù)
s.bind()? ? 綁定(主機,端口號)到套接字
s.listen()? 開始TCP監(jiān)聽
s.accept()? 被動接受TCP客戶的連接,(阻塞式)等待連接的到來
客戶端套接字函數(shù)
s.connect()? ? 主動初始化TCP服務器連接
s.connect_ex()? connect()函數(shù)的擴展版本,出錯時返回出錯碼,而不是拋出異常
公共用途的套接字函數(shù)
s.recv()? ? ? ? ? ? 接收TCP數(shù)據(jù)
s.send()? ? ? ? ? ? 發(fā)送TCP數(shù)據(jù)
s.sendall()? ? ? ? 發(fā)送TCP數(shù)據(jù)
s.recvfrom()? ? ? ? 接收UDP數(shù)據(jù)
s.sendto()? ? ? ? ? 發(fā)送UDP數(shù)據(jù)
s.getpeername()? ? 連接到當前套接字的遠端的地址
s.getsockname()? ? 當前套接字的地址
s.getsockopt()? ? ? 返回指定套接字的參數(shù)
s.setsockopt()? ? ? 設置指定套接字的參數(shù)
s.close()? ? ? ? ? 關閉套接字
面向鎖的套接字方法
s.setblocking()? ? 設置套接字的阻塞與非阻塞模式
s.settimeout()? ? ? 設置阻塞套接字操作的超時時間
s.gettimeout()? ? ? 得到阻塞套接字操作的超時時間
面向文件的套接字的函數(shù)
s.fileno()? ? ? ? ? 套接字的文件描述符
s.makefile()? ? ? ? 創(chuàng)建一個與該套接字相關的文件
官方文檔對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()的返回值是發(fā)送的字節(jié)數(shù)量羡蛾,這個數(shù)量值可能小于要發(fā)送的string的字節(jié)數(shù),也就是說可能無法發(fā)送string中所有的數(shù)據(jù)锨亏。如果有錯誤則會拋出異常痴怨。
–
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.
嘗試發(fā)送string的所有數(shù)據(jù)忙干,成功則返回None,失敗則拋出異常浪藻。
故捐迫,下面兩段代碼是等價的:#sock.sendall('Hello world\n')#buffer = 'Hello world\n'
#while buffer:
#? ? bytes = sock.send(buffer)
#? ? buffer = buffer[bytes:]
七.驗證客戶端鏈接的合法性
如果你想在分布式系統(tǒng)中實現(xiàn)一個簡單的客戶端鏈接認證功能,又不像SSL那么復雜爱葵,那么利用hmac+加鹽的方式來實現(xiàn)
服務端
#_*_coding:utf-8_*_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__ = 'Linhaifeng'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__ = 'Linhaifeng'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)
八.socketserver
解讀socketserver源碼 —— http://www.cnblogs.com/Eva-J/p/5081851.html?
server端
import socketserverclass Myserver(socketserver.BaseRequestHandler):
? ? def handle(self):
? ? ? ? self.data = self.request.recv(1024).strip()
? ? ? ? print("{} wrote:".format(self.client_address[0]))
? ? ? ? print(self.data)
? ? ? ? self.request.sendall(self.data.upper())if __name__ == "__main__":
? ? HOST, PORT = "127.0.0.1", 9999
? ? # 設置allow_reuse_address允許服務器重用地址? ? socketserver.TCPServer.allow_reuse_address = True
? ? # 創(chuàng)建一個server, 將服務地址綁定到127.0.0.1:9999? ? server = socketserver.TCPServer((HOST, PORT),Myserver)
? ? # 讓server永遠運行下去施戴,除非強制停止程序? ? server.serve_forever()
client
import socket
HOST, PORT = "127.0.0.1", 9999
data = "hello"# 創(chuàng)建一個socket鏈接,SOCK_STREAM代表使用TCP協(xié)議with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
? ? sock.connect((HOST, PORT))? ? ? ? ? # 鏈接到客戶端? ? sock.sendall(bytes(data + "\n", "utf-8")) # 向服務端發(fā)送數(shù)據(jù)? ? received = str(sock.recv(1024), "utf-8")# 從服務端接收數(shù)據(jù)print("Sent:? ? {}".format(data))print("Received: {}".format(received))