TCP 網路連線
## TCP 通訊協定的主要特性
TCP(Transmission Control Protocol)位於傳輸層,主要特性如下:
1. **連線導向(connection-oriented)**
傳送資料前,必須先建立連線。通常透過三向交握(three-way handshake)建立 TCP 連線。
2. **可靠傳輸**
TCP 使用序號、確認訊息(ACK)與逾時重傳,確保資料能夠可靠抵達。
3. **按順序傳遞**
即使資料封包經由不同路徑抵達,TCP 仍會依序交給應用程式。
4. **雙向傳輸(full duplex)**
連線兩端可以同時傳送與接收資料。
5. **流量控制與壅塞控制**
TCP 會根據接收端處理能力及網路狀況調整傳輸速度,避免接收端緩衝區溢位或網路壅塞。
6. **位元組串流(byte stream)**
TCP 不保留應用程式每次 `send()` 的訊息邊界。例如,兩次 `send()` 的資料可能被一次 `recv()` 讀取;一次 `send()` 的資料也可能需要多次 `recv()` 才能讀完。
---
## C 如何透過 System Call 使用 TCP
在 Linux/Unix 中,C 程式通常透過 socket API 使用 TCP。這些函式大多是對核心 System Call 的包裝:
### TCP Server 常見流程
```text
socket()
↓
bind()
↓
listen()
↓
accept()
↓
recv() / send()
↓
close()
```
- `socket()`:建立 socket。
- `bind()`:指定本機 IP 位址與 Port。
- `listen()`:讓 socket 進入等待連線狀態。
- `accept()`:接受客戶端連線,並產生一個新的已連線 socket。
- `recv()` / `send()`:接收或傳送資料。
- `close()`:關閉連線。
### TCP Client 常見流程
```text
socket()
↓
connect()
↓
send() / recv()
↓
close()
```
- `connect()`:連線到伺服器,核心會執行 TCP 三向交握。
- `send()`:將使用者空間的資料交給核心 TCP/IP 協定堆疊。
- TCP 核心負責分段、加上序號、重傳、確認及重新組合。
- `recv()`:從核心接收已按照順序組合好的位元組資料。
---
## 簡單範例:TCP Echo/回覆程式
此範例中:
- Server 監聽 TCP Port `8080`
- Client 連線後傳送 `"Hello TCP"`
- Server 收到後顯示資料並回傳 `"收到資料"`
### Server:`server.c`
```c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#define PORT 8080
void error_exit(const char *msg)
{
perror(msg);
exit(EXIT_FAILURE);
}
int main(void)
{
int listen_fd, client_fd;
struct sockaddr_in server_addr;
char buffer[1024];
/* 建立 TCP socket */
listen_fd = socket(AF_INET, SOCK_STREAM, 0);
if (listen_fd < 0)
error_exit("socket");
memset(&server_addr, 0, sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
server_addr.sin_port = htons(PORT);
/* 綁定 IP 與 Port */
if (bind(listen_fd,
(struct sockaddr *)&server_addr,
sizeof(server_addr)) < 0)
error_exit("bind");
/* 開始監聽 */
if (listen(listen_fd, 5) < 0)
error_exit("listen");
printf("Server listening on port %d...\n", PORT);
/* 接受一個客戶端連線 */
client_fd = accept(listen_fd, NULL, NULL);
if (client_fd < 0)
error_exit("accept");
/* 接收資料 */
ssize_t n = recv(client_fd, buffer, sizeof(buffer) - 1, 0);
if (n < 0)
error_exit("recv");
buffer[n] = '\0';
printf("Server received: %s\n", buffer);
/* 回覆資料 */
const char reply[] = "收到資料";
if (send(client_fd, reply, strlen(reply), 0) < 0)
error_exit("send");
close(client_fd);
close(listen_fd);
return 0;
}
```
### Client:`client.c`
```c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#define PORT 8080
void error_exit(const char *msg)
{
perror(msg);
exit(EXIT_FAILURE);
}
int main(void)
{
int sock_fd;
struct sockaddr_in server_addr;
char buffer[1024];
/* 建立 TCP socket */
sock_fd = socket(AF_INET, SOCK_STREAM, 0);
if (sock_fd < 0)
error_exit("socket");
memset(&server_addr, 0, sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(PORT);
/* 設定伺服器位址:本機 127.0.0.1 */
if (inet_pton(AF_INET, "127.0.0.1",
&server_addr.sin_addr) <= 0)
error_exit("inet_pton");
/* 連線到 Server */
if (connect(sock_fd,
(struct sockaddr *)&server_addr,
sizeof(server_addr)) < 0)
error_exit("connect");
/* 傳送資料 */
const char message[] = "Hello TCP";
if (send(sock_fd, message, strlen(message), 0) < 0)
error_exit("send");
/* 接收 Server 回覆 */
ssize_t n = recv(sock_fd, buffer, sizeof(buffer) - 1, 0);
if (n < 0)
error_exit("recv");
buffer[n] = '\0';
printf("Client received: %s\n", buffer);
close(sock_fd);
return 0;
}
```
### 編譯與執行
開啟兩個終端機:
```bash
gcc server.c -o server
gcc client.c -o client
```
先執行 Server:
```bash
./server
```
再執行 Client:
```bash
./client
```
預期結果:
```text
Server received: Hello TCP
Client received: 收到資料
```
在這個過程中,`connect()` 會觸發 TCP 連線建立;`send()` 將資料交給核心的 TCP/IP 協定堆疊;TCP 負責封裝、傳送、確認與必要的重傳;對端的 `recv()` 則取得可靠且依序排列的資料。實際程式中還需注意 `send()` 和 `recv()` 可能只處理部分資料,因此傳送大型資料時通常要以迴圈確保全部資料都被處理。
相關學習地圖、教學課程
C 語言,系統程式