1. 程式人生 > >《TCP/IP網路程式設計》第一章:理解網路程式設計和套接字 筆記

《TCP/IP網路程式設計》第一章:理解網路程式設計和套接字 筆記

第一章:理解網路程式設計和套接字

本章程式碼,在TCP-IP-NetworkNote中可以找到。

1.1 理解網路程式設計和套接字

1.1.1構建打電話套接字

以電話機打電話的方式來理解套接字。

呼叫 socket 函式(安裝電話機)時進行的對話

問:接電話需要準備什麼?

答:當然是電話機。

有了電話機才能安裝電話,於是就要準備一個電話機,下面函式相當於電話機的套接字。

#include <sys/socket.h>
int socket(int domain, int type, int protocol);
//成功時返回檔案描述符,失敗時返回-1

呼叫 bind 函式(分配電話號碼)時進行的對話

問:請問我的電話號碼是多少

答:我的電話號碼是123-1234

套接字同樣如此。就想給電話機分配電話號碼一樣,利用以下函式給建立好的套接字分配地址資訊(IP地址和埠號):

#include <sys/socket.h>
int bind(int sockfd, struct sockaddr *myaddr, socklen_t addrlen);
//成功時返回0,失敗時返回-1

呼叫 bind 函式給套接字分配地址之後,就基本完成了所有的準備工作。接下來是需要連線電話線並等待來電。

呼叫 listen 函式(連線電話線)時進行的對話

問:已架設完電話機後是否只需連結電話線?

答:對,只需要連線就能接聽電話。

一連線電話線,電話機就可以轉換為可接聽狀態,這時其他人可以撥打電話請求連線到該機。同樣,需要把套接字轉化成可接受連線狀態。

#include <sys/socket.h>
int listen(int sockfd, int backlog);
//成功時返回0,失敗時返回-1

連線好電話線以後,如果有人撥打電話就響鈴,拿起話筒才能接聽電話。

呼叫 accept 函式(拿起話筒)時進行的對話

問:電話鈴響了,我該怎麼辦?

答:接聽啊。

#include
<sys/socket.h>
int accept(int sockfd,struct sockaddr *addr,socklen_t *addrlen); //成功時返回檔案描述符,失敗時返回-1

網路程式設計中和接受連線請求的套接字建立過程可整理如下:

  1. 第一步:呼叫 socket 函式建立套接字。
  2. 第二步:呼叫 bind 函式分配IP地址和埠號。
  3. 第三步:呼叫 listen 函式轉換為可接受請求狀態。
  4. 第四步:呼叫 accept 函式受理套接字請求。

1.1.2 編寫Hello World套接字程式

服務端

伺服器端(server)是能夠受理連線請求的程式。下面構建服務端以驗證之前提到的函式呼叫過程,該伺服器端收到連線請求後向請求者返回Hello World!答覆。除各種函式的呼叫順序外,我們還未涉及任何實際程式設計。因此,閱讀程式碼時請重點關注套接字相關的函式呼叫過程,不必理解全過程。

伺服器端程式碼請參見:hello_server.c

客戶端

客戶端程式只有呼叫 socket 函式建立套接字呼叫 connect 函式向服務端傳送連線請求這兩個步驟,下面給出客戶端,需要檢視以下兩方面的內容:

  1. 呼叫 socket 函式 和 connect 函式
  2. 與服務端共同執行以收發字串資料

客戶端程式碼請參見:hello_client.c

編譯

分別對客戶端和服務端程式進行編譯:

gcc hello_server.c -o hserver
gcc hello_client.c -o hclient

執行

./hserver 9190
./hclient 127.0.0.1 9190

執行的時候,首先再 9190 埠啟動服務,然後 heserver 就會一直等待客戶端進行響應,當客戶端監聽位於本地的 IP 為 127.0.0.1 的地址的9190埠時,客戶端就會收到服務端的迴應,輸出Hello World!

1.2 基於 Linux 的檔案操作

討論套接字的過程中突然談及檔案也許有些奇怪。但是對於 Linux 而言,socket 操作與檔案操作沒有區別,因而有必要詳細瞭解檔案。在 Linux 世界裡,socket 也被認為是檔案的一種,因此在網路資料傳輸過程中自然可以使用 I/O 的相關函式。Windows 與 Linux 不同,是要區分 socket 和檔案的。因此在 Windows 中需要呼叫特殊的資料傳輸相關函式。

1.2.1 底層訪問和檔案描述符

分配給標準輸入輸出及標準錯誤的檔案描述符。

檔案描述符 物件
0 標準輸入:Standard Input
1 標準輸出:Standard Output
2 標準錯誤:Standard Error

檔案和套接字一般經過建立過程才會被分配檔案描述符。

檔案描述符也被稱為「檔案控制代碼」,但是「控制代碼」主要是 Windows 中的術語。因此,在本書中如果設計 Windows 平臺將使用「控制代碼」,如果是 Linux 將使用「描述符」。

1.2.2 開啟檔案:

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int open(const char *path, int flag);
/*
成功時返回檔案描述符,失敗時返回-1
path : 檔名的字串地址
flag : 檔案開啟模式資訊
*/

檔案開啟模式如下表:

開啟模式 含義
O_CREAT 必要時建立檔案
O_TRUNC 刪除全部現有資料
O_APPEND 維持現有資料,儲存到其後面
O_RDONLY 只讀開啟
O_WRONLY 只寫開啟
O_RDWR 讀寫開啟

1.2.3 關閉檔案:

#include <unistd.h>
int close(int fd);
/*
成功時返回 0 ,失敗時返回 -1
fd : 需要關閉的檔案或套接字的檔案描述符
*/

若呼叫此函式同時傳遞檔案描述符引數,則關閉(終止)響應檔案。另外需要注意的是,此函式不僅可以關閉檔案,還可以關閉套接字。再次證明了「Linux 作業系統不區分檔案與套接字」的特點。

1.2.4 將資料寫入檔案:

#include <unistd.h>
ssize_t write(int fd, const void *buf, size_t nbytes);
/*
成功時返回寫入的位元組數 ,失敗時返回 -1
fd : 顯示資料傳輸物件的檔案描述符
buf : 儲存要傳輸資料的緩衝值地址
nbytes : 要傳輸資料的位元組數
*/

在此函式的定義中,size_t 是通過 typedef 宣告的 unsigned int 型別。對 ssize_t 來說,ssize_t 前面多加的 s 代表 signed ,即 ssize_t 是通過 typedef 宣告的 signed int 型別。

建立新檔案並儲存資料:

程式碼見:low_open.c

編譯執行:

gcc low_open.c -o lopen
./lopen

然後會生成一個data.txt的檔案,裡面有Let's go!

1.2.5 讀取檔案中的資料:

與之前的write()函式相對應,read()用來輸入(接收)資料。

#include <unistd.h>
ssize_t read(int fd, void *buf, size_t nbytes);
/*
成功時返回接收的位元組數(但遇到檔案結尾則返回 0),失敗時返回 -1
fd : 顯示資料接收物件的檔案描述符
buf : 要儲存接收的資料的緩衝地址值。
nbytes : 要接收資料的最大位元組數
*/

下面示例通過 read() 函式讀取 data.txt 中儲存的資料。

程式碼見:low_read.c

編譯執行:

gcc low_read.c -o lread
./lread

在上一步的 data.txt 檔案與沒有刪的情況下,會輸出:

file descriptor: 3
file data: Let's go!

關於檔案描述符的 I/O 操作到此結束,要明白,這些內容同樣適合於套接字。

1.2.6 檔案描述符與套接字

下面將同時建立檔案和套接字,並用整數型態比較返回的檔案描述符的值.

程式碼見:fd_seri.c

編譯執行

gcc fd_seri.c -o fds
./fds

輸出結果:

file descriptor 1: 3
file descriptor 2: 15
file descriptor 3: 16

1.3 基於 Windows 平臺的實現

暫略

1.4 基於 Windows 的套接字相關函式及示例

暫略

1.5 習題

❗️以下部分的答案,僅代表我個人觀點,可能不是正確答案

  1. 套接字在網路程式設計中的作用是什麼?為何稱它為套接字?

    答:作業系統會提供「套接字」(socket)的部件,套接字是網路資料傳輸用的軟體裝置。因此,「網路程式設計」也叫「套接字程式設計」。「套接字」就是用來連線網路的工具。

  2. 在伺服器端建立套接字以後,會依次呼叫 listen 函式和 accept 函式。請比較二者作用。

    答:呼叫 listen 函式將套接字轉換成可受連線狀態(監聽),呼叫 accept 函式受理連線請求。如果在沒有連線請求的情況下呼叫該函式,則不會返回,直到有連線請求為止。

  3. Linux 中,對套接字資料進行 I/O 時可以直接使用檔案 I/O 相關函式;而在 Windows 中則不可以。原因為何?

    答:暫略。

  4. 建立套接字後一般會給他分配地址,為什麼?為了完成地址分配需要呼叫哪個函式?

    答:套接字被建立之後,只有為其分配了IP地址和埠號後,客戶端才能夠通過IP地址及埠號與伺服器端建立連線,需要呼叫 bind 函式來完成地址分配。

  5. Linux 中的檔案描述符與 Windows 的控制代碼實際上非常類似。請以套接字為物件說明它們的含義。

答:暫略。

  1. 底層 I/O 函式與 ANSI 標準定義的檔案 I/O 函式有何區別?

    答:檔案 I/O 又稱為低階磁碟 I/O,遵循 POSIX 相關標準。任何相容 POSIX 標準的作業系統上都支援檔案I/O。標準 I/O 被稱為高階磁碟 I/O,遵循 ANSI C 相關標準。只要開發環境中有標準 I/O 庫,標準 I/O 就可以使用。(Linux 中使用的是 GLIBC,它是標準C庫的超集。不僅包含 ANSI C 中定義的函式,還包括 POSIX 標準中定義的函式。因此,Linux 下既可以使用標準 I/O,也可以使用檔案 I/O)。

  2. 參考本書給出的示例low_open.clow_read.c,分別利用底層檔案 I/O 和 ANSI 標準 I/O 編寫檔案複製程式。可任意指定複製程式的使用方法。

    答:暫略。