C++ 网络编程入门:TCP 协议下的简易计算器项目
个人主页:chian-ocean
文章专栏-Linux
网络编程入门:TCP 协议下的简易计算器项目
- 个人主页:chian-ocean
- 文章专栏-Linux
- 前言:
- 文件组成
- TCP服务端
- `TcpServer.hpp`
- 代码说明:
- `TcpServer.cc`
- 代码说明:
- TCP客户端
- 代码说明:
- 计算器
- 代码说明:
- 1. **`ServerCal` 类**:
- 2. **`Calculatate(Requset &req)` 函数**:
- 3. **`Calculator(std::string &package)` 函数**:
- 请求和响应服务
- 代码总结:
- 1. **`Encode` 和 `Decode`**:
- 2. **`Requset` 类**:
- 3. **`Response` 类**:
- 网络组件
- 代码说明:
- 1. **`Socket()`**:
- 2. **`Bind(uint16_t port)`**:
- 3. **`Listen()`**:
- 4. **`Accept(std::string \*clientip, uint16_t \*clientport)`**:
- 5. **`Connect(const std::string &ip, const uint16_t &port)`**:
- 6. **`Close()`**:
- 7. **`GetFd()`**:
- 效果图
- 源码地址
前言:
根据前面的经验:网络套接字,序列化与反序列化,守护进程等等,写出一个小项目。
文件组成
.vscode/log.hpp // 记录日志的头文件,用于定义日志功能的类和函数。Makefile // 项目的构建文件,定义如何使用 make 编译、链接代码。Protocol.hpp // 定义通信协议的头文件,包含数据格式、消息结构、序列化与反序列化规则等。ServerCal.hpp // 服务器计算相关的头文件,包含服务器端任务计算、数据处理等的声明。Socket.hpp // 定义套接字操作的头文件,包含与网络连接、数据传输等相关的类和函数。TcpClient/ // 存放与 TCP 客户端相关的文件夹TcpClient.cc // TCP 客户端实现源文件,处理与服务器的连接、数据发送和接收。TcpClient.hpp // TCP 客户端头文件,声明客户端与服务器进行交互的类和方法。TcpServer/ // 存放与 TCP 服务器相关的文件夹TcpServer.cc // TCP 服务器实现源文件,处理客户端的连接、接收和处理数据。TcpServer.hpp // TCP 服务器头文件,声明用于启动和管理服务器端的类和函数。
TCP服务端
TcpServer.hpp
#include <functional>
#include "Socket.hpp" // 引入套接字操作相关的类
#include "ServerCal.hpp" // 引入服务器端计算相关的类// 定义回调函数类型,接受一个字符串引用并返回一个字符串
using func_t = std::function<std::string(std::string& package)>;// 定义 TcpServer 类
class TcpServer
{
public:// 构造函数,接受端口号和回调函数作为参数TcpServer(uint16_t port, func_t callback): port_(port), callback_(callback){}// 初始化函数,设置监听套接字void Init(){listensock_.Socket(); // 创建套接字listensock_.Bind(port_); // 绑定端口listensock_.Listen(); // 开始监听lg(INFO, "Server Init"); // 输出日志,表示服务器已初始化}// 启动服务器函数,开始接收客户端连接void Start(){while (true){std::string ip;uint16_t port;// 接受客户端连接,返回客户端的文件描述符int fd = listensock_.Accept(&ip, &port);lg(INFO, "Server Accept"); // 输出日志,表示有客户端连接// 创建子进程处理客户端请求if (fork() == 0){lg(INFO, "fork success"); // 子进程成功创建listensock_.Close(); // 子进程关闭监听套接字std::string inbuffer_stream; // 缓存接收的数据while (true){char buffer[1024];// 从客户端读取数据int n = read(fd, &buffer, sizeof(buffer));if (n > 0){lg(INFO, "read success"); // 输出日志,表示读取成功buffer[n] = 0; // 添加字符串结束符inbuffer_stream += buffer; // 将读取的内容添加到缓冲区while (true){// 调用回调函数处理接收到的包,并返回响应信息std::string info = callback_(inbuffer_stream);if (info.empty()) break; // 如果回调返回空字符串,退出循环// 将处理后的数据写回客户端write(fd, info.c_str(), info.size());}}else if (n == 0) break; // 如果读取到 0,表示客户端断开连接else break; // 读取失败,退出循环}exit(0); // 子进程结束}// 关闭客户端连接的文件描述符,在父进程中继续等待下一个连接close(fd);}}private:uint16_t port_; // 服务器端口号Sock listensock_; // 监听套接字对象func_t callback_; // 回调函数,用于处理客户端发送的数据
};
代码说明:
- 构造函数
TcpServer(uint16_t port, func_t callback)
:- 初始化服务器的端口和回调函数。回调函数
callback_
将在收到客户端请求时被调用,用于处理数据。
- 初始化服务器的端口和回调函数。回调函数
Init()
:- 该方法用于初始化服务器,创建套接字、绑定端口并开始监听客户端连接。
Start()
:- 该方法是服务器的主循环,用于接受客户端的连接请求。
- 每当有新的客户端连接时,服务器会通过
fork()
创建子进程来处理该客户端的请求。 - 在子进程中,读取客户端发送的数据,并通过回调函数处理数据。处理结果将通过套接字返回给客户端。
- 子进程处理完毕后会退出,而父进程继续等待新的客户端连接。
Sock listensock_
:Sock
类的对象,用于处理底层套接字操作,包括创建、绑定、监听、接收连接等。
- 回调函数
func_t callback_
:- 回调函数类型,负责处理客户端发来的数据包,并返回处理后的结果。
TcpServer.cc
#include <iostream>
#include <string>
#include <functional>
#include "log.hpp" // 引入日志记录功能
#include "TcpServer.hpp" // 引入 TCP 服务器类
#include "Protocal.hpp" // 引入协议相关类(用于数据格式、协议处理等)// 测试请求函数的声明
void ResquestTest();
// 测试响应函数的声明
void ResponseTest();// 用法说明函数,输出程序的使用方式
void Usage()
{std::cout << "\n\r" << "[Usage]:prot" << "\n" << std::endl;
}// 主程序入口函数
int main(int argc, char* argv[])
{ // 检查命令行参数的数量是否正确if(argc != 2){// 如果参数数量不为2,则调用Usage函数输出使用方法Usage();return -1; // 返回错误代码}// 输出服务器启动的日志lg(INFO, "Server start");ServerCal cal; // 创建一个 ServerCal 对象,用于计算请求数据// 获取命令行参数中的端口号,并将其转换为整数std::string port_1 = argv[1];uint16_t port = std::stoi(port_1); // 将字符串端口号转换为无符号短整型// 创建一个 TcpServer 对象,绑定回调函数// 使用 std::bind 绑定 ServerCal::Calculator 函数与 ServerCal 对象,作为回调函数TcpServer* tsur = new TcpServer(port, std::bind(&ServerCal::Calculator, &cal, std::placeholders::_1));// 初始化服务器tsur->Init();// 启动服务器tsur->Start();return 0; // 程序执行成功,返回 0
}
代码说明:
- 头文件引入:
#include "log.hpp"
:用于记录日志,lg(INFO, "message")
可以在程序中输出日志信息。#include "TcpServer.hpp"
:引入定义TcpServer
类的头文件,用于创建 TCP 服务器。#include "Protocal.hpp"
:引入协议相关的头文件,可能包含协议的定义和数据格式的处理方法。
Usage()
函数:- 该函数在命令行参数不正确时,输出程序的使用方式,提示用户如何正确传递命令行参数。
main()
函数:- 程序的入口点,首先检查命令行参数的数量。如果参数不正确,则调用
Usage()
输出帮助信息,并返回错误。 - 如果参数正确,程序继续执行:
- 创建
ServerCal
对象cal
,该对象负责计算接收到的数据。 - 通过
argv[1]
获取并转换端口号。 - 创建
TcpServer
对象tsur
,并将ServerCal::Calculator
函数作为回调函数绑定到TcpServer
中。此回调函数会处理客户端请求的数据。 - 调用
tsur->Init()
初始化服务器,tsur->Start()
启动服务器,开始监听和处理客户端连接。
- 创建
- 程序的入口点,首先检查命令行参数的数量。如果参数不正确,则调用
TCP客户端
#include <iostream> // 引入标准输入输出流,用于控制台输出
#include <unistd.h> // 引入UNIX标准库,提供sleep函数和系统调用等功能
#include <time.h> // 引入时间相关的库,用于生成随机数种子
#include <string> // 引入字符串类
#include "log.hpp" // 引入日志记录功能
#include "Socket.hpp" // 引入套接字操作相关的类
#include "Protocal.hpp" // 引入协议相关类(用于数据序列化、反序列化、编码、解码等)// 用法说明函数,输出程序的使用方法
void Usage()
{std::cout << "\n\r" << "[Usage]:port" << "\n" << std::endl;
}// 主程序入口
int main(int argc, char* argv[])
{// 检查命令行参数的数量是否正确if(argc != 3){Usage(); // 如果参数数量不为3,调用Usage函数输出使用方法return -1; // 返回错误代码}srand(time(nullptr)); // 使用当前时间作为随机数生成的种子Sock sock; // 创建一个套接字对象sock.Socket(); // 初始化套接字// 获取命令行参数中的IP地址和端口号std::string ip = argv[1];std::string port_1 = argv[2];std::cout << "port: " << port_1 << std::endl; // 输出连接的端口号// 将端口号从字符串转换为无符号短整型uint16_t port = std::stoi(port_1);// 连接到服务器sock.Connect(ip, port);lg(INFO, "connect successful"); // 输出连接成功的日志信息std::string op = "+-*/%"; // 运算符集合int cnt = 1; // 计算请求的次数// 持续进行请求while(true){cnt++;// 创建一个请求对象,生成随机数据Requset req;req.x_ = rand() % 100 + 1; // 随机生成1到100之间的整数作为xreq.y_ = rand() % 100; // 随机生成0到99之间的整数作为yreq.op_ = op[rand() % op.size()]; // 随机从运算符集合中选择一个运算符std::string con; // 序列化后的字符串std::string out_stream; // 编码后的数据流// 序列化请求对象bool r = req.Serialize(&con);if(!r) lg(WARNING, "Serialize failure"); // 如果序列化失败,输出警告日志out_stream = Encode(con); // 对序列化后的数据进行编码// 输出请求的详细信息std::cout << "============" << " The " << cnt << " Request " << "============" << std::endl;std::cout << "x: " << req.x_ << std::endl;std::cout << "op: " << req.op_ << std::endl;std::cout << "y: " << req.y_ << std::endl;// 发送请求数据给服务器write(sock.GetFd(), out_stream.c_str(), out_stream.size());// 从服务器接收响应数据std::string in_stream;char inbuffer[1024];int n = read(sock.GetFd(), &inbuffer, sizeof(inbuffer));if(n > 0){inbuffer[n] = 0; // 添加字符串结束符}in_stream += inbuffer; // 将接收到的数据追加到输入流中std::string bon; // 解码后的数据Response resp; // 响应对象Decode(in_stream, &bon); // 解码接收到的数据resp.Deserialize(bon); // 反序列化响应数据resp.Print(); // 打印响应结果sleep(1); // 每次请求之间暂停1秒钟std::cout << "========================================" << std::endl; // 输出分隔线}return 0; // 程序结束
}
代码说明:
Usage()
:- 用于输出程序的使用说明,提示用户需要传递端口号作为参数。
main()
:- 主程序入口,首先检查命令行参数是否正确。如果参数不正确,调用
Usage()
输出帮助信息。 - 使用
srand(time(nullptr))
设置随机数种子,确保每次运行时生成不同的随机数。 - 创建一个
Sock
对象,初始化并连接到指定的 IP 地址和端口号。 - 在
while
循环中,程序持续进行请求:- 生成随机的请求数据,包括
x_
、y_
和运算符op_
。 - 使用
Serialize()
方法将请求数据序列化,调用Encode()
方法进行编码。 - 发送请求数据到服务器。
- 接收来自服务器的响应数据,使用
Decode()
进行解码,并使用Deserialize()
方法将响应数据反序列化。 - 最后,输出计算结果。
- 生成随机的请求数据,包括
- 主程序入口,首先检查命令行参数是否正确。如果参数不正确,调用
- 日志记录:
- 使用
lg(INFO, "message")
输出程序的日志信息,帮助调试和跟踪程序的状态。
- 使用
计算器
#pragma once#include <iostream>
#include "log.hpp" // 引入日志功能,用于输出日志
#include "Protocal.hpp" // 引入协议相关的类和功能// 定义错误符号的枚举类型
enum err_symbol
{div_zero = 1, // 除法为零错误mod_zero // 取余为零错误
};// 服务器计算类
class ServerCal
{
public:// 构造函数,初始化 ServerCal 对象ServerCal(){}// 计算函数,根据传入的请求计算结果Response Calculatate(Requset &req){Response resp(0, 0); // 创建一个默认响应对象,初始值为 0// 根据请求的运算符执行相应的数学运算switch (req.op_){case '+': // 加法resp.result_ = req.Getx() + req.Gety();break;case '-': // 减法resp.result_ = req.Getx() - req.Gety();break;case '*': // 乘法resp.result_ = req.Getx() * req.Gety();break;case '/': // 除法if (req.Gety() == 0) // 检查除数是否为零{resp.code_ = div_zero; // 如果除数为零,设置错误代码为 `div_zero`break;}resp.result_ = req.Getx() / req.Gety(); // 执行除法运算break;case '%': // 取余if (req.Gety() == 0) // 检查除数是否为零{resp.code_ = mod_zero; // 如果除数为零,设置错误代码为 `mod_zero`break;}resp.result_ = req.Getx() % req.Gety(); // 执行取余运算break;default:break; // 如果运算符不匹配,直接退出}return resp; // 返回计算结果}// 计算器函数,处理客户端发送的请求数据包std::string Calculator(std::string &package){// 移除报头部分,解码数据包std::string context;bool rDecode = Decode(package, &context); // 解码包头if(!rDecode) return ""; // 解码失败时返回空字符串// 反序列化请求数据Requset req;req.Deserialize(context);// 根据请求数据进行计算Response resp;resp = Calculatate(req);sleep(3); // 模拟计算延时(可能用于测试)// 序列化计算结果并返回std::string in;bool r = resp.Serialize(&in);if(!r) return ""; // 如果序列化失败,返回空字符串// 编码处理后的结果context = "";context = Encode(in); // 编码计算结果并准备返回return context; // 返回最终的编码结果}// 析构函数,销毁 ServerCal 对象~ServerCal(){}
};
代码说明:
1. ServerCal
类:
ServerCal
类包含了处理数学计算的逻辑,通过接收客户端发送的请求并计算结果,然后返回计算结果给客户端。
2. Calculatate(Requset &req)
函数:
- 该函数接收一个
Requset
对象,表示客户端发送的请求。 - 根据请求的操作符(
op_
),执行相应的数学运算(加、减、乘、除、取余)。如果请求中存在除数为零的情况(除法和取余),则返回相应的错误代码(div_zero
或mod_zero
)。 - 最终返回一个
Response
对象,包含计算结果。
3. Calculator(std::string &package)
函数:
- 该函数接收客户端发送的包含请求数据的字符串
package
。 - 首先解码数据包头部,去除冗余信息并获取有效数据。
- 然后将解码后的数据反序列化为
Requset
对象。 - 使用
Calculatate()
方法进行实际的计算。 - 计算结果通过
Serialize()
序列化,之后进行编码。 - 返回最终的结果给客户端。
请求和响应服务
#pragma once#include <iostream>
#include <string>
#include <cstring>
#include <jsoncpp/json/json.h> // 引入JSON库,用于数据的序列化和反序列化
#include "log.hpp" // 引入日志功能// 定义协议分隔符常量
const std::string space_sep = " "; // 空格分隔符
const std::string protocal_sep = "\n"; // 协议分隔符(换行符)// 编码函数:将数据进行编码,格式为 "len/n x + y/n"
std::string Encode(std::string &context)
{std::string s = std::to_string(context.size()); // 获取数据长度并转换为字符串s += protocal_sep; // 添加协议分隔符s += context; // 添加实际数据内容s += protocal_sep; // 添加协议分隔符return s; // 返回编码后的数据
}// 解码函数:将协议数据解码,移除头部长度并提取有效数据
bool Decode(std::string &package, std::string *context)
{auto pos = package.find(protocal_sep); // 查找协议分隔符位置if (pos == std::string::npos)return false; // 如果没有找到协议分隔符,返回解码失败std::string len_str = package.substr(0, pos); // 获取数据长度部分std::size_t len = std::stoi(len_str); // 将长度字符串转换为整数int total_len = len + len_str.size() + 2; // 总长度 = 数据长度 + 协议头的长度(包括分隔符)if (package.size() < total_len) // 检查数据包是否完整return false;*context = package.substr(pos + 1, len); // 提取有效数据// 移除解码数据,剩余部分为下一个包的内容package.erase(0, total_len); return true; // 解码成功
}// 请求类(Client Request)
class Requset
{
public:// 构造函数,初始化请求数据Requset(int data1, char op, int data2): x_(data1), op_(op), y_(data2){}Requset() // 默认构造函数{}~Requset() // 析构函数{}void Print() // 打印请求内容{std::cout << x_ << op_ << y_ << std::endl;}// 序列化函数:将请求数据(x, op, y)序列化为字符串bool Serialize(std::string *out){
#ifdef Myself// 在Myself模式下:直接构建字符串格式 "x op y"std::string s = std::to_string(x_);s += space_sep;s += op_;s += space_sep;s += std::to_string(y_);*out = s; // 返回序列化后的数据return true;
#else// 否则使用JSON格式序列化Json::Value root;root["x"] = x_;root["y"] = y_;root["op"] = op_;Json::FastWriter w;*out = w.write(root); // 返回序列化后的JSON字符串return true;
#endif}// 反序列化函数:从字符串中提取数据(x, op, y)bool Deserialize(const std::string &in){
#ifdef Myself// 在Myself模式下:解析有效载荷auto pos1 = in.find(space_sep);if (pos1 == std::string::npos)return false; // 如果没有找到空格分隔符,返回失败std::string part_x = in.substr(0, pos1); // 提取x部分auto pos2 = in.rfind(space_sep);std::string oper = in.substr(pos1 + 1, pos2); // 提取操作符部分std::string part_y = in.substr(pos2 + 1); // 提取y部分if (pos2 != pos1 + 2) // 如果格式不正确,返回失败return false;op_ = in[pos1 + 1]; // 设置操作符x_ = std::stoi(part_x); // 转换x为整数y_ = std::stoi(part_y); // 转换y为整数return true;
#else// 否则使用JSON格式反序列化Json::Value root;Json::Reader r;r.parse(in, root); // 解析JSON字符串x_ = root["x"].asInt(); // 提取xy_ = root["y"].asInt(); // 提取yop_ = root["op"].asString()[0]; // 提取操作符(假设只有一个字符)return true;
#endif}int Getx() { return x_; } // 获取x值int Gety() { return y_; } // 获取y值char Getop() { return op_; } // 获取操作符private:int x_; // 操作数xchar op_; // 运算符int y_; // 操作数y
};// 响应类(Server Response)
class Response
{
public:Response(int ret, int code): result_(ret), code_(code){}Response() {}~Response() {}// 序列化函数:将响应数据(result_, code_)序列化为字符串bool Serialize(std::string *out){
#ifdef Myself// 在Myself模式下:构建字符串格式 "result code"std::string s = std::to_string(result_);s += space_sep;s += std::to_string(code_);*out = s;return true;
#else// 否则使用JSON格式序列化Json::Value root;root["result"] = result_;root["code"] = code_;Json::FastWriter w;*out = w.write(root); // 返回序列化后的JSON字符串return true;
#endif}// 反序列化函数:从字符串中提取响应数据(result_, code_)bool Deserialize(const std::string &in){
#ifdef Myselfauto pos = in.find(space_sep);std::string res = in.substr(0, pos);std::string code = in.substr(pos + 1);if (pos != in.rfind(space_sep)) // 如果没有找到正确的分隔符,返回失败return false;result_ = std::stoi(res); // 转换result_为整数code_ = std::stoi(code); // 转换code_为整数return true;
#else// 否则使用JSON格式反序列化Json::Value root;Json::Reader r;r.parse(in, root); // 解析JSON字符串result_ = root["result"].asInt(); // 提取result_code_ = root["code"].asInt(); // 提取code_return true;
#endif}void Print() // 打印响应内容{std::cout << "result_: " << result_ << " code_: " << code_ << std::endl;}private:int result_; // 计算结果int code_; // 错误代码(例如除数为零等错误)
};
代码总结:
1. Encode
和 Decode
:
Encode
:将字符串的大小和内容打包为带有协议头的格式,便于传输。Decode
:从带有协议头的包中提取有效数据,移除头部信息并返回有效部分。
2. Requset
类:
- 表示客户端的计算请求,包括
x_
、op_
、y_
三个字段(操作数和运算符)。 Serialize
和Deserialize
用于数据的序列化和反序列化。- 提供
Getx
、Gety
和Getop
获取请求参数的函数。
3. Response
类:
- 表示服务器的响应,包括计算结果
result_
和错误代码code_
。 Serialize
和Deserialize
用于数据的序列化和反序列化。- 提供
Print
方法输出响应内容。
网络组件
#pragma once#include <iostream>
#include <string>
#include <cstring>
#include <strings.h> // 用于处理字节操作
#include <sys/types.h> // 引入系统数据类型
#include <sys/socket.h> // 引入套接字API
#include <arpa/inet.h> // 引入IP地址相关函数
#include <netinet/in.h> // 引入IPv4协议结构和常量#include "log.hpp" // 引入日志记录功能int backlog = 10; // 定义监听队列的最大连接数// 定义错误枚举,表示不同的错误类型
enum err
{Socketerr = 1, // 套接字创建失败Bindeterr, // 套接字绑定失败Listeneterr, // 监听失败Accepteterr, // 接受连接失败
};// 套接字类,封装了套接字的创建、绑定、监听、连接和关闭等操作
class Sock
{
public:// 默认构造函数Sock(){}// 析构函数~Sock(){}// 创建套接字void Socket(){sockfd_ = socket(AF_INET, SOCK_STREAM, 0); // 创建一个IPv4 TCP套接字if (sockfd_ < 0) // 检查套接字是否创建成功{lg(FATAL, "Socket error: %d,%s", errno, strerror(errno)); // 输出日志并退出exit(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; // 设置地址族为IPv4peer.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); // 错误退出}}// 开始监听连接请求void Listen(){if (listen(sockfd_, backlog) < 0) // 监听套接字,最大连接数为 `backlog`{lg(FATAL, "Listen error: %d,%s", errno, strerror(errno)); // 输出日志并退出exit(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); // 错误退出}// 获取客户端的IP地址char ip[64];inet_ntop(AF_INET, &peer.sin_addr.s_addr, ip, sizeof(ip));*clientip = ip; // 返回客户端IP*clientport = ntohs(peer.sin_port); // 返回客户端端口号(使用主机字节序)return newfd; // 返回新的文件描述符}// 连接到服务器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()); // 设置目标IPpeer.sin_port = htons(port); // 设置目标端口(使用网络字节顺序)peer.sin_family = AF_INET; // 设置地址族为IPv4int n = connect(sockfd_, (struct sockaddr*)&(peer), len); // 连接到服务器if (n < 0){lg(WARNING, "Connect error: %d,%s", errno, strerror(errno)); // 输出日志并返回失败return false;}return true; // 连接成功}// 关闭套接字void Close(){close(sockfd_);}// 获取套接字的文件描述符int GetFd(){return sockfd_; // 返回套接字的文件描述符}private:int sockfd_; // 套接字的文件描述符
};
代码说明:
1. Socket()
:
- 创建一个 IPv4 TCP 套接字,并检查是否成功创建。如果创建失败,输出日志并退出程序。
2. Bind(uint16_t port)
:
- 将套接字绑定到指定的端口,并设置为监听来自所有网络接口的请求。如果绑定失败,输出日志并退出程序。
3. Listen()
:
- 开始监听客户端连接,
backlog
变量定义了最大等待连接队列。如果监听失败,输出日志并退出程序。
4. Accept(std::string \*clientip, uint16_t \*clientport)
:
- 接受一个客户端的连接请求,并返回一个新的文件描述符,用于与客户端进行通信。还会返回客户端的 IP 地址和端口号。
5. Connect(const std::string &ip, const uint16_t &port)
:
- 连接到指定的服务器 IP 和端口。如果连接失败,输出日志并返回
false
;否则返回true
。
6. Close()
:
- 关闭当前套接字,释放资源。
7. GetFd()
:
- 返回套接字的文件描述符,这个文件描述符可用于读取或写入数据。