Socket 程序間通訊
## IPC Socket 的特性
Socket 是一種由作業系統核心管理的通訊端點,可讓不同程序交換資料。常見特性如下:
- **程序間通訊(IPC)**:可讓同一台主機上的不同程序互相傳送資料。
- **支援多種通訊模式**:
- `SOCK_STREAM`:串流式通訊,可靠、有順序,常用於類似 TCP 的通訊。
- `SOCK_DGRAM`:資料報通訊,以一個封包為單位,不保證可靠送達。
- **支援本機與網路通訊**:
- `AF_UNIX`/`AF_LOCAL`:同一台主機內的 IPC,透過檔案路徑識別 Socket。
- `AF_INET`:IPv4 網路通訊。
- `AF_INET6`:IPv6 網路通訊。
- **核心提供緩衝區與同步機制**:程序可使用 `read()`、`write()`、`send()`、`recv()` 傳送或接收資料。
- **可透過檔案權限控管**:Unix Domain Socket 通常會對應到檔案,因此可利用檔案權限限制存取。
---
## C 如何透過 System Call 進行 Socket 通訊
典型的伺服器流程如下:
```text
socket()
↓
bind()
↓
listen()
↓
accept()
↓
recv()/read()
send()/write()
↓
close()
```
用戶端流程如下:
```text
socket()
↓
connect()
↓
send()/write()
recv()/read()
↓
close()
```
這些函式通常由 C 函式庫提供介面,但底層會呼叫核心的 System Call,例如:
- `socket()`:建立 Socket
- `bind()`:指定本地位址,例如 Unix Socket 路徑
- `listen()`:讓串流 Socket 等待連線
- `accept()`:接受用戶端連線
- `connect()`:連線至伺服器
- `send()`/`write()`:傳送資料
- `recv()`/`read()`:接收資料
- `close()`:關閉 Socket
---
## 簡單範例:Unix Domain Socket
以下程式可同時作為伺服器或用戶端,使用 `AF_UNIX` 在同一台主機上的兩個程序間傳送訊息。
### `ipc_socket.c`
```c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <sys/un.h>
#define SOCKET_PATH "/tmp/demo_ipc.sock"
void error_exit(const char *msg)
{
perror(msg);
exit(EXIT_FAILURE);
}
void run_server(void)
{
int server_fd, client_fd;
struct sockaddr_un addr;
char buffer[128];
server_fd = socket(AF_UNIX, SOCK_STREAM, 0);
if (server_fd == -1)
error_exit("socket");
unlink(SOCKET_PATH); // 移除可能存在的舊 Socket 檔案
memset(&addr, 0, sizeof(addr));
addr.sun_family = AF_UNIX;
strncpy(addr.sun_path, SOCKET_PATH,
sizeof(addr.sun_path) - 1);
if (bind(server_fd, (struct sockaddr *)&addr,
sizeof(addr)) == -1)
error_exit("bind");
if (listen(server_fd, 5) == -1)
error_exit("listen");
printf("Server waiting...\n");
client_fd = accept(server_fd, NULL, NULL);
if (client_fd == -1)
error_exit("accept");
ssize_t n = recv(client_fd, buffer, sizeof(buffer) - 1, 0);
if (n == -1)
error_exit("recv");
buffer[n] = '\0';
printf("Server received: %s\n", buffer);
const char *reply = "Hello from server";
send(client_fd, reply, strlen(reply), 0);
close(client_fd);
close(server_fd);
unlink(SOCKET_PATH);
}
void run_client(void)
{
int client_fd;
struct sockaddr_un addr;
char buffer[128];
client_fd = socket(AF_UNIX, SOCK_STREAM, 0);
if (client_fd == -1)
error_exit("socket");
memset(&addr, 0, sizeof(addr));
addr.sun_family = AF_UNIX;
strncpy(addr.sun_path, SOCKET_PATH,
sizeof(addr.sun_path) - 1);
if (connect(client_fd, (struct sockaddr *)&addr,
sizeof(addr)) == -1)
error_exit("connect");
const char *message = "Hello from client";
send(client_fd, message, strlen(message), 0);
ssize_t n = recv(client_fd, buffer, sizeof(buffer) - 1, 0);
if (n == -1)
error_exit("recv");
buffer[n] = '\0';
printf("Client received: %s\n", buffer);
close(client_fd);
}
int main(int argc, char *argv[])
{
if (argc != 2) {
fprintf(stderr, "Usage: %s server|client\n", argv[0]);
return EXIT_FAILURE;
}
if (strcmp(argv[1], "server") == 0)
run_server();
else if (strcmp(argv[1], "client") == 0)
run_client();
else {
fprintf(stderr, "Argument must be server or client\n");
return EXIT_FAILURE;
}
return EXIT_SUCCESS;
}
```
### 編譯
```bash
gcc ipc_socket.c -o ipc_socket
```
### 執行
先開啟伺服器:
```bash
./ipc_socket server
```
再開啟另一個終端機執行用戶端:
```bash
./ipc_socket client
```
可能輸出:
```text
Server received: Hello from client
Client received: Hello from server
```
此範例使用:
- `AF_UNIX`:表示同一台主機內的 Unix Domain Socket。
- `SOCK_STREAM`:表示可靠且有順序的串流通訊。
- `bind()`:伺服器建立 `/tmp/demo_ipc.sock`。
- `connect()`:用戶端連線至該 Socket。
- `send()`/`recv()`:雙方交換資料。
若要改成網路通訊,通常會改用 `AF_INET`,並使用 IP 位址與 Port 作為 Socket 位址。
相關學習地圖、教學課程
C 語言,系統程式