一、进程间通信方式回顾
1. 同一主机(单机内部通信)
传统方式:
-
管道:
-
有名管道(FIFO)
-
无名管道(pipe)
-
-
信号(signal)
IPC方式(System V IPC):
-
共享内存(share memory)
-
信号量(semaphore)
-
消息队列(message queue)
2. 不同主机(网络通信)
- Socket(套接字):用于不同主机之间的进程通信
二、网络通信核心问题
举例说明:
- 西安的 PC(QQ_A)←→ 成都的 PC(QQ_B)
需要解决两个核心问题:
-
找到对方的电脑 → 需要 IP 地址
-
找到电脑中的进程(QQ 程序)→ 需要 端口号(Port)
三、OSI 七层网络模型
OSI 七层模型(理想模型,尚未完全实现):

四、TCP/IP 模型(实际工业标准)
五、Linux 系统中的网络层次对应关系
如何通过编程实现网络通信?

[用户空间] ←→ 应用层(应用程序)
------------------------------------ Socket 接口(套接字)
[内核空间] ←→ 传输层(TCP/UDP)
网络层(IP)
链路层(网卡驱动)
物理层(硬件)
如何实现应用层代码?
- 通过 Socket 函数接口
六、Socket 编程基础
Socket 是什么?
-
socket() 是一个函数接口,用来使用网络功能
-
Linux 一切皆文件的思想:socket 也是一种特殊的文件,专门用来进行网络通信
网络编程两大协议:
-
基于 TCP 协议
-
基于 UDP 协议
七、TCP 协议
TCP(Transmit Control Protocol,传输控制协议)
类比:打电话
TCP 三大特点:
-
面向连接:通信前必须建立好逻辑通路
-
可靠传输(四个"无"):
-
无丢失
-
无失序
-
无差错
-
无重复
-
-
字节流:有好处也有问题
TCP 三次握手(建立连接):
客户端(Client) 服务器(Server)
1. ----- SYN(我要通话)----→
2. ←--- SYN+ACK(嗯,我知道了,你可以)----
3. ----- ACK(嗯,好的)----→
TCP 应用场合:
-
对可靠性要求较高的场合
-
QQ、微信等软件的登录过程
八、UDP 协议
UDP(User Datagram Protocol,用户数据报协议)
类比:生活中的广播
UDP 三大特点:
-
无连接:数据发送时不需要建立连接
-
不保证可靠传输
-
数据报:一包一包发送
UDP 应用场合:
-
小数据量,但对速度要求较高
-
QQ 即时文本信息
-
语音通话
-
实时性要求较高的场合
-
-
广播、组播
- 电子教室(VNC 广播)
-
无线网络传输
九、网络编程模型
三种模型:
-
C/S 模型(Client/Server,客户端/服务器模型) --- 重点(本次学习的)
-
B/S 模型(Browser/Server,浏览器/服务器模型)
-
P2P 模型(Peer to Peer,点对点模型)
- 正在使用 P2P 加速技术
TCP C/S 通信模型(重点)
两个阶段:
-
建立连接(三次握手)
-
通信(数据收发)
十、TCP 客户端/服务器编程流程

客户端流程(主动方):
1. socket() // 买个手机
2. bind() // 可选的,一般不绑定
3. connect() // 拨打电话
4. read() / write()
5. close()
服务器端流程(被动方):
1. socket() // 买个手机
2. bind() // 插 SIM 卡,绑定服务器地址信息
3. listen() // 待机,监听是否有客户端连接
4. accept() // 接听电话,处理客户端连接,完成连接建立
5. read() / write()
6. close()
十一、Socket 函数
1. socket() 函数
#include <sys/types.h>
#include <sys/socket.h>
int socket(int domain, int type, int protocol);
功能: 创建通信的一端
参数:
-
@domain:地址族-
AF_UNIX, AF_LOCAL:本地通信 -
AF_INET:IPv4 互联网协议 -
AF_INET6:IPv6 互联网协议
-
-
@type:套接字类型-
SOCK_STREAM:流式套接字(TCP)-
提供有序、可靠、双向、基于连接的字节流
-
支持带外数据传输
-
-
SOCK_DGRAM:数据报套接字(UDP)- 支持数据报(无连接、不可靠、固定最大长度)
-
-
@protocol:协议类型-
TCP 协议
-
UDP 协议
-
填 0 表示使用默认协议
-
返回值:
-
成功:返回 socket 的文件描述符
-
失败:返回 -1,并设置 errno
2. connect() 函数(客户端)
#include <sys/types.h>
#include <sys/socket.h>
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
功能: 发起连接请求(拨打电话)
参数:
-
@sockfd:使用的 socket 文件描述符 -
@addr:要通信的地址(对于客户端就是服务器的 IP + 端口) -
@addrlen:addr 参数地址的大小
返回值:
-
成功:0
-
失败:-1,并设置 errno
3. 地址结构 struct sockaddr_in
struct sockaddr_in {
sa_family_t sin_family; // 地址族:AF_INET
in_port_t sin_port; // 端口号(网络字节序)
struct in_addr sin_addr; // IP 地址(网络字节序)
};
/* Internet address. */
struct in_addr {
uint32_t s_addr; // IP 地址(网络字节序)
};
十二、字节序(Byte Order)
大端 vs 小端
-
大端(Big Endian):高字节存放在低地址
-
小端(Little Endian):低字节存放在低地址(x86 架构)
-
网络字节序 :采用大端字节序
字节序转换函数:
端口号转换:htons()
uint16_t htons(uint16_t hostshort);
功能: 将主机字节序的端口号转换为网络字节序
IP 地址转换:inet_addr()
in_addr_t inet_addr(const char *cp);
功能: 将点分十进制字符串形式的 IP 地址(如 "192.168.1.1")转换为网络字节序的 IP 地址
IP 地址反向转换:inet_ntoa()
char *inet_ntoa(struct in_addr in);
功能: 将网络字节序的 IP 地址转换为点分十进制字符串形式的 IP 地址
十三、IP 地址详解
IP 地址的本质:
-
在 IPv4 网络中,IP 地址是 32 位二进制数
-
采用大端字节序(网络字节序)
点分十进制表示法:
-
将 32 位二进制码划分为 4 个字节
-
每个字节转换为十进制数
-
字节之间用
.分隔 -
例如:
192.168.31.243- 二进制:
11000000.10101000.00011111.11110011
- 二进制:
IP 地址分类(IPv4):
-
A 类
-
B 类
-
C 类
-
D 类
-
E 类
子网掩码(Subnet Mask):
-
作用:
-
用来区分网络号
-
也可以获取主机号
-
十四、端口号(Port)
端口号的作用:
-
用来标识一个网络进程
-
在网络中找到主机后(IP),通过端口号找到主机中的进程
端口号的组成:
-
16 位数据(2 字节)
-
范围:0 ~ 65535
端口号分类:
-
1 ~ 1023:知名端口号(Well-known Ports)
-
80:HTTP
-
23:Telnet
-
21:FTP
-
-
1024 ~ 50000:系统注册端口(Registered Ports)
-
≥ 50000:动态端口(Dynamic/Private Ports)
网络进程唯一标识:
- IP 地址 + 端口号 = 网络中进程的唯一地址
十五、服务器端其他函数
4. bind() 函数(服务器)
#include <sys/types.h>
#include <sys/socket.h>
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
功能: 给 socket 绑定一个名字(地址信息)
参数:
-
@sockfd:要绑定的 socket 文件描述符 -
@addr:绑定服务器自己的地址信息 -
@addrlen:服务器地址的大小
返回值:
-
成功:0
-
失败:-1,并设置 errno
5. listen() 函数(服务器)
#include <sys/types.h>
#include <sys/socket.h>
int listen(int sockfd, int backlog);
功能: 将 socket 设置为监听套接字(被动套接字)
参数:
-
@sockfd:要设置的 socket -
@backlog:表示等待队列的大小
返回值:
-
成功:0
-
失败:-1,并设置 errno
6. accept() 函数(服务器)
#include <sys/types.h>
#include <sys/socket.h>
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
功能:
-
从 listen 的等待队列中提取第一个连接请求
-
完成连接
-
同时创建一个新的 socket(表示已经连接好的 socket)
-
最终返回这个已连接好的 socket 的文件描述符
参数:
-
@sockfd:监听套接字的文件描述符 -
@addr:用来保存对端(客户端)的地址信息 -
@addrlen:值结果参数- 必须初始化:
sizeof(客户端地址类型)
- 必须初始化:
返回值:
-
成功:返回已连接好的 socket 的文件描述符
-
失败:返回 -1,并设置 errno
注意:

代码:


十六、客户端与服务器的 Socket 数量
-
客户端 :只有 1 个 socket
-
服务器 :有 2 个 socket
-
a. 监听 socket(用于接受连接)
-
b. 通信 socket(用于数据收发)
-
十七、练习
练习 1:客户端输入信息,服务器打印
客户端(client):
1. socket()
2. connect()
3. while(1) {
fgets() // 从键盘获取输入
write(fd) // 发送给服务器
read(fd) // 接收服务器响应
}
4. close()
服务器端(server):
1. socket()
2. bind()
3. listen()
4. connfd = accept()
5. 通信:
while(1) {
read(connfd, buf, ...) // 读取客户端数据
printf("%s", buf) // 打印输出
// 可选:回显修改后的数据
char sbuf[1024];
sprintf(sbuf, "server+%s\n", buf);
write(connfd, sbuf);
}
6. close()


练习 2:点对点聊天(多进程版本)
客户端(client):
1. socket()
2. connect()
3. fork()
if (pid > 0) {
// 父进程:发送消息
} else if (pid == 0) {
// 子进程:接收服务器消息
}
4. close()
服务器端(server):
1. socket()
2. bind()
3. listen()
4. connfd = accept()
5. fork()
if (pid > 0) {
// 父进程:发送消息
} else if (pid == 0) {
// 子进程:接收客户端消息
}
6. close()
多任务分工:
-
任务1:专门负责发送消息
-
任务2:专门负责接收消息
练习 3:点对点聊天(多线程版本)




十八、总结
关键函数列表:
| 函数 | 用途 | 使用方 |
|---|---|---|
socket() |
创建套接字 | 双方 |
bind() |
绑定地址 | 服务器 |
listen() |
监听连接 | 服务器 |
accept() |
接受连接 | 服务器 |
connect() |
发起连接 | 客户端 |
read()/write() |
数据收发 | 双方 |
close() |
关闭连接 | 双方 |
