Linux网络:TCP网络编程(1)
前面我们已经学习了用UDP进行网络编程。接下来我们就来学习利用另一个重要网络协议——TCP,来进行网络项目编程
相关代码已经上传至作者的个人gitee:楼田莉子/Linux学习喜欢请关注
目录
套接字地址结构体
在进行任何网络通信前,首先需要指定通信的对端。在TCP/IPv4编程中,最关键的结构体是 sockaddr_in 和 in_addr,它们用于存储和传递地址信息。
sockaddr_in 结构体
这是进行IPv4地址编程时最常用的结构体,它专门用来处理IP地址和端口号。为了方便编程,它被设计成与通用的 sockaddr 结构体在内存布局上兼容,因此在使用相关函数时,通常需要将 sockaddr_in* 强制转换为 sockaddr* 类型。
作用: 用于标识一个IPv4的套接字地址,包含地址族、端口号和IP地址。
表达式 (C/C++定义):
struct sockaddr_in {
short sin_family; // 地址族,对于IPv4,必须设置为 AF_INET
unsigned short sin_port; // 端口号(16位),需要使用网络字节序
struct in_addr sin_addr; // IPv4地址结构体(32位)
char sin_zero[8]; // 填充字段,通常设置为0,以确保与struct sockaddr大小一致
};
-
参数选项详解:
-
sin_family: 必须设置为AF_INET,表明这是一个IPv4地址。 -
sin_port: 16位端口号。关键点:需要调用htons()(host to network short)函数将主机字节序转换为网络字节序(大端序)。 -
sin_addr: 这是一个in_addr结构体,专门用来存放32位的IPv4地址。 -
sin_zero: 仅作为填充,使sockaddr_in的大小与通用的sockaddr结构体一致。编程时应使用bzero()或memset()将其置零。
-
in_addr 结构体
这个结构体仅用于表示一个32位的IPv4地址。
作用: 存储IPv4地址。
表达式 (C/C++定义):
struct in_addr {
union {
struct { u_char s_b1,s_b2,s_b3,s_b4; } S_un_b;
struct { u_short s_w1,s_w2; } S_un_w;
u_long S_addr; // 最常用的方式
} S_un;
};
// 为了方便,通常直接使用 s_addr 宏
// #define s_addr S_un.S_addr
在实际代码中,我们通常直接操作 s_addr 字段,它被定义为一个 unsigned long 类型的整数。
-
参数选项详解:
-
s_addr: 32位的IPv4地址。同样需要是网络字节序。可以通过inet_addr()函数将点分十进制字符串(如 "192.168.1.1")直接转换为此格式,或者使用更现代、更安全的inet_pton()函数进行转换。
-
使用示例
以下代码片段展示了如何填充一个服务器的地址结构体:
struct sockaddr_in server_addr;
bzero(&server_addr, sizeof(server_addr)); // 将整个结构体清零
server_addr.sin_family = AF_INET;
server_addr.sin_addr.s_addr = inet_addr("服务器IP地址"); // 或使用 INADDR_ANY 表示本机所有IP
server_addr.sin_port = htons(服务器端口号);
TCP相关的接口
socket()
一切的开始,先要创建一个套接字描述符。
-
作用: 创建一个新的套接字,可以理解为向操作系统申请一个用于网络通信的文件描述符。
-
表达式:
int socket(int domain, int type, int protocol); -
参数选项详解:
-
domain(协议域): 选择通信的协议族。对于IPv4,使用AF_INET(或PF_INET,二者在Linux中通常等价)。 -
type(套接字类型): 指定通信方式。对于TCP,使用SOCK_STREAM(面向连接的字节流)。 -
protocol(协议): 通常设为0,表示根据前两个参数选择默认协议。对于SOCK_STREAM,默认协议就是TCP。
-
-
返回值:
-
成功: 返回一个非负整数,即新套接字的文件描述符。
-
失败: 返回 -1,并设置
errno以指示错误。
-
bind()
服务器端需要将创建好的套接字与一个具体的IP地址和端口号绑定起来,这样客户端才能找到它。
作用: 将套接字描述符与一个本地协议地址(IP和端口)绑定。
表达式:
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
-
参数选项详解:
-
sockfd: 上一步socket()返回的文件描述符。 -
addr: 指向一个sockaddr结构体的指针。实际传递的通常是sockaddr_in结构体的地址,需要强制类型转换。 -
addrlen: 传入的地址结构体的大小,通常使用sizeof(struct sockaddr_in)。
-
-
返回值:
-
成功: 返回0。
-
失败: 返回 -1,并设置
errno。常见错误如EADDRINUSE(地址已被使用)。
-
listen()
服务器端在绑定地址后,需要告知内核自己将作为一个服务端,准备接受来自客户端的连接请求。
作用: 将套接字由主动套接字转化为被动监听套接字,并为它维护两个队列:存放未完成三次握手的连接请求(SYN_RCVD状态)的队列,以及已完成三次握手、等待 accept() 的连接(ESTABLISHED状态)的队列。
-
表达式:
int listen(int sockfd, int backlog); -
参数选项详解:
-
sockfd: 已绑定的套接字描述符。 -
backlog: 指定已完成连接队列的最大长度。即允许有多少个客户端已经完成了TCP三次握手,但在被服务器应用程序调用accept()取走之前,处于挂起状态。如果队列满了,新的连接请求可能会被拒绝或延迟。
-
-
返回值:
-
成功: 返回0。
-
失败: 返回 -1,并设置
errno。
-
accept()
这是一个阻塞函数,服务器调用它来从已完成连接队列的队首取走一个连接。如果没有已完成的连接,进程会进入睡眠状态直到有新的连接到来。
-
作用: 从监听套接字的已完成连接队列中取出第一个连接,如果队列为空则阻塞。成功时,它会创建一个新的套接字描述符,这个新描述符用于和这个特定的客户端进行通信。
-
表达式:
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen); -
参数选项详解:
-
sockfd: 监听套接字描述符(listen的那个)。 -
addr: 这是一个传出参数,指向一个sockaddr结构体,用于接收客户端的协议地址。如果不关心客户端地址,可以设为NULL。 -
addrlen: 这是一个值-结果参数。调用前,应初始化为addr指向的缓冲区大小;返回时,包含内核存放的客户端地址的实际字节数。
-
-
返回值:
-
成功: 返回一个全新的套接字描述符(已连接套接字),后续的
send()和recv()都使用这个新描述符。 -
失败: 返回 -1,并设置
errno。
-
connect()
这是客户端主动连接服务器的函数。
-
作用: 客户端调用此函数,向服务器发起TCP连接请求,触发三次握手过程。
-
表达式:
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen); -
参数选项详解:
-
sockfd: 客户端的套接字描述符。 -
addr: 指向服务器的地址结构体(包含服务器的IP和端口)。 -
addrlen: 地址结构体的大小。
-
-
返回值:
-
成功: 返回0。注意,对于阻塞式套接字,
connect只有在连接成功建立(三次握手完成)或出错时才会返回。 -
失败: 返回 -1,并设置
errno。
-
send() 和 recv()
连接建立后,就可以使用这两个函数来交换数据了。
-
作用: 在已连接的套接字上发送和接收数据。
-
表达式:
ssize_t send(int sockfd, const void *buf, size_t len, int flags); ssize_t recv(int sockfd, void *buf, size_t len, int flags); -
参数选项详解:
-
sockfd: 已连接套接字描述符(服务器端由accept返回的,客户端是自己的那个)。 -
buf: 指向要发送数据的缓冲区,或接收数据的缓冲区。 -
len: 缓冲区的大小。 -
flags: 一般设为0。可以选择MSG_OOB处理带外数据,或MSG_DONTWAIT进行非阻塞操作等。
-
-
返回值:
-
send()成功: 返回实际发送的字节数。注意,这个返回值可能小于请求发送的长度len,需要在循环中继续发送剩余部分。 -
recv()成功: 返回实际接收到的字节数。关键点:如果返回0,表示对端已经正常关闭了连接(发送了FIN)。 -
失败: 两者失败均返回 -1,并设置
errno。
-
close()
通信结束后,需要关闭套接字以释放资源。
-
作用: 关闭一个套接字描述符,终止TCP连接。
-
表达式:
int close(int sockfd); -
参数选项详解:
-
sockfd: 要关闭的套接字描述符。
-
-
返回值:
-
成功: 返回0。
-
失败: 返回 -1。
-
套接字选项 (setsockopt)
除了以上内容,也可以通过 setsockopt 和 getsockopt 来设置或获取这些选项。
SO_REUSEADDR: 作用:允许重用本地地址和端口。场景:服务器重启时,可以立即再次使用同一个端口,而不必等待 TIME_WAIT 状态结束,避免"Address already in use"错误。
SO_KEEPALIVE: 作用:定期探测连接是否存活。场景:对于长时间无数据交换的连接,可以自动检测客户端是否崩溃或网络是否中断。
SO_LINGER: 作用:设置 close() 关闭连接时的行为。场景:控制 close() 返回前是否需要等待未发送数据发送完成,以及如何处理未发送数据。
SO_RCVBUF / SO_SNDBUF: 作用:调整套接字接收和发送缓冲区的大小。场景:根据网络环境和应用需求,优化吞吐量。
日志
#pragma once
#include <iostream>
#include <pthread.h>
#include <cstdio>
#include <string>
#include <memory>
#include <sstream>
#include <chrono>
#include <iomanip>
#include <ctime>
#include <unistd.h>
#include <filesystem> // C++17
#include <fstream>
//RAII风格代码:资源获取即初始化
// 互斥锁类,封装了pthread_mutex_t
class Mutex
{
public:
Mutex()
{
pthread_mutex_init(&_lock, nullptr);
}
void Lock()
{
pthread_mutex_lock(&_lock);
}
pthread_mutex_t *Ptr()
{
return &_lock;
}
void Unlock()
{
pthread_mutex_unlock(&_lock);
}
~Mutex()
{
pthread_mutex_destroy(&_lock);
}
private:
pthread_mutex_t _lock;
};
class LockGuard // RAII风格代码
{
public:
LockGuard(Mutex &lock):_lockref(lock)
{
_lockref.Lock();
}
~LockGuard()
{
_lockref.Unlock();
}
private:
Mutex &_lockref;
};
//日志本体
namespace Logger
{
enum class LogLevel
{
INFO, ///< 信息性消息,记录应用程序的正常运行状态(如启动、配置加载等)
WARNING, ///< 警告,表示潜在的问题或非预期的情形,但应用程序仍能继续运行
ERROR, ///< 错误,表示发生了严重的操作失败,但不影响整个应用程序的继续运行
FATAL, ///< 致命错误,表示严重的故障,通常会导致应用程序终止
DEBUG ///< 调试信息,用于开发和排错阶段,记录详细的内部状态或流程
};
std::string LogLevelToString(LogLevel level)
{
switch (level)
{
case LogLevel::INFO:
return "INFO";
case LogLevel::WARNING:
return "WARNING";
case LogLevel::ERROR:
return "ERROR";
case LogLevel::FATAL:
return "FATAL";
case LogLevel::DEBUG:
return "DEBUG";
default:
return "UNKNOWN";
}
}
std::string GetCurrentDateTime()
{
auto now = std::chrono::system_clock::now();
std::time_t now_c = std::chrono::system_clock::to_time_t(now);
std::tm tm_info; // 用户提供的缓冲区
localtime_r(&now_c, &tm_info); // POSIX 线程安全函数
std::ostringstream oss;
oss << std::put_time(&tm_info, "%Y-%m-%d %H:%M:%S");
return oss.str();
}
// 输出角度 -- 刷新策略
// 1. 显示器打印
// 2. 文件写入
// 日志的生成:
// 1. 构建日志字符串
// 2. 根据不同的策略,进行刷新
//策略模式接口
class LogStrategy
{
public:
virtual void LogRefresh(const std::string &message) = 0;
virtual ~LogStrategy() = default;
};
// 控制台日志刷新策略, 日志将来要向显示器打印
class ConsoleStrategy : public LogStrategy
{
public:
// 显示器打印策略刷新
void LogRefresh(const std::string &message) override
{
LockGuard lockguard(_mutex);
std::cerr << message << std::endl; // ??
}
~ConsoleStrategy()
{
}
private:
Mutex _mutex;
};
const std::string defaultpath = "./log";
const std::string defaultfilename = "log.txt";
namespace fs = std::filesystem;
// 文件策略
class FileLogStrategy : public LogStrategy
{
public:
FileLogStrategy(const std::string &path = defaultpath, const std::string &name = defaultfilename)
: _logpath(path),
_logfilename(name)
{
if (std::filesystem::exists(_logpath))
return;
try
{
std::filesystem::create_directories(_logpath);
}
catch(const std::filesystem::filesystem_error &e)
{
std::cerr << e.what() << '\n';
}
if(!_logpath.empty()&&_logpath.back() != '/')
_logpath += '/';
}
// 文件策略刷新
void LogRefresh(const std::string &message) override
{
{
std::string targetlogfile = _logpath + _logfilename;
LockGuard lockguard(_mutex);
std::ofstream logFile(targetlogfile, std::ios::app);// 以追加模式打开文件
if (!logFile.is_open())
{
std::cerr << "无法打开日志文件: " << targetlogfile << std::endl;
return;
}
logFile << message << "\n";
logFile.close();
}
}
~FileLogStrategy()
{
}
private:
std::string _logpath;
std::string _logfilename;
Mutex _mutex;
};
// 根据日志等级分类保存的策略类
class LevelFileLogStrategy : public LogStrategy {
public:
/**
* @brief 构造函数,指定日志根目录
* @param log_dir 存放等级日志文件的目录,默认为 "./logs"
*/
LevelFileLogStrategy(const std::string &log_dir = "./logs") : _log_dir(log_dir) {
// 确保目录存在
if (!std::filesystem::exists(_log_dir)) {
std::filesystem::create_directories(_log_dir);
}
// 规范化目录路径,末尾添加 '/'
if (!_log_dir.empty() && _log_dir.back() != '/') {
_log_dir += '/';
}
}
/**
* @brief 刷新日志:根据等级写入对应文件
* @param message 完整的日志消息(格式由 Logger::LogMessage 生成)
*/
void LogRefresh(const std::string &message) override {
// 1. 从消息中提取日志等级
LogLevel level = extractLogLevel(message);
// 2. 构造对应的文件名(例如:INFO.log)
std::string filename = _log_dir + LogLevelToString(level) + ".log";
// 3. 线程安全地追加写入文件
LockGuard lockguard(_mutex);
std::ofstream logFile(filename, std::ios::app);
if (!logFile.is_open()) {
std::cerr << "无法打开日志文件: " << filename << std::endl;
return;
}
logFile << message << "\n";
logFile.close();
}
private:
std::string _log_dir; // 日志根目录
Mutex _mutex; // 文件写入互斥锁
/**
* @brief 从日志消息中解析出等级
* @param msg 完整日志消息,格式为 "[时间][等级][PID][文件:行号] 用户内容"
* @return 对应的 LogLevel 枚举值,解析失败时默认返回 INFO
*/
LogLevel extractLogLevel(const std::string &msg) {
// 寻找第一个 ']' 的位置
size_t first_close = msg.find(']');
if (first_close == std::string::npos) {
return LogLevel::INFO; // 格式错误,默认 INFO
}
// 寻找第二个 '[' 的位置
size_t second_open = msg.find('[', first_close);
if (second_open == std::string::npos) {
return LogLevel::INFO;
}
// 寻找第二个 ']' 的位置
size_t second_close = msg.find(']', second_open);
if (second_close == std::string::npos) {
return LogLevel::INFO;
}
// 提取等级字符串(如 "INFO")
std::string level_str = msg.substr(second_open + 1, second_close - second_open - 1);
// 映射到 LogLevel 枚举
if (level_str == "INFO") return LogLevel::INFO;
if (level_str == "WARNING") return LogLevel::WARNING;
if (level_str == "ERROR") return LogLevel::ERROR;
if (level_str == "FATAL") return LogLevel::FATAL;
if (level_str == "DEBUG") return LogLevel::DEBUG;
return LogLevel::INFO; // 未知等级,默认 INFO
}
};
class Logger
{
public:
Logger()
{}
void UseConsoleStrategy()
{
_strategy = std::make_unique<ConsoleStrategy>();
}
void UseFileStrategy()
{
_strategy = std::make_unique<FileLogStrategy>();
}
void UseLevelFileStrategy(const std::string &log_dir = "./logs")
{
_strategy = std::make_unique<LevelFileLogStrategy>(log_dir);
}
void Debug(const std::string &message)
{
if (_strategy!= nullptr)
{
_strategy->LogRefresh("[DEBUG] " + GetCurrentDateTime() + " - " + message);
}
}
//日志内容
//一条完整的日志信息=> [日志级别] + 当前时间 + 进程ID + 文件名 + 行号 + 日志信息
//我们想以RAII形式刷新日志信息
class LogMessage
{
public:
LogMessage(LogLevel level, const std::string &filename, size_t line, Logger &logger)
: _level(level), _filename(filename), _line(line),_logger(logger)
{
_cur_time = GetCurrentDateTime();
_pid=getpid();
// 构建日志左半部分信息
std::stringstream oss;
oss <<"["<<_cur_time<<"]"
<<"["<<LogLevelToString(_level)<<"]"
<<"["<<_pid<<"]"
<<"[" << filename << ":"
<< line << "]";
_LogInfo = oss.str();
}
template<typename T>
LogMessage& operator<<(const T&info)
{
std::stringstream oss;
oss<< info;
_LogInfo += oss.str();
return *this;
}
~LogMessage()
{
if (_logger._strategy != nullptr)
{
_logger._strategy->LogRefresh(_LogInfo);
}
}
private:
std::string _cur_time;
LogLevel _level;
pid_t _pid;
std::string _filename;
size_t _line;
std::string _LogInfo;
Logger &_logger ;//方便进行后续策略方式刷新
};
//对LogMessage进行()重载
//必须用拷贝,否则会导致<<重载的时候内容消失
LogMessage operator()(LogLevel level, const std::string filename, size_t line)
{
return LogMessage(level, filename, line, *this);
}
~Logger()
{}
private:
std::unique_ptr<LogStrategy> _strategy;
};
//日志对象全局使用
Logger logger;
#define ENABLE_LOG_CONSOLE() logger.UseConsoleStrategy()
#define ENABLE_LOG_FILE() logger.UseFileStrategy()
#define Log(level) logger(level, __FILE__, __LINE__)
#define ENABLE_LOG_LEVEL_FILE(log_dir) logger.UseLevelFileStrategy(log_dir)
}
网络客户端代码
#pragma once
#include<iostream>
#include<string>
#include<cstring>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
//网络客户端封装
class InetAddr
{
private:
std::string IP_;
uint16_t port_;
struct sockaddr_in address_;
socklen_t len_;
public:
InetAddr()=default;
InetAddr(const struct sockaddr_in& address) : address_(address),len_(sizeof(address))
{
IP_ = inet_ntoa(address_.sin_addr); // 将二进制IP转换为字符串
port_ = ntohs(address_.sin_port); // 端口转换正确
}
InetAddr(uint16_t port,const std::string ip="0.0.0.0"):IP_(ip),port_(port)
{
bzero(&address_, sizeof(address_));
address_.sin_family = AF_INET;
address_.sin_addr.s_addr = inet_addr(IP_.c_str());
address_.sin_port = htons(port_);
len_=sizeof(address_);
}
struct sockaddr_in*GetNetAddress()
{
return &address_;
}
~InetAddr()=default;
socklen_t len()
{
return len_;
}
bool operator==(const InetAddr&addrs)
{
return (this-> IP_==addrs.IP_)&&(this->port_==addrs.port_);
}
std::string ToString()
{
return "[" + IP_ + ":" + std::to_string(port_) + "]";
}
};
TCP服务器(单线程)
服务器代码
#pragma once
#include"Log.h"
#include"InetAddr.h"
#include<unistd.h>
using namespace Logger;
enum
{
SUCCESS=0,
SOCKET_ERR,
BIND_ERR,
LISTEN_ERR,
USAGE_ERR
};
//链接队列长度
static const int gblocklist=16;
//默认端口号
static const uint16_t defaultport=8080;
class TCPServer
{
private:
uint16_t port_;//端口号
//不需要显式包含IP
int ListenSocketfd_;
public:
TCPServer(uint16_t port=defaultport):port_(port)
{}
void Init()
{
//创建套接字
ListenSocketfd_=socket(AF_INET,SOCK_STREAM,0);
if(ListenSocketfd_<0)
{
Log(LogLevel::ERROR)<<"服务器创建套接字失败";
exit(SOCKET_ERR);
}
Log(LogLevel::DEBUG)<<"创建套接字成功"<<ListenSocketfd_;
//初始化网络信息
struct sockaddr_in local;
memset(&local,0,sizeof(local));
local.sin_family=AF_INET;
local.sin_port=htons(port_);
local.sin_addr.s_addr=INADDR_ANY;//任意IPbind
//bind
int n=bind(ListenSocketfd_,(struct sockaddr*)&local,sizeof(local));
if(n<0)
{
Log(LogLevel::ERROR)<<"绑定失败";
exit(BIND_ERR);
}
Log(LogLevel::DEBUG)<<"绑定成功";
//tcp是面向连接的,所以必须出于监听状态
int m=listen(ListenSocketfd_,gblocklist);
if(m<0)
{
Log(LogLevel::ERROR)<<"监听失败";
exit(LISTEN_ERR);
}
Log(LogLevel::DEBUG)<<"监听成功";
}
void ServerIO(int fd,InetAddr&address)
{
Log(LogLevel::DEBUG)<<"客户端信息"<<address.ToString();
while(1)
{
char inbuffer[1024]={0};
//读
ssize_t n=read(fd,inbuffer,sizeof(inbuffer)-1);
//写
if(n>0)
{
inbuffer[n]={0};
Log(LogLevel::INFO)<<address.ToString()<<"say#"<<inbuffer;
std::string echo_server="server echo# ";
echo_server+=inbuffer;
write(fd,echo_server.c_str(),echo_server.size());
}
else if(n==0)
{
Log(LogLevel::INFO)<<"客户端退出"<<address.ToString();
break;
}
//读出错
else
{
Log(LogLevel::WARNING)<<"客户端出错"<<address.ToString();
break;
}
}
}
void Start()
{
while(1)
{
struct sockaddr_in clientaddr;
socklen_t len=sizeof(clientaddr);
int socketfd=accept(ListenSocketfd_,(struct sockaddr*)&clientaddr,&len);
if(socketfd<0)
{
Log(LogLevel::WARNING)<<"监听失败";
continue;
}
Log(LogLevel::DEBUG)<<"监听成功";
//处理ListenSocketfd_
InetAddr clientaddress(clientaddr);
ServerIO(socketfd,clientaddress);
close(socketfd);
}
}
~TCPServer()
{
close(ListenSocketfd_);
}
};
服务器运行
#include"EchoTCPServer.hpp"
#include<memory>
inline static void Usage(const std::string &proc)
{
std::cerr<<"输入错误\n\t";
std::cerr<<proc<<"你应该告诉我你的端口号"<<std::endl;
}
int main(int argc,char*argv[])
{
if(argc!=2)
{
Usage(argv[0]);
exit(USAGE_ERR);
}
ENABLE_LOG_CONSOLE();
// std::string ip=argv[1];
uint16_t port=std::atoi(argv[1]);
auto tsvr=std::make_unique<TCPServer>(port);
tsvr->Init();
tsvr->Start();
return 0;
}
客户端运行
#include"Log.h"
#include"InetAddr.h"
#include<iostream>
#include<cstdlib>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <cstring>
using namespace Logger;
inline static void Usage(const std::string &proc)
{
std::cerr<<"输入错误\n\t";
std::cerr<<proc<<"你应该告诉我服务器的IP和端口号"<<std::endl;
}
//我们如何获取server的IP地址和端口号?
int main(int argc,char *argv[])
{
if(argc!=3)
{
Usage(argv[0]);
exit(1);
}
std::string ServerIp=argv[1];
uint16_t ServerPort=std::atoi(argv[2]);
//创建socket
int sockfd=socket(AF_INET,SOCK_STREAM,0);
if(sockfd<0)
{
std::cerr<<"客户端套接字创建失败"<<std::endl;
exit(-1);
}
//客户端TCP绑定必须需要显示绑定
//服务器TCP不需要自己手动显示的进行bind,OS会自动bind的!
//OS 帮助我们进行随机端口,防止端口号冲突
// 应该干什么?bind端口号ip,发起建立连接的工作
// {client ip, client port} <-> {server ip, server port}
InetAddr serveraddress(ServerPort,ServerIp);
int n=connect(sockfd,(struct sockaddr*)serveraddress.GetNetAddress(),serveraddress.len());
if(n<0)
{
std::cerr << "连接服务器 " << serveraddress.GetNetAddress() << " 失败, 错误码: " << errno << " (" << strerror(errno) << ")" << std::endl;
exit(3);
exit(3);
}
std::cerr<<"链接服务器"<<serveraddress.GetNetAddress()<<"成功"<<std::endl;
//通信
while(1)
{
std::string line;
std::cout << "please Enter# ";
std::getline(std::cin, line);
write(sockfd, line.c_str(), line.size());
char inbuffer[1024];
ssize_t n = read(sockfd, inbuffer, sizeof(inbuffer));
if(n > 0)
{
inbuffer[n] = 0;
std::cout << inbuffer << std::endl;
}
else if(n == 0)
{
std::cout << "read enff of file!" << std::endl;
break;
}
else
{
std::cerr << "read error!" << std::endl;
break;
}
}
return 0;
}
结果为:

TCP服务器(多进程版本)
只有服务器代码改了,所以这里只发服务器代码
版本一:(最佳实践)
#pragma once
#include"Log.h"
#include"InetAddr.h"
#include<unistd.h>
#include<signal.h>
using namespace Logger;
enum
{
SUCCESS=0,
SOCKET_ERR,
BIND_ERR,
LISTEN_ERR,
USAGE_ERR,
FORK_ERR
};
//链接队列长度
static const int gblocklist=16;
//默认端口号
static const uint16_t defaultport=8080;
class TCPServer
{
private:
uint16_t port_;//端口号
//不需要显式包含IP
int ListenSocketfd_;
public:
TCPServer(uint16_t port=defaultport):port_(port)
{}
void Init()
{
//创建套接字
ListenSocketfd_=socket(AF_INET,SOCK_STREAM,0);
if(ListenSocketfd_<0)
{
Log(LogLevel::ERROR)<<"服务器创建套接字失败";
exit(SOCKET_ERR);
}
Log(LogLevel::DEBUG)<<"创建套接字成功"<<ListenSocketfd_;
//初始化网络信息
struct sockaddr_in local;
memset(&local,0,sizeof(local));
local.sin_family=AF_INET;
local.sin_port=htons(port_);
local.sin_addr.s_addr=INADDR_ANY;//任意IPbind
//bind
int n=bind(ListenSocketfd_,(struct sockaddr*)&local,sizeof(local));
if(n<0)
{
Log(LogLevel::ERROR)<<"绑定失败";
exit(BIND_ERR);
}
Log(LogLevel::DEBUG)<<"绑定成功";
//tcp是面向连接的,所以必须出于监听状态
int m=listen(ListenSocketfd_,gblocklist);
if(m<0)
{
Log(LogLevel::ERROR)<<"监听失败";
exit(LISTEN_ERR);
}
Log(LogLevel::DEBUG)<<"监听成功";
}
void ServerIO(int fd,InetAddr&address)
{
Log(LogLevel::DEBUG)<<"客户端信息"<<address.ToString();
while(1)
{
char inbuffer[1024]={0};
//读
ssize_t n=read(fd,inbuffer,sizeof(inbuffer)-1);
//写
if(n>0)
{
inbuffer[n]={0};
Log(LogLevel::INFO)<<address.ToString()<<"say#"<<inbuffer;
std::string echo_server="server echo# ";
echo_server+=inbuffer;
write(fd,echo_server.c_str(),echo_server.size());
}
else if(n==0)
{
Log(LogLevel::INFO)<<"客户端退出"<<address.ToString();
break;
}
//读出错
else
{
Log(LogLevel::WARNING)<<"客户端出错"<<address.ToString();
break;
}
}
}
void Start()
{
signal(SIGCHLD,SIG_IGN);
while(1)
{
struct sockaddr_in clientaddr;
socklen_t len=sizeof(clientaddr);
int socketfd=accept(ListenSocketfd_,(struct sockaddr*)&clientaddr,&len);
if(socketfd<0)
{
Log(LogLevel::WARNING)<<"监听失败";
continue;
}
Log(LogLevel::DEBUG)<<"监听成功";
// //处理ListenSocketfd_
// InetAddr clientaddress(clientaddr);
// ServerIO(socketfd,clientaddress);
//多进程版本
pid_t id=fork();
if(id<0)//fork失败
{
Log(LogLevel::ERROR)<<"fork失败";
exit(FORK_ERR);
}
else if(id==0) //子进程
{
InetAddr clientaddress(clientaddr);
ServerIO(socketfd, clientaddress);
close(socketfd);
exit(0); // 重要
}
else //父进程
{
}
close(socketfd);
}
}
~TCPServer()
{
close(ListenSocketfd_);
}
};
版本二:(邪修)
#pragma once
#include"Log.h"
#include"InetAddr.h"
#include<unistd.h>
#include<sys/wait.h>
using namespace Logger;
enum
{
SUCCESS=0,
SOCKET_ERR,
BIND_ERR,
LISTEN_ERR,
USAGE_ERR,
FORK_ERR
};
//链接队列长度
static const int gblocklist=16;
//默认端口号
static const uint16_t defaultport=8080;
class TCPServer
{
private:
uint16_t port_;//端口号
//不需要显式包含IP
int ListenSocketfd_;
public:
TCPServer(uint16_t port=defaultport):port_(port)
{}
void Init()
{
//创建套接字
ListenSocketfd_=socket(AF_INET,SOCK_STREAM,0);
if(ListenSocketfd_<0)
{
Log(LogLevel::ERROR)<<"服务器创建套接字失败";
exit(SOCKET_ERR);
}
Log(LogLevel::DEBUG)<<"创建套接字成功"<<ListenSocketfd_;
//初始化网络信息
struct sockaddr_in local;
memset(&local,0,sizeof(local));
local.sin_family=AF_INET;
local.sin_port=htons(port_);
local.sin_addr.s_addr=INADDR_ANY;//任意IPbind
//bind
int n=bind(ListenSocketfd_,(struct sockaddr*)&local,sizeof(local));
if(n<0)
{
Log(LogLevel::ERROR)<<"绑定失败";
exit(BIND_ERR);
}
Log(LogLevel::DEBUG)<<"绑定成功";
//tcp是面向连接的,所以必须出于监听状态
int m=listen(ListenSocketfd_,gblocklist);
if(m<0)
{
Log(LogLevel::ERROR)<<"监听失败";
exit(LISTEN_ERR);
}
Log(LogLevel::DEBUG)<<"监听成功";
}
void ServerIO(int fd,InetAddr&address)
{
Log(LogLevel::DEBUG)<<"客户端信息"<<address.ToString();
while(1)
{
char inbuffer[1024]={0};
//读
ssize_t n=read(fd,inbuffer,sizeof(inbuffer)-1);
//写
if(n>0)
{
inbuffer[n]={0};
Log(LogLevel::INFO)<<address.ToString()<<"say#"<<inbuffer;
std::string echo_server="server echo# ";
echo_server+=inbuffer;
write(fd,echo_server.c_str(),echo_server.size());
}
else if(n==0)
{
Log(LogLevel::INFO)<<"客户端退出"<<address.ToString();
break;
}
//读出错
else
{
Log(LogLevel::WARNING)<<"客户端出错"<<address.ToString();
break;
}
}
}
void Start()
{
while(1)
{
struct sockaddr_in clientaddr;
socklen_t len=sizeof(clientaddr);
int socketfd=accept(ListenSocketfd_,(struct sockaddr*)&clientaddr,&len);
if(socketfd<0)
{
Log(LogLevel::WARNING)<<"监听失败";
continue;
}
Log(LogLevel::DEBUG)<<"监听成功";
// //处理ListenSocketfd_
// InetAddr clientaddress(clientaddr);
// ServerIO(socketfd,clientaddress);
//多进程版本
pid_t id=fork();
if(id<0)//fork失败
{
Log(LogLevel::ERROR)<<"fork失败";
exit(FORK_ERR);
}
else if(id == 0)
{
close(ListenSocketfd_);
// 子进程退出
if(fork() > 0) exit(0); // A
//孙子进程执行——孤儿进程,执行完毕后被OS回收
InetAddr clientaddress(clientaddr);
ServerIO(socketfd, clientaddress);
close(socketfd);
exit(0); // 重要
}
else
{
close(socketfd);
// 父进程
pid_t rid = waitpid(id, nullptr, 0);
(void)rid;
}
}
}
~TCPServer()
{
close(ListenSocketfd_);
}
};
TCP服务器(多线程版本)
版本一(正常多线程)
#pragma once
#include"Log.h"
#include"InetAddr.h"
#include<unistd.h>
#include<memory>
#include<pthread.h>
using namespace Logger;
enum
{
SUCCESS=0,
SOCKET_ERR,
BIND_ERR,
LISTEN_ERR,
USAGE_ERR
};
//链接队列长度
static const int gblocklist=16;
//默认端口号
static const uint16_t defaultport=8080;
class TCPServer
{
private:
uint16_t port_;//端口号
//不需要显式包含IP
int ListenSocketfd_;
public:
TCPServer(uint16_t port=defaultport):port_(port)
{}
void Init()
{
//创建套接字
ListenSocketfd_=socket(AF_INET,SOCK_STREAM,0);
if(ListenSocketfd_<0)
{
Log(LogLevel::ERROR)<<"服务器创建套接字失败";
exit(SOCKET_ERR);
}
Log(LogLevel::DEBUG)<<"创建套接字成功"<<ListenSocketfd_;
//初始化网络信息
struct sockaddr_in local;
memset(&local,0,sizeof(local));
local.sin_family=AF_INET;
local.sin_port=htons(port_);
local.sin_addr.s_addr=INADDR_ANY;//任意IPbind
//bind
int n=bind(ListenSocketfd_,(struct sockaddr*)&local,sizeof(local));
if(n<0)
{
Log(LogLevel::ERROR)<<"绑定失败";
exit(BIND_ERR);
}
Log(LogLevel::DEBUG)<<"绑定成功";
//tcp是面向连接的,所以必须出于监听状态
int m=listen(ListenSocketfd_,gblocklist);
if(m<0)
{
Log(LogLevel::ERROR)<<"监听失败";
exit(LISTEN_ERR);
}
Log(LogLevel::DEBUG)<<"监听成功";
}
void ServerIO(int fd,InetAddr&address)
{
Log(LogLevel::DEBUG)<<"客户端信息"<<address.ToString();
while(1)
{
char inbuffer[1024]={0};
//读
ssize_t n=read(fd,inbuffer,sizeof(inbuffer)-1);
//写
if(n>0)
{
inbuffer[n]={0};
Log(LogLevel::INFO)<<address.ToString()<<"say#"<<inbuffer;
std::string echo_server="server echo# ";
echo_server+=inbuffer;
write(fd,echo_server.c_str(),echo_server.size());
}
else if(n==0)
{
Log(LogLevel::INFO)<<"客户端退出"<<address.ToString();
break;
}
//读出错
else
{
Log(LogLevel::WARNING)<<"客户端出错"<<address.ToString();
break;
}
}
}
class ThreadData
{
public:
ThreadData( TCPServer *ts, int sockfd,InetAddr addr)
:_this(ts), _sockfd(sockfd),_addr(addr)
{}
~ThreadData(){
}
public:
TCPServer *_this;
int _sockfd;
InetAddr _addr;
};
static void*route(void*arg)
{
std::unique_ptr<ThreadData> td(static_cast<ThreadData*>(arg));
pthread_detach(pthread_self());
td->_this->ServerIO(td->_sockfd,td->_addr);
return nullptr;
}
void Start()
{
while(1)
{
struct sockaddr_in clientaddr;
socklen_t len=sizeof(clientaddr);
int socketfd=accept(ListenSocketfd_,(struct sockaddr*)&clientaddr,&len);
if(socketfd<0)
{
Log(LogLevel::WARNING)<<"监听失败";
continue;
}
Log(LogLevel::DEBUG)<<"监听成功";
pthread_t tid;
InetAddr clientaddress(clientaddr);
std::unique_ptr<ThreadData> td=std::make_unique<ThreadData>(this,socketfd,clientaddress);
pthread_create(&tid,nullptr,route,(void*)td.get());
}
}
~TCPServer()
{
close(ListenSocketfd_);
}
};
版本二(线程池)
#pragma once
#include"Log.h"
#include"InetAddr.h"
#include"ThreadPool/ThreadPool.hpp"
#include<unistd.h>
#include<functional>
using namespace Logger;
using namespace ThreadPool;
using task_t =std::function<void()>;
enum
{
SUCCESS=0,
SOCKET_ERR,
BIND_ERR,
LISTEN_ERR,
USAGE_ERR
};
//链接队列长度
static const int gblocklist=16;
//默认端口号
static const uint16_t defaultport=8080;
class TCPServer
{
private:
uint16_t port_;//端口号
//不需要显式包含IP
int ListenSocketfd_;
public:
TCPServer(uint16_t port=defaultport):port_(port)
{}
void Init()
{
//创建套接字
ListenSocketfd_=socket(AF_INET,SOCK_STREAM,0);
if(ListenSocketfd_<0)
{
Log(LogLevel::ERROR)<<"服务器创建套接字失败";
exit(SOCKET_ERR);
}
Log(LogLevel::DEBUG)<<"创建套接字成功"<<ListenSocketfd_;
//初始化网络信息
struct sockaddr_in local;
memset(&local,0,sizeof(local));
local.sin_family=AF_INET;
local.sin_port=htons(port_);
local.sin_addr.s_addr=INADDR_ANY;//任意IPbind
//bind
int n=bind(ListenSocketfd_,(struct sockaddr*)&local,sizeof(local));
if(n<0)
{
Log(LogLevel::ERROR)<<"绑定失败";
exit(BIND_ERR);
}
Log(LogLevel::DEBUG)<<"绑定成功";
//tcp是面向连接的,所以必须出于监听状态
int m=listen(ListenSocketfd_,gblocklist);
if(m<0)
{
Log(LogLevel::ERROR)<<"监听失败";
exit(LISTEN_ERR);
}
Log(LogLevel::DEBUG)<<"监听成功";
}
void ServerIO(int fd,InetAddr address)
{
Log(LogLevel::DEBUG)<<"客户端信息"<<address.ToString();
while(1)
{
char inbuffer[1024]={0};
//读
ssize_t n=read(fd,inbuffer,sizeof(inbuffer)-1);
//写
if(n>0)
{
inbuffer[n]={0};
Log(LogLevel::INFO)<<address.ToString()<<"say#"<<inbuffer;
std::string echo_server="server echo# ";
echo_server+=inbuffer;
write(fd,echo_server.c_str(),echo_server.size());
}
else if(n==0)
{
Log(LogLevel::INFO)<<"客户端退出"<<address.ToString();
break;
}
//读出错
else
{
Log(LogLevel::WARNING)<<"客户端出错"<<address.ToString();
break;
}
}
}
class ThreadData
{
public:
ThreadData( TCPServer *ts, int sockfd,InetAddr addr)
:_this(ts), _sockfd(sockfd),_addr(addr)
{}
~ThreadData(){
}
public:
TCPServer *_this;
int _sockfd;
InetAddr _addr;
};
void Start()
{
while(1)
{
struct sockaddr_in clientaddr;
socklen_t len=sizeof(clientaddr);
int socketfd=accept(ListenSocketfd_,(struct sockaddr*)&clientaddr,&len);
if(socketfd<0)
{
Log(LogLevel::WARNING)<<"监听失败";
continue;
}
Log(LogLevel::DEBUG)<<"监听成功";
//处理ListenSocketfd_
InetAddr clientaddress(clientaddr);
threadpool<task_t>::Instance()->Enqueue([this,socketfd,&clientaddress](){
this->ServerIO(socketfd,clientaddress);
});
}
}
~TCPServer()
{
close(ListenSocketfd_);
}
};
线程池相关
Mutex.hpp
#pragma once
#include <iostream>
#include <string>
#include <pthread.h>
#include "../Log.h"
namespace LockModule
{
// 对锁进行封装,可以独立使用
class Mutex
{
public:
// 删除不需要的拷贝和赋值
Mutex(const Mutex &) = delete;
const Mutex &operator=(const Mutex &) = delete;
// 构造函数
Mutex()
{
int n = pthread_mutex_init(&_mutex, nullptr);
(void)n;
}
// 加锁
void Lock()
{
int n = pthread_mutex_lock(&_mutex);
(void)n;
}
// 解锁
void Unlock()
{
int n = pthread_mutex_unlock(&_mutex);
(void)n;
}
// 获取原始指针
pthread_mutex_t *GetMutexOriginal()
{
return &_mutex;
}
// 析构函数
~Mutex()
{
int n = pthread_mutex_destroy(&_mutex);
(void)n;
}
private:
pthread_mutex_t _mutex;
};
// 采用RAII风格,进行锁管理
class LockGuard
{
public:
// 构造函数,创建时自动加锁
LockGuard(Mutex &mutex) : _mutex(mutex)
{
_mutex.Lock();
}
// 析构函数,自动解锁
~LockGuard()
{
_mutex.Unlock();
}
private:
Mutex &_mutex; // 引用被管理的互斥锁
};
}
Cond.hpp
#pragma once
#include <iostream>
#include <string>
#include <pthread.h>
#include "Mutex.hpp"
namespace CondModule
{
class Cond
{
public:
Cond()
{
pthread_cond_init(&_cond, nullptr);
}
void Wait(LockModule::Mutex &mutex)
{
int n = pthread_cond_wait(&_cond, mutex.GetMutexOriginal());
(void)n;
}
void Signal()
{
int n = pthread_cond_signal(&_cond);
(void)n;
}
void Broadcast()
{
int n = pthread_cond_broadcast(&_cond);
(void)n;
}
~Cond()
{
pthread_cond_destroy(&_cond);
}
private:
pthread_cond_t _cond;
};
}
Thread.hpp
#pragma once
#include <iostream>
#include <string>
#include <functional>
#include <pthread.h>
#include "../Log.h"
namespace ThreadPool
{
using namespace Logger;
static int gnumber = 1;
using callback_t = std::function<void()>;
enum class TSTATUS
{
THREAD_NEW,
THREAD_RUNNING,
THREAD_STOP
};
std::string Status2String(TSTATUS s)
{
switch (s)
{
case TSTATUS::THREAD_NEW:
return "THREAD_NEW";
case TSTATUS::THREAD_RUNNING:
return "THREAD_RUNNING";
case TSTATUS::THREAD_STOP:
return "THREAD_STOP";
default:
return "UNKNOWN";
}
}
std::string IsJoined(bool joinable)
{
return joinable ? "true" : "false";
}
class Thread
{
private:
void ToRunning()
{
_status = TSTATUS::THREAD_RUNNING;
}
void ToStop()
{
_status = TSTATUS::THREAD_STOP;
}
static void *ThreadRoutine(void *args)
{
Thread *self = static_cast<Thread *>(args);
pthread_setname_np(self->_tid, self->_name.c_str());
self->_cb();
self->ToStop();
return nullptr;
}
public:
Thread(callback_t cb)
: _tid(-1), _status(TSTATUS::THREAD_NEW), _joinable(true), _cb(cb), _result(nullptr)
{
_name = "New-Thread-" + std::to_string(gnumber++);
}
bool Start()
{
int n = pthread_create(&_tid, nullptr, ThreadRoutine, this);
if (n != 0)
return false;
ToRunning();
return true;
}
void Join()
{
if (_joinable)
{
int n = pthread_join(_tid, &_result);
if (n != 0)
{
std::cerr << "join error: " << n << std::endl;
return;
}
(void)_result;
_status = TSTATUS::THREAD_STOP;
Log(LogLevel::DEBUG) << _name << " join success";
}
else
{
std::cerr << "error, thread join status: " << IsJoined(_joinable) << std::endl;
}
}
// 暂停
// void Stop() // restart()
// {
// // 让线程暂停
// }
void Die()
{
if (_status == TSTATUS::THREAD_RUNNING)
{
pthread_cancel(_tid);
_status = TSTATUS::THREAD_STOP;
}
}
void Detach()
{
if (_status == TSTATUS::THREAD_RUNNING && _joinable)
{
pthread_detach(_tid);
_joinable = false;
}
else
{
std::cerr << "detach " << _name << " failed" << std::endl;
}
}
void PrintInfo()
{
std::cout << "thread name : " << _name << std::endl;
std::cout << "thread _tid : " << _tid << std::endl;
std::cout << "thread _status : " << Status2String(_status) << std::endl;
std::cout << "thread _joinable : " << IsJoined(_joinable) << std::endl;
}
~Thread()
{
}
private:
std::string _name;
pthread_t _tid;
TSTATUS _status;
bool _joinable;
// 线程要有自己的任务处理,即回调函数
callback_t _cb;
// 线程退出信息
void *_result;
};
}
ThreadPool.hpp
#pragma once
#include "../Log.h"
#include "Thread.hpp"
#include "Mutex.hpp"
#include "Cond.hpp"
#include <iostream>
#include <memory>
#include <vector>
#include <queue>
namespace ThreadPool
{
using namespace Logger;
const int default_thread_num = 5;
// 测试用函数
void test()
{
while(1)
{
Log(LogLevel::DEBUG) << "我是一个新线程,我再运行ing....";
sleep(1);
}
}
template<typename T>
class threadpool
{
private:
void HandlerTask()
{
char name[128];
pthread_getname_np(pthread_self(), name, sizeof(name));
while (true)
{
T task;
{
LockModule::LockGuard lockguard(_mutex); // 使用 RAII 管理锁,避免手动解锁
while (_tasks.empty() && _isRunning)
{
_Slaves_Sleep_Count++;
_cond.Wait(_mutex);
_Slaves_Sleep_Count--;
}
if (!_isRunning && _tasks.empty())
{
// 退出前解锁(LockGuard 析构时会自动解锁,无需手动)
break;
}
task = _tasks.front();
_tasks.pop();
}
Log(LogLevel::INFO) << name << "处理任务:";
task();
// Log(LogLevel::DEBUG) << task.Result();
}
Log(LogLevel::INFO) << name << " quit...";
}
// 构造函数私有,禁止外部直接创建
threadpool(int slave_num = default_thread_num)
: _isRunning(false), _Slavernum(slave_num), _Slaves_Sleep_Count(0)
{
_Slaves.reserve(_Slavernum);
auto fun = std::bind(&threadpool::HandlerTask, this);
for (size_t i = 0; i < _Slavernum; ++i)
{
_Slaves.emplace_back(fun);
}
}
// 禁止拷贝和赋值(修正:类名应为 threadpool)
threadpool(const threadpool &) = delete;
threadpool &operator=(const threadpool &) = delete;
public:
static threadpool<T>* Instance()
{
if (_instance == nullptr)
{
LockModule::LockGuard lockguard(_lock);
if (_instance == nullptr)
{
// 使用 reset(new ...) 替代 std::make_unique,因为构造函数私有
_instance.reset(new threadpool<T>());
_instance->Init(); // 调用 Init 而非 Start
Log(LogLevel::INFO) << "第一次使用线程池,创建线程池对象";
}
}
return _instance.get();
}
~threadpool()
{
}
void Init()
{
if (_isRunning)
{
Log(LogLevel::WARNING) << "线程池已经运行";
return;
}
_isRunning = true;
for (auto &slave : _Slaves)
{
slave.Start();
}
}
void Stop()
{
if (!_isRunning)
{
Log(LogLevel::WARNING) << "线程池未运行";
return;
}
// 停止线程池
_mutex.Lock();
_isRunning = false;
if (_Slaves_Sleep_Count > 0)
_cond.Broadcast();
_mutex.Unlock();
// 等待所有线程退出(可选)
// Wait();
}
void Wait()
{
for (auto &slave : _Slaves)
{
slave.Join();
}
}
void Enqueue(T in)
{
_mutex.Lock();
_tasks.push(in);
if (_Slaves_Sleep_Count > 0)
_cond.Signal();
_mutex.Unlock();
}
private:
bool _isRunning;
size_t _Slavernum;
std::vector<Thread> _Slaves;
std::queue<T> _tasks; // 临界资源
LockModule::Mutex _mutex;
CondModule::Cond _cond;
size_t _Slaves_Sleep_Count; // 休眠的线程数量
// 单例模式静态成员
static std::unique_ptr<threadpool<T>> _instance;
static LockModule::Mutex _lock; // 类型改为 LockModule::Mutex
};
// 静态成员定义(必须放在命名空间内)
template<typename T>
std::unique_ptr<threadpool<T>> threadpool<T>::_instance = nullptr;
template<typename T>
LockModule::Mutex threadpool<T>::_lock;
} // namespace ThreadPool
本期内容到这里先结束了,后续我们还会学习更多的网络相关的内容,敬请期待
封面图如下:

更多推荐


所有评论(0)