目录
1.应用层
再谈 "协议"
网络版计算器
序列化 和 反序列化
2.重新理解read、write、recv、send和tcp为什么支持全双工
1.socket的封装—模版方法模式
2.自定义协议
char buffer[1024]; ssize_t n = read(sockfd,buffer,sizeof(buffer)-1); //读取字符串,其实是有bug的
1.应用层
我们程序员写的一个个解决我们实际问题, 满足我们日常需求的网络程序, 都是在应用层.
再谈 "协议"
协议是一种 "约定". socket api的接口, 在读写数据时, 都是按 "字符串" 的方式来发送接收的. 如果我们 要传输一些 "结构化的数据" 怎么办呢?
其实,协议就是双方约定好的结构化的数据
头像->图片的路径
昵称->string
消息内容->字符串
struct message
{ 方便上层进行处理
string path;
string nick_name;
std::string msg;
}
头像->图片的路径 昵称->string 消息内容->字符串
将多个字符串合成一个字符串 –保证报文完整性,方便网络传送
把多条结构化数据转成字符串的过程叫做序列化 将一行的变成多行的过程叫做反序列化
c/s模式
网络版计算器
例如, 我们需要实现一个服务器版的加法器. 我们需要客户端把要计算的两个加数发过去, 然后由服务器 进行计算, 最后再把结果返回给客户端.
约定方案一: • 客户端发送一个形如"1+2"的字符串; • 这个字符串中有两个操作数, 都是整形; • 两个数字之间会有一个字符是运算符, 运算符只能是 + ; • 数字和运算符之间没有空格; • …
约定方案二: • 定义结构体来表示我们需要交互的信息; • 发送数据时将这个结构体按照一个规则转换成字符串, 接收到数据的时候再按照相同的规则把字 符串转化回结构体; • 这个过程叫做 "序列化" 和 "反序列化"
序列化 和 反序列化

无论我们采用方案一, 还是方案二, 还是其他的方案, 只要保证, 一端发送时构造的数据, 在另一端能够正确的进行解析, 就是ok的. 这种约定, 就是 应用层协议
但是,为了让我们深刻理解协议,我们打算自定义实现一下协议的过程。 • 我们采用方案2,我们也要体现协议定制的细节 • 我们要引入序列化和反序列化,只不过我们课堂直接采用现成的方案 — jsoncpp库 • 我们要对socket进行字节流的读取处理

OS内部,协议全部都是传递结构体对象 因为OS都是c语言写的
可以直接发送二进制对象吗?可以,但是不推荐
因为cs双方都能认识这个结构体
1.c和s 内存对齐问题
2.C/C++
C/C++,python,Java
没有办法进行混合式的编写
有了序列化,可以把结构转成字符串。有了反序列化可以把字符串转结构化。将来要是其中结构化字段发生变更,序列化统一都是字符串,并不影响网络的发送。
序列化和反序列化本质是对软件进行解耦
从今天开始,如果我们要进行网络协议式的通信,在应用层,强烈建议使用序列化和反序列化方案,
至于直接传递结构体的方案,除非场景特殊,否则,不建议
2.重新理解read、write、recv、send和tcp为什么支持全双工

所以:
• 在任何⼀台主机上,TCP连接既有发送缓冲区,⼜有接受缓冲区,所以,在内核中,可以在发消息的同时,也可以收消息,即全双工 • 这就是为什么⼀个tcpsockfd读写都是它的原因 • 实际数据什么时候发,发多少,出错了怎么办,由TCP控制,所以TCP叫做传输控制协议
1.write,read就是收发消息—write和read是不是把数据发送到了网络中?
不是,只是写到了发送缓冲区里
write本质是拷贝!
有tpc协议自主决定:什么时候发,发多少,出错了怎么办? —主要进行的是数据拷贝
所以TCP叫做传输控制协议
OS把数据发送到网络中,本质也是拷贝!
在计算机世界:通信即拷贝!
read,其实检测接收缓冲区有没有数据
如果没有,read就会阻塞 如果有,read把接收缓冲区数据拷贝到用户缓冲区里 所以read本质也是拷贝
2.主机间通信的本质:把发送方的发送缓冲区内部的数据,拷贝到对端的接受缓冲区!
3.为什么TCP通信的时候,是全双工的?因为有两对发送和接受缓冲区!!!!!!
一个往缓冲区里写,一个从缓冲区拿—也就像内核和用户之间的生产者消费者模型
所以没有数据,read会阻塞
读条件不就绪,在和系统同步


不建议把结构化数据直接扔到发送缓冲区里,需要把信息做序列化,序列化之后的字符串拷贝传到发送缓冲区里。到对端的接收缓冲区里,得到请求之后拿到完整请求,将其反序列化成结构化数据
序列化的本质还是为了方便网络发送!
如果主机A序列化了,形成一条序列化数据,拷贝到发送缓冲区里。理论上客户端会就该字符串完整发过来。但是如果我们上层序列化了,形成了字符串信息,对应的OS在发的时候一定要把对应的字符串打个包做个整体给对方吗?
在UDP那里是这样的,发一个用户读一个,这个过程叫做用户数据报。
可是TCP:面向字节流。数据什么时候发?发多少?出错了怎么办?这个过程完全由发送方的OS和TCP协议自主决定。对于接下来发送方把自己结构化的字符串拷贝到发送缓冲区里,那么对于上层就认为自己已经把数据拷贝完write返回了。
我们的OS并不关心给你拷贝的字符串是什么,OS根据发送的实际情况你给的报文发送一部分,由此可能就不能拿到完整报文了,这样反序列化就出问题了。
结论:在TCP中,要读到完整的报文,有接收方应用是自己搞定
面向字节流,对方发了多少次和你收了多少次不一定对等
ssize_t n = read(sockfd, buffer, sizeof(buffer) – 1);//有bug的! 为什么说这个有bug呢?因为可能读取来只有半个单词
TCP面向字节流
我们未来定制的协议: 1.结构化的字段,提供好序列和反序列化方案 2.解决因为字节流问题,导致读取报文不完整的问题(只处理读取)
网络版计算器
1.socket的封装—模版方法模式
Socket.hpp
#pragma once
namespace SocketModule
{
// 模板方法模式
// 基类socket,大部分方法,都是纯虚方法
class Socket
{
public:
virtual ~Socket() {}
virtual int SocketOrDie() = 0; // 创建
virtual bool BindOrDie() = 0; // 绑定
virtual bool ListenOrDie() = 0; // 监听
virtual int Accept() = 0;
public:
void BuildListenSocketMethod()
{
SocketOrDie();
BindOrDie();
ListenOrDie();
}
void BuildUdpSocketMethod()
{
SocketOrDie();
BindOrDie();
}
};
class TcpSocket : public Socket
{
public:
~TcpSocket() {}
int SocketOrDie() override
{
return 0;
}
bool BindOrDie() override
{
return true;
}
bool ListenOrDie() override
{
return true;
}
int Accept() override
{
return 1;
}
};
class UdpSocket:public Socket
{
};
}
main.cc
#include"Socket.hpp"
#include <memory>
using namespace SocketModule;
int main(int argc, char *argv[])
{
std::unique_ptr<Socket> tcpsockp = std::unique_ptr<TcpSocket>();
tcpsockp->BuildListenSocketMethod();
std::unique_ptr<Socket> udpsockp = std::unique_ptr<UdpSocket>();
udpsockp->BuildUdpSocketMethod();
}
父类提供一套基本的纯虚方法交给不同的子类来实现,父类当中存在一个固定化的模板方法完成套接字初始化的固定套路,这个方法就是对上面程序化的组合。这就叫做模板方法模式
以后想让自己套接字有什么服务,就子类继承,编译上对应的构建方法,然后用父类的指向确定的子类,直接调用对应的方法做方法组合,完成对应的OS。
main.cc
#include "TcpServer.hpp"
#include <memory>
void Usage(std::string proc)
{
std::cerr << "Usage: " << proc << " port" << std::endl;
}
// ./tcpserver 8080
int main(int argc, char *argv[])
{
if (argc != 2)
{
Usage(argv[0]);
exit(USAGE_ERR);
}
// std::unique_ptr<Protocol> protocol = std::make_unique<Protocol>();
std::unique_ptr<TcpServer> tsvr = std::make_unique<TcpServer>(std::stoi(argv[1]));
tsvr->Start();
return 0;
}
Makefile
.PHONY:all
all:tcpserver
tcpserver:main.cc
g++ -o $@ $^ -std=c++17
.PHONY:clean
clean:
rm -f tcpserver
Socket.hpp
#pragma once
#include <iostream>
#include <string>
#include <unistd.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <cstdlib>
#include "Common.hpp"
#include "Log.hpp"
#include "InetAddr.hpp"
namespace SocketModule
{
using namespace LogModule;
const static int gbacklog = 16;
// 模板方法模式
// 基类socket,大部分方法,都是纯虚方法
class Socket
{
public:
virtual ~Socket() {}
virtual void SocketOrDie() = 0;//创建
virtual void BindOrDie(uint16_t port) = 0;//绑定
virtual void ListenOrDie(int backlog) = 0;//L监听
virtual int Accept() = 0;
public:
void BuildTcpSocketMethod(uint16_t port, int backlog = gbacklog)
{
SocketOrDie();
BindOrDie(port);
ListenOrDie(backlog);
}
};
class TcpSocket : public Socket
{
public:
~TcpSocket() {}
void SocketOrDie() override
{
_sockfd = ::socket(AF_INET, SOCK_STREAM, 0);
if (_sockfd < 0)
{
LOG(LogLevel::FATAL) << "socket error";
exit(SOCKET_ERR);
}
LOG(LogLevel::INFO) << "socket success";
}
void BindOrDie(uint16_t port) override
{
InetAddr localaddr(port);
int n = ::bind(_sockfd, localaddr.NetAddrPtr(), localaddr.NetAddrLen());
if (n < 0)
{
LOG(LogLevel::FATAL) << "bind error";
exit(BIND_ERR);
}
LOG(LogLevel::INFO) << "bind success";
}
void ListenOrDie(int backlog) override
{
int n = ::listen(_sockfd, backlog);
if (n < 0)
{
LOG(LogLevel::FATAL) << "listen error";
exit(LISTEN_ERR);
}
LOG(LogLevel::INFO) << "listen success";
}
int Accept() override
{
sleep(1);
return 1;//TODO
}
private:
int _sockfd; // _sockfd, listensockfd, sockfd;
};
}
TcpServer.hpp
#include "Socket.hpp"
#include <iostream>
#include <memory>
#include <sys/wait.h>
#include <functional>
using namespace SocketModule;
using namespace LogModule;
using ioservice_t = std::function<void(std::shared_ptr<Socket> &sock, InetAddr &client)>;
// 主要解决:连接的问题,IO通信的问题
// 细节: TcpServer,需不需要关心自己未来传递的信息是什么?不需要关心!!
// 网络版本的计算器,长服务
class TcpServer
{
public:
TcpServer(uint16_t port) :
_port(port),
_listensockptr(std::make_unique<TcpSocket>()),
_isrunning(false)
{
_listensockptr->BuildTcpSocketMethod(_port);
}
void Start()
{
_isrunning = true;
while(_isrunning)
{
_listensockptr->Accept();
LOG(LogLevel::DEBUG) << "server is running …";
}
_isrunning = false;
}
~TcpServer() {}
private:
uint16_t _port;
std::unique_ptr<Socket> _listensockptr;
bool _isrunning;
};
Common.hpp
#pragma once
#include <iostream>
#include <functional>
#include <unistd.h>
#include <string>
#include <cstring>
#include <sys/socket.h>
#include <sys/types.h>
#include <arpa/inet.h>
#include <netinet/in.h>
enum ExitCode
{
OK = 0,
USAGE_ERR,
SOCKET_ERR,
BIND_ERR,
LISTEN_ERR,
CONNECT_ERR,
FORK_ERR
};
class NoCopy
{
public:
NoCopy(){}
~NoCopy(){}
NoCopy(const NoCopy &) = delete;
const NoCopy &operator = (const NoCopy&) = delete;
};
#define CONV(addr) ((struct sockaddr*)&addr)
InetAddr.hpp
#pragma once
#include "Common.hpp"
// 网络地址和主机地址之间进行转换的类
class InetAddr
{
public:
InetAddr() {}
InetAddr(struct sockaddr_in &addr)
{
SetAddr(addr);
}
InetAddr(const std::string &ip, uint16_t port) : _ip(ip), _port(port)
{
// 主机转网络
memset(&_addr, 0, sizeof(_addr));
_addr.sin_family = AF_INET;
inet_pton(AF_INET, _ip.c_str(), &_addr.sin_addr);
_addr.sin_port = htons(_port);
// local.sin_addr.s_addr = inet_addr(_ip.c_str()); // TODO
}
InetAddr(uint16_t port) : _port(port), _ip()
{
// 主机转网络
memset(&_addr, 0, sizeof(_addr));
_addr.sin_family = AF_INET;
_addr.sin_addr.s_addr = INADDR_ANY;
_addr.sin_port = htons(_port);
}
void SetAddr(struct sockaddr_in &addr)
{
_addr = addr;
// 网络转主机
_port = ntohs(_addr.sin_port); // 从网络中拿到的!网络序列
// _ip = inet_ntoa(_addr.sin_addr); // 4字节网络风格的IP -> 点分十进制的字符串风格的IP
char ipbuffer[64];
inet_ntop(AF_INET, &_addr.sin_addr, ipbuffer, sizeof(_addr));
_ip = ipbuffer;
}
uint16_t Port() { return _port; }
std::string Ip() { return _ip; }
const struct sockaddr_in &NetAddr() { return _addr; }
const struct sockaddr *NetAddrPtr()
{
return CONV(_addr);
}
socklen_t NetAddrLen()
{
return sizeof(_addr);
}
bool operator==(const InetAddr &addr)
{
return addr._ip == _ip && addr._port == _port;
}
std::string StringAddr()
{
return _ip + ":" + std::to_string(_port);
}
~InetAddr()
{
}
private:
struct sockaddr_in _addr;
std::string _ip;
uint16_t _port;
};
Log.hpp
#ifndef __LOG_HPP__
#define __LOG_HPP__
#include <iostream>
#include <cstdio>
#include <string>
#include <filesystem> //C++17
#include <sstream>
#include <fstream>
#include <memory>
#include <ctime>
#include <unistd.h>
#include "Mutex.hpp"
namespace LogModule
{
using namespace MutexModule;
const std::string gsep = "\\r\\n";
// 策略模式,C++多态特性
// 2. 刷新策略 a: 显示器打印 b:向指定的文件写入
// 刷新策略基类
class LogStrategy
{
public:
~LogStrategy() = default;
virtual void SyncLog(const std::string &message) = 0;
};
// 显示器打印日志的策略 : 子类
class ConsoleLogStrategy : public LogStrategy
{
public:
ConsoleLogStrategy()
{
}
void SyncLog(const std::string &message) override
{
LockGuard lockguard(_mutex);
std::cout << message << gsep;
}
~ConsoleLogStrategy()
{
}
private:
Mutex _mutex;
};
// 文件打印日志的策略 : 子类
const std::string defaultpath = "./log";
const std::string defaultfile = "my.log";
class FileLogStrategy : public LogStrategy
{
public:
FileLogStrategy(const std::string &path = defaultpath, const std::string &file = defaultfile)
: _path(path),
_file(file)
{
LockGuard lockguard(_mutex);
if (std::filesystem::exists(_path))
{
return;
}
try
{
std::filesystem::create_directories(_path);
}
catch (const std::filesystem::filesystem_error &e)
{
std::cerr << e.what() << '\\n';
}
}
void SyncLog(const std::string &message) override
{
LockGuard lockguard(_mutex);
std::string filename = _path + (_path.back() == '/' ? "" : "/") + _file; // "./log/" + "my.log"
std::ofstream out(filename, std::ios::app); // 追加写入的 方式打开
if (!out.is_open())
{
return;
}
out << message << gsep;
out.close();
}
~FileLogStrategy()
{
}
private:
std::string _path; // 日志文件所在路径
std::string _file; // 日志文件本身
Mutex _mutex;
};
// 形成一条完整的日志&&根据上面的策略,选择不同的刷新方式
// 1. 形成日志等级
enum class LogLevel
{
DEBUG,
INFO,
WARNING,
ERROR,
FATAL
};
std::string Level2Str(LogLevel level)
{
switch (level)
{
case LogLevel::DEBUG:
return "DEBUG";
case LogLevel::INFO:
return "INFO";
case LogLevel::WARNING:
return "WARNING";
case LogLevel::ERROR:
return "ERROR";
case LogLevel::FATAL:
return "FATAL";
default:
return "UNKNOWN";
}
}
std::string GetTimeStamp()
{
time_t curr = time(nullptr);
struct tm curr_tm;
localtime_r(&curr, &curr_tm);
char timebuffer[128];
snprintf(timebuffer, sizeof(timebuffer),"%4d-%02d-%02d %02d:%02d:%02d",
curr_tm.tm_year+1900,
curr_tm.tm_mon+1,
curr_tm.tm_mday,
curr_tm.tm_hour,
curr_tm.tm_min,
curr_tm.tm_sec
);
return timebuffer;
}
// 1. 形成日志 && 2. 根据不同的策略,完成刷新
class Logger
{
public:
Logger()
{
EnableConsoleLogStrategy();
}
void EnableFileLogStrategy()
{
_fflush_strategy = std::make_unique<FileLogStrategy>();
}
void EnableConsoleLogStrategy()
{
_fflush_strategy = std::make_unique<ConsoleLogStrategy>();
}
// 表示的是未来的一条日志
class LogMessage
{
public:
LogMessage(LogLevel &level, std::string &src_name, int line_number, Logger &logger)
: _curr_time(GetTimeStamp()),
_level(level),
_pid(getpid()),
_src_name(src_name),
_line_number(line_number),
_logger(logger)
{
// 日志的左边部分,合并起来
std::stringstream ss;
ss << "[" << _curr_time << "] "
<< "[" << Level2Str(_level) << "] "
<< "[" << _pid << "] "
<< "[" << _src_name << "] "
<< "[" << _line_number << "] "
<< "- ";
_loginfo = ss.str();
}
// LogMessage() << "hell world" << "XXXX" << 3.14 << 1234
template <typename T>
LogMessage &operator<<(const T &info)
{
// a = b = c =d;
// 日志的右半部分,可变的
std::stringstream ss;
ss << info;
_loginfo += ss.str();
return *this;
}
~LogMessage()
{
if (_logger._fflush_strategy)
{
_logger._fflush_strategy->SyncLog(_loginfo);
}
}
private:
std::string _curr_time;
LogLevel _level;
pid_t _pid;
std::string _src_name;
int _line_number;
std::string _loginfo; // 合并之后,一条完整的信息
Logger &_logger;
};
// 这里故意写成返回临时对象
LogMessage operator()(LogLevel level, std::string name, int line)
{
return LogMessage(level, name, line, *this);
}
~Logger()
{
}
private:
std::unique_ptr<LogStrategy> _fflush_strategy;
};
// 全局日志对象
Logger logger;
// 使用宏,简化用户操作,获取文件名和行号
#define LOG(level) logger(level, __FILE__, __LINE__)
#define Enable_Console_Log_Strategy() logger.EnableConsoleLogStrategy()
#define Enable_File_Log_Strategy() logger.EnableFileLogStrategy()
}
#endif
Mutex.hpp
#pragma once
#include <iostream>
#include <pthread.h>
namespace MutexModule
{
class Mutex
{
public:
Mutex()
{
pthread_mutex_init(&_mutex, nullptr);
}
void Lock()
{
int n = pthread_mutex_lock(&_mutex);
(void)n;
}
void Unlock()
{
int n = pthread_mutex_unlock(&_mutex);
(void)n;
}
~Mutex()
{
pthread_mutex_destroy(&_mutex);
}
pthread_mutex_t *Get()
{
return &_mutex;
}
private:
pthread_mutex_t _mutex;
};
class LockGuard
{
public:
LockGuard(Mutex &mutex):_mutex(mutex)
{
_mutex.Lock();
}
~LockGuard()
{
_mutex.Unlock();
}
private:
Mutex &_mutex;
};
}
root@iZ5waahoxw3q2bZ:~/linux-learning/linux/26-7-22.2/NetCal# make
g++ -o tcpserver main.cc -std=c++17
root@iZ5waahoxw3q2bZ:~/linux-learning/linux/26-7-22.2/NetCal# ./tcpserver
Usage: ./tcpserver port
root@iZ5waahoxw3q2bZ:~/linux-learning/linux/26-7-22.2/NetCal# ./tcpserver 8080
[2026-07-22 09:36:27] [INFO] [471906] [Socket.hpp] [59] – socket success
[2026-07-22 09:36:27] [INFO] [471906] [Socket.hpp] [70] – bind success
[2026-07-22 09:36:27] [INFO] [471906] [Socket.hpp] [80] – listen success
[2026-07-22 09:36:28] [DEBUG] [471906] [TcpServer.hpp] [33] – server is running …
[2026-07-22 09:36:29] [DEBUG] [471906] [TcpServer.hpp] [33] – server is running …
[2026-07-22 09:36:30] [DEBUG] [471906] [TcpServer.hpp] [33] – server is running …
[2026-07-22 09:36:31] [DEBUG] [471906] [TcpServer.hpp] [33] – server is running …
[2026-07-22 09:36:32] [DEBUG] [471906] [TcpServer.hpp] [33] – server is running …
^C
2.自定义协议

改main.cc
#include "Protocol.hpp"
#include "TcpServer.hpp"
#include <memory>
void Usage(std::string proc)
{
std::cerr << "Usage: " << proc << " port" << std::endl;
}
// ./tcpserver 8080
int main(int argc, char *argv[])
{
if (argc != 2)
{
Usage(argv[0]);
exit(USAGE_ERR);
}
std::unique_ptr<Protocol> protocol = std::make_unique<Protocol>();
std::unique_ptr<TcpServer> tsvr = std::make_unique<TcpServer>(std::stoi(argv[1]),
[&protocol](std::shared_ptr<Socket> &sock, InetAddr &client){
protocol->GetRequest(sock, client);
});
tsvr->Start();
return 0;
}
改Protocol.hpp
#pragma once
#include <iostream>
#include <string>
#include <memory>
#include "Socket.hpp"
// 实现一个自定义的网络版本的计算器
using namespace SocketModule;
// 约定好各个字段的含义,本质就是约定好协议!
// client -> server
// 如何要做序列化和反序列化:
// 1. 我们自己写(怎么做) —> 往往不具备很好的扩展性
// 2. 使用现成的方案(这个是我们要写的) —> json -> jsoncpp
class Request
{
public:
Request()
{
}
Request(int x, int y, char oper) : _x(x), _y(y), _oper(oper)
{
}
//Serialize 序列化
std::string Serialize()
{
// _x = 10 _y = 20, _oper = '+'
// "10" "20" '+' : 用空格作为分隔符
}
//Deserialize 反序列化
bool Deserialize(std::string &in)
{
// "10" "20" '+' -> 以空格作为分隔符 -> 10 20 '+'
}
~Request() {}
private:
int _x;
int _y;
char _oper; // + – * / % -> _x _oper _y -> 10 + 20
};
// server -> client
class Response
{
public:
Response() {}
Response(int result, int code) : _result(result), _code(code)
{
}
std::string Serialize()
{
}
bool Deserialize(std::string &in)
{
}
~Response() {}
private:
int _result; // 运算结果,无法区分清楚应答是计算结果,还是异常值
int _code; // 运算结果的退出码。0:sucess, 1,2,3,4->不同的运算异常的情况.
};
// 协议(基于TCP的)需要解决两个问题:
// 1. request和response必须得有序列化和反序列化功能
// 2. 你必须保证,读取的时候,读到完整的请求(TCP, UDP不用考虑)
class Protocol
{
public:
Protocol()
{}
void GetRequest(std::shared_ptr<Socket> &sock, InetAddr &client)
{
}
~Protocol()
{}
private:
// 因为我们用的是多进程
// Request _req;
// Response _resp;
};
改Socket.hpp
#pragma once
#include <iostream>
#include <string>
#include <unistd.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <cstdlib>
#include "Common.hpp"
#include "Log.hpp"
#include "InetAddr.hpp"
namespace SocketModule
{
using namespace LogModule;
const static int gbacklog = 16;
// 模板方法模式
// 基类socket,大部分方法,都是纯虚方法
class Socket
{
public:
virtual ~Socket() {}
virtual void SocketOrDie() = 0;//创建
virtual void BindOrDie(uint16_t port) = 0;//绑定
virtual void ListenOrDie(int backlog) = 0;//L监听
virtual std::shared_ptr<Socket> Accept(InetAddr *client) = 0;
virtual void Close() = 0;
public:
void BuildTcpSocketMethod(uint16_t port, int backlog = gbacklog)
{
SocketOrDie();
BindOrDie(port);
ListenOrDie(backlog);
}
// void BuildUdpSocketMethod()
// {
// SocketOrDie();
// BindOrDie();
// }
};
const static int defaultfd = -1;
class TcpSocket : public Socket
{
public:
TcpSocket():_sockfd(defaultfd)
{}
TcpSocket(int fd):_sockfd(fd)
{}
~TcpSocket() {}
void SocketOrDie() override
{
_sockfd = ::socket(AF_INET, SOCK_STREAM, 0);
if (_sockfd < 0)
{
LOG(LogLevel::FATAL) << "socket error";
exit(SOCKET_ERR);
}
LOG(LogLevel::INFO) << "socket success";
}
void BindOrDie(uint16_t port) override
{
InetAddr localaddr(port);
int n = ::bind(_sockfd, localaddr.NetAddrPtr(), localaddr.NetAddrLen());
if (n < 0)
{
LOG(LogLevel::FATAL) << "bind error";
exit(BIND_ERR);
}
LOG(LogLevel::INFO) << "bind success";
}
void ListenOrDie(int backlog) override
{
int n = ::listen(_sockfd, backlog);
if (n < 0)
{
LOG(LogLevel::FATAL) << "listen error";
exit(LISTEN_ERR);
}
LOG(LogLevel::INFO) << "listen success";
}
std::shared_ptr<Socket> Accept(InetAddr *client) override
{
struct sockaddr_in peer;
socklen_t len = sizeof(peer);
int fd = ::accept(_sockfd, CONV(peer), &len);
if(fd < 0)
{
LOG(LogLevel::WARNING) << "accept warning …";
return nullptr; // TODO
}
client->SetAddr(peer);
return std::make_shared<TcpSocket>(fd);
}
void Close() //??
{
if(_sockfd >= 0)
::close(_sockfd);
}
private:
int _sockfd; // _sockfd, listensockfd, sockfd;
};
// class UdpSocket:public Socket
// {
// };
}
改TcpServer.hpp
#include "Socket.hpp"
#include <iostream>
#include <memory>
#include <sys/wait.h>
#include <functional>
using namespace SocketModule;
using namespace LogModule;
using ioservice_t = std::function<void(std::shared_ptr<Socket> &sock, InetAddr &client)>;
// 主要解决:连接的问题,IO通信的问题
// 细节: TcpServer,需不需要关心自己未来传递的信息是什么?不需要关心!!
// 网络版本的计算器,长服务
class TcpServer
{
public:
TcpServer(uint16_t port, ioservice_t service)
:_port(port),
_listensockptr(std::make_unique<TcpSocket>()),
_isrunning(false),
_service(service)
{
_listensockptr->BuildTcpSocketMethod(_port);
}
void Start()
{
_isrunning = true;
while(_isrunning)
{
InetAddr client;
auto sock = _listensockptr->Accept(&client); // 1. 和client通信sockfd 2. client 网络地址
if(sock == nullptr)
{
continue;
}
LOG(LogLevel::DEBUG) << "accept success …";
// sock && client
pid_t id = fork();
if(id < 0)
{
LOG(LogLevel::FATAL) << "fork error …";
exit(FORK_ERR);
}
else if(id == 0)
{
// 子进程 -> listensock
_listensockptr->Close();
if(fork() > 0)
exit(OK);
// 孙子进程在执行任务,已经是孤儿了
_service(sock, client);
exit(OK);
}
else
{
// 父进程 -> sock
sock->Close();
pid_t rid = ::waitpid(id, nullptr, 0);
(void)rid;
}
}
_isrunning = false;
}
~TcpServer() {}
private:
uint16_t _port;
std::unique_ptr<Socket> _listensockptr;
bool _isrunning;
ioservice_t _service;
};
协议(基于TCP的)需要解决两个问题: 1. request和response必须得有序列化和反序列化功能 2. 你必须保证,读取的时候,读到完整的请求(TCP, UDP不用考虑)
如何要做序列化和反序列化: 1. 我们自己写(怎么做) —> 往往不具备很好的扩展性
//Serialize 序列化
std::string Serialize()
{
// _x = 10 _y = 20, _oper = '+'
// "10" "20" '+' : 用空格作为分隔符
}
//Deserialize 反序列化
bool Deserialize(std::string &in)
{
// "10" "20" '+' -> 以空格作为分隔符 -> 10 20 '+'
}
2. 使用现成的方案(这个是我们要写的) —> json -> jsoncpp
感谢你的观看,期待我们下次再见!




