Python 網絡編程3:tcp客戶端與tcp服務器以及下載器

一、TCP介紹

1.1、TCP協議,傳輸控制協議(英語:Transmission Control Protocol,縮寫爲 TCP)是一種面向連接的、可靠的、基於字節流的傳輸層通信協議,由IETF的RFC 793定義。
1.2、TCP通信需要經過 創建連接、數據傳送、終止連接 三個步驟。
1.3、TCP通信模型中,在通信開始之前,一定要先建立相關的鏈接,才能發送數據,類似於生活中,“打電話”,而DCP猶如生活中的 “寫信”。
TCP
二、TCP特點

2.1、面向連接
通信雙方必須先建立連接才能進行數據的傳輸,雙方都必須爲該連接分配必要的系統內核資源,以管理連接的狀態和連接上的傳輸。
雙方間的數據傳輸都可以通過這一個連接進行。
完成數據交換後,雙方必須斷開此連接,以釋放系統資源。
這種連接是一對一的,因此TCP不適用於廣播的應用程序,基於廣播的應用程序請使用UDP協議。
2.2、可靠傳輸
(1)、TCP採用發送應答機制
TCP發送的每個報文段都必須得到接收方的應答才認爲這個TCP報文段傳輸成功
(2)、超時重傳
發送端發出一個報文段之後就啓動定時器,如果在定時時間內沒有收到應答就重新發送這個報文段。
TCP爲了保證不發生丟包,就給每個包一個序號,同時序號也保證了傳送到接收端實體的包的按序接收。然後接收端實體對已成功收到的包發回一個相應的確認(ACK);如果發送端實體在合理的往返時延(RTT)內未收到確認,那麼對應的數據包就被假設爲已丟失將會被進行重傳。
(3)、錯誤校驗
TCP用一個校驗和函數來檢驗數據是否有錯誤;在發送和接收時都要計算校驗和。
(4)、流量控制和阻塞管理
流量控制用來避免主機發送得過快而使接收方來不及完全收下。
三、TCP與UDP的不同點

3.1、有如下不同點

面向連接(確認有創建三方交握,連接已創建才作傳輸。)
有序數據傳輸
重發丟失的數據包
捨棄重複的數據包
無差錯的數據傳輸
阻塞/流量控制
3.2、udp通信模型
udp通信模型中,在通信開始之前,不需要建立相關的鏈接,只需要發送數據即可,類似於生活中,“寫信”"

udp通信模型

udp通信模型
3.3、TCP通信模型
tcp通信模型中,在通信開始之前,一定要先建立相關的鏈接,才能發送數據,類似於生活中,“打電話”"

TCP通信模型
四、tcp客戶端構建流程

4.1、tcp的客戶端要比服務器端簡單很多,如果說服務器端是需要自己買手機、查手機卡、設置鈴聲、等待別人打電話流程的話,那麼客戶端就只需要找一個電話亭,拿起電話撥打即可,流程要少很多

4.2、實例代碼(四步)

(1)、創建TCP套接字

tcp_socket = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
(2)、創建鏈接服務器,一個元組:服務器的IP與端口

server_ip = input(“請輸入要連接的服務器ip:”)
server_port = input(“請輸入要連接的服務器port:”)
server_addr = (server_ip,server_port)
tcp_socket.connect(server_addr)
(3)、發送內容或者接收內容

發送內容

send_data = input(“親輸入要發送的內容:”)
tcp_socket.send(send_data.encode(“utf-8”))

接收對方發送過來的數據,最大接收1024個字節

recvData = tcp_client_socket.recv(1024)
print(‘接收到的數據爲:’, recvData.decode(“utf-8”))
(4)、關閉套接字

tcp_socket.close()
4.3、完整的tcp客戶端代碼

import socket

def run_client():
“”“tcp客戶端代碼”""

  # 1、創建TCP套接字
  tcp_socket = socket.socket(socket.AF_INET,socket.SOCK_STREAM)

  # 2、創建鏈接服務器,一個元組:服務器的IP與端口
  server_ip = input("請輸入要連接的服務器ip:")
  server_port = int(input("請輸入要連接的服務器port:"))
  server_addr = (server_ip,server_port)
  tcp_socket.connect(server_addr)

  # 3、發送內容或者接收內容
  send_data = input("親輸入要發送的內容:")
  tcp_socket.send(send_data.encode("utf-8"))

  # 接收對方發送過來的數據,最大接收1024個字節
  recvData = tcp_socket.recv(1024)
  print('接收到的數據爲:', recvData.decode("utf-8"))

  # 4、關閉套接字
  tcp_socket.close()

if name == ‘main’:

  run_client()

五、tcp服務器端構建流程

5.1、服務器端主要有以下6步(固定)

(1)、socket創建一個套接字
(2)、bind綁定ip和port
(3)、listen使套接字變爲可以被動鏈接
(4)、accept等待客戶端的鏈接
(5)、recv/send接收發送數據
(6)、關閉套接字
5.2、socket創建一個tcp服務器套接字

tcp_server_socket = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
5.3、bind綁定ip和port

tcp_server_socket.bind((“服務器的ip”,服務器的端口))
5.4、改變服務器套接字由默認的主動變被動 listen

tcp_server_socket.listen(128)
5.5、等待別人發信息過來(等待客戶端的鏈接 accept)
如果有新的客戶端來鏈接服務器,那麼就產生一個新的套接字專門爲這個客戶端服務

new_client_socket:用來爲這個客戶端服務

tcp_server_socket:就可以省下來專門等待其他新客戶端的鏈接

new_client_socket,client_addr = tcp_server_socket.accept()
5.6、接收客戶端發送過來的數據

receive_data = new_client_socket.recv(1024)
print(receive_data.decode(“utf-8”))
5.7、回覆一部分數據給客戶端

new_client_socket.send(“哈哈哈哈😆😆😆😆😆😆”.encode(“utf-8”))
5.8、關閉套接字

new_client_socket.close()
tcp_server_socket.close()
5.9、完整的服務器端代碼

import socket

def run_server():

   # 1、創建tcp服務器套接字
   tcp_server_socket = socket.socket(socket.AF_INET,socket.SOCK_STREAM)

   # 2、綁定服務器信息
   tcp_server_socket.bind(("192.168.3.223",7890))

   # 3、改變服務器套接字由默認的主動變被動 listen
   tcp_server_socket.listen(128)

   print('---1---')

   # 4、等待別人發信息過來(等待客戶端的鏈接 accept)
   # 如果有新的客戶端來鏈接服務器,那麼就產生一個新的套接字專門爲這個客戶端服務
   # new_client_socket:用來爲這個客戶端服務
   # tcp_server_socket:就可以省下來專門等待其他新客戶端的鏈接
   new_client_socket,client_addr = tcp_server_socket.accept()

   print('---2---')

   # 5、接收客戶端發送過來的數據
   receive_data = new_client_socket.recv(1024)
   print(receive_data.decode("utf-8"))

   # 6、回覆一部分數據給客戶端
   new_client_socket.send("哈哈哈哈😆😆😆😆😆😆".encode("utf-8"))

   # 7、關閉套接字
   new_client_socket.close()
   tcp_server_socket.close()

if name == ‘main’:

  run_server()

六、tcp服務器循環爲多個客戶端服務(對上面tcp服務器代碼的改進)

6.1、分析:
在我們開啓tcp服務器後,服務器的套接字進入等待客戶端的狀態,當客戶端進行了連接服務器,服務器將爲其服務,等待客戶端的數據,接收到用戶的數據後爲其服務,服務完再等待客戶端的新的指令,服務器接到客戶端的數據後爲其服務,再次等待客戶端的數據,如果客戶端關閉了自己的套接字,服務器端將收到一個空數據,這時服務器也將關閉爲這個客戶端服務的套接字,一個客戶端與服務器的鏈接結束

6.2、完善後的代碼

import socket

def run_server():

 # 1、創建tcp服務器套接字
 tcp_server_socket = socket.socket(socket.AF_INET,socket.SOCK_STREAM)

 # 2、綁定服務器信息
 tcp_server_socket.bind(("192.168.3.6",7890))

 # 3、改變服務器套接字由默認的主動變被動 listen
 tcp_server_socket.listen(128)

 # 4、等待別人發信息過來(等待客戶端的鏈接 accept)
 # new_client_socket:用來爲這個客戶端服務
 # tcp_server_socket:就可以省下來專門等待其他新客戶端的鏈接

# 爲多個客戶端服務
while True:

      print("等待一個新的客戶端的到來")
      new_client_socket, client_addr = tcp_server_socket.accept()
      print("一個新的客戶端已經到來%s"%str(client_addr))

      # 循環的目的:爲同一個客戶端 服務多次
      while True:

            # 5、接收客戶端發送過來的請求數據
            receive_data = new_client_socket.recv(1024)
            print(receive_data.decode("utf-8"))

            # 如果recv解堵塞,那麼有2種方式:
            # 1、客戶端發送過來數據
            # 2、客戶端調用 close 導致了 這裏 recv 堵塞
            if receive_data:

                 # 6、回覆一部分數據給客戶端
                 new_client_socket.send("哈哈哈哈😆😆😆😆😆😆".encode("utf-8"))
                 print("給客戶端回覆了:哈哈哈哈😆😆😆😆😆😆")
            else:
                 print("客戶端關閉了自己的套接字或則斷開了鏈接")
                 break

       # 7、關閉套接字,關閉accept 返回的套接字 意味着 不會在爲這個客戶端服務
       new_client_socket.close()

# 如果將監聽套接字 關閉了,那麼會導致 不能再次等待新客戶端的到來,也就是 套接字.accept()會失敗
tcp_server_socket.close()

if name == ‘main’:

       run_server()

上面兩個while的作用:

第1個: 爲多個客戶端服務
第2個: 爲同一個客戶端 服務多次
七、TCP下載器:客戶端 與 服務器端(註釋都在代碼裏面)

7.1、客戶端

import socket

def main():

  # 1、創建tcp套接字
  tcp_client_socket = socket.socket(socket.AF_INET,socket.SOCK_STREAM)

  # 2.創建與服務器的鏈接
  server_ip = input("請輸入連接服務器的ip:")
  server_port = int(input("請輸入服務器對應的端口:"))
  tcp_client_socket.connect((server_ip,server_port))

  # 3、發送下載的文件名字給服務器
  file_name = input("請輸入要下載的文件名字:")
  tcp_client_socket.send(file_name.encode("utf-8"))

  # 4、接收服務器返回的文件的數據 ,接收一個小的試試
  receive_data = tcp_client_socket.recv(1024)

  # 5、判斷數據是否有(保存接收到的數據到一個文件夾中)
  if receive_data:

       with open("[new]"+file_name,'wb') as file:

             file.write(receive_data)

  # 6、關閉tcp套接字
  tcp_client_socket.close()

if name == ‘main’:

    main()

7.2、服務器

import socket

def exec_file(new_tcp_socket,client_addr):

   print("等待客戶端的指令")
   # 1、接收客戶端要下載的文件名
   file_name = new_tcp_socket.recv(1024).decode("utf-8")
   print("客戶端:%s 要下載的文件名是:%s"%(str(client_addr),file_name))

   # 2、在服務器找到這個文件,打開讀取,發送給客戶端
   file_data = None
   try:

       file = open(file_name,'rb')
       file_data = file.read()
       file.close()
   except:
       print("% 文件不存在" % file_name)

  # 3、發送文件內的數據給客戶端
  new_tcp_socket.send(file_data)

def server_down():

  # 1、創建服務器套接字
  tcp_server_socket = socket.socket(socket.AF_INET,socket.SOCK_STREAM)

  # 2、綁定本地的服務器與端口
  tcp_server_socket.bind(("192.168.3.6",7893))

  # 3、讓tcp套接字由主動變被動
  tcp_server_socket.listen(128)

  while True:

       print("等待客戶端的連接")

       # 4、等待客戶端的連接
       new_tcp_socket,client_addr = tcp_server_socket.accept()

       # 5、處理客戶端的內容
       exec_file(new_tcp_socket,client_addr)

       # 6.關閉爲這個客戶端服務的套接字
       new_tcp_socket.close()

  # 7.關閉監聽服務器套接字
  tcp_server_socket.close()

if name == ‘main’:

  server_down()

7.3、說一下 寫與讀文件的注意點,在往文件裏面寫內容的時候,文件可以不存在,可以自己根據文件名在當前目錄下創建,然後如果是讀取文件的內容的時候,文件是必須存在的纔可以進行讀取,否則會報錯,還有一個注意點是:with 的使用,with可以保證文件打開後,一定會關閉,如上面客戶端的代碼

with open("[new]"+file_name,‘wb’) as file:
file.write(receive_data)
八、tcp注意點

8.1、tcp服務器一般情況下都需要綁定,否則客戶端找不到這個服務器
8.2、tcp客戶端一般不綁定,因爲是主動鏈接服務器,所以只要確定好服務器的ip、port等信息就好,本地客戶端可以隨機
8.3、tcp服務器中通過listen可以將socket創建出來的主動套接字變爲被動的,這是做tcp服務器時必須要做的
8.4、當客戶端需要鏈接服務器時,就需要使用connect進行鏈接,udp是不需要鏈接的而是直接發送,但是tcp必須先鏈接,只有鏈接成功才能通信
8.5、當一個tcp客戶端連接服務器時,服務器端會有1個新的套接字,這個套接字用來標記這個客戶端,單獨爲這個客戶端服務
8.6、listen後的套接字是被動套接字,用來接收新的客戶端的鏈接請求的,而accept返回的新套接字是標記這個新客戶端的
8.7、關閉listen後的套接字意味着被動套接字關閉了,會導致新的客戶端不能夠鏈接服務器,但是之前已經鏈接成功的客戶端正常通信。
8.8、關閉accept返回的套接字意味着這個客戶端已經服務完畢
8.9、當客戶端的套接字調用close後,服務器端會recv解堵塞,並且返回的長度爲0,因此服務器可以通過返回數據的長度來區別客戶端是否已經下線

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章