前言
网络编程中的套接字(Socket)是通信的基本接口,允许不同计算机之间通过网络交换数据。套接字是计算机网络中通信的'端点',通过它,应用程序可以与网络中的其他计算机进行数据通信。网络套接字接口提供了一种抽象的、平台无关的方式来进行进程间通信(IPC)或网络通信。
C++ TCP Socket 网络编程涉及 socket、bind、listen、accept、connect 等核心系统调用。内容详解各函数参数与返回值,并提供基于 C++ 类的 Socket 封装示例,涵盖头文件引用、错误处理及连接管理逻辑,帮助开发者快速掌握底层网络通信实现原理。

网络编程中的套接字(Socket)是通信的基本接口,允许不同计算机之间通过网络交换数据。套接字是计算机网络中通信的'端点',通过它,应用程序可以与网络中的其他计算机进行数据通信。网络套接字接口提供了一种抽象的、平台无关的方式来进行进程间通信(IPC)或网络通信。
编写网络的常用 4 个头文件,基本常用的函数都在这 4 个头文件里面。
#include <sys/types.h> // 包含各种系统数据类型
#include <sys/socket.h> // 包含套接字操作相关函数和常量
#include <arpa/inet.h> // 包含与 Internet 地址转换相关的函数
#include <netinet/in.h> // 定义与网络字节序及 IPv4/IPv6 地址相关的结构体和常量
socket() 函数是创建网络通信套接字的基础。它用于创建一个套接字(socket)并返回一个套接字描述符(socket descriptor),这个描述符将被用来进行后续的网络通信(例如发送和接收数据)。
int socket(int domain, int type, int protocol);
AF_INET:IPv4 地址族(用于 TCP/IP 通信)。AF_INET6:IPv6 地址族。AF_UNIX:本地通信,适用于 Unix 域套接字。SOCK_STREAM:流套接字(用于 TCP)。SOCK_DGRAM:数据报套接字(用于 UDP)。SOCK_RAW:原始套接字,用于底层协议。0 让系统自动选择协议。
0:自动选择合适的协议。IPPROTO_TCP:用于 TCP。IPPROTO_UDP:用于 UDP。socket() 返回一个 非负整数,这是一个套接字描述符,代表这个套接字。该描述符将用于后续的套接字操作(如绑定、连接、发送数据等)。errno 来指示错误类型。int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
intsocket() 创建的套接字描述符。客户端使用该套接字发起连接请求。struct sockaddr *struct sockaddr 结构体的指针,包含了服务器的地址(IP 地址和端口号)。struct sockaddr_in(用于 IPv4 地址)或者 struct sockaddr_in6(用于 IPv6 地址)。这个结构体包含了目标服务器的 IP 地址和端口号。socklen_tsizeof(struct sockaddr_in) 或 sizeof(struct sockaddr_in6),用于告诉 connect() 函数地址结构的实际长度。0,表示成功将套接字与指定的本地地址绑定。errno 设置为具体的错误码。int listen(int sockfd, int backlog);
intsocket() 创建的,并且应该已经通过 bind() 绑定了本地地址(如 IP 地址和端口)。intbacklog 参数设置了队列的最大长度。backlog 数量的连接请求,新的连接请求会被拒绝,或者它们会根据操作系统的实现策略被丢弃。backlog 值一般设置为 5 到 128,根据服务器的需求而定。对于高并发系统,可能需要更大的 backlog 值。0,表示成功将套接字转换为监听状态。-1,并设置 errno 以指示错误原因。int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
intsocket() 创建的套接字描述符。客户端使用该套接字发起连接请求。struct sockaddr *struct sockaddr 结构体的指针,包含了服务器的地址(IP 地址和端口号)。struct sockaddr_in(用于 IPv4 地址)或者 struct sockaddr_in6(用于 IPv6 地址)。这个结构体包含了目标服务器的 IP 地址和端口号。socklen_tsizeof(struct sockaddr_in) 或 sizeof(struct sockaddr_in6),用于告诉 connect() 函数地址结构的实际长度。accept() 函数创建的,它与原始的监听套接字不同,可以用于数据发送和接收。-1,并设置 errno 以指示错误原因。intsocket() 创建的套接字描述符。客户端使用该套接字发起连接请求。struct sockaddr *struct sockaddr 结构体的指针,包含了服务器的地址(IP 地址和端口号)。struct sockaddr_in(用于 IPv4 地址)或者 struct sockaddr_in6(用于 IPv6 地址)。这个结构体包含了目标服务器的 IP 地址和端口号。socklen_tsizeof(struct sockaddr_in) 或 sizeof(struct sockaddr_in6),用于告诉 connect() 函数地址结构的实际长度。0,表示连接成功。errno 来指示错误的具体原因。int close(int fd);
intsocket() 函数返回的套接字描述符(sockfd)。fd 是表示套接字的描述符,它可以是通过 socket() 创建的套接字描述符。关闭该描述符会释放套接字占用的资源。0,表示成功关闭套接字或文件描述符。errno 为具体的错误代码。#include <iostream>
#include <string>
#include <cstring>
#include <strings.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include "log.hpp"
<iostream>:提供输入输出流的功能,常用于打印日志或错误信息。<string>:提供 C++ 标准库的字符串类 std::string,用于字符串处理。<cstring> 和 <strings.h>:用于处理字符串相关的操作,如 bzero() 和 strerror()。<sys/types.h> 和 <sys/socket.h>:提供套接字编程所需的类型定义和系统调用。<arpa/inet.h>:提供与 IP 地址转换相关的函数(如 inet_ntop() 和 inet_addr())。<netinet/in.h>:定义了用于 IPv4 地址和端口的结构体和常量(如 sockaddr_in 和 htons())。"log.hpp":这是一个自定义的日志头文件,包含了日志记录相关的内容。lg() 函数用于记录日志,lg() 宏应该在 log.hpp 中定义。int backlog = 10;
enum err {
Socketerr = 1,
Bindeterr,
Listeneterr,
Accepteterr,
};
backlog:这是传递给 listen() 函数的参数,定义了监听队列的最大长度(即最大客户端连接数)。设置为 10。enum err:定义了与套接字相关的错误类型。
Socketerr = 1:表示套接字创建失败。Bindeterr:表示套接字绑定失败。Listeneterr:表示监听失败。Accepteterr:表示接受客户端连接失败。Sock() {}
~Sock() {}
void Socket() {
sockfd_ = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd_ < 0) {
lg(FATAL, "Socket error: %d,%s", errno, strerror(errno));
exit(Socketerr);
}
}
AF_INET:表示 IPv4 地址族。SOCK_STREAM:表示 TCP 流套接字(面向连接的套接字)。0:表示默认协议,通常是 TCP 协议。socket() 返回值小于 0,表示套接字创建失败,记录日志并退出程序,退出代码为 Socketerr。void Bind(uint16_t port) {
struct sockaddr_in peer;
socklen_t len = sizeof(peer);
bzero(&peer, len);
peer.sin_port = htons(port);
peer.sin_family = AF_INET;
peer.sin_addr.s_addr = INADDR_ANY;
if (bind(sockfd_, (struct sockaddr*)&(peer), len) < 0) {
lg(FATAL, "Bind error: %d,%s", errno, strerror(errno));
exit(Bindeterr);
}
}
INADDR_ANY 将套接字绑定到所有可用的网络接口上,接受来自任何 IP 地址的连接。
htons():将端口号从主机字节序转换为网络字节序。bind() 返回值小于 0,表示绑定失败,记录日志并退出程序,退出代码为 Bindeterr。void Listen() {
if (listen(sockfd_, backlog) < 0) {
lg(FATAL, "Listen error: %d,%s", errno, strerror(errno));
exit(Listeneterr);
}
}
backlog:监听队列的最大长度,定义最多能排队等待的连接数。listen() 返回值小于 0,表示监听失败,记录日志并退出程序,退出代码为 Listeneterr。int Accept(std::string * clientip, uint16_t* clientport) {
struct sockaddr_in peer;
socklen_t len = sizeof(peer);
bzero(&peer, len);
int newfd = accept(sockfd_, (struct sockaddr*)&(peer), &len);
if (newfd < 0) {
lg(FATAL, "Accept error: %d,%s", errno, strerror(errno));
exit(Accepteterr);
}
char ip[64];
inet_ntop(AF_INET, &peer.sin_addr.s_addr, ip, sizeof(ip));
*clientip = ip;
*clientport = ntohs(peer.sin_port);
return newfd;
}
accept() 函数返回一个新的套接字 newfd,用于与客户端交换数据。inet_ntop() 将客户端的 IP 地址从二进制转换为字符串格式,ntohs() 将客户端的端口号转换为主机字节序。accept() 返回值小于 0,表示接受连接失败,记录日志并退出程序,退出代码为 Accepteterr。bool Connect(const std::string& ip, const uint16_t& port) {
struct sockaddr_in peer;
socklen_t len = sizeof(peer);
bzero(&peer, len);
peer.sin_addr.s_addr = inet_addr(ip.c_str());
peer.sin_port = htons(port);
peer.sin_family = AF_INET;
int n = connect(sockfd_, (struct sockaddr*)&(peer), len);
if (n < 0) {
lg(WARNING, "Connect error: %d,%s", errno, strerror(errno));
return false;
}
return true;
}
inet_addr():将 IP 地址从字符串转换为网络字节序的二进制格式。htons():将端口号转换为网络字节序。connect() 失败,记录警告日志并返回 false,否则返回 true 表示连接成功。int GetFd() {
return sockfd_;
}
send(),recv() 等)。
微信公众号「极客日志」,在微信中扫描左侧二维码关注。展示文案:极客日志 zeeklog
将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online
将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online
将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML转Markdown在线工具,online
通过删除不必要的空白来缩小和压缩JSON。 在线工具,JSON 压缩在线工具,online
将JSON字符串修饰为友好的可读格式。 在线工具,JSON美化和格式化在线工具,online