乙個簡單的客戶單與服務端程式

2022-05-26 11:48:09 字數 4525 閱讀 7833

實驗環境是linux系統,效果如下:

1.啟動服務端程式,監聽在6666埠上

2.啟動客戶端,與服務端建立tcp連線

3.建立完tcp連線,在客戶端上向服務端傳送訊息

4.斷開連線

實現的功能很簡單,但是對於初來乍到的我費了不少勁,因此在此總結一下,如有錯點請各位大神指點指點

什麼是socket(插口):

這裡不用 "套接字" 而是用 "插口" 是因為在《tcp/ip協議卷二》中,翻譯時也是用 "插口" 來表示socket的。

"套接字" 這詞不知道又是哪個教授級人物造出來的,聽起來總是很怪,雖然可以避免語義上的歧義,但不明顯。

對插口通俗的理解就是:它是乙個可以用來輸入或者輸出的網路端,另一端也具有同樣相對應的操作。

具體其他高階的定義不是這裡的重點。值得說的是:

每個插口都可以標識某個程式通訊的一端,通過系統呼叫使得程式與網路裝置之間的交流連線起來。

應用程式 -> 系統呼叫 -> 插口層 -> 協議層 -> 介面層  ->傳送(接收的話與之相反)

如何標識乙個socket:

如上定義所述,可以通過位址,協議,埠三要素來確定乙個通訊端,而在linux c程式中使用 識別符號 來標識乙個

socket,unix系統對裝置的讀寫操作等同於對描述符的讀寫操作,識別符號可以用於:插口 管道 目錄 裝置 檔案等等

描述符是個正整數,事實上他是檢查表表項中的乙個下標,用於指向開啟檔案表的結構。

述符前三個識別符號0  1  2 分別系統保留:標準輸入(鍵盤),標準輸出(螢幕),標準錯誤輸出

當我們使用新的描述符來建立socket時,他一般從最小未使用的數字開始分配,也就是3

服務端實現的流程:

1.服務端開啟乙個socket(socket函式)

2.使用socket繫結乙個埠號(bind函式)

3.在這個埠號上開啟監聽功能(listen函式)

4.當有對端傳送連線請求,向其傳送ack+syn建立連線(accept函式)

5.接收或者回覆訊息(read函式 write函式)

客戶端實現流程:

1.開啟乙個socket

2.向指定的ip 和埠號發起連線(connect函式)

3.接收或者傳送訊息(send函式  recv函式)

如何併發處理:

如果按照以上流程實現其實並不難,但是有個缺陷,因為c語言是按順序單一流程執行,也就是說如果

直接在程式當中使用accept函式(建立連線)的話,那麼程式會阻塞在accept這裡,這是因為如果客戶端

一直沒有傳送connect連線,那麼accept就無法得知客戶端的ip和埠,也就只能一直等待(阻塞)直到

有請求觸發繼續執行為止,這樣就導致如果同時多個客戶向服務端傳送請求連線,那麼服務端只能按照

單一執行緒去處理第乙個客戶端,無法開啟多個執行緒同時處理多個使用者的請求。

如何解決:

下面摘文擷取網上的資料,有興趣者可以看看

系統提供select函式來實現多路復用輸入/輸出模型,該函式用於在非阻塞中,當乙個套接字或一組套接字有訊號時通知你

int select(int nfds, fd_set *readfds, fd_set *writefds, exceptfds, const struct timeval* timeout);
所在的標頭檔案為:

#include #include
功能:測試指定的fd是否可讀,可寫 或者 是否有異常條件待處理

readset  用來檢查可讀性的一組檔案描述字。

writeset 用來檢查可寫性的一組檔案描述字。

exceptset用來檢查是否有異常條件出現的檔案描述字。(注:不包括錯誤)

timeout  用於描述一段時間長度,如果在這個時間內,需要監視的描述符沒有事件發生則函式返回,返回值為0。

對於select函式的功能簡單的說就是對檔案fd做乙個測試。測試結果有三種可能:

返回值:

返回對應位仍然為1的fd的總數。注意啦:只有那些可讀,可寫以及有異常條件待處理的fd位仍然為1。

否則為0哦。舉個例子,比如recv(), 在沒有資料到來呼叫它的時候,你的執行緒將被阻塞,如果資料一直不來,

你的執行緒就要阻塞很久.這樣顯然不好。所以採用select來檢視套節字是否可讀(也就是是否有資料讀了) 。

現在,unix系統通常會在標頭檔案中定義常量fd_setsize,它是資料型別fd_set的描述字數量,

其值通常是1024,這樣就能表示<1024的fd。

fd_set結構體:

檔案描述符集合,用於存放多個fd(檔案描述符,這裡就是套接字)

可以存放服務端的fd,有客戶端的fd。下面是對這個檔案描述符集合的操作:

fd_zero(*fds):     將fds設為空集

fd_clr(fd,*fds): 從集合fds中刪除指定的fd

fd_set(fd,*fds): 從集合fds中新增指定的fd

fd_isset(fd,*fds): 判斷fd是否屬於fds的集合

步驟如下

socket s;

.....

fd_set set;

while(1)

//do something here

}

假設fd_set長度為1位元組,fd_set中的每一位可以對應乙個檔案描述符,那麼1位元組最大可以對應8個fd

(1)執行fd_set set; fd_zero(&set);  則set用位為0000,0000。

(2)若fd=5,執行fd_set(fd,&set); 後set變為 0001,0000(第5位置為1)

(3)若再加入fd=2,fd=1 則set變為 0001,0011

(4)執行select(6,&set,0,0,0) 阻塞等待

(5)若fd=1,fd=2 上都發生可讀事件,則select返回,此時set變為0000,0011。注意:沒有事件發生的fd=5被清空。

1.可監控描述符的個數取決與sizeof(fd_set)的值

2.檔案描述符的上限可以修改

3.將fd加入select監控集時,還需要乙個array陣列儲存所有值

因為每次select掃瞄之後,有訊號的fd在集合中應被保留,但select將集合清空

因此array陣列可以將活躍的fd存放起來,方便下次加入fd集合中

對集合fe_set與array進行遍歷儲存,即所有fd都重新加入fd_set集合中

另外活躍狀態在array中的值是1,非活躍狀態的值是0

4.具體過程看**會好理解

使用select函式的過程一般是:

先呼叫巨集fd_zero將指定的fd_set清零,然後呼叫巨集fd_set將需要測試的fd加入fd_set,

接著呼叫函式select測試fd_set中的所有fd,最後用巨集fd_isset檢查某個fd在函式select呼叫後,相應位是否仍然為1

複製貼上的摘文排版起來真的是痛苦,我已經盡力排版了。。。

客戶端:

#include #include 

#include

#include

#include

#include

#include

in.h>#include

#include

#include

#include

#define remote_port 6666        //

伺服器端口

#define remote_addr "127.0.0.1"    //

伺服器位址

intmain()

}

服務端:

#include #include 

#include

#include

#include

#include

#include

in.h>#include

#include

#define local_port 6666      //

本地服務埠

#define max 5            //

最大連線數量

intmain()

else

else}}

}}}

乙個簡單的服務端與客戶端TCP通訊

乙個簡單的服務端與客戶端tcp通訊 from socket import ip port 10.33.0.75 8000 定義建立連線的ip位址與ip埠 back log 5 設定連線池為2 buffer size 1024 設定每次傳送與接收的資料大小 tcp sever socket af in...

乙個簡單的TCP客戶端 服務端會話程式 C

服務端 1.new 乙個tcplistener listner 2.listner.start 3.while true 4.獲取socket socket s listner.acceptsocket 5.獲取netstream networkstream ns new networkstream...

iOS 乙個HTTPS連線的客戶端與服務端互動過程

答 具體經歷了一下8步 客戶端打包請求。其中包括url 埠 賬號和密碼等。使用賬號和密碼登陸應該用的是post方式,所以相關的使用者資訊會被載入到body中。這個請求應該包含3個方面 網路位址 協議和資源路徑。注意 這裡用的是https,即http ssl tls,在http上又加了一層處理加密資訊...