基於TCP協議的socket套接字編程


一、什么是Scoket

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

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

121-基於TCP協議的套接字編程-socket層.jpg?x-oss-process=style/watermark

  • 注意:也有人將socket說成ip+port,ip是用來標識互聯網中的一台主機的位置,而port是用來標識這台機器上的一個應用程序,ip地址是配置到網卡上的,而port是應用程序開啟的,ip與port的綁定就標識了互聯網中獨一無二的一個應用程序,而程序的pid是同一台機器上不同進程或者線程的標識。

二、套接字發展史及分類

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

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

套接字家族的名字:AF_UNIX

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

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

套接字家族的名字:AF_INET

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

三、套接字工作流程

一個生活中的場景。你要打電話給一個朋友,先撥號,朋友聽到電話鈴聲后提起電話,這時你和你的朋友就建立起了連接,就可以講話了。等交流結束,掛斷電話結束此次交談。 生活中的場景就解釋了這工作原理。

121-基於TCP協議的套接字編程-socket流程.jpg?x-oss-process=style/watermark

先從服務器端說起。服務器端先初始化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)

# 由於 socket 模塊中有太多的屬性。我們在這里破例使用了'from module import *'語句。使用 'from socket import *',我們就把 socket 模塊里的所有屬性都帶到我們的命名空間里了,這樣能大幅減短我們的代碼
tcpSock = socket(AF_INET, SOCK_STREAM)

121-基於TCP協議的套接字編程-小兵潮.gif

3.1 服務端套接字函數

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

3.2 客戶端套接字函數

方法 用途
s.connect() 主動初始化TCP服務器連接
s.connect_ex() connect()函數的擴展版本,出錯時返回出錯碼,而不是拋出異常

3.3 公共用途的套接字函數

方法 用途
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() 關閉套接字

3.4 面向鎖的套接字方法

方法 用途
s.setblocking() 設置套接字的阻塞與非阻塞模式
s.settimeout() 設置阻塞套接字操作的超時時間
s.gettimeout() 得到阻塞套接字操作的超時時間

3.5 面向文件的套接字的函數

方法 用途
s.fileno() 套接字的文件描述符
s.makefile() 創建一個與該套接字相關的文件

121-基於TCP協議的套接字編程-打電話.jpg?x-oss-process=style/watermark

四、基於TCP協議的套接字編程(簡單)

  • 可以通過netstat -an | findstr 8080查看套接字狀態

4.1 服務端

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', 8081))

#3、開機
phone.listen(5)  # 半連接池,限制的是請求數

#4、等待電話連接
print('start....')
conn, client_addr = phone.accept()  #(三次握手建立的雙向連接,(客戶端的ip,端口))
print(conn)
print(client_addr)

#5、通信:收\發消息
data = conn.recv(1024)  #最大接收的字節數
print('來自客戶端的數據', data)
conn.send(data.upper())

# import time
# time.sleep(500)
#6、掛掉電話連接
conn.close()

#7、關機
phone.close()

4.2 客戶端

import socket
# socket.AF
#1、買手機
phone = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
print(phone)
#2、撥電話
phone.connect(('127.0.0.1', 8081))  # 指定服務端ip和端口

#3、通信:發\收消息
phone.send('hello'.encode('utf-8'))
# phone.send(bytes('hello',encoding='utf-8'))
data = phone.recv(1024)
print(data)

# import time
# time.sleep(500)
#4、關閉
phone.close()

五、基於TCP協議的套接字編程(循環)

5.1 服務端

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()

5.2 客戶端1

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()

5.3 客戶端2

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()
    phone.send(msg.encode('utf-8'))
    data = phone.recv(1024)
    print(data)

#4、關閉
phone.close()

六、地址占用問題

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

121-基於TCP協議的套接字編程-bug.png?x-oss-process=style/watermark

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

6.1 方法一

# 加入一條socket配置,重用ip和端口

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

6.2 方法二(Linux)

發現系統存在大量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 時間


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM