C/C++ Socket网络编程 介绍
前言:对于C/C++初学者来说,网络编程似乎是一道"门槛",而Socket就是打开这扇门的钥匙。今天我们一起来看看如何入门Socket网络编程。
目录
一、什么是Socket
我们平时在浏览器输入网址,按下回车,为什么能够进入某个网页?为什么我们能够通过本地电脑上的微信或其他聊天APP,与远在他乡的其他伙伴进行交流?这些背后都离不开Socket。
1) Socket概念
Socket(套接字) 是操作系统提供的网络通信编程接口,是应用层与传输层之间的桥梁。
┌─────────────────────────────────────┐
│ 应用层 (HTTP/FTP/DNS)
├─────────────────────────────────────┤
│ Socket 接口 ← 我们编程的位置
├─────────────────────────────────────┤
│ 传输层 (TCP/UDP)
├─────────────────────────────────────┤
│ 网络层 (IP/ICMP)
├─────────────────────────────────────┤
│ 网络接口层 (以太网/WiFi)
└─────────────────────────────────────┘
我们类比一个生活场景:你要打电话给某人,先拨号,那边听到电话铃声后提起电话,这时你和那人就建立起了连接,你们就可以交流了。挂断电话即可结束此次交流。socket其实也是类似的原理。
2) Socket的作用:
- 通信信道:Socket 为网络中的数据传输提供了一个虚拟信道,数据可以通过这个信道从发送方传输到接收方
- 地址识别:通过 IP 地址和端口号,Socket 能够标识网络中的特定设备和应用程序
- 协议兼容性:Socket 允许应用使用不同的传输层协议来进行通信,如 TCP 和 UDP。
3) Socket的类型:
| 类型 | 协议 | 特点 | 应用场景 |
|---|---|---|---|
| 流式套接字 | TCP | 面向连接、可靠、有序、无边界 | 文件传输、网页浏览、数据库 |
| 数据报套接字 | UDP | 无连接、不可靠、有边界、高效 | 视频直播、DNS、在线游戏 |
| 原始套接字 | IP/ICMP | 直接访问底层协议 | 网络诊断、安全工具 |
二、Socket编程流程
1) 服务端编程步骤:
-
创建套接字(socket):使用socket()函数创建一个新的套接字。
-
绑定套接字(bind):通过bind()函数将套接字与特定的IP地址和端口号关联起来。
-
监听连接(listen):使用listen()函数使服务器套接字监听来自客户端的连接请求。
-
接受连接(accept):当客户端请求连接时,accept()函数会接受这个连接。
-
读取数据(read/recv):从客户端接收数据。
-
发送数据(write/send):向客户端发送数据。
-
关闭套接字(close):完成数据传输后,关闭连接。
2) 客户端编程步骤:
-
创建套接字(socket):同服务端。
-
发起连接(connect):使用connect()函数向服务器发起连接请求。
-
发送数据(write/send):向服务器发送数据。
-
读取数据(read/recv):从服务器接收数据。
-
关闭套接字(close):完成数据传输后,关闭连接。
例如,TCP Socket通信流程:
服务端(Server) 客户端(Client)
│ │
▼ ▼
┌─────────────┐ ┌─────────────┐
│ socket() │ 创建套接字 │ socket() │
│ 创建监听fd │ │ 创建通信fd |
└──────┬──────┘ └──────┬──────┘
│ │
▼ │
┌─────────────┐ │
│ bind() │ 绑定IP:端口 │
│ 0.0.0.0:8080│ │
└──────┬──────┘ │
│ │
▼ │
┌─────────────┐ │
│ listen() │ 开始监听(backlog队列) │
│ 被动等待连接│ │
└──────┬──────┘ │
│◄────────────────────────────────────────┤
│ connect() 发起连接 │
│ 三次握手开始... │
▼ ▼
┌─────────────┐ ┌─────────────┐
│ accept() │◄───────────────────────► │ 连接建立 │
│ 阻塞等待连接 │ 返回新的通信fd(connfd) │ │
└──────┬──────┘ └──────┬──────┘
│ │
│◄────────── 双向数据传输 ───────────────► │
│ read()/write() 或 recv()/send() │
│ │
│ 四次挥手断开连接... │
│◄───────────────────────────────────────►│
▼ ▼
┌─────────────┐ ┌─────────────┐
│ close() │ │ close() │
│ 关闭connfd │ │ 关闭fd │
│ 关闭listenfd│ │ │
└─────────────┘ └─────────────┘
三、TCP Socket编程示例
为方便理解概念,这里仅演示基础版本示例 — 阻塞式TCP通信(单线程)。
服务器编程:
// server_basic.cpp
#include <iostream>
#include <cstring>
#include <string>
#include <unistd.h> // close, read, write
#include <arpa/inet.h> // inet_addr, htons
#include <sys/socket.h> // socket, bind, listen, accept
int main() {
// 1. 创建套接字 (IPv4, TCP, 默认协议)
int server_fd = socket(AF_INET, SOCK_STREAM, 0);
if (server_fd == -1) {
std::cerr << "创建socket失败" << std::endl;
return -1;
}
std::cout << "[1] Socket创建成功, fd=" << server_fd << std::endl;
// 2. 绑定地址和端口
struct sockaddr_in server_addr;
memset(&server_addr, 0, sizeof(server_addr)); // 清零
server_addr.sin_family = AF_INET; // IPv4
server_addr.sin_port = htons(8080); // 端口8080 (转网络字节序)
server_addr.sin_addr.s_addr = INADDR_ANY; // 监听所有网卡 0.0.0.0
if (bind(server_fd, (struct sockaddr*)&server_addr, sizeof(server_addr)) == -1) {
std::cerr << "绑定失败" << std::endl;
close(server_fd);
return -1;
}
std::cout << "[2] 绑定成功 0.0.0.0:8080" << std::endl;
// 3. 开始监听 (最大等待队列长度5)
if (listen(server_fd, 5) == -1) {
std::cerr << "监听失败" << std::endl;
close(server_fd);
return -1;
}
std::cout << "[3] 开始监听..." << std::endl;
// 4. 接受客户端连接 (阻塞等待)
struct sockaddr_in client_addr;
socklen_t addr_len = sizeof(client_addr);
std::cout << "[4] 等待客户端连接..." << std::endl;
int client_fd = accept(server_fd, (struct sockaddr*)&client_addr, &addr_len);
if (client_fd == -1) {
std::cerr << "接受连接失败" << std::endl;
close(server_fd);
return -1;
}
char client_ip[16];
inet_ntop(AF_INET, &client_addr.sin_addr, client_ip, sizeof(client_ip));
std::cout << "[5] 客户端连接! IP=" << client_ip
<< " 端口=" << ntohs(client_addr.sin_port) << std::endl;
// 5. 数据收发循环
char buffer[1024];
while (true) {
// 接收数据
memset(buffer, 0, sizeof(buffer));
int recv_len = recv(client_fd, buffer, sizeof(buffer) - 1, 0);
if (recv_len <= 0) {
std::cout << "[6] 客户端断开连接" << std::endl;
break;
}
std::cout << "[收到] " << buffer << std::endl;
// 发送响应
std::string response = "服务器收到: ";
response += buffer;
send(client_fd, response.c_str(), response.length(), 0);
}
// 6. 关闭连接
close(client_fd);
close(server_fd);
std::cout << "[7] 服务器关闭" << std::endl;
return 0;
}
客户端编程:
// client_basic.cpp
#include <iostream>
#include <cstring>
#include <string>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
int main() {
// 1. 创建套接字
int sock_fd = socket(AF_INET, SOCK_STREAM, 0);
if (sock_fd == -1) {
std::cerr << "创建socket失败" << std::endl;
return -1;
}
std::cout << "[1] Socket创建成功" << std::endl;
// 2. 设置服务器地址
struct sockaddr_in server_addr;
memset(&server_addr, 0, sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(8080); // 服务器端口
// 将IP字符串转为网络字节序
if (inet_pton(AF_INET, "127.0.0.1", &server_addr.sin_addr) <= 0) {
std::cerr << "无效的IP地址" << std::endl;
close(sock_fd);
return -1;
}
// 3. 连接服务器
std::cout << "[2] 正在连接服务器..." << std::endl;
if (connect(sock_fd, (struct sockaddr*)&server_addr, sizeof(server_addr)) == -1) {
std::cerr << "连接失败" << std::endl;
close(sock_fd);
return -1;
}
std::cout << "[3] 连接成功!" << std::endl;
// 4. 发送和接收数据
char buffer[1024];
while (true) {
// 输入消息
std::cout << "请输入消息 (输入quit退出): ";
std::string msg;
std::getline(std::cin, msg);
if (msg == "quit") {
break;
}
// 发送
if (send(sock_fd, msg.c_str(), msg.length(), 0) == -1) {
std::cerr << "发送失败" << std::endl;
break;
}
// 接收响应
memset(buffer, 0, sizeof(buffer));
int recv_len = recv(sock_fd, buffer, sizeof(buffer) - 1, 0);
if (recv_len <= 0) {
std::cout << "服务器断开连接" << std::endl;
break;
}
std::cout << "[服务器回复] " << buffer << std::endl;
}
// 5. 关闭
close(sock_fd);
std::cout << "[4] 客户端关闭" << std::endl;
return 0;
}
四、一些注意事项
1) 网络字节序问题:
一般来说,端口号、IP 地址在协议头中都是网络字节序(大端模式),如果我们直接按照主机字节序发送,那么,在大小端不同的机器上就会发生数据错乱。
2) 返回值与错误码:
每个系统调用都需要检查返回值,并且打印相关错误信息,防止因为传入参数异常等原因而发生非预期行为,方便定位问题。
3) 必须关闭Socket:
编程过程中,我们要养成良好的习惯,最后结束的时候一定要关闭socket。如果不关闭,文件描述符或句柄还存在系统中,资源会被慢慢耗尽。
openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构
更多推荐



所有评论(0)