零基础入门:用C++从零实现TCP Socket网络小工具
个人主页:chian-ocean
文章专栏-Linux
前言:
网络编程中的套接字(Socket)是通信的基本接口,允许不同计算机之间通过网络交换数据。套接字是计算机网络中通信的“端点”,通过它,应用程序可以与网络中的其他计算机进行数据通信。网络套接字接口提供了一种抽象的、平台无关的方式来进行进程间通信(IPC)或网络通信。

网络套接字接口
头文件
- 编写网络的常用的4个头文件,基本常用的函数都在这4个头文件里面。
#include // 包含各种系统数据类型#include // 包含套接字操作相关函数和常量#include // 包含与Internet地址转换相关的函数#include // 定义与网络字节序及IPv4/IPv6地址相关的结构体和常量
接口
socket
socket() 函数是创建网络通信套接字的基础。它用于创建一个套接字(socket)并返回一个套接字描述符(socket descriptor),这个描述符将被用来进行后续的网络通信(例如发送和接收数据)。

int socket(int domain, int type, int protocol);
参数说明
domain(地址族):指定通信使用的协议族。- 常用值:
AF_INET:IPv4 地址族(用于 TCP/IP 通信)。AF_INET6:IPv6 地址族。AF_UNIX:本地通信,适用于 Unix 域套接字。
- 常用值:
type(套接字类型):指定套接字的类型,决定数据传输的方式。- 常用值:
SOCK_STREAM:流套接字(用于 TCP)。SOCK_DGRAM:数据报套接字(用于 UDP)。SOCK_RAW:原始套接字,用于底层协议。
- 常用值:
protocol(协议):指定使用的具体协议,通常设置为0让系统自动选择协议。- 常用值:
0:自动选择合适的协议。IPPROTO_TCP:用于 TCP。IPPROTO_UDP:用于 UDP。
- 常用值:
返回值
- 成功时,
socket()返回一个 非负整数,这是一个套接字描述符,代表这个套接字。该描述符将用于后续的套接字操作(如绑定、连接、发送数据等)。 - 失败时,返回
-1,并且设置全局变量errno来指示错误类型。
bind()

int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
参数说明
-
sockfd(套接字描述符)-
类型:
int -
描述:这是通过
socket()创建的套接字描述符。客户端使用该套接字发起连接请求。 -
说明:该套接字应该是已经创建并且可以进行连接的有效套接字。
-
-
addr(目标地址)-
类型:
struct sockaddr * -
描述:指向一个
struct sockaddr结构体的指针,包含了服务器的地址(IP 地址和端口号)。 -
说明:具体的结构类型通常为
struct sockaddr_in(用于 IPv4 地址)或者struct sockaddr_in6(用于 IPv6 地址)。这个结构体包含了目标服务器的 IP 地址和端口号。
-
-
addrlen(地址长度)-
类型:
socklen_t -
描述:指定目标地址结构体的大小(字节数)。
-
说明:通常设置为
sizeof(struct sockaddr_in)或sizeof(struct sockaddr_in6),用于告诉connect()函数地址结构的实际长度。
-
返回值
- 成功时:返回
0,表示成功将套接字与指定的本地地址绑定。 - 失败时:返回
-1,并将errno设置为具体的错误码。
listen()

int listen(int sockfd, int backlog);
参数说明
sockfd:- 类型:
int - 描述:表示要进入监听状态的套接字描述符。这个套接字通常是通过
socket()创建的,并且应该已经通过bind()绑定了本地地址(如 IP 地址和端口)。 - 说明:套接字需要是一个有效的连接套接字,用于接受客户端连接。
- 类型:
backlog:- 类型:
int - 描述:表示 监听队列的最大长度,也就是可以等待的连接请求数量。如果有多个客户端同时请求连接,系统会将这些请求放入队列中,
backlog参数设置了队列的最大长度。 - 说明:如果有超过
backlog数量的连接请求,新的连接请求会被拒绝,或者它们会根据操作系统的实现策略被丢弃。 - 推荐值:常见的
backlog值一般设置为 5 到 128,根据服务器的需求而定。对于高并发系统,可能需要更大的backlog值。
- 类型:
返回值
- 成功时:返回
0,表示成功将套接字转换为监听状态。 - 失败时:返回
-1,并设置errno以指示错误原因。
accept()

int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
参数说明
-
sockfd(套接字描述符)-
类型:
int -
描述:这是通过
socket()创建的套接字描述符。客户端使用该套接字发起连接请求。 -
说明:该套接字应该是已经创建并且可以进行连接的有效套接字。
-
-
addr(目标地址)-
类型:
struct sockaddr * -
描述:指向一个
struct sockaddr结构体的指针,包含了服务器的地址(IP 地址和端口号)。 -
说明:具体的结构类型通常为
struct sockaddr_in(用于 IPv4 地址)或者struct sockaddr_in6(用于 IPv6 地址)。这个结构体包含了目标服务器的 IP 地址和端口号。
-
-
addrlen(地址长度)-
类型:
socklen_t -
描述:指定目标地址结构体的大小(字节数)。
-
说明:通常设置为
sizeof(struct sockaddr_in)或sizeof(struct sockaddr_in6),用于告诉connect()函数地址结构的实际长度。
-
返回值
-
成功时:返回 新的套接字描述符,用于与客户端进行通信。这个新的套接字是通过
accept()函数创建的,它与原始的监听套接字不同,可以用于数据发送和接收。 -
失败时:返回
-1,并设置errno以指示错误原因。
connect()

参数说明
-
sockfd(套接字描述符)-
类型:
int -
描述:这是通过
socket()创建的套接字描述符。客户端使用该套接字发起连接请求。 -
说明:该套接字应该是已经创建并且可以进行连接的有效套接字。
-
-
addr(目标地址)-
类型:
struct sockaddr * -
描述:指向一个
struct sockaddr结构体的指针,包含了服务器的地址(IP 地址和端口号)。 -
说明:具体的结构类型通常为
struct sockaddr_in(用于 IPv4 地址)或者struct sockaddr_in6(用于 IPv6 地址)。这个结构体包含了目标服务器的 IP 地址和端口号。
-
-
addrlen(地址长度)-
类型:
socklen_t -
描述:指定目标地址结构体的大小(字节数)。
-
说明:通常设置为
sizeof(struct sockaddr_in)或sizeof(struct sockaddr_in6),用于告诉connect()函数地址结构的实际长度。
-
返回值
-
成功时:返回
0,表示连接成功。 -
失败时:返回
-1,并且会设置errno来指示错误的具体原因。
close()

int close(int fd);
参数说明
fd(文件描述符):- 类型:
int - 描述:这是要关闭的文件描述符。对于套接字编程而言,这通常是由
socket()函数返回的套接字描述符(sockfd)。 - 说明:在网络编程中,
fd是表示套接字的描述符,它可以是通过socket()创建的套接字描述符。关闭该描述符会释放套接字占用的资源。
- 类型:
返回值
- 成功时:返回
0,表示成功关闭套接字或文件描述符。 - 失败时:返回
-1,并设置errno为具体的错误代码
网络套接字封装(TCP)
1. 头文件引用
#include #include #include #include #include #include #include #include #include \"log.hpp\"
:提供输入输出流的功能,常用于打印日志或错误信息。:提供 C++ 标准库的字符串类std::string,用于字符串处理。和:用于处理字符串相关的操作,如bzero()和strerror()。和:提供套接字编程所需的类型定义和系统调用。:提供与 IP 地址转换相关的函数(如inet_ntop()和inet_addr())。:定义了用于 IPv4 地址和端口的结构体和常量(如sockaddr_in和htons())。\"log.hpp\":这是一个自定义的日志头文件,包含了日志记录相关的内容。lg()函数用于记录日志,lg()宏应该在log.hpp中定义。
2. 全局变量和枚举类型
int backlog = 10;enum err{ Socketerr = 1, Bindeterr, Listeneterr, Accepteterr, };
backlog:这是传递给listen()函数的参数,定义了监听队列的最大长度(即最大客户端连接数)。设置为10。enum err:定义了与套接字相关的错误类型。Socketerr = 1:表示套接字创建失败。Bindeterr:表示套接字绑定失败。Listeneterr:表示监听失败。Accepteterr:表示接受客户端连接失败。
3. Sock 类
3.1 构造函数和析构函数
Sock() {}~Sock() {}
- 构造函数:默认构造函数,没有进行任何初始化操作。
- 析构函数:默认析构函数,没有执行任何资源清理操作。
3.2 Socket() - 创建套接字
void Socket(){ sockfd_ = socket(AF_INET, SOCK_STREAM, 0); if(sockfd_ < 0) { lg(FATAL,\"Socket error: %d,%s\",errno,strerror(errno)); exit(Socketerr); }}
- 目的:创建一个 TCP 套接字。
AF_INET:表示 IPv4 地址族。SOCK_STREAM:表示 TCP 流套接字(面向连接的套接字)。0:表示默认协议,通常是 TCP 协议。
- 错误处理:如果
socket()返回值小于0,表示套接字创建失败,记录日志并退出程序,退出代码为Socketerr。
3.3 Bind(uint16_t port) - 绑定套接字
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); }}
- 目的:将套接字与本地 IP 地址和端口号绑定。通过
INADDR_ANY将套接字绑定到所有可用的网络接口上,接受来自任何 IP 地址的连接。htons():将端口号从主机字节序转换为网络字节序。
- 错误处理:如果
bind()返回值小于0,表示绑定失败,记录日志并退出程序,退出代码为Bindeterr。
3.4 Listen() - 开始监听
void Listen(){ if(listen(sockfd_, backlog) < 0) { lg(FATAL,\"Listen error: %d,%s\",errno,strerror(errno)); exit(Listeneterr); }}
- 目的:将套接字设置为监听状态,准备接受客户端的连接。
backlog:监听队列的最大长度,定义最多能排队等待的连接数。
- 错误处理:如果
listen()返回值小于0,表示监听失败,记录日志并退出程序,退出代码为Listeneterr。
3.5 Accept(std::string \\* clientip, uint16_t\\* clientport) - 接受连接
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。
3.6 Connect(const std::string& ip, const uint16_t& port) - 连接服务器
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;}
- 目的:客户端通过此函数连接到远程服务器,指定服务器的 IP 地址和端口。
inet_addr():将 IP 地址从字符串转换为网络字节序的二进制格式。htons():将端口号转换为网络字节序。
- 错误处理:如果
connect()失败,记录警告日志并返回false,否则返回true表示连接成功。
3.7 GetFd() - 获取套接字描述符
int GetFd(){ return sockfd_;}
- 目的:返回套接字描述符,便于外部访问该套接字,用于进一步的操作(如
send(),recv()等)。


