《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
網路程式設計中和接受連線請求的套接字建立過程可整理如下:
- 第一步:呼叫 socket 函式建立套接字。
- 第二步:呼叫 bind 函式分配IP地址和埠號。
- 第三步:呼叫 listen 函式轉換為可接受請求狀態。
- 第四步:呼叫 accept 函式受理套接字請求。
1.1.2 編寫Hello World
套接字程式
服務端:
伺服器端(server)是能夠受理連線請求的程式。下面構建服務端以驗證之前提到的函式呼叫過程,該伺服器端收到連線請求後向請求者返回Hello World!
答覆。除各種函式的呼叫順序外,我們還未涉及任何實際程式設計。因此,閱讀程式碼時請重點關注套接字相關的函式呼叫過程,不必理解全過程。
伺服器端程式碼請參見:hello_server.c
客戶端:
客戶端程式只有呼叫 socket 函式建立套接字
和 呼叫 connect 函式向服務端傳送連線請求
這兩個步驟,下面給出客戶端,需要檢視以下兩方面的內容:
- 呼叫 socket 函式 和 connect 函式
- 與服務端共同執行以收發字串資料
客戶端程式碼請參見: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 習題
❗️以下部分的答案,僅代表我個人觀點,可能不是正確答案
-
套接字在網路程式設計中的作用是什麼?為何稱它為套接字?
答:作業系統會提供「套接字」(socket)的部件,套接字是網路資料傳輸用的軟體裝置。因此,「網路程式設計」也叫「套接字程式設計」。「套接字」就是用來連線網路的工具。
-
在伺服器端建立套接字以後,會依次呼叫 listen 函式和 accept 函式。請比較二者作用。
答:呼叫 listen 函式將套接字轉換成可受連線狀態(監聽),呼叫 accept 函式受理連線請求。如果在沒有連線請求的情況下呼叫該函式,則不會返回,直到有連線請求為止。
-
Linux 中,對套接字資料進行 I/O 時可以直接使用檔案 I/O 相關函式;而在 Windows 中則不可以。原因為何?
答:暫略。
-
建立套接字後一般會給他分配地址,為什麼?為了完成地址分配需要呼叫哪個函式?
答:套接字被建立之後,只有為其分配了IP地址和埠號後,客戶端才能夠通過IP地址及埠號與伺服器端建立連線,需要呼叫 bind 函式來完成地址分配。
-
Linux 中的檔案描述符與 Windows 的控制代碼實際上非常類似。請以套接字為物件說明它們的含義。
答:暫略。
-
底層 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)。
-
參考本書給出的示例
low_open.c
和low_read.c
,分別利用底層檔案 I/O 和 ANSI 標準 I/O 編寫檔案複製程式。可任意指定複製程式的使用方法。答:暫略。