---
# System prepended metadata

title: 網路是怎樣連接的(五)Socket API
tags: [' socket', 計算機網路]

---

---
creator: zach
tags: 計算機網路, socket
created: 2021-09-20 
---

# 網路是怎樣連接的(五)Socket API
## 思考重點
- 如何將應用程序消息委託給協議棧發送?
- socket是調用那些函式進行收發操作?
## 核心知識
![](https://i.imgur.com/DjOw83s.png)
<center>委託協議棧收發重點</center>

## 協議棧如何進行收發操作
現在將擁有的數據整理一下，首先HTTP消息封包已經由應用程式打包完成，服務器IP地址也已經透過DNS[^1]請求機制獲得。在兩個前提條件都滿足的狀況下，我們就可以著手思考要怎麼將這些數據發給對方服務器的應用程式

發送數據其實是調用**多個socket庫函式**達成的，藉由委託多個函式API進行一連串的任務交互，每個任務完成的項目不同，有建立連接部分、斷開連線等等，這些操作的用意就是為了保證雙方是否接收到消息與回應是否正常[^2]

## 使用socket實現
為了使雙方應用程式之間建立一條專屬的溝通管道，我們調用了socket庫函式，很多書上將建立socket形容成搭建一條無形的*通道*，雙方可透過這條通道來實現消息的收發操作，不過並不是說建立socket後計算機才被允許與網路進行通訊，其實早在建立socket之前計算機就可以向網路收發消息了，**建立socket比較像是彼此確定我們該走哪一條傳輸通道找到對方的應用程式**

順著這個思路這小節將介紹應用程式是如何調用socket庫函式向下層委託收發。我喜歡用一個網路訂房的比喻來形容建立socket連線的步驟，就像我們是使用手機app進行訂房，委託系統進行操作，真正的流程實際上是不得而知的，這就像站在應用層的視角看待socket連線[^3]

### 創建階段
**目的**: 依照指定類型創建socket
> 就下載這個訂房app吧!

#### 程式案例
我們先來看看socket create部分:
```c:socket_create
int socket(int domain, int type, int protocol);
```

![](https://i.imgur.com/ES3W38o.png)
<center>socket種類表</center>

- domain
	- 決定socket在網路傳輸中要使用哪個通訊協定的家族系列
	- AF_INET為TCP/IP網路通訊協定
- type
	- 指定socket的類型
	- SOCK_STREAM對應的是TCP協定
	- SOCK_DGRAM對應的是UDP協定
	-  SOCK_RAW可以是IP或ICMP
- protocol
	- 通常在設定完domain與type以後通訊種類就大抵完成了，因此 protocol 一般都設為0，表示依照指定類型設定預設協議
	
```c:舉例
socket(AF_INET, SOCK_STREAM, 0); // 選擇 TCP 
socket(AF_INET, SOCK_STREAM, 6); // 還是 TCP 
socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); // 依然是 TCP 
socket(AF_INET, SOCK_DGRAM, 0); // 這次是 UDP
```

```c:實例
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <sys/socket.h>

// #define AF_INET 2
// #define SOCK_STREAM 1

int main(int argc, char *argv[]){  
 uint32_t socket_identifier = 0;
 /*創建socket*/
 socket_identifier = socket(AF_INET , SOCK_STREAM , 0);
  
 switch(socket_identifier){
	 case 1: // 正數
	 case 2:
		 ...
	 case n:
		 printf("socket create successfully!\n");
		 break;
	 case -1:
		 printf("socket create error!\n");
 }
  
 return 0;  
}
```

socket創建成功一般都會回傳一個大於0的標示符，若回傳負數則代表socket創建異常
### 連線階段
**目的**: 使兩個應用程式建立連線通道
>就用這個帳號登入app吧

#### 程式案例
來看看連現階段吧:
```c:socket_connect
int connect(int fd, struct sockaddr *server, int addrlen);
```
connect函數是客戶端發起的請求，目的是為了與服務器建立連線，介紹參數前，先來講講sockaddr這個結構體，它裡面裝的主要就是socket連線需要的消息，這裡暫且以IPv4為例:
```c:sockaddr
struct in_addr{
        ip_addr_t s_addr;
};

 struct sockaddr{
     unsigned char  sin_family; //AF_INET所以是IPv4
     unsigned short sin_port; // 應用程式端口號
     struct in_addr sin_addr; // 服務器IP地址
     unsigned char  sin_zero[8]; // 不會用到

};
```
- fd
	- socket的描述符
	- 其實就是上面創建socket的回傳值標示符socket_identifier
- server
	- sockaddr結構體，負責提供socket所有的連線消息
- addrlen
	- 結構體sockaddr長度

```c:實例
#include <stdio.h>  
#include <stdlib.h>  
#include <stdint.h>  
#include <string.h>  
#include <malloc.h>
#include <sys/types.h>   
#include <sys/socket.h>  
#include <netinet/in.h>

#define SERV_PORT 8080

typedef sockaddr* info;

int main(argc, char* argv[]){
	info serv=(info)calloc(0, sizeof(sockaddr));
	uint8_t resp;
	/*創建socket部分*/
	/*填入socket消息*/
	serv->sin_family = AF_INET;
	serv->sin_port = htons(SERV_PORT);
	inet_pton(AF_INET, "127.0.0.1", serv->sin_addr);

 	resp = connect(socket_identifier, (info)serv, sizeof(sockaddr));  
	    if(resp < 0)
	        printf("socket connect error!\n");  
	
	return 0;  
}
```
藉由創建socket函式API，我們取得本地端的socket編號socket_identifier[^4]，接下來的任務就是要跟服務器上的應用程式進行連接，IP地址可以幫助我們找到服務器地址，而端口號[^5]則可以幫我們找到執行在服務器上的應用程式
### 收發階段
當我們成功建立連接後，資料就可以透過socket在兩個應用程式之間流通，接著我們可以透過使用read()/recv()來獲取資料，使用write()/send()來傳輸資料。read()/write與recv()/send()的不同只差在recv()/send()的輸入參數多了一個描述符flag，這個描述符提供操作更多的細節控制選項，不過我們以下還是使用通用的收發socket API &rarr; read()/write
#### 發送消息
**目的**: 將資料寫入 Socket 中並發送出去
>就是這間了，趕緊下單!
##### 程式案例
```c:socket_write
ssize_t write(int fd, const void *buf, size_t nbyte);
```
- fd是描述符
- buf是寫入資料的緩衝區，使用const修飾參數buf防止內容被更改
- nbyte是buffer大小

```c:實例
#include <stdio.h>  
#include <stdlib.h>  
#include <stdint.h>  
#include <string.h>  
#include <malloc.h>
#include <unistd.h>
#include <sys/types.h>   
#include <sys/socket.h>  
#include <netinet/in.h>

define MAX 1024

char* buf=(char*)malloc(max); // buffer

int main(argc, char* argv[]){
	ssize_t s_write;
	/*創建socket部分*/
	/*socket連線部分*/
	/*socket write*/
	strcpy(buf, "socket test");
	s_write = write(socket_identifier, buf, MAX);
	
	if(s_write < 0){
		printf("socket write error!\n");
	}
	else{
		printf("socket write data length=%d\n", s_read); // 送出了多少資料長度
	}
	...
	return 0;
}
```
透過調用write函式將資料發送出去，回傳值可以判斷發送的資料長度，若是buffer大小為0會返回0，失敗則回傳-1。它跟待會要介紹的接收消息read()其實就是兩個死對頭，一個急著將資料壓到buffer裡，一個忙著將資料拿出來發出去

#### 接收消息
**目的**: 透過連線中的 Socket讀取資料
>系統提示~您已經下訂成功!
##### 程式案例
```c:socket_read
ssize_t read(int fd, void* buf, size_t nbyte);
```
- fd前面講過了
- buf是讀取資料的緩衝區，socket就是把資料推送到這裡
- nbyte是buffer大小

```c:實例
#include <stdio.h>  
#include <stdlib.h>  
#include <stdint.h>  
#include <string.h>  
#include <malloc.h>
#include <unistd.h>
#include <sys/types.h>   
#include <sys/socket.h>  
#include <netinet/in.h>

define MAX 1024

char* buf=(char*)malloc(max); // buffer

int main(argc, char* argv[]){
	ssize_t s_read;
	/*創建socket部分*/
	/*socket連線部分*/
	/*socket read*/
	s_read = read(socket_identifier, buf, MAX);
	
	if(s_read < 0){
		printf("socket read error!\n");
	}
	else{
		printf("socket read data length=%d\n", s_read); // 讀取buffer內資料長度
	}
	...
	return 0;
}
```
藉由操作read()可以透過回傳值得知讀取狀況，負數代表有錯誤產生，0或者正數代表讀取buffer的資料長度
假如我們得到的回傳值是0可能有幾個特別意思:
- buffer空空如也，甚麼都沒有
- 通訊雙方的socket domain不一致，就是上面創建socket講的AF_INET, AF_INET6那些
- 通訊雙方突然有然段開連線時

### 關閉階段
**目的**: 關閉socket
>若完成訂房，請登出帳號

#### 程式範例
```c:socket_close
int close(int fd);
```
- fd不用多說了吧

```c:實例
#include <stdio.h>  
#include <stdlib.h>  
#include <stdint.h>  
#include <string.h>  
#include <malloc.h>
#include <unistd.h>
#include <sys/types.h>   
#include <sys/socket.h>  
#include <netinet/in.h>

int main(argc, char* argv[]){
	int s_close;
	/*創建socket部分*/
	/*socket連線部分*/
	/*socket的收發操作*/
	/*關閉socket*/
	s_close = close(socket_identifier);
	
	(s_close < 0) ? printf("socket close error!") : printf("close successfully!");
	
	return 0;
}
```
藉由socket斷開雙方之間的通訊，執行成功返回0，若發生錯誤則返回-1

[^1]:[[網路是怎樣連接的(四)DNS]]
[^2]:傳輸層使用TCP協議
[^3]:關於socket連線部分將在介紹傳輸層時介紹
[^4]:標示符是應用程式用來識別眾多本地端socket用
[^5]:客戶端服務端通訊間用來識別眾多對方socket用