跳到主要内容
C++ 网络编程实战:基于 TCP 协议的简易计算器 | 极客日志
C++ 算法
C++ 网络编程实战:基于 TCP 协议的简易计算器 C++ 网络编程实战项目展示 TCP 协议下的简易计算器实现。项目包含服务器端与客户端代码,利用 Socket 封装网络通信,通过 Fork 多进程处理并发请求。数据传输采用序列化与自定义编码格式,支持加减乘除及取余运算,并包含除零错误处理。代码结构清晰,涵盖日志记录、构建配置及协议定义,适合学习 Linux 网络编程基础。
并发大师 发布于 2026/3/30 更新于 2026/6/1 16 浏览前言
根据前面的经验:网络套接字,序列化与反序列化,守护进程等等,写出一个小项目。
文件组成
.vscode/
log .hpp
Makefile
Protocol.hpp
ServerCal.hpp
Socket.hpp
TcpClient/
TcpClient.cc
TcpClient.hpp
TcpServer/
TcpServer.cc
TcpServer.hpp
TCP 服务端
TcpServer.hpp
#include <functional>
#include "Socket.hpp"
#include "ServerCal.hpp"
using func_t = std::function<std::string (std::string& package)>;
class TcpServer {
public :
TcpServer (uint16_t port, func_t callback) : port_ (port), callback_ (callback) {}
{
listensock_. ();
listensock_. (port_);
listensock_. ();
(INFO, );
}
{
( ) {
std::string ip;
port;
fd = listensock_. (&ip, &port);
(INFO, );
(fork() == ) {
(INFO, );
listensock_. ();
std::string inbuffer_stream;
( ) {
buffer[ ];
n = (fd, &buffer, (buffer));
(n > ) {
(INFO, );
buffer[n] = ;
inbuffer_stream += buffer;
( ) {
std::string info = (inbuffer_stream);
(info. ()) ;
(fd, info. (), info. ());
}
} (n == )
;
;
}
( );
}
(fd);
}
}
:
port_;
Socket listensock_;
callback_;
};
void Init ()
Socket
Bind
Listen
lg
"Server Init"
void Start ()
while
true
uint16_t
int
Accept
lg
"Server Accept"
if
0
lg
"fork success"
Close
while
true
char
1024
int
read
sizeof
if
0
lg
"read success"
0
while
true
callback_
if
empty
break
write
c_str
size
else
if
0
break
else
break
exit
0
close
private
uint16_t
func_t
代码说明:
构造函数 TcpServer(uint16_t port, func_t callback) :
初始化服务器的端口和回调函数。回调函数 callback_ 将在收到客户端请求时被调用,用于处理数据。
Init() :
该方法用于初始化服务器,创建套接字、绑定端口并开始监听客户端连接。
Start() :
该方法是服务器的主循环,用于接受客户端的连接请求。
每当有新的客户端连接时,服务器会通过 fork() 创建子进程来处理该客户端的请求。
在子进程中,读取客户端发送的数据,并通过回调函数处理数据。处理结果将通过套接字返回给客户端。
子进程处理完毕后会退出,而父进程继续等待新的客户端连接。
Socket listensock_ :
Socket 类的对象,用于处理底层套接字操作,包括创建、绑定、监听、接收连接等。
回调函数 func_t callback_ :
回调函数类型,负责处理客户端发来的数据包,并返回处理后的结果。
TcpServer.cc #include <iostream>
#include <string>
#include <functional>
#include "log.hpp"
#include "TcpServer.hpp"
#include "Protocol.hpp"
void RequestTest () ;
void ResponseTest () ;
void Usage () {
std::cout << "\n\r" << "[Usage]:prot" << "\n" << std::endl;
}
int main (int argc, char * argv[]) {
if (argc != 2 ) {
Usage ();
return -1 ;
}
lg (INFO, "Server start" );
ServerCal cal;
std::string port_1 = argv[1 ];
uint16_t port = std::stoi (port_1);
TcpServer* tsur = new TcpServer (port, std::bind (&ServerCal::Calculator, &cal, std::placeholders::_1));
tsur->Init ();
tsur->Start ();
return 0 ;
}
代码说明:
头文件引入 :
#include "log.hpp":用于记录日志,lg(INFO, "message") 可以在程序中输出日志信息。
#include "TcpServer.hpp":引入定义 TcpServer 类的头文件,用于创建 TCP 服务器。
#include "Protocol.hpp":引入协议相关的头文件,可能包含协议的定义和数据格式的处理方法。
Usage() 函数 :
该函数在命令行参数不正确时,输出程序的使用方式,提示用户如何正确传递命令行参数。
main() 函数 :
程序的入口点,首先检查命令行参数的数量。如果参数不正确,则调用 Usage() 输出帮助信息,并返回错误。
如果参数正确,程序继续执行:
创建 ServerCal 对象 cal,该对象负责计算接收到的数据。
通过 argv[1] 获取并转换端口号。
创建 TcpServer 对象 tsur,并将 ServerCal::Calculator 函数作为回调函数绑定到 TcpServer 中。此回调函数会处理客户端请求的数据。
调用 tsur->Init() 初始化服务器,tsur->Start() 启动服务器,开始监听和处理客户端连接。
TCP 客户端 #include <iostream>
#include <unistd.h>
#include <time.h>
#include <string>
#include "log.hpp"
#include "Socket.hpp"
#include "Protocol.hpp"
void Usage () {
std::cout << "\n\r" << "[Usage]:port" << "\n" << std::endl;
}
int main (int argc, char * argv[]) {
if (argc != 3 ) {
Usage ();
return -1 ;
}
srand (time (nullptr ));
Socket sock;
sock.Socket ();
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++;
Request req;
req.x_ = rand () % 100 + 1 ;
req.y_ = rand () % 100 ;
req.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 );
std::cout << "========================================" << std::endl;
}
return 0 ;
}
代码说明:
Usage() :
用于输出程序的使用说明,提示用户需要传递端口号作为参数。
main() :
主程序入口,首先检查命令行参数是否正确。如果参数不正确,调用 Usage() 输出帮助信息。
使用 srand(time(nullptr)) 设置随机数种子,确保每次运行时生成不同的随机数。
创建一个 Socket 对象,初始化并连接到指定的 IP 地址和端口号。
在 while 循环中,程序持续进行请求:
生成随机的请求数据,包括 x_、y_ 和运算符 op_。
使用 Serialize() 方法将请求数据序列化,调用 Encode() 方法进行编码。
发送请求数据到服务器。
接收来自服务器的响应数据,使用 Decode() 进行解码,并使用 Deserialize() 方法将响应数据反序列化。
最后,输出计算结果。
日志记录 :
使用 lg(INFO, "message") 输出程序的日志信息,帮助调试和跟踪程序的状态。
计算器 #pragma once
#include <iostream>
#include "log.hpp"
#include "Protocol.hpp"
enum err_symbol {
div_zero = 1 ,
mod_zero
};
class ServerCal {
public :
ServerCal () {}
Response Calculate (Request &req) {
Response resp (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;
break ;
}
resp.result_ = req.Getx () / req.Gety ();
break ;
case '%' :
if (req.Gety () == 0 ) {
resp.code_ = 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 "" ;
Request req;
req.Deserialize (context);
Response resp;
resp = Calculate (req);
sleep (3 );
std::string in;
bool r = resp.Serialize (&in);
if (!r) return "" ;
context = "" ;
context = Encode (in);
return context;
}
~ServerCal () {}
};
代码说明:
1. ServerCal 类 :
ServerCal 类包含了处理数学计算的逻辑,通过接收客户端发送的请求并计算结果,然后返回计算结果给客户端。
2. Calculate(Request &req) 函数 :
该函数接收一个 Request 对象,表示客户端发送的请求。
根据请求的操作符(op_),执行相应的数学运算(加、减、乘、除、取余)。如果请求中存在除数为零的情况(除法和取余),则返回相应的错误代码(div_zero 或 mod_zero)。
最终返回一个 Response 对象,包含计算结果。
3. Calculator(std::string &package) 函数 :
该函数接收客户端发送的包含请求数据的字符串 package。
首先解码数据包头部,去除冗余信息并获取有效数据。
然后将解码后的数据反序列化为 Request 对象。
使用 Calculate() 方法进行实际的计算。
计算结果通过 Serialize() 序列化,之后进行编码。
返回最终的结果给客户端。
请求和响应服务 #pragma once
#include <iostream>
#include <string>
#include <cstring>
#include <jsoncpp/json/json.h>
#include "log.hpp"
const std::string space_sep = " " ;
const std::string protocal_sep = "\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 ;
}
class Request {
public :
Request (int data1, char op, int data2) : x_ (data1), op_ (op), y_ (data2) {}
Request () {}
~Request () {}
void Print ()
{
std::cout << x_ << op_ << y_ << std::endl;
}
bool Serialize (std::string *out) {
#ifdef Myself
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::Value root;
root["x" ] = x_;
root["y" ] = y_;
root["op" ] = op_;
Json::FastWriter w;
*out = w.write (root);
return true ;
#endif
}
bool Deserialize (const std::string &in) {
#ifdef Myself
auto pos1 = in.find (space_sep);
if (pos1 == std::string::npos) return false ;
std::string part_x = in.substr (0 , pos1);
auto pos2 = in.rfind (space_sep);
std::string oper = in.substr (pos1 + 1 , pos2);
std::string part_y = in.substr (pos2 + 1 );
if (pos2 != pos1 + 2 ) return false ;
op_ = in[pos1 + 1 ];
x_ = std::stoi (part_x);
y_ = std::stoi (part_y);
return true ;
#else
Json::Value root;
Json::Reader r;
r.parse (in, root);
x_ = root["x" ].asInt ();
y_ = root["y" ].asInt ();
op_ = root["op" ].asString ()[0 ];
return true ;
#endif
}
int Getx () { return x_; }
int Gety () { return y_; }
char Getop () { return op_; }
private :
int x_;
char op_;
int y_;
};
class Response {
public :
Response (int ret, int code) : result_ (ret), code_ (code) {}
Response () {}
~Response () {}
bool Serialize (std::string *out) {
#ifdef Myself
std::string s = std::to_string (result_);
s += space_sep;
s += std::to_string (code_);
*out = s;
return true ;
#else
Json::Value root;
root["result" ] = result_;
root["code" ] = code_;
Json::FastWriter w;
*out = w.write (root);
return true ;
#endif
}
bool Deserialize (const std::string &in) {
#ifdef Myself
auto 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);
code_ = std::stoi (code);
return true ;
#else
Json::Value root;
Json::Reader r;
r.parse (in, root);
result_ = root["result" ].asInt ();
code_ = root["code" ].asInt ();
return true ;
#endif
}
void Print ()
{
std::cout << "result_: " << result_ << " code_: " << code_ << std::endl;
}
private :
int result_;
int code_;
};
代码总结:
1. Encode 和 Decode :
Encode:将字符串的大小和内容打包为带有协议头的格式,便于传输。
Decode:从带有协议头的包中提取有效数据,移除头部信息并返回有效部分。
2. Request 类 :
表示客户端的计算请求,包括 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>
#include <arpa/inet.h>
#include <netinet/in.h>
#include "log.hpp"
int backlog = 10 ;
enum err {
Socketerr = 1 ,
Bindeterr,
Listeneterr,
Accepteterr
};
class Socket {
public :
Socket () {}
~Socket () {}
void Socket () {
sockfd_ = socket (AF_INET, SOCK_STREAM, 0 );
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;
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);
}
}
void Listen () {
if (listen (sockfd_, backlog) < 0 ) {
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);
}
char ip[64 ];
inet_ntop (AF_INET, &peer.sin_addr.s_addr, ip, sizeof (ip));
*clientip = 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 ());
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 ;
}
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() :
返回套接字的文件描述符,这个文件描述符可用于读取或写入数据。
相关免费在线工具 加密/解密文本 使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online
Gemini 图片去水印 基于开源反向 Alpha 混合算法去除 Gemini/Nano Banana 图片水印,支持批量处理与下载。 在线工具,Gemini 图片去水印在线工具,online
Base64 字符串编码/解码 将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
Base64 文件转换器 将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online
Markdown转HTML 将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online
HTML转Markdown 将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML转Markdown在线工具,online