C++ socket编程是基于套接字实现进程间或跨设备网络通信的技术,在即时通讯、分布式系统、网络服务开发等场景中应用广泛。通过操作系统提供的socket接口,开发者可以在C++程序中完成网络数据的收发和连接管理。

socket通信基础概念
socket本质是网络通信的端点,分为流式套接字(SOCK_STREAM)和数据报套接字(SOCK_DGRAM)两类,前者基于TCP协议提供可靠有序的字节流传输,后者基于UDP协议提供无连接的不可靠传输。本文以最常用的TCP流式套接字为例讲解实现流程。
TCP socket通信整体流程
TCP通信分为服务端和客户端两个角色,两者的执行流程存在差异:
- 服务端流程:创建socket、绑定地址端口、监听连接、接受客户端连接、收发数据、关闭连接
- 客户端流程:创建socket、连接服务端、收发数据、关闭连接
环境准备说明
Windows平台下socket编程需要包含<winsock2.h>头文件,并且链接ws2_32.lib库;Linux平台下需要包含<sys/socket.h>、<netinet/in.h>、<arpa/inet.h>等头文件,无需额外链接库。以下示例以Linux平台为基础编写,Windows平台仅部分初始化和关闭逻辑有差异。
服务端实现步骤与代码
1. 创建socket
使用socket()函数创建套接字,指定协议族为AF_INET(IPv4)、套接字类型为SOCK_STREAM、协议为0(默认TCP)。
#include <iostream>
#include <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h>
#include <cstring>
#define PORT 8080
#define BUFFER_SIZE 1024
int main() {
int server_fd;
// 创建TCP socket
if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
std::cerr << "socket创建失败" << std::endl;
return -1;
}
return 0;
}
2. 绑定地址和端口
使用sockaddr_in结构体设置服务端地址信息,然后调用bind()函数将socket和地址端口绑定。
// 设置地址结构体
struct sockaddr_in address;
int opt = 1;
socklen_t addrlen = sizeof(address);
// 设置地址复用,避免端口占用问题
if (setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)) < 0) {
std::cerr << "setsockopt失败" << std::endl;
close(server_fd);
return -1;
}
address.sin_family = AF_INET;
address.sin_addr.s_addr = INADDR_ANY; // 绑定所有本地网卡
address.sin_port = htons(PORT); // 端口转换为网络字节序
// 绑定地址和端口
if (bind(server_fd, (struct sockaddr*)&address, sizeof(address)) < 0) {
std::cerr << "bind失败" << std::endl;
close(server_fd);
return -1;
}
3. 监听连接
调用listen()函数将socket设置为监听状态,指定最大等待连接队列长度。
// 开始监听,最大等待队列长度为3
if (listen(server_fd, 3) < 0) {
std::cerr << "listen失败" << std::endl;
close(server_fd);
return -1;
}
std::cout << "服务端启动,监听端口:" << PORT << std::endl;
4. 接受客户端连接
调用accept()函数阻塞等待客户端连接,成功后会返回一个新的socket描述符用于和该客户端通信。
int new_socket;
// 接受客户端连接
if ((new_socket = accept(server_fd, (struct sockaddr*)&address, &addrlen)) < 0) {
std::cerr << "accept失败" << std::endl;
close(server_fd);
return -1;
}
5. 收发数据并关闭连接
使用read()和write()函数进行数据收发,通信完成后关闭socket描述符。
char buffer[BUFFER_SIZE] = {0};
// 读取客户端发送的数据
int valread = read(new_socket, buffer, BUFFER_SIZE);
std::cout << "收到客户端消息:" << buffer << std::endl;
// 向客户端发送响应
const char* response = "服务端已收到消息";
write(new_socket, response, strlen(response));
// 关闭客户端连接socket
close(new_socket);
// 关闭服务端监听socket
close(server_fd);
return 0;
}
客户端实现步骤与代码
1. 创建socket并连接服务端
客户端创建socket后,使用connect()函数连接服务端的地址和端口。
#include <iostream>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <cstring>
#define PORT 8080
#define SERVER_IP "127.0.0.1"
#define BUFFER_SIZE 1024
int main() {
int sock = 0;
struct sockaddr_in serv_addr;
char buffer[BUFFER_SIZE] = {0};
// 创建TCP socket
if ((sock = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
std::cerr << "socket创建失败" << std::endl;
return -1;
}
serv_addr.sin_family = AF_INET;
serv_addr.sin_port = htons(PORT);
// 将IP地址字符串转换为二进制格式
if (inet_pton(AF_INET, SERVER_IP, &serv_addr.sin_addr) <= 0) {
std::cerr << "IP地址转换失败" << std::endl;
return -1;
}
// 连接服务端
if (connect(sock, (struct sockaddr*)&serv_addr, sizeof(serv_addr)) < 0) {
std::cerr << "连接服务端失败" << std::endl;
close(sock);
return -1;
}
return 0;
}
2. 收发数据并关闭连接
连接成功后即可进行数据收发,完成后关闭socket。
// 向服务端发送消息
const char* message = "你好,我是客户端";
send(sock, message, strlen(message), 0);
std::cout << "消息已发送到服务端" << std::endl;
// 接收服务端响应
int valread = read(sock, buffer, BUFFER_SIZE);
std::cout << "收到服务端响应:" << buffer << std::endl;
// 关闭socket
close(sock);
return 0;
}
常见问题说明
- 端口占用问题:如果bind时提示地址已被使用,可以设置SO_REUSEADDR选项,或者更换端口号
- 字节序问题:网络传输使用大端字节序,本地端口和IP地址需要用htons、htonl等函数转换为网络字节序
- 阻塞问题:默认socket是阻塞模式,accept、read等调用会阻塞等待,实际开发中可以根据需求设置为非阻塞模式
总结
C++ socket编程的核心是按照TCP通信的流程,依次调用系统提供的socket相关接口完成服务端和客户端的搭建。本文的示例实现了最基本的TCP回显通信功能,开发者可以在此基础上扩展数据解析、多客户端处理、异常处理等功能,满足更复杂的网络通信需求。