1. 程式人生 > 實用技巧 >Delphi讀寫ini檔案(C++Builder類似)

Delphi讀寫ini檔案(C++Builder類似)

一 軟體開發架構

我們瞭解的涉及到兩個程式之間通訊的應用大致可以分為兩種:

第一種是應用類:qq、微信、網盤、優酷這一類是屬於需要安裝的桌面應用

第二種是web類:比如百度、知乎、部落格園等使用瀏覽器訪問就可以直接使用的應用

這些應用的本質其實都是兩個程式之間的通訊。而這兩個分類又對應了兩個軟體開發的架構~

1 C/S架構

C/S即:Client與Server ,中文意思:客戶端與伺服器端架構,這種架構也是從使用者層面(也可以是物理層面)來劃分的。

這裡的客戶端一般泛指客戶端應用程式EXE,程式需要先安裝後,才能執行在使用者的電腦上,對使用者的電腦作業系統環境依賴較大。

2 B/S架構

B/S即:Browser與Server,中文意思:瀏覽器端與伺服器端架構,這種架構是從使用者層面來劃分的。

Browser瀏覽器,其實也是一種Client客戶端,只是這個客戶端不需要大家去安裝什麼應用程式,只需在瀏覽器上通過HTTP請求伺服器端相關的資源(網頁資源),客戶端Browser瀏覽器就能進行增刪改查。

二 網路基礎

1 socket概念

socket層

理解socket

Socket是應用層與TCP/IP協議族通訊的中間軟體抽象層,它是一組介面。在設計模式中,Socket其實就是一個門面模式,它把複雜的TCP/IP協議族隱藏在Socket介面後面,對使用者來說,一組簡單的介面就是全部,讓Socket去組織資料,以符合指定的協議。

2 套接字(socket)的發展史

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

基於檔案型別的套接字家族

套接字家族的名字:AF_UNIX

unix一切皆檔案,基於檔案的套接字呼叫的就是底層的檔案系統來取資料,兩個套接字程序執行在同一機器,可以通過訪問同一個檔案系統間接完成通訊

基於網路型別的套接字家族

套接字家族的名字:AF_INET

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

3 tcp協議和udp協議

TCP

(Transmission Control Protocol)可靠的、面向連線的協議(eg:打電話)、傳輸效率低全雙工通訊(傳送快取&接收快取)、面向位元組流。使用TCP的應用:Web瀏覽器;電子郵件、檔案傳輸程式。

UDP(User Datagram Protocol)不可靠的、無連線的服務,傳輸效率高(傳送前時延小),一對一、一對多、多對一、多對多、面向報文,盡最大努力服務,無擁塞控制。使用UDP的應用:域名系統 (DNS);視訊流;IP語音(VoIP)。

我知道說這些你們也不懂,直接上圖。

三 套接字(socket)初使用

1 基於tcp協議的socket

tcp是基於連結的,必須先啟動服務端,然後再啟動客戶端去連結服務端

server端

import socket
sk = socket.socket()
sk.bind(('127.0.0.1',8898))  #把地址繫結到套接字
sk.listen()          #監聽連結
conn,addr = sk.accept() #接受客戶端連結
ret = conn.recv(1024)  #接收客戶端資訊
print(ret)       #列印客戶端資訊
conn.send(b'hi')        #向客戶端傳送資訊
conn.close()       #關閉客戶端套接字
sk.close()        #關閉伺服器套接字(可選)

client端

import socket
sk = socket.socket()           # 建立客戶套接字
sk.connect(('127.0.0.1',8898))    # 嘗試連線伺服器
sk.send(b'hello!')
ret = sk.recv(1024)         # 對話(傳送/接收)
print(ret)
sk.close()            # 關閉客戶套接字

2 基於udp協議的socket

udp是無連結的,啟動服務之後可以直接接受訊息*,*不需要提前建立連結

server端

import socket
udp_sk = socket.socket(type=socket.SOCK_DGRAM)   #建立一個伺服器的套接字
udp_sk.bind(('127.0.0.1',9000))        #繫結伺服器套接字
msg,addr = udp_sk.recvfrom(1024)
print(msg)
udp_sk.sendto(b'hi',addr)                 # 對話(接收與傳送)
udp_sk.close()                         # 關閉伺服器套接字

client端

import socket
ip_port=('127.0.0.1',9000)
udp_sk=socket.socket(type=socket.SOCK_DGRAM)
udp_sk.sendto(b'hello',ip_port)
back_msg,addr=udp_sk.recvfrom(1024)
print(back_msg.decode('utf-8'),addr)

四 粘包問題

1 什麼是粘包

須知:只有TCP有粘包現象,UDP永遠不會粘包,為何,且聽我娓娓道來

首先需要掌握一個socket收發訊息的原理

傳送端可以是一K一K地傳送資料,而接收端的應用程式可以兩K兩K地提走資料,當然也有可能一次提走3K或6K資料,或者一次只提走幾個位元組的資料,也就是說,應用程式所看到的資料是一個整體,或說是一個流(stream),一條訊息有多少位元組對應用程式是不可見的,因此TCP協議是面向流的協議,這也是容易出現粘包問題的原因。而UDP是面向訊息的協議,每個UDP段都是一條訊息,應用程式必須以訊息為單位提取資料,不能一次提取任意位元組的資料,這一點和TCP是很不同的。怎樣定義訊息呢?可以認為對方一次性write/send的資料為一個訊息,需要明白的是當對方send一條資訊的時候,無論底層怎樣分段分片,TCP協議層會把構成整條訊息的資料段排序完成後才呈現在核心緩衝區。

例如基於tcp的套接字客戶端往服務端上傳檔案,傳送時檔案內容是按照一段一段的位元組流傳送的,在接收方看了,根本不知道該檔案的位元組流從何處開始,在何處結束

所謂粘包問題主要還是因為接收方不知道訊息之間的界限,不知道一次性提取多少位元組的資料所造成的。

此外,傳送方引起的粘包是由TCP協議本身造成的,TCP為提高傳輸效率,傳送方往往要收集到足夠多的資料後才傳送一個TCP段。若連續幾次需要send的資料都很少,通常TCP會根據優化演算法把這些資料合成一個TCP段後一次傳送出去,這樣接收方就收到了粘包資料。

  1. TCP(transport control protocol,傳輸控制協議)是面向連線的,面向流的,提供高可靠性服務。收發兩端(客戶端和伺服器端)都要有一一成對的socket,因此,傳送端為了將多個發往接收端的包,更有效的發到對方,使用了優化方法(Nagle演算法),將多次間隔較小且資料量小的資料,合併成一個大的資料塊,然後進行封包。這樣,接收端,就難於分辨出來了,必須提供科學的拆包機制。 即面向流的通訊是無訊息保護邊界的。
  2. UDP(user datagram protocol,使用者資料報協議)是無連線的,面向訊息的,提供高效率服務。不會使用塊的合併優化演算法,, 由於UDP支援的是一對多的模式,所以接收端的skbuff(套接字緩衝區)採用了鏈式結構來記錄每一個到達的UDP包,在每個UDP包中就有了訊息頭(訊息來源地址,埠等資訊),這樣,對於接收端來說,就容易進行區分處理了。 即面向訊息的通訊是有訊息保護邊界的。
  3. tcp是基於資料流的,於是收發的訊息不能為空,這就需要在客戶端和服務端都新增空訊息的處理機制,防止程式卡住,而udp是基於資料報的,即便是你輸入的是空內容(直接回車),那也不是空訊息,udp協議會幫你封裝上訊息頭,實驗略

udp的recvfrom是阻塞的,一個recvfrom(x)必須對唯一一個sendinto(y),收完了x個位元組的資料就算完成,若是y>x資料就丟失,這意味著udp根本不會粘包,但是會丟資料,不可靠

tcp的協議資料不會丟,沒有收完包,下次接收,會繼續上次繼續接收,己端總是在收到ack時才會清除緩衝區內容。資料是可靠的,但是會粘包。

兩種情況下會發生粘包。

傳送端需要等緩衝區滿才傳送出去,造成粘包(傳送資料時間間隔很短,資料了很小,會合到一起,產生粘包)

接收方不及時接收緩衝區的包,造成多個包接收(客戶端傳送了一段資料,服務端只收了一小部分,服務端下次再收的時候還是從緩衝區拿上次遺留的資料,產生粘包)

2 粘包問題解決方式

我們可以把報頭做成字典,字典裡包含將要傳送的真實資料的詳細資訊,然後json序列化,然後用struck將序列化後的資料長度打包成4個位元組(4個自己足夠用了)

傳送時:

先發報頭長度

再編碼報頭內容然後傳送

最後發真實內容

接收時:

先手報頭長度,用struct取出來

根據取出的長度收取報頭內容,然後解碼,反序列化

從反序列化的結果中取出待取資料的詳細資訊,然後去取真實的資料內容

server端

import socket
import struct
import json
import os

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

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

server.listen()

while True:
    conn, adress = server.accept()
    print(conn)
    while True:
        try:
            msg = conn.recv(1024).decode('utf-8')
            cmd, path_file = msg.split()
            if len(cmd) == 0:
                break
            if cmd == 'get':
                # 先只做報頭
                header_dic = {
                    'total_size': os.path.getsize(path_file),
                    'file_name': os.path.basename(path_file)  # 檔案型別
                }
                # 先傳報頭的大小
                # 將報頭轉成bytes
                header_dic_json = json.dumps(header_dic)
                header_dic_bytes = header_dic_json.encode('utf-8')

                # 為了防止報頭產生客戶端的快取區的遺留,需要先傳報頭大小
                header_size = struct.pack('i', len(header_dic_bytes))
                conn.send(header_size)

                # 再發送報頭,客戶端進行迴圈讀取
                conn.send(header_dic_bytes)

                # 客戶端會進行拆包,得到檔案大小,以及檔案型別,此時直接傳送檔案即可
                with open(r'%s' % path_file, 'rb')as f:
                    for line in f:
                        conn.send(line)
        except Exception:
            break
        conn.close()

client端

import socket
import struct
import json

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

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

while True:
    cmd = input('>>>>:')
    client.send(cmd.encode('utf-8'))

    # 先接收報頭的大小
    header_size = client.recv(4)
    header_size = struct.unpack('i', header_size)[0]
    header_recv = 0
    header_res = b''

    while header_recv < header_size:
        header_data = client.recv(1024)
        header_res += header_data
        header_recv += len(header_data)
    header_dic = json.loads(header_res.decode('utf-8'))
    print(header_dic)

    total_size = header_dic['total_size']
    file_name = header_dic['file_name']

    file_recv = 0
    with open(r'%s' % file_name, 'wb')as f:
         while file_recv < total_size:
            file_data = client.recv(1024)
            f.write(file_data)
            file_recv += len(file_data)
    client.close()