1. 程式人生 > >python基礎之socket編程

python基礎之socket編程

ron 模塊 執行 優化 控制 端口號 文件 斷開連接 page

一 客戶端/服務器架構

即C/S架構,包括
1.硬件C/S架構(打印機)
2.軟件C/S架構(web服務)
最常用的軟件服務器是 Web 服務器。一臺機器裏放一些網頁或 Web 應用程序,然後啟動 服務。這樣的服務器的任務就是接受客戶的請求,把網頁發給客戶(如用戶計算機上的瀏覽器),然 後等待下一個客戶請求。這些服務啟動後的目標就是“永遠運行下去”。雖然它們不可能實現這樣的 目標,但只要沒有關機或硬件出錯等外力幹擾,它們就能運行非常長的一段時間。
生活中的C/S架構:
老男孩是S端,所有學員是C端(CRM客戶關系管理系統也是C/S架構)
飯店是S端,所有的食客是C端
互聯網中處處是C/S架構(黃色網站是服務端,你的瀏覽器是客戶端;騰訊作為服務端為你提供視頻,你得下個騰訊視頻客戶端才能看狗日的視頻)
C/S架構與socket的關系:

我們學習socket就是為了完成C/S架構的開發

二 osi七層

引子:

須知一個完整的計算機系統是由硬件、操作系統、應用軟件三者組成,具備了這三個條件,一臺計算機系統就可以自己跟自己玩了(打個單機遊戲,玩個掃雷啥的)

如果你要跟別人一起玩,那你就需要上網了(訪問個黃色網站,發個黃色微博啥的),互聯網的核心就是由一堆協議組成,協議就是標準,全世界人通信的標準是英語,如果把計算機比作人,互聯網協議就是計算機界的英語。所有的計算機都學會了互聯網協議,那所有的計算機都就可以按照統一的標準去收發信息從而完成通信了。人們按照分工不同把互聯網協議從邏輯上劃分了層級,詳見我另一篇博客

技術分享

OSI五層模型數據傳輸

網絡通信原理:http://www.cnblogs.com/bingabcd/p/6803610.html

為何學習socket一定要先學習互聯網協議:

1.首先:本節課程的目標就是教會你如何基於socket編程,來開發一款自己的C/S架構軟件

2.其次:C/S架構的軟件(軟件屬於應用層)是基於網絡進行通信的

3.然後:網絡的核心即一堆協議,協議即標準,你想開發一款基於網絡通信的軟件,就必須遵循這些標準。

4.最後:就讓我們從這些標準開始研究,開啟我們的socket編程之旅

技術分享

TCP/IP協議族包括運輸層、網絡層、鏈路層。現在你知道TCP/IP與UDP的關系了吧。

三 socket層

在圖1中,我們沒有看到Socket的影子,那麽它到底在哪裏呢?還是用圖來說話,一目了然。

技術分享

四 socket是什麽

Socket是應用層與TCP/IP協議族通信的中間軟件抽象層,它是一組接口。在設計模式中,Socket其實就是一個門面模式,它把復雜的TCP/IP協議族隱藏在Socket接口後面,對用戶來說,一組簡單的接口就是全部,讓Socket去組織數據,以符合指定的協議。

所以,我們無需深入理解tcp/udp協議,socket已經為我們封裝好了,我們只需要遵循socket的規定去編程,寫出的程序自然就是遵循tcp/udp標準的。

技術分享
也有人將socket說成ip+port,ip是用來標識互聯網中的一臺主機的位置,而port是用來標識這臺機器上的一個應用程序,ip地址是配置到網卡上的,而port是應用程序開啟的,ip與port的綁定就標識了互聯網中獨一無二的一個應用程序

而程序的pid是同一臺機器上不同進程或者線程的標識
socket=ip+port

五 套接字發展史及分類

套接字起源於 20 世紀 70 年代加利福尼亞大學伯克利分校版本的 Unix,即人們所說的 BSD Unix。 因此,有時人們也把套接字稱為“伯克利套接字”或“BSD 套接字”。一開始,套接字被設計用在同 一臺主機上多個應用程序之間的通訊。這也被稱進程間通訊,或 IPC。套接字有兩種(或者稱為有兩個種族),分別是基於文件型的和基於網絡型的。

基於文件類型的套接字家族

套接字家族的名字:AF_UNIX

unix一切皆文件,基於文件的套接字調用的就是底層的文件系統來取數據,兩個套接字進程運行在同一機器,可以通過訪問同一個文件系統間接完成通信

基於網絡類型的套接字家族

套接字家族的名字:AF_INET

(還有AF_INET6被用於ipv6,還有一些其他的地址家族,不過,他們要麽是只用於某個平臺,要麽就是已經被廢棄,或者是很少被使用,或者是根本沒有實現,所有地址家族中,AF_INET是使用最廣泛的一個,python支持很多種地址家族,但是由於我們只關心網絡編程,所以大部分時候我麽只使用AF_INET)

技術分享

              圖3

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

socket()模塊函數用法

技術分享
先從服務器端說起。服務器端先初始化Socket,然後與端口綁定(bind),對端口進行監聽(listen),調用accept阻塞,等待客戶端連接。在這時如果有個客戶端初始化一個Socket,然後連接服務器(connect),如果連接成功,這時客戶端與服務器端的連接就建立了。客戶端發送數據請求,服務器端接收請求並處理請求,然後把回應數據發送給客戶端,客戶端讀取數據,最後關閉連接,一次交互結束

socket()模塊函數用法
socket()模塊函數用法
服務端套接字函數
技術分享
s.bind()    綁定(主機,端口號)到套接字
s.listen()  開始TCP監聽
s.accept()  被動接受TCP客戶的連接,(阻塞式)等待連接的到來
View Code

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

公共用途的套接字函數
技術分享
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()           關閉套接字
View Code
面向鎖的套接字方法
技術分享
s.setblocking()     設置套接字的阻塞與非阻塞模式
s.settimeout()      設置阻塞套接字操作的超時時間
s.gettimeout()      得到阻塞套接字操作的超時時間
View Code
面向文件的套接字的函數
技術分享
s.fileno()          套接字的文件描述符
s.makefile()        創建一個與該套接字相關的文件
View Code 技術分享
1:用打電話的流程快速描述socket通信
2:服務端和客戶端加上基於一次鏈接的循環通信
3:客戶端發送空,卡主,證明是從哪個位置卡的
服務端:
from socket import *
phone=socket(AF_INET,SOCK_STREAM)
phone.bind((127.0.0.1,8081))
phone.listen(5)

conn,addr=phone.accept()
while True:
    data=conn.recv(1024)
    print(server===>)
    print(data)
    conn.send(data.upper())
conn.close()
phone.close()
客戶端:
from socket import *

phone=socket(AF_INET,SOCK_STREAM)
phone.connect((127.0.0.1,8081))

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

說明卡的原因:緩沖區為空recv就卡住,引出原理圖



4.演示客戶端斷開鏈接,服務端的情況,提供解決方法

5.演示服務端不能重復接受鏈接,而服務器都是正常運行不斷來接受客戶鏈接的

6:簡單演示udp
服務端
from socket import *
phone=socket(AF_INET,SOCK_DGRAM)
phone.bind((127.0.0.1,8082))
while True:
    msg,addr=phone.recvfrom(1024)
    phone.sendto(msg.upper(),addr)
客戶端
from socket import *
phone=socket(AF_INET,SOCK_DGRAM)
while True:
    msg=input(>>: )
    phone.sendto(msg.encode(utf-8),(127.0.0.1,8082))
    msg,addr=phone.recvfrom(1024)
    print(msg)

udp客戶端可以並發演示
udp客戶端可以輸入為空演示,說出recvfrom與recv的區別,暫且不提tcp流和udp報的概念,留到粘包去說
用打電話的流程快速描述socket通信

七 基於TCP的套接字

tcp服務端

技術分享
ss = socket() #創建服務器套接字
ss.bind()      #把地址綁定到套接字
ss.listen()      #監聽鏈接
inf_loop:      #服務器無限循環
    cs = ss.accept() #接受客戶端鏈接
    comm_loop:         #通訊循環
        cs.recv()/cs.send() #對話(接收與發送)
    cs.close()    #關閉客戶端套接字
ss.close()        #關閉服務器套接字(可選)
TCP Server 技術分享
1 cs = socket()    # 創建客戶套接字
2 cs.connect()    # 嘗試連接服務器
3 comm_loop:        # 通訊循環
4     cs.send()/cs.recv()    # 對話(發送/接收)
5 cs.close()            # 關閉客戶套接字
TCP Client

socket通信流程與打電話流程類似,我們就以打電話為例來實現一個low版的套接字通信

技術分享
#_*_coding:utf-8_*_
__author__ = bing
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__ = bing
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(linhaifeng nb.encode(utf-8))         #發消息,說話(只能發送字節類型)

feedback=s.recv(BUFSIZE)                           #收消息,聽話
print(feedback.decode(utf-8))

s.close()                                       #掛電話

客戶端
客戶端

上述流程的問題是,服務端只能接受一次鏈接,然後就徹底關閉掉了,實際情況應該是,服務端不斷接受鏈接,然後循環通信,通信完畢後只關閉鏈接,服務器能夠繼續接收下一次鏈接,下面是修改版

技術分享
#_*_coding:utf-8_*_
__author__ = Linhaifeng
import socket
ip_port=(127.0.0.1,8081)#電話卡
BUFSIZE=1024
s=socket.socket(socket.AF_INET,socket.SOCK_STREAM) #買手機
s.bind(ip_port) #手機插卡
s.listen(5)     #手機待機


while True:                         #新增接收鏈接循環,可以不停的接電話
    conn,addr=s.accept()            #手機接電話
    # print(conn)
    # print(addr)
    print(接到來自%s的電話 %addr[0])
    while True:                         #新增通信循環,可以不斷的通信,收發消息
        msg=conn.recv(BUFSIZE)             #聽消息,聽話

        # if len(msg) == 0:break        #如果不加,那麽正在鏈接的客戶端突然斷開,recv便不再阻塞,死循環發生

        print(msg,type(msg))

        conn.send(msg.upper())          #發消息,說話

    conn.close()                    #掛電話

s.close()                       #手機關機

服務端改進版
服務端改進版 技術分享
#_*_coding:utf-8_*_
__author__ = bing
import socket
ip_port=(127.0.0.1,8081)
BUFSIZE=1024
s=socket.socket(socket.AF_INET,socket.SOCK_STREAM)

s.connect_ex(ip_port)           #撥電話

while True:                             #新增通信循環,客戶端可以不斷發收消息
    msg=input(>>: ).strip()
    if len(msg) == 0:continue
    s.send(msg.encode(utf-8))         #發消息,說話(只能發送字節類型)

    feedback=s.recv(BUFSIZE)                           #收消息,聽話
    print(feedback.decode(utf-8))

s.close()                                       #掛電話

客戶端改進版
客戶端改進版

問題:

有的同學在重啟服務端時可能會遇到

技術分享

技術分享

這個是由於你的服務端仍然存在四次揮手的time_wait狀態在占用地址(如果不懂,請深入研究1.tcp三次握手,四次揮手 2.syn洪水攻擊 3.服務器高並發情況下會有大量的time_wait狀態的優化方法)

關於tcp三次握手,四次揮手的詳解,請看這篇博客,http://www.cnblogs.com/bingabcd/p/6802408.html

技術分享
TCP三次握手:首先Client端發送連接請求報文,Server段接受連接後回復ACK報文,並為這次連接分配資源。Client端接收到ACK報文後也向Server段發生ACK報文,並分配資源,這樣TCP連接就建立了。
服務器高並發情況下會有大量的time_wait狀態的優化方法

TCP四次揮手
【註意】中斷連接端可以是Client端,也可以是Server端。
假設Client端發起中斷連接請求,也就是發送FIN報文。Server端接到FIN報文後,意思是說"我Client端沒有數據要發給你了",但是如果你還有數據沒有發送完成,則不必急著關閉Socket,可以繼續發送數據。所以你先發送ACK,"告訴Client端,你的請求我收到了,但是我還沒準備好,請繼續你等我的消息"。這個時候Client端就進入FIN_WAIT狀態,繼續等待Server端的FIN報文。當Server端確定數據已發送完成,則向Client端發送FIN報文,"告訴Client端,好了,我這邊數據發完了,準備好關閉連接了"。Client端收到FIN報文後,"就知道可以關閉連接了,但是他還是不相信網絡,怕Server端不知道要關閉,所以發送ACK後進入TIME_WAIT狀態,如果Server端沒有收到ACK則可以重傳。“,Server端收到ACK後,"就知道可以斷開連接了"。Client端等待了2MSL後依然沒有收到回復,則證明Server端已正常關閉,那好,我Client端也可以關閉連接了。Ok,TCP連接就這樣關閉了!
整個過程Client端所經歷的狀態如下:
【註意】 在TIME_WAIT狀態中,如果TCP client端最後一次發送的ACK丟失了,它將重新發送。TIME_WAIT狀態中所需要的時間是依賴於實現方法的。典型的值為30秒、1分鐘和2分鐘。等待之後連接正式關閉,並且所有的資源(包括端口號)都被釋放。
【問題1】為什麽連接的時候是三次握手,關閉的時候卻是四次握手?
答:因為當Server端收到Client端的SYN連接請求報文後,可以直接發送SYN+ACK報文。其中ACK報文是用來應答的,SYN報文是用來同步的。但是關閉連接時,當Server端收到FIN報文時,很可能並不會立即關閉SOCKET,所以只能先回復一個ACK報文,告訴Client端,"你發的FIN報文我收到了"。只有等到我Server端所有的報文都發送完了,我才能發送FIN報文,因此不能一起發送。故需要四步握手。
【問題2】為什麽TIME_WAIT狀態需要經過2MSL(最大報文段生存時間)才能返回到CLOSE狀態?
答:雖然按道理,四個報文都發送完畢,我們可以直接進入CLOSE狀態了,但是我們必須假象網絡是不可靠的,有可以最後一個ACK丟失。所以TIME_WAIT狀態就是用來重發可能丟失的ACK報文。
TCP三次握手四次揮手 技術分享
TCP SYN泛洪發生在OSI第四層,這種方式利用TCP協議的特性,就是三次握手。攻擊者發送TCP SYN,SYN是TCP三次握手中的第一個數據包,而當服務器返回ACK後,該攻擊者就不對其進行再確認,那這個TCP連接就處於掛起狀態,也就是所謂的半連接狀態,服務器收不到再確認的話,還會重復發送ACK給攻擊者。這樣更加會浪費服務器的資源。攻擊者就對服務器發送非常大量的這種TCP連接,由於每一個都沒法完成三次握手,所以在服務器上,這些TCP連接會因為掛起狀態而消耗CPU和內存,最後服務器可能死機,就無法為正常用戶提供服務了。
TCP SYN泛洪 技術分享
服務器高並發情況下會有大量的time_wait狀態的優化方法
系統層面:
#讓TIME_WAIT狀態可以重用,這樣即使TIME_WAIT占滿了所有端口,也不會拒絕新的請求造成障礙
echo "1" > /proc/sys/net/ipv4/tcp_tw_reuse
#讓TIME_WAIT盡快回收,我也不知是多久,觀察大概是一秒鐘
echo "1" > /proc/sys/net/ipv4/tcp_tw_recycle
發現系統存在大量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 時間

應用層面
1)將TCP短連接改造為長連接。通常情況下,如果發起連接的目標也是自己可控制的服務器時,它們自己的TCP通信最好采用長連接,避免大量TCP短連接每次建立/釋放產生的各種開銷;如果建立連接的目標是不受自己控制的機器時,能否使用長連接就需要考慮對方機器是否支持長連接方式了。
2)通過getsockopt/setsockoptapi設置socket的SO_LINGER選項,關於SO_LINGER選項的設置方法,《UNP Volume1》一書7.5節給出了詳細說明,想深入理解的同學可以去查閱該教材,也可以參考這篇文章,講的還算清楚。
服務器高並發情況下會有大量的time_wait狀態的優化方法

解決方法:

技術分享
#加入一條socket配置,重用ip和端口

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 時間
方法二:調整linux內核參數

python基礎之socket編程