Files
obsidian/操作系统/09_网络编程基础/09_网络编程基础.md

297 lines
6.7 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 第09讲网络编程
> 🎯 **本节目标**:掌握 Socket 编程,理解客户端-服务器模型
## 📋 前置知识
- [[04_文件IO编程]] — 文件描述符的概念
- [[06_进程控制]] — 进程创建
---
## 🤔 为什么需要这个?
你每天都在使用网络:浏览网页、发送消息、观看视频。但你有没有想过:
- 浏览器是怎么从服务器获取网页的?
- 两台电脑之间是怎么通信的?
**网络编程**就是让你能够编写这样的程序。
**生活比喻**
- **Socket** = 电话插座
- **服务器** = 客服中心(等待来电)
- **客户端** = 拨打电话的用户
- **端口** = 分机号
---
## 📖 核心概念
### 1. 客户端-服务器模型
```mermaid
sequenceDiagram
participant 客户端
participant 服务器
服务器->>服务器: socket() 创建套接字
服务器->>服务器: bind() 绑定地址
服务器->>服务器: listen() 监听连接
服务器->>服务器: accept() 等待连接
客户端->>客户端: socket() 创建套接字
客户端->>服务器: connect() 发起连接
服务器->>客户端: 连接建立
客户端->>服务器: send() 发送数据
服务器->>客户端: recv() 接收数据
服务器->>客户端: send() 发送响应
客户端->>服务器: recv() 接收响应
客户端->>客户端: close() 关闭连接
服务器->>服务器: close() 关闭连接
```
### 2. Socket 编程流程
```mermaid
graph TD
subgraph 服务器端
A1[socket] --> A2[bind]
A2 --> A3[listen]
A3 --> A4[accept]
A4 --> A5[read/write]
A5 --> A6[close]
end
subgraph 客户端
B1[socket] --> B2[connect]
B2 --> B3[write/read]
B3 --> B4[close]
end
style A1 fill:#e8f5e9
style B1 fill:#e1f5fe
```
**核心函数**
| 函数 | 作用 | 服务器/客户端 |
|------|------|:---:|
| `socket()` | 创建套接字 | 都需要 |
| `bind()` | 绑定地址和端口 | 服务器 |
| `listen()` | 开始监听 | 服务器 |
| `accept()` | 接受连接 | 服务器 |
| `connect()` | 发起连接 | 客户端 |
| `send()` | 发送数据 | 都需要 |
| `recv()` | 接收数据 | 都需要 |
| `close()` | 关闭连接 | 都需要 |
### 3. IP 地址与端口
```mermaid
graph LR
A[计算机] -->|IP地址| B[192.168.1.100]
A -->|端口| C[:80]
B --> D[唯一标识一台电脑]
C --> E[唯一标识一个服务]
style B fill:#e1f5fe
style C fill:#e8f5e9
```
**常见端口**
| 端口 | 服务 | 说明 |
|:---:|------|------|
| 22 | SSH | 远程登录 |
| 80 | HTTP | 网页浏览 |
| 443 | HTTPS | 安全网页 |
| 3306 | MySQL | 数据库 |
| 8080 | HTTP备用 | 开发常用 |
### 4. 字节序
不同 CPU 存储多字节数据的方式不同:
```mermaid
graph LR
A[0x12345678] --> B[大端序 Big-endian]
A --> C[小端序 Little-endian]
B --> B1[12 34 56 78]
C --> C1[78 56 34 12]
style B fill:#e1f5fe
style C fill:#e8f5e9
```
**网络字节序**大端序Big-endian
**转换函数**
```c
htonl() // host to network long
htons() // host to network short
ntohl() // network to host long
ntohs() // network to host short
```
### 5. DNS 域名解析
```mermaid
graph LR
A[www.example.com] -->|DNS查询| B[DNS服务器]
B -->|返回IP| C[93.184.216.34]
C -->|连接| D[Web服务器]
style A fill:#e1f5fe
style C fill:#e8f5e9
```
---
## 💻 动手实践
### 示例1查询主机信息
```c
// hostinfo.c - 查询主机信息
#include "wrapper.h"
int main(int argc, char **argv) {
char **pp;
struct in_addr addr;
struct hostent *hostp;
if (argc != 2) {
fprintf(stderr, "usage: %s <domain name or dotted-decimal>\n", argv[0]);
exit(0);
}
// 判断是域名还是IP地址
if (inet_aton(argv[1], &addr) != 0)
hostp = Gethostbyaddr((const char *)&addr, sizeof(addr), AF_INET);
else
hostp = Gethostbyname(argv[1]);
// 打印主机信息
printf("official hostname: %s\n", hostp->h_name);
for (pp = hostp->h_aliases; *pp != NULL; pp++)
printf("alias: %s\n", *pp);
for (pp = hostp->h_addr_list; *pp != NULL; pp++) {
addr.s_addr = *((unsigned int *)*pp);
printf("address: %s\n", inet_ntoa(addr));
}
exit(0);
}
```
**编译运行**
```bash
gcc -o hostinfo hostinfo.c -L. -lwrapper
./hostinfo www.baidu.com
```
**预期输出**
```
official hostname: www.a.shifen.com
alias: www.baidu.com
address: 110.242.68.66
address: 110.242.68.3
```
### 示例2TCP 服务器(大小写转换)
```c
// toggle.c - TCP 服务器
#include "wrapper.h"
void toggle(int conn_sock) {
size_t n;
int i;
char buf[MAXLINE];
while ((n = recv(conn_sock, buf, MAXLINE, 0)) > 0) {
printf("toggle server received %d bytes\n", n);
// 转换大小写
for (i = 0; i < n; i++)
if (isupper(buf[i]))
buf[i] = tolower(buf[i]);
else if (islower(buf[i]))
buf[i] = toupper(buf[i]);
send(conn_sock, buf, n, 0);
}
}
```
### 示例3TCP 客户端
```c
// togglec.c - TCP 客户端
#include "wrapper.h"
int main(int argc, char **argv) {
int client_sock, port;
char *host, buf[MAXLINE];
rio_t rio;
if (argc != 3) {
fprintf(stderr, "usage: %s <host> <port>\n", argv[0]);
exit(1);
}
host = argv[1];
port = atoi(argv[2]);
// 连接服务器
client_sock = open_client_sock(host, port);
// 从标准输入读取,发送到服务器,接收响应
while (fgets(buf, MAXLINE, stdin) != NULL) {
send(client_sock, buf, strlen(buf), 0);
recv(client_sock, buf, MAXLINE, 0);
fputs(buf, stdout);
}
close(client_sock);
exit(0);
}
```
**编译运行**
```bash
# 编译
gcc -o toggle toggle.c -L. -lwrapper
gcc -o togglec togglec.c -L. -lwrapper
# 终端1启动服务器
./toggle 8080
# 终端2启动客户端
./togglec localhost 8080
Hello World # 输入
HELLO WORLD # 输出(大小写转换)
```
---
## 🔗 知识关联
- Socket 是文件描述符,详见 [[04_文件IO编程]]
- 并发服务器在 [[10_并发服务器]] 中有详细讲解
- 网络编程在 [[实验05_Linux网络通信编程]] 中有实践练习
---
## 📝 思考题
1. **TCP vs UDP**:什么时候用 TCP什么时候用 UDP
2. **为什么需要字节序转换?** 如果不转换会怎样?
3. **服务器为什么需要 bind()** 客户端为什么不需要?
---
## 📚 扩展阅读
- 《UNIX网络编程》第1卷套接字联网API
- [Beej's Guide to Network Programming](https://beej.us/guide/bgnet/)
- [Socket 编程详解](https://www.cs.rpi.edu/~moorthy/Courses/os98/Pggrams/socket.html)