6.7 KiB
6.7 KiB
第09讲:网络编程
🎯 本节目标:掌握 Socket 编程,理解客户端-服务器模型
📋 前置知识
🤔 为什么需要这个?
你每天都在使用网络:浏览网页、发送消息、观看视频。但你有没有想过:
- 浏览器是怎么从服务器获取网页的?
- 两台电脑之间是怎么通信的?
网络编程就是让你能够编写这样的程序。
生活比喻:
- Socket = 电话插座
- 服务器 = 客服中心(等待来电)
- 客户端 = 拨打电话的用户
- 端口 = 分机号
📖 核心概念
1. 客户端-服务器模型
sequenceDiagram
participant 客户端
participant 服务器
服务器->>服务器: socket() 创建套接字
服务器->>服务器: bind() 绑定地址
服务器->>服务器: listen() 监听连接
服务器->>服务器: accept() 等待连接
客户端->>客户端: socket() 创建套接字
客户端->>服务器: connect() 发起连接
服务器->>客户端: 连接建立
客户端->>服务器: send() 发送数据
服务器->>客户端: recv() 接收数据
服务器->>客户端: send() 发送响应
客户端->>服务器: recv() 接收响应
客户端->>客户端: close() 关闭连接
服务器->>服务器: close() 关闭连接
2. Socket 编程流程
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 地址与端口
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 存储多字节数据的方式不同:
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)
转换函数:
htonl() // host to network long
htons() // host to network short
ntohl() // network to host long
ntohs() // network to host short
5. DNS 域名解析
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:查询主机信息
// 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);
}
编译运行:
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
示例2:TCP 服务器(大小写转换)
// 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);
}
}
示例3:TCP 客户端
// 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);
}
编译运行:
# 编译
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网络通信编程 中有实践练习
📝 思考题
- TCP vs UDP:什么时候用 TCP,什么时候用 UDP?
- 为什么需要字节序转换? 如果不转换会怎样?
- 服务器为什么需要 bind()? 客户端为什么不需要?
📚 扩展阅读
- 《UNIX网络编程》第1卷:套接字联网API
- Beej's Guide to Network Programming
- Socket 编程详解