這裡部會講述太難的網路概念,僅說明一個系統軟體工程師對於網路所需要的基本概念
一切的概念都是從
client-server
開始的
舉例來說:現在你想在亞馬遜上買東西
當然,你也可以同時是客戶端與服務器,例如:你現在打電話給別人
對於網路,電腦將他視為 I/O
對應硬碟,從網路獲取的資料經過I/O
和內存總線(BUS)複製到內存;相反的,也可以從內存將資料複製到網路。這個概念適用於目前常見的作業系統,例如:windows, macOS, UNIX…
這邊我們如果是用 "internet" 代表描述一般的網路概念;"Internet"則是描述一種具體的實現手法,例如通訊協定等等
46:85:fd:b3:3b:f2
Hosts send bits to any other host in chunks called frames
我們可以將區域網路簡化成上圖,可以想像成一堆主機在對話
LAN1
和LAN2
是不同的區域網路,因此這兩種網域的溝通就要經過協調,也就是通訊協定
現實生活中也是利用這些概念進行,只是範圍擴大到全世界,這種網路稱為廣域網路(Wide Area Network, WAN)
這張投影片介紹傳輸資料的8個基本步驟:
data | Packet Header(PH) | Frame Header(FH) |
---|---|---|
Frame Payload | Frame Payload | Frame Header |
將頭部加上去的動作就叫做封裝
延伸問題:
IP
、UDP
及TCP
socket
介面寫程式
這邊可以看到,可以利用 socket
從 User Mode
就可以進行資料傳輸
IP address
:由四個十進位的數字用點連起來,這四個數字只能從0~255
IP address
用十進位表示,但是我們也可以使用16進位表示他IP address
是Big Endian
,而一般的作業系統都是使用Little Endian
IP address
可以知道你在用哪個網域,例如128.2
就是CMU的IP address
IPv4
是 32 位元的地址,但是世界太多人了,地址可能不夠用,因此有了IPv6
,也就是128位元的地址IPv6
已經出現了15年,人類已經找出如何用IPv4
的表示很多地址,因此IPv6
還是很少人用IP address
需要將 Big Endian
轉換成 Little Endian
,這邊可以使用標準函式庫IP Address
是用四個十進位表示,而他也可以轉換成16進位,這時利用標準函式庫即可得到
0x8002C2F2 = 128.2.194.242
GOOGLE
,我們不會使用他的IP,相反的我會使用網址,www.google.com
.com
是指商業公司、.edu
是指教育機構等等,當然不同的地區也會有不同的後綴,例如.de
代表德國、.us
代表美國等等IP
與網域間的關係,也就是 Domain Naming System(DNS)
DNS
養得太複雜,他其實就是一個映射函數,也就是給他網域名稱,他就給你 IP
地址,反之亦然
這邊我們可以利用系統,給予電腦網域名稱就可以讓他給我 IP
地址
這邊我們做個實驗
$ nslookup www.cs.cmu.edu
Server: 127.0.0.53
Address: 127.0.0.53#53
Non-authoritative answer:
www.cs.cmu.edu canonical name = scs-web-lb.andrew.cmu.edu.
Name: scs-web-lb.andrew.cmu.edu
Address: 128.2.42.95
Not find www.cs.standford.edu: No answer
IP
並不是一一對應的,是多對多映射的
Name: twitter.com
Address: 104.244.42.65
Name: twitter.com
Address: 104.244.42.129
這邊也可以看到,如果我們想要到同一個網域,他可能會給我們不同地址,這些地址都在變化
IP
地址的分配分為兩種,一種是靜態分配,這種地址不會變;另外一種是動態分配,這種地址可能是一段時間之內分配給你的
TCP
,它可以使兩台主機進行連接,而且類似電話依樣不會有數據突然消失的問題socket
包含一個IP
地址和一個端口,端口號是16個位元組
目前比較熱門的服務器都會指派這些知名端口(Well-Known Ports)進行,如上
假設現在連線開始,客戶端視使用臨時接口,要求連接伺服器第80號接口
伺服器第80號接口是網路服務,如果想要使用其他服務的話需要連接其他端口,而這些端口是獨立的不會互相影響
Socket Interface(台譯:插座介面、陸譯:套接字接口)
socket
是一個連線的終點socket
就像是硬碟一樣,只是對象不是電腦中的硬體而是網路socket API
套接字接口前兩的字節(Bytes)是描述這個socket
是甚麼類型的?
例如:TCP
、IPv6
、UDP
本圖的左邊是客戶端、右邊是伺服器端
roi
代表 reliable i/o以 linux 核心的角度來說,一個套接字就是通信的一個端點,以程式碼的角度來說,套接字就是一個文件
struct sockaddr_in{
uint16_t sin_family; /* Protocol Family (Always AF_INET) */
uint16_t sin_port; /* Port number in network byte order */
struct in_addr sin_addr; /* IP Address */
unsigned char sin_zero[8];
};
struct sockaddr{
uint16_t sa_family;
char sa_data[14];
};
socket
常用函式socket
函式 (套接字描述符)
int socket(int domain, int type, int protocol);
socket
變成一個端點,可以用編碼的參數調用:
clientfd = Socket(AF_INET, SOCK_STREAM, 0);
// AF_INET: 使用32位元的IP地址
// SOCK_STREAM: socket連接的一的端點
getaddrinfo
來自動生成這些參數socket
的 clientfd
是部分打開的,還不能用於讀寫。如何打開套接字的工作取決於我們是客戶端還是服務器。connect
函式
int connect(int clientfd, const struct sockaddr *addr, socklen_t addrlen);
connect
函數是將客戶端和伺服器連接,啟動連接後,connect
會停止一陣子,直到確定連接伺服器或是連接失敗才會繼續動作
如果成功,clientfd
描述現在就準備好可以讀寫了,得到的連接是socket字對
(x:y, addr.sin_addr:addr.sin_port)
其中 x
, y
分別對應客戶端的地址與端口,它唯一確定了客戶端主機上的客戶端進程。對於socket最好的方式是用 getaddrinfo
來未connect提供參數
bind
函式
int bind(int sockfd, const struct *addr, socklen_t addrlen);
addr
中的伺服器套接字 (socket) 地址和套接字描述符 sockfd
連接起來listen
函式
accept
函式
int accept(int listenfd, struct sockaddr *addr, int *addrlen);
accept
等待來自可護端的連線請求到達偵聽描述符 listenfd,然後在 addr中填寫客戶端的套接字地址,返回一個已連接描述符(Connected Descriptor)為什麼要分為監聽描述符和已連接描述符?
因為在並行 (Concurrency) 的服務器中,監聽描述符可以一次處理很多客戶端的連接,每次請求一個連接時,監聽描述符都可以 fork 初一個新的行程,使得已連接描述符可以與之連接。
getaddrinfo 函式
將相應的主機名稱、主機地址、服務器名稱和字串符號轉換成套接字地址結構
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
int getaddrinfo(const char *host, const char *service,
const struct addrinfo *hints,
struct addrinfo **results);
void freeaddrinfo(struct addrinfo *result);
在客戶端調用 getaddrinfo 之後會一次走訪這個資料結構中的地址,直到調用 socket 和 connect 成功;相似的、主機則是會走訪這個資料結構中的地址,直到 socket 和 bind 成功。
getnameinfo 函式
與 getaddrinfo 相反,他是將套接字地址結構轉回相應的主機與服務器的字串