天天看點

Python進階 網絡tcp

Python進階 網絡tcp

一. TCP介紹

1. tcp協定 
	TCP協定,傳輸控制協定(英語:Transmission Control Protocol,縮寫為 TCP)是一種面向連接配接的、可靠的、基于位元組流的通信協定
	 1. tcp是面向連接配接的,可靠的,基于位元組流的通信協定,好比打電話模型,先連接配接,再通信
	 2. udp是面向無連接配接的,不可靠,基于資料報的通信協定, 好比發短信模型,隻管發
	     資料報方式: 獨立的資料單元,包含目标主機的ip和端口和要發送的内容






ps: 協定是什麼

通信雙方的一種約定,一種規則,通信雙方都認同和遵守,且按照這種約定或規則,能實作雙方更好的,有條不紊的通信
           

二、采用tcp協定實作網絡通信(重點)

tcp通信方式 是嚴格的服務端與用戶端之分,而udp沒有
1. TCP用戶端實作
  編寫步驟:
    1. 建立tcp 的socket對象
    2. 與服務端建立連接配接
    3. 通信(發送與接收資料)
    4. 關閉socket對象,釋放連接配接
"""TCP用戶端實作
編寫步驟:
1. 建立tcp 的socket對象
2. 與服務端建立連接配接
3. 通信(發送與接收資料)
4. 關閉socket對象,釋放連接配接
"""
import socket




def main():

    # 1. 建立tcp 的socket對象

    tcp_client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)



    # 2. 與服務端建立連接配接
    # the address is a pair (host, port)
    # server_addr = ('192.168.110.66',8080) # 是元組類型, 第一個元素: ip(字元串) ,第二個 端口号(整數)
    server_addr = ('192.168.110.71', 9999)  # 是元組類型, 第一個元素: ip(字元串) ,第二個 端口号(整數)
    tcp_client_socket.connect(server_addr)

    # 3. 通信(發送與接收資料)
    # 3.1 發送資料到服務端
    # 準備要發送的資料
    send_data = input("請輸入要發送的資料:")

    # 字元串轉為二進制串
    data = send_data.encode('utf-8')
    tcp_client_socket.send(data)

    # 3.2 等待接收服務端回傳的消息, 阻塞狀态
    # recv_data=tcp_client_socket.recvfrom(1024) # 一次最多接收1024位元組
    recv_data = tcp_client_socket.recv(1024)  # 一次最多接收1024位元組

    # 顯示接收到的資料
    # 通過socket.recvfrom取得的資料 (b'www', None)
    # 通過socket.recv取得的資料 : b'uuu'
    # 解碼, 把二進制串轉為字元串
    # data=recv_data.decode('gbk')
    data = recv_data.decode('utf-8')
    print("接收到的資料:", data)

    # 4.關閉socket對象,釋放連接配接
    tcp_client_socket.close()




if name == 'main':

    main()  # alt+enter

  

2. tcp服務端實作
   編寫步驟:
    1. 建立服務端的socket,該socket用于監聽用戶端的請求
    2. 綁定端口
    3. 開啟監聽
    4. 等待接受用戶端的連接配接請求,一有請求,則立即接受,且建立一個新的socket,該socket是用于與用戶端通信的
    5. 通過新建立的socket與用戶端通信
    6. 關閉新建立的與用戶端通信的socket, 該socket關閉,則與用戶端通信結束,即不再為該用戶端服務
    7. 關閉用于監聽的socket,該socket關閉,則不再接收新的用戶端請求
"""tcp服務端實作
   編寫步驟:
    1. 建立服務端的socket,該socket用于監聽用戶端的請求
    2. 綁定端口
    3. 開啟監聽
    4. 等待接受用戶端的連接配接請求,一有請求,則立即接受,且建立一個新的socket,該socket是用于與用戶端通信的
    5. 通過新建立的socket與用戶端通信
    6. 關閉新建立的與用戶端通信的socket, 該socket關閉,則與用戶端通信結束,即不再為該用戶端服務
    7. 關閉用于監聽的socket,該socket關閉,則不再接收新的用戶端請求
"""
import socket




def main():

    # 1. 建立服務端的socket,該socket用于監聽用戶端的請求

    server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)



    # 2. 綁定端口
    server_socket.bind(('', 9999))

    # 3. 開啟監聽
    server_socket.listen(5)
    print("靜靜等待用戶端的請求")

    # 4.等待接受用戶端的連接配接請求,一有請求,則立即接受,且建立一個新的socket,該socket是用于與用戶端通信的
    # accept:  阻塞狀态
    # new_sock: 新建立的socket,是與用戶端通信的socket
    # client_addr: 來自用戶端的位址
    new_sock, client_addr = server_socket.accept()
    print("有新的用戶端請求,來自于-->", client_addr)

    # 5. 通信新建立的socket與用戶端通信
    # 5.1: 等待接收用戶端的請求消息
    recv_data = new_sock.recv(1024)
    # 解碼
    data = recv_data.decode('gbk')
    print("接收到的用戶端的資料:", data)

    # 5.2 回複消息給用戶端,發送消息給用戶端
    new_sock.send('歡迎光臨'.encode('utf-8'))

    # 6.關閉新建立的與用戶端通信的socket, 該socket關閉,則與用戶端通信結束
    new_sock.close()

    # 7.關閉用于監聽的socket,該socket關閉,則不再接收新的用戶端請求
    server_socket.close()




if name == 'main':

    main()
           
  1. tcp用戶端與服務端互動過程

    (結合圖了解)

三、tcp與udp深入認識

1. tcp通信的三個步驟
   - 建立連接配接
   - 傳輸資料
   - 關閉連接配接
  
2. TCP特點
	a>面向連接配接
	  - 1. tcp是面向連接配接的, 先連接配接,再通信
	  - 2. tcp的連接配接是一對一的
	       udp可以一對一,可以一對多 ,udp适合做廣播相關的程式
	  
	b>可靠傳輸 ,tcp比udp更可靠
	  - 發送應答機制
	  - 逾時重傳
	  - 錯誤校驗
	  - 流量控制| 阻塞管理

3. TCP與UDP的不同點
	面向連接配接
	有序資料傳輸
	重發丢失的資料包
	舍棄重複的資料包
	無差錯的資料傳輸
	阻塞/流量控制

4. tcp與upd應用場景:  
   1. udp: 面向無連接配接的,可一對一或一對多,相對tcp速度更快,實時性更好,耗資源更少,但穩定性、可靠性比tcp差 
   		- 多點通信,比如廣播通信 
   	    - 當對網絡通訊品質要求不高的時候,要求網絡通訊速度能盡量的快,更實時,使用UDP ,比如QQ語音,QQ視訊
   2. tcp: 面向連接配接,且一對一,比udp更穩定,可靠,适合通信品質要求較高的場景
      http傳輸,檔案傳輸,smtp等等,目前大部分的傳輸都會基于tcp協定傳輸
           

四、TCP網絡程式設計加強

1. 同時為多個用戶端提供服務

 	    while True:
	        # 等待接收客戶度的請求,一有請求,則立即響應,并且建立一個與用戶端對接的socket,實作與用戶端通信
	        new_sock, client_addr = server_socket.accept()


 2. 為同一個用戶端提供多次服務
 		 while True:
	        # 5. 通過新建立的socket與用戶端的socket實作通信
	        recv_data = new_sock.recv(1024)
	        print("收到的消息:",recv_data)
	        new_sock.send('歡迎光臨!'.encode('utf-8'))

 3. 如何判斷用戶端已經下線
 	當用戶端的套接字調用close後,服務端會recv解堵塞,并且傳回的長度為0(一個空的消息), 是以服務端可以通過傳回資料的長度來差別用戶端是否已經下線

 		    # 5.1 接收用戶端發送的消息
		    recv_data = new_sock.recv(1024)
		    if recv_data:
		        print("接收到的用戶端消息:", recv_data.decode('gbk'))
		        # 5.2 發送消息給用戶端
		        new_sock.send('歡迎光臨!'.encode('utf-8'))
		    else:
		        print("用戶端已經下線...")
		        break # 退出内部的循環,跳到外部的循環

 4. listen中數值的含義
 	server_socket.listen(5)  # backlog: 積壓未辦之事, 好比設定排隊隊列中的最大排隊人數

 5. 伺服器如何立即複用同一個端口而不會提示端口被占用

 	"""
 	    server_socket.setsockopt(level,option,value) :配置socket 
        level:等級,對哪個等級操作(ip,tcp,udp,socket等級)
        option:設定哪個選項
        		socket.SO_REUSEADDR: 複用位址 
        value: True:表示複用,False,表示不複用
    """
    server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, True)
    # 提示:socket.setsockopt()方法要在 socket.bind()之前設定
           

五、三次握手、四次揮手(了解)

a> 三次握手

		第一次握手: 用戶端A向服務端B發送連接配接請求(用戶端-->服務端方向)

		第二次握手: 服務端B向用戶端A發送确認連接配接,同時向用戶端A發送連接配接請求(服務端-->用戶端方向) 

		第三次握手: 用戶端A收到服務端的确認資訊,正确無誤後,再向服務端發送确認連接配接資訊



	b> 四次揮手
           

六、TCP網絡程式設計的幾個注意小點

1.tcp服務端一般情況下都需要綁定,否則用戶端找不到這個服務端
2.tcp用戶端一般不綁定,因為是主動連接配接服務端,是以隻要确定好服務端的ip、port等資訊就好,本地用戶端可以随機
3.tcp服務端中通過listen可以将socket建立出來的主動套接字變為被動的,這是做tcp服務端時必須要做的
4.當用戶端需要連接配接服務端時,就需要使用connect進行連接配接,udp是不需要連接配接的而是直接發送,但是tcp必須先連接配接,隻有連接配接成功才能通信
5.當一個tcp用戶端連接配接服務端時,服務端會有1個新的套接字,這個套接字用來标記這個用戶端,單獨為這個用戶端服務
6.listen後的套接字是被動套接字,用來接收新的用戶端的連接配接請求的,而accept傳回的新套接字是标記這個新用戶端的
7.關閉listen後的套接字意味着被動套接字關閉了,會導緻新的用戶端不能夠連接配接服務端,但是之前已經連接配接成功的用戶端正常通信。
8.關閉accept傳回的套接字意味着這個用戶端已經服務完畢
9.當用戶端的套接字調用close後,服務端會recv解堵塞,并且傳回的長度為0,是以服務端可以通過傳回資料的長度來差別用戶端是否已經下線
           

七、綜合案例:檔案下載下傳器

a>用戶端實作
	"""檔案下載下傳器用戶端實作流程
		1. 建立一個tcp socket
		2. 連接配接服務端
		3. 擷取使用者輸入的檔案名
		4. 把要下載下傳的檔案名發給服務端
		5. 等待接收服務端回傳的檔案内容
		6. 假如内容不為空,則寫入到檔案中
		7. 關閉tcp socket		
	"""
"""tcp用戶端實作
檔案下載下傳器用戶端實作流程
    1. 建立一個tcp socket
    2. 連接配接服務端
    3. 擷取使用者輸入的檔案名
    4. 把要下載下傳的檔案名發給服務端
    5. 等待接收服務端回傳的檔案内容
    6. 假如内容不為空,則寫入到檔案中
    7. 關閉tcp socket
"""
import socket




def main():

    # 1.1. 建立一個tcp socket

    tcp_client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)



    # 2. 連接配接服務端
    # tcp_client_socket.connect(('192.168.110.66', 8080))
    tcp_client_socket.connect(('192.168.110.71', 9999))

    # 3. 擷取使用者輸入的檔案名
    file_name = input("請輸入要下載下傳的檔案名:")

    # 4.把要下載下傳的檔案名發給服務端
    tcp_client_socket.send(file_name.encode())  # 不寫編碼,linux預設是utf-8編碼

    # 5.等待接收服務端回傳的檔案内容
    recv_data = tcp_client_socket.recv(1024*1024)

    print(len(recv_data))  # 測試接收到的資料的長度
    # 6. 假如内容不為空,則寫入到檔案中
    if recv_data:
        file = open("/home/python/Desktop/" + file_name, 'wb')
        file.write(recv_data)
        file.close()

    # 7.關閉tcp socket
    tcp_client_socket.close()




if name == 'main':

    main()

	

b>服務端實作
	""" 檔案下載下傳服務端實作流程
		1. 建立服務端的socket,用來做監聽的
		2. 綁定端口
		3. 開啟監聽 listen
		4. 循環接收用戶端的下載下傳請求
		5. 擷取用戶端發送過來的要下載下傳的檔案名
		6. 通過函數取得要下載下傳的檔案的内容
		7. 把内容回傳給用戶端
		8. 關閉與用戶端對接的socket
		9. 關閉服務端server_socket,不再接收新的下載下傳檔案的請求
	"""
""" 檔案下載下傳服務端實作流程
    1. 建立服務端的socket,用來做監聽的
    2. 綁定端口
    3. 開啟監聽 listen
    4. 循環接收用戶端的下載下傳請求
    5. 擷取用戶端發送過來的要下載下傳的檔案名
    6. 通過函數取得要下載下傳的檔案的内容
    7. 把内容回傳給用戶端
    8. 關閉與用戶端對接的socket
    9. 關閉服務端server_socket,不再接收新的下載下傳檔案的請求
"""
import socket




def get_file_content(file_name):

    """

    通過檔案名取得檔案内容

    :param file_name: 檔案名

    :return: 檔案的内容

    """

    try:

        file = open(file_name, 'rb')

        content = file.read()

        file.close()

        return content

    except:

        print("%s檔案名找不到" % file_name)



def main():

    # 1. 建立服務端的socket,用來做監聽的

    server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)



    # 複用端口
    server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, True)

    # 2. 綁定端口
    server_socket.bind(('', 9999))

    # 3. 開啟監聽 listen
    server_socket.listen(6)
    print("等待用戶端的請求:")

    # 4. 循環接收用戶端的下載下傳請求
    while True:
        new_sock, client_addr = server_socket.accept()

        # 5. 擷取用戶端發送過來的要下載下傳的檔案名
        recv_data = new_sock.recv(1024)
        # 解碼
        file_name = recv_data.decode('utf-8')
        print("用戶端要下載下傳的檔案名:%s,來自-->%s" % (file_name, str(client_addr)))

        # 6.通過函數取得要下載下傳的檔案的内容
        file_content = get_file_content(file_name)

        # 7. 把内容回傳給用戶端
        if file_content:
            new_sock.send(file_content)  # file_content已經是二進制串了

        # 8. 關閉與用戶端對接的socket
        new_sock.close()

    # 9. 關閉服務端server_socket,不再接收新的下載下傳檔案的請求
    server_socket.close()




if name == 'main':

    main()
           
上一篇: TCP&IP