網絡編程

三次握手:


TCP 
在中,提供可靠的連接服務,採用三次握手建立一個連接.
第一次握手:建立連接時,客戶端發送syn包(syn=j)到,並進入SYN_SEND狀態,等待服務器確認; 
SYN:同步序列編號(Synchronize Sequence Numbers)
第二次握手:服務器收到syn包,必須確認客戶的SYN(ack=j+1),同時自己也發送一個SYN包(syn=k),即SYN+ACK包,此時服務器進入狀態; 
第三次握手:客戶端收到服務器的SYN+ACK包,向服務器發送確認包ACK(ack=k+1),此包發送完畢,客戶端和服務器進入ESTABLISHED狀態,完成三次握手.

完成三次握手,客戶端與服務器開始傳送數據


blob.png


       (1)第一次握手:Client將標誌位SYN置爲1,隨機產生一個值seq=J,並將該數據包發送給Server,Client進入SYN_SENT狀態,等待Server確認。
        (2)第二次握手:Server收到數據包後由標誌位SYN=1知道Client請求建立連接,Server將標誌位SYN和ACK都置爲1,ack=J+1,隨機產生一個值seq=K,並將該數據包發送給Client以確認連接請求,Server進入SYN_RCVD狀態。
        (3)第三次握手:Client收到確認後,檢查ack是否爲J+1,ACK是否爲1,如果正確則將標誌位ACK置爲1,ack=K+1,並將該數據包發送給Server,Server檢查ack是否爲K+1,ACK是否爲1,如果正確則連接建立成功,Client和Server進入ESTABLISHED狀態,完成三次握手,隨後Client與Server之間可以開始傳輸數據了。
        
        SYN***:
                在三次握手過程中,Server發送SYN-ACK之後,收到Client的ACK之前的TCP連接稱爲半連接(half-open connect),此時Server處於SYN_RCVD狀態,當收到ACK後,Server轉入ESTABLISHED狀態。SYN***就是Client在短時間內僞造大量不存在的IP地址,並向Server不斷地發送SYN包,Server回覆確認包,並等待Client的確認,由於源地址是不存在的,因此,Server需要不斷重發直至超時,這些僞造的SYN包將產時間佔用未連接隊列,導致正常的SYN請求因爲隊列滿而被丟棄,從而引起網絡堵塞甚至系統癱瘓。SYN***時一種典型的DDOS***,檢測SYN***的方式非常簡單,即當Server上有大量半連接狀態且源IP地址是隨機的,則可以斷定遭到SYN***了,使用如下命令可以讓之現行: #netstat -nap | grep SYN_RECV




四次揮手
         所謂四次揮手(Four-Way Wavehand)即終止TCP連接,就是指斷開一個TCP連接時,需要客戶端和服務端總共發送4個包以確認連接的斷開。在socket編程中,這一過程由客戶端或服務端任一方執行close來觸發

blob.png

 由於TCP連接時全雙工的,因此,每個方向都必須要單獨進行關閉,這一原則是當一方完成數據發送任務後,發送一個FIN來終止這一方向的連接,收到一個FIN只是意味着這一方向上沒有數據流動了,即不會再收到數據了,但是在這個TCP連接上仍然能夠發送數據,直到這一方向也發送了FIN。首先進行關閉的一方將執行主動關閉,而另一方則執行被動關閉,上圖描述的即是如此。
       (1)第一次揮手:Client發送一個FIN,用來關閉Client到Server的數據傳送,Client進入FIN_WAIT_1狀態

      (2)第二次揮手:Server收到FIN後,發送一個ACK給Client,確認序號爲收到序號+1(與SYN相同,一個FIN佔用一個序號),Server進入CLOSE_WAIT狀態
      (3)第三次揮手:Server發送一個FIN,用來關閉Server到Client的數據傳送,Server進入LAST_ACK狀態

       (4)第四次揮手:Client收到FIN後,Client進入TIME_WAIT狀態,接着發送一個ACK給Server,確認序號爲收到序號+1Server進入CLOSED狀態,完成四次揮手。



任何一種通信協議都必須包含兩部分:

        1 報頭:必須是固定長度(如果不固定長度,會有粘包現象)
        2 數據: 數據可以用字典的形式來傳.比如 數據的名字,大小,內容,描述

標識地址的方式


ip+mac就能標識全世界範圍內獨一無二的一臺計算機
ip+mac+port就能標識全世界範圍內獨一無二的一個基於網絡通信的應用軟件
url地址:標識全世界範圍內獨一無二的一個資源

DHCP 默認端口是 67 
DNS 默認端口  53

爲何建立連接要三次而斷開連接卻需要四次


三次握手是爲了建立連接,建立連接時並沒有數據產生
        四次揮手斷開連接是因爲客戶端與服務端已經產生了數據交互,
                     這時客戶端發送請求只斷開了客戶端與服務端的連接,
                     而服務端說不定還有別的數據沒有傳送完畢,所有一定要四次


爲何tcp協議是可靠協議,而udp協議是不可靠協議



tcp調用的操作系統,操作系統發出數據,接受到對方傳來的確認信息時纔會清空數據
        優點: 數據安全      缺點: 工作效率低
udp是直接發送, 發完就刪
        優點: 效率高        缺點: 數據不安全




爲何tcp協議會有粘包問題? 
因爲tcp想優化效率,裏面有個叫nagle算法.這個算法規定了tcp協議在傳輸數據的時候會將數據較小,傳輸間隔較短的多條數據合併成一條發送
而tcp是通過操作系統來發送數據的,操作系統想什麼時候發就什麼時候發,應用層管不到操作系統, tcp把數據交給操作系統是告訴了操作系統一件事,讓操作系統把數據較小,傳輸間隔較短的多條數據合併成一條發送.就造成了粘包現象



struct模塊

blob.png


blob.png



模擬ssh遠程執行命令

客戶端:




from socket import *

import struct

import json


client = socket(AF_INET, SOCK_STREAM)

client.connect(('127.0.0.1', 8081))


# 通信循環

while True:

    cmd=input('>>: ').strip()

    if len(cmd) == 0:continue

    client.send(cmd.encode('utf-8'))

    #1. 先收4bytes,解出報頭的長度

    header_size=struct.unpack('i',client.recv(4))[0]


    #2. 再接收報頭,拿到header_dic

    header_bytes=client.recv(header_size)

    header_json=header_bytes.decode('utf-8')

    header_dic=json.loads(header_json)

    print(header_dic)

    total_size=header_dic['total_size']


    #3. 接收真正的數據

    cmd_res=b''

    recv_size=0

    while recv_size < total_size:

        data=client.recv(1024)

        recv_size+=len(data)

        cmd_res+=data


    print(cmd_res.decode('gbk'))


client.close()




服務端:



# 服務端必須滿足至少三點:

# 1. 綁定一個固定的ip和port

# 2. 一直對外提供服務,穩定運行

# 3. 能夠支持併發

from socket import *

import subprocess

import struct

import json


server = socket(AF_INET, SOCK_STREAM)

server.bind(('127.0.0.1', 8081))

server.listen(5)


# 鏈接循環

while True:

    conn, client_addr = server.accept()

    print(client_addr)


    # 通信循環

    while True:

        try:

            cmd = conn.recv(1024)  # cmd=b'dir'

            if len(cmd) == 0: break  # 針對linux系統

            obj = subprocess.Popen(cmd.decode('utf-8'),

                                   shell=True,

                                   stdout=subprocess.PIPE,

                                   stderr=subprocess.PIPE

                                   )

            stdout = obj.stdout.read()

            stderr = obj.stderr.read()

            # 1. 先製作報頭

            header_dic = {

                'filename': 'a.txt',

                'md5': 'asdfasdf123123x1',

                'total_size': len(stdout) + len(stderr)

            }

            header_json = json.dumps(header_dic)

            header_bytes = header_json.encode('utf-8')


            # 2. 先發送4個bytes(包含報頭的長度)

            conn.send(struct.pack('i', len(header_bytes)))

            # 3  再發送報頭

            conn.send(header_bytes)


            # 4. 最後發送真實的數據

            conn.send(stdout)

            conn.send(stderr)

        except ConnectionResetError:

            break


    conn.close()


server.close()









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