什么是Socket与Socket编程?
Socket 是网络编程的 “抽象接口”;
- 从程序角度看,socket 是一个文件描述符;
- 从系统角度看,它是网络协议栈中的一个通信端点。
本质上,socket 也是一个文件描述符。
网络通信的唯一标识由四元组构成:(本地IP, 本地端口, 远端IP, 远端端口)
TCP 协议中,该四元组唯一确定一个网络连接
Socket 编程的核心逻辑围绕该通信端点的创建、绑定、连接建立、数据传输、连接关闭展开。
-
TCP 编程核心流程为:
socket() → bind() → listen() → accept() → send()/recv() → close(); -
TCP 面向连接、可靠传输、无消息边界;UDP 无连接、不可靠、保留消息边界;
Socket 编程
Socket 编程就是基于 Socket 接口,编写网络通信程序,最常见的是 C/S(客户端 - 服务器)模型:
- 服务器端:先启动,监听某个端口,等待客户端连接;
- 客户端:主动向服务器的 IP + 端口 发起连接,连接成功后双方收发数据。
sock_fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
这个方法会返回socket_fd,它是socket文件的句柄,是个数字,相当于socket的身份证号。
| 类型 | 协议 | 特点 | 适用场景 |
|---|---|---|---|
SOCK_STREAM | TCP | 面向连接、可靠传输(数据不丢、不重复、有序)、基于 “字节流” | 网页、文件传输、微信 |
SOCK_DGRAM | UDP | 无连接、不可靠传输(不保证数据到达)、基于 “数据报”、速度快 | 直播、游戏、DNS 解析 |
得到了socket_fd之后,对于服务端,就可以依次执行bind(), listen(), accept()方法,然后坐等客户端的连接请求。
对于客户端,得到socket_fd之后,你就可以执行connect()方法向服务端发起建立连接的请求,此时就会发生TCP三次握手。
连接建立完成后,客户端可以执行send() 方法发送消息,服务端可以执行recv()方法接收消息,反过来,服务器也可以执行send(),客户端执行recv()方法。
| 服务器端核心步骤 | 客户端核心步骤 |
|---|---|
| 1. 创建 Socket 套接字 | 1. 创建 Socket 套接字 |
| 2. 绑定 IP 地址与端口(bind) | 2. 向服务器发起连接(connect) |
| 3. 开启端口监听(listen) | 3. 双向收发数据(send/recv) |
| 4. 等待客户端连接(accept) | 4. 关闭套接字,释放资源 |
| 5. 双向收发数据(recv/send) | |
| 6. 关闭套接字,释放资源 |
在Linux中实现Socket编程
服务器端
#include <iostream>
#include <sys/socket.h> // Socket核心API头文件
#include <netinet/in.h> // IPv4地址结构体头文件
#include <arpa/inet.h> // 字节序转换、IP地址转换函数
#include <unistd.h> // close、read、write函数
#include <cstring> // memset字符串操作
// 定义端口号和缓冲区大小
const int PORT = 8080;
const int BUF_SIZE = 1024;
int main() {
// ===================== 1. 创建Socket套接字 =====================
// AF_INET: 使用IPv4协议;SOCK_STREAM: 使用TCP流式套接字;0: 自动匹配默认协议
int server_fd = socket(AF_INET, SOCK_STREAM, 0);
if (server_fd < 0) {
std::cerr << "创建Socket失败" << std::endl;
return -1;
}
std::cout << "Socket创建成功" << std::endl;
// ===================== 2. 配置地址结构体,绑定IP与端口 =====================
struct sockaddr_in server_addr;
memset(&server_addr, 0, sizeof(server_addr)); // 清空结构体
server_addr.sin_family = AF_INET; // 地址族:IPv4
server_addr.sin_port = htons(PORT); // 端口号:主机字节序转网络字节序
server_addr.sin_addr.s_addr = INADDR_ANY; // 监听本机所有网卡的IP地址
// 绑定Socket到指定IP+端口
int ret = bind(server_fd, (struct sockaddr*)&server_addr, sizeof(server_addr));
if (ret < 0) {
std::cerr << "绑定端口失败" << std::endl;
close(server_fd);
return -1;
}
std::cout << "端口绑定成功,监听端口: " << PORT << std::endl;
// ===================== 3. 开启端口监听 =====================
// 第二个参数backlog: 内核中已完成三次握手的等待队列最大长度
ret = listen(server_fd, 5);
if (ret < 0) {
std::cerr << "开启监听失败" << std::endl;
close(server_fd);
return -1;
}
std::cout << "服务器启动成功,等待客户端连接..." << std::endl;
// ===================== 4. 阻塞等待客户端连接 =====================
struct sockaddr_in client_addr;
socklen_t client_addr_len = sizeof(client_addr);
// accept会阻塞,直到有客户端连接成功,返回新的Socket文件描述符,专门用于和该客户端通信
int client_fd = accept(server_fd, (struct sockaddr*)&client_addr, &client_addr_len);
if (client_fd < 0) {
std::cerr << "接受客户端连接失败" << std::endl;
close(server_fd);
return -1;
}
std::cout << "客户端连接成功!客户端IP: " << inet_ntoa(client_addr.sin_addr)
<< " 端口: " << ntohs(client_addr.sin_port) << std::endl;
// ===================== 5. 双向收发数据 =====================
char buf[BUF_SIZE] = {0};
// 接收客户端发送的数据
ssize_t recv_len = recv(client_fd, buf, BUF_SIZE - 1, 0);
if (recv_len < 0) {
std::cerr << "接收数据失败" << std::endl;
} else if (recv_len == 0) {
std::cout << "客户端已断开连接" << std::endl;
} else {
std::cout << "收到客户端数据: " << buf << std::endl;
// 向客户端发送响应数据
const char* response = "你好,我是C++ TCP服务器,已收到你的消息!";
send(client_fd, response, strlen(response), 0);
std::cout << "响应数据已发送" << std::endl;
}
// ===================== 6. 关闭套接字,释放资源 =====================
close(client_fd);
close(server_fd);
std::cout << "服务器已关闭" << std::endl;
return 0;
}
客户端
#include <iostream>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <cstring>
const int PORT = 8080;
const char* SERVER_IP = "127.0.0.1"; // 服务器IP,本地测试用回环地址
const int BUF_SIZE = 1024;
int main() {
// ===================== 1. 创建Socket套接字 =====================
int client_fd = socket(AF_INET, SOCK_STREAM, 0);
if (client_fd < 0) {
std::cerr << "创建Socket失败" << std::endl;
return -1;
}
std::cout << "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(PORT);
// 将点分十进制的IP字符串转换为网络字节序的二进制地址
if (inet_pton(AF_INET, SERVER_IP, &server_addr.sin_addr) <= 0) {
std::cerr << "IP地址格式错误" << std::endl;
close(client_fd);
return -1;
}
// 向服务器发起TCP连接(三次握手)
int ret = connect(client_fd, (struct sockaddr*)&server_addr, sizeof(server_addr));
if (ret < 0) {
std::cerr << "连接服务器失败" << std::endl;
close(client_fd);
return -1;
}
std::cout << "成功连接到服务器!" << std::endl;
// ===================== 3. 双向收发数据 =====================
// 向服务器发送数据
const char* send_msg = "你好,我是C++ TCP客户端!";
send(client_fd, send_msg, strlen(send_msg), 0);
std::cout << "消息已发送至服务器" << std::endl;
// 接收服务器响应
char buf[BUF_SIZE] = {0};
ssize_t recv_len = recv(client_fd, buf, BUF_SIZE - 1, 0);
if (recv_len > 0) {
std::cout << "收到服务器响应: " << buf << std::endl;
}
// ===================== 4. 关闭套接字,释放资源 =====================
close(client_fd);
std::cout << "客户端已关闭" << std::endl;
return 0;
}