网络编程(part9)--socket套接字编程之TCP套接字
鄙人學習筆記
文章目錄
- 套接字介紹
- 定義
- 套接字分類(針對TCP和UDP的分類)
- TCP套接字編程
- 服務端流程
- 代碼實現
- 舉個例子
- 客戶端流程
- 代碼實現
- 舉個例子
- TCP套接字數據傳輸特點
- 做個練習
- 網絡收發(fā)緩沖區(qū)
- 舉個例子
- TCP粘包
套接字介紹
定義
套接字是實現網絡編程進行數據傳輸的一種技術手段
套接字分類(針對TCP和UDP的分類)
①流式套接字(SOCK_STREAM): 以字節(jié)流方式(就像是管道中的水流一樣)傳輸數據,實現TCP網絡傳輸方案。
②數據報套接字(SOCK_DGRAM):以數據報形式(就像是用瓶子打包好的水一樣)傳輸數據,實現UDP網絡傳輸方案。
TCP套接字編程
服務端流程
先來看一個流程圖:
代碼實現
備注:底層/系統(tǒng)層套接字也針對某種協(xié)議,這些協(xié)議,有的時候會產生一些分支,也就是子協(xié)議。那么為啥在應用層網絡編程中用不到proto這個參數呢?因為TCP協(xié)議和UDP協(xié)議沒有子協(xié)議,所以參數設為0,即不選擇任何子協(xié)議。
備注:addr為一個元組,這個元組有兩個元素一個是網絡地址(IP),一個是端口號(port)。
備注1:并不是所有的套接字都具備監(jiān)聽的功能,即被客戶端連接的功能。通過調用listen,我們的套接字對象才能被客戶端連接。
備注2:一個服務端套接字對象可以同時連接多個客戶端,與客戶端進行連接需要進行3次握手,且連接的過程需要一個一個來進行。比如說,同時有5個客戶端發(fā)起了連接請求,這時我們就會形成一個”先來后到”的隊列,對這5個客戶端的連接請求,一個一個進行處理。比如說,我們設置監(jiān)聽隊列大小為5,表示隊列最多容納5個客戶端等待處理。但有10個客戶端同時發(fā)起了連接請求,這時,服務端會先處理第1個發(fā)起請求的那個客戶端,前2~6個客戶端則在等待隊列中,最晚發(fā)起請求的4個客戶端,則會被拒絕。
阻塞函數:當程序運行到這個函數時,則程序暫停執(zhí)行。程序在等待某種條件,當條件滿足后才繼續(xù)執(zhí)行,如:input()、sleep()。阻塞函數在IO操作中很常見。
備注1:所有和網絡相關的消息傳送都得是bytes格式(字節(jié)串格式).發(fā)送和接收都得是字節(jié)串。
備注2:我們發(fā)送/接收的是字節(jié)串,但平時寫/讀的是字符串,所以我們需要用encode()/dencode()進行轉換。
舉個例子
服務端代碼:
import socket#創(chuàng)建流式套接字 sockfd = socket.socket(socket.AF_INET, \socket.SOCK_STREAM) #綁定地址 sockfd.bind(('127.0.0.1', 8888))#設置監(jiān)聽 sockfd.listen(5)#等待處理客戶端鏈接 print("Waiting for connect....") connfd, addr = sockfd.accept()#收發(fā)消息 data = connfd.recv(1024) print("接收到的消息:", data.decode())n = connfd.send(b'Receive your message') print("發(fā)送了 %d 個字節(jié)數據" % n)#關閉套接字 connfd.close() sockfd.close()我們運行一下,得到以下結果:
結果說明,程序阻塞在accept,等待客戶端連接。所以這時,我們就要找一個客戶端與之連接,下一節(jié),我們就學一下客戶端流程。
客戶端流程
先看看流程圖:
代碼實現
創(chuàng)建套接字(和客戶端代碼相同)
注意:只有相同類型的套接字才能進行通信
請求連接
收發(fā)消息(和客戶端代碼相同)
注意: 為了防止兩端都阻塞,故recv和send要配合使用。比如,服務端是先send后recv,那么客戶端則需要先recv后send。否則,若兩端同時recv,則兩端都會阻塞。
關閉套接字(和客戶端代碼相同)
舉個例子
客戶端代碼:
服務端代碼:
我們想要運行,但是若先運行客戶端,則會報錯。所以我們要先啟動服務端
①運行服務端
查看服務端的控制臺Console 2/A輸出的結果:
②運行客戶端
我們先看一下服務端的控制臺Console 2/A輸出的結果:
我們再看一下客戶端的控制臺Console 3/A輸出的結果:
消息收發(fā)成功了,很好~
TCP套接字數據傳輸特點
①TCP連接中,當一端退出,另一端如果阻塞在recv,此時recv會立即返回一個空字串。
②TCP連接中,如果一端已經不存在,仍然試圖通過send發(fā)送信息,則會產生BrokenPipeError
③一個監(jiān)聽套接字可以同時連接多個客戶端,也能夠重復被連接。
做個練習
要求1:一個客戶端退出了,服務器不會退出,而是連接下一個客戶端
要求2:客戶端可以不停的循環(huán)發(fā)送消息
服務端代碼:
#-*- coding: utf-8 -*-import socket#創(chuàng)建流式套接字 sockfd = socket.socket(socket.AF_INET, \socket.SOCK_STREAM)#綁定地址 sockfd.bind(('127.0.0.1', 8888))#設置監(jiān)聽 sockfd.listen(5)#等待處理客戶端鏈接 while True:print("Waiting for connect....")try:connfd, addr = sockfd.accept()print("Connect from:", addr)except KeyboardInterrupt:print("退出服務")break# 收發(fā)消息while True:data = connfd.recv(1024)# 得到空則退出循環(huán)if not data:breakprint("接收到的消息:", data.decode())n = connfd.send(b'Receive your message')print("發(fā)送了 %d 個字節(jié)數據" % n)connfd.close()#關閉套接字 sockfd.close()客戶端代碼:
#-*- coding: utf-8 -*-from socket import *#創(chuàng)建tcp套接字 sockfd = socket()#發(fā)起連接 server_addr = ('127.0.0.1',8888) sockfd.connect(server_addr)#收發(fā)消息 while True:data = input("消息:")if not data:breaksockfd.send(data.encode())data = sockfd.recv(1024)print("From server:",data.decode())#關閉 sockfd.close()先運行服務端,再運行客戶端并發(fā)送消息。
客戶端結果:
服務端結果:
網絡收發(fā)緩沖區(qū)
①網絡緩沖區(qū)有效的協(xié)調了消息的收發(fā)速度、緩解收發(fā)壓力。
②send和recv實際是向緩沖區(qū)發(fā)送接收消息,當緩沖區(qū)不為空recv就不會阻塞。
網絡緩沖區(qū)完成消息傳遞示意圖:
舉個例子
還記得我們上面那個練習么?其中一段代碼是:
它表示,每次最多可接收消息大小為1024個字節(jié).
我們來更改一下最大可接受的消息字節(jié)數為5:
用客戶端發(fā)送消息:
服務端結果:
客戶端結果:
則說明,客戶端1次發(fā)送,服務端分3次接收了,同時返回給服務端3句’ Receive your message’也就是說,服務端的內層循環(huán)(如下圖所示)循環(huán)了3次
備注1:所以我們一開始說【函數recv()是阻塞函數,當發(fā)送方不發(fā)消息,就會阻塞】。但其實更準確的來說,并不是發(fā)送方不發(fā)消息就會阻塞,接收方其實是先從緩沖區(qū)去拿消息,當緩沖區(qū)為空時,會阻塞,當緩沖區(qū)一直有消息時,則會一直獲取消息,一直不阻塞。
注意! 運行上面的代碼時,客戶端有時候也會出現以下這種情況:
按照上面的說法,當客戶端發(fā)送1條消息,客戶端會返回3條’ Receive your message’。但是也會出現,當第1條’ Receive your message’進入客戶端緩沖區(qū),客戶端就從客戶端緩沖區(qū)recv()了這條消息,導致剩下2條’ Receive your message’停留在緩沖區(qū)沒被接受。等到下一次,客戶端再從緩沖區(qū)中recv()時,才把剩下’ Receive your message’接收到。
備注1:我們第一次運行代碼時,服務端快速的連續(xù)3次send()了1條’ Receive your message’,客戶端1次recv()了3條’ Receive your message’。接收端一次接收了多條發(fā)送端消息,我們稱這種情況叫:粘包。
TCP粘包
-
原因
TCP以字節(jié)流方式傳輸,沒有消息邊界。多次發(fā)送的消息被一次接收,此時就會形成粘包。 -
影響(分情況,比如:傳一部電影/發(fā)送用戶名消息)
①如果發(fā)送的內容中,每個信息都有獨立的含義(比如:發(fā)送幾個用戶姓名),需要接收端獨立解析,此時,這時粘包會有影響。
②如果發(fā)送的是一個字節(jié)流文件,是一個連在一起的整體(比如:發(fā)送一部電影),接收端無需單獨解析,而是將所有接收到內容最終合成一個整體,這時粘包沒啥影響。 -
處理方法
①人為的添加消息邊界,比如:每次發(fā)送一個姓名時,在姓名后加一個特殊符號。
②控制發(fā)送速度(因為粘包的產生是因為收發(fā)速度不協(xié)調),比如用sleep()函數調節(jié)。
總結
以上是生活随笔為你收集整理的网络编程(part9)--socket套接字编程之TCP套接字的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 万能五笔输入法22大使用技巧分享
- 下一篇: 网络编程(part10)--socket