目录
1. 相关函数介绍
其中一些函数在之前已经介绍过,参考Linux 的 UDP 网络编程 -- 回显服务器,翻译服务器。
1.1 listen()
listen()
是 C 语言网络编程中的一个重要函数,主要用于将一个套接字(socket)转换为被动监听套接字,使其能够接受来自其他客户端的连接请求。这个函数是实现 TCP 服务器的关键步骤之一。
原型:
int listen(int sockfd, int backlog);
头文件:
#include <sys/types.h>
#include <sys/socket.h>
参数:
sockfd:这是通过 socket() 函数创建的套接字描述符,并且该套接字已经通过 bind() 函数绑定到了
特定的地址和端口。
backlog:表示请求队列的最大长度,即允许在服务器处理当前连接请求的同时,积压的未处理连接请求的
最大数量。当请求队列已满时,新的连接请求可能会被拒绝(具体行为取决于操作系统)。
返回值:
成功。返回 0.
失败,返回 -1,并设置 errno 来指示具体的错误原因。
功能:
主要用于将一个套接字(socket)转换为被动监听套接字,使其能够接受来自其他客户端的连接请求。这
个函数是实现 TCP 服务器的关键步骤之一。
1.2 accept()
accept()
是 C 语言网络编程中的一个核心函数,主要用于从已完成连接队列中取出一个客户端连接请求,并创建一个新的套接字来专门处理该连接。这个函数是实现 TCP 服务器的关键步骤之一。
原型:
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
头文件:
#include <sys/types.h>
#include <sys/socket.h>
参数:
sockfd:这是通过 socket()、bind() 和 listen() 函数创建并配置好的监听套接字描述符,用于接收
客户端的连接请求。
addr(可选):指向 struct sockaddr 类型的指针,用于存储客户端的地址信息(如 IP 地址和端口
号)。如果不需要客户端地址,可以传入 NULL。
addrlen(可选):指向 socklen_t 类型的指针,用于指定 addr 结构的长度。函数返回时,该参数会
被更新为实际存储的地址结构长度。如果 addr 为 NULL,则 addrlen 也应设为 NULL。
返回值:
成功,返回一个新的套接字描述符,用于与客户端进行数据通信。原监听套接字 sockfd 依然保持监听状
态,可以继续接收其他连接请求。
失败:返回 -1,并设置 errno 来指示具体的错误原因。
功能:
用于从已完成连接队列中取出一个客户端连接请求,并创建一个新的套接字来专门处理该连接。
1.3 connect()
connect()
是 C 语言网络编程中的一个基础函数,主要用于客户端向服务器发起连接请求。通过这个函数,客户端可以与指定 IP 地址和端口的服务器建立 TCP 连接。
原型:
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
头文件:
#include <sys/types.h>
#include <sys/socket.h>
参数:
sockfd:这是通过 socket() 函数创建的客户端套接字描述符。
addr:指向 struct sockaddr 类型的指针,其中包含了服务器的地址信息(如 IP 地址和端口号)。对
于 IPv4,通常使用 struct sockaddr_in 结构体;对于 IPv6,则使用 struct sockaddr_in6 结构体。
addrlen:addr 结构体的长度,类型为 socklen_t。
返回值:
成功,返回 0,表示连接已建立。
失败,返回 -1,并设置 errno 来指示具体的错误原因。
功能:
主要用于客户端向服务器发起连接请求。通过这个函数,客户端可以与指定 IP 地址和端口的服务器建立
TCP 连接。
2. TCP 回显服务器
互斥锁的封装模块和线程安全的日志模块参考Linux 的 UDP 网络编程 -- 回显服务器,翻译服务器。
这里先给出封装的条件变量模块,线程模块和线程池模块。
// 条件变量模块 -- Cond.hpp
#pragma once
#include <iostream>
#include <pthread.h>
#include "Mutex.hpp"
using namespace MutexModule;
namespace CondModule
{
class Cond
{
public:
Cond()
{
pthread_cond_init(&_cond, nullptr);
}
void Wait(Mutex &mutex)
{
int n = pthread_cond_wait(&_cond, mutex.Get());
(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 <cstring>
#include <functional>
#include <pthread.h>
#include "Log.hpp"
namespace ThreadModule
{
using namespace LogModule;
static uint32_t number = 1;
class Thread
{
using func_t = std::function<void()>;
private:
void EnableDetach()
{
// LOG(LogLevel::DEBUG) << "thread's detach flag become to true";
_isdetach = true;
}
void EnableRunning()
{
// LOG(LogLevel::DEBUG) << _name << " is started";
_isrunning = true;
}
static void* Routine(void* args)
{
Thread * self = static_cast<Thread*>(args);
// 将运行标志位置为true
self->EnableRunning();
// 如果分离标志位为true,则分离线程
if (self->_isdetach)
{
int n = pthread_detach(self->_tid);
// LOG(LogLevel::DEBUG) << "thread is detached in Routine, the return value is " << n;
}
pthread_setname_np(self->_tid, self->_name.c_str());
self->_func();
return nullptr;
}
public:
// 构造函数,需要传入一个入口函数地址
Thread(func_t func)
: _tid(0), _isdetach(false), _isrunning(false), res(nullptr), _func(func)
{
_name = "thread-" + std::to_string(number++);
}
bool Start()
{
// 1. 如果线程已经运行起来,防止再次启动,直接返回false
if (_isrunning)
return false;
// 2. 如果线程第一次启动,则创建线程
// 这里如果Routine不是静态成员函数,默认会有一个this指针参数,与pthread_create中的参数不匹配
// 所以这里使用静态成员函数,将该线程对象以参数this的形式传给pthread_create
int n = pthread_create(&_tid, nullptr, Routine, this);
// 创建线程失败返回false
if (n != 0)
{
// LOG(LogLevel::DEBUG) << "create thread error " << strerror(n);
return false;
}
else
{
// LOG(LogLevel::DEBUG) << _name << " create success";
return true;
}
}
void Detach()
{
// // 需要处理两种情况
// // 情况1:在线程还没有启动的时候,调用Detach设置线程分离标志位,然后线程启动之后在Routine函数中进行分离
// // 情况2:在线程启动之后调用Detach设置线程分离标志位,以及分离线程
// 如果线程已经分离,直接返回
if (_isdetach)
{
// LOG(LogLevel::DEBUG) << _name << " is already detached. No further action needed.";
return;
}
// 如果线程还没有启动,设置线程分离标志位
if (!_isrunning)
{
EnableDetach();
return;
}
else
{
// 启动后设置线程分离,需要设置标志位之后再进行线程分离
EnableDetach();
int n = pthread_detach(_tid);
// LOG(LogLevel::DEBUG) << "thread is detched, the return value is " << n;
}
}
bool Stop()
{
// 如果运行标志位为true,取消线程并将运行标志位置为false
if (_isrunning)
{
int n = pthread_cancel(_tid);
if (n != 0)
{
// LOG(LogLevel::DEBUG) << "cancel thread error" << strerror(n);
return false;
}
else
{
_isrunning = false;
// LOG(LogLevel::DEBUG) << _name << " stop";
return true;
}
}
return false;
}
void Join()
{
// 分离的线程不能被等待
if (_isdetach)
{
// LOG(LogLevel::DEBUG) << "thread is detached. it can't be joined! ";
return;
}
int n = pthread_join(_tid, &res);
if (n != 0)
{
// LOG(LogLevel::DEBUG) << "join thread error";
}
else
{
// LOG(LogLevel::DEBUG) << "join thread success";
}
}
std::string GetName()
{
return _name;
}
pthread_t Id()
{
return _tid;
}
~Thread()
{
}
private:
pthread_t _tid; // 线程ID
std::string _name; // 线程名字
bool _isdetach; // 线程分离标志位
bool _isrunning; // 线程运行标志位
void *res; // 线程返回值
func_t _func; // 线程入口函数
};
}
// 线程池模块 -- ThreadPool.hpp
// 懒汉式单例模式线程池
#pragma once
#include <iostream>
#include <string>
#include <vector>
#include <queue>
#include "Log.hpp"
#include "Thread.hpp"
#include "Cond.hpp"
#include "Mutex.hpp"
namespace ThreadPoolModule
{
using namespace ThreadModule;
using namespace LogModule;
using namespace CondModule;
using namespace MutexModule;
static const int gnum = 5; // 使用全局变量来表示一个线程池默认的线程数量
template <typename T> // 使用模版的方式使线程池支持多类型的任务
class ThreadPool
{
private:
void WakeUpAllThread()
{
if (_sleep_num)
_cond.Broadcast();
LOG(LogLevel::DEBUG) << "唤醒所有休眠线程";
}
void WakeOne()
{
_cond.Signal();
LOG(LogLevel::INFO) << "唤醒一个休眠的线程";
}
// 私有化构造函数
ThreadPool(int num = gnum)
: _num(num),
_isrunning(false),
_sleep_num(0)
{
for (int i = 0; i < _num; i++)
{
_threads.emplace_back([this]()
{ HandlerTask(); }); // 调用线程的构造函数,线程的构造函数形参是一个回调函数
}
}
void Start()
{
if (_isrunning)
return;
_isrunning = true;
for (auto &thread : _threads)
{
thread.Start();
}
}
// 禁用拷贝构造和赋值运算符
ThreadPool(const ThreadPool<T> &) = delete;
ThreadPool<T> &operator=(const ThreadPool<T> &) = delete;
public:
static ThreadPool<T> *GetInstance()
{
if (inc == nullptr) // 第一次创建的时候需要加锁,保证创建是原子性的
{
LockGuard lockGuard(_gmutex);
if (inc == nullptr) // 双层判断,保证只会创建一个单例
{
LOG(LogLevel::DEBUG) << "首次使用, 创建单例...";
inc = new ThreadPool<T>();
inc->Start();
}
}
return inc;
}
void HandlerTask()
{
char name[64];
pthread_getname_np(pthread_self(), name, sizeof(name));
while (true)
{
T t;
// 处理任务
{
LockGuard lockGuard(_mutex);
// 1. 队列为空,线程池没有退出,进行休眠
while (_taskq.empty() && _isrunning)
{
_sleep_num++;
LOG(LogLevel::INFO) << name << " 进入休眠";
_cond.Wait(_mutex);
_sleep_num--;
}
// 2. 任务为空,线程池退出,则该线程退出
if (!_isrunning && _taskq.empty())
{
LOG(LogLevel::INFO) << name << " 退出, 因为线程池退出&&任务队列为空";
break;
}
// 3. 获取任务
t = _taskq.front();
_taskq.pop();
}
t(); // 4. 处理任务
// LOG(LogLevel::DEBUG) << name << " is running";
}
}
bool Enqueue(const T &in)
{
if (_isrunning) // 如果线程池停止,则停止入任务
{
LockGuard lockGuard(_mutex);
_taskq.push(in);
// if (_threads.size() == _sleep_num) // 如果全部线程都在休眠,则唤醒一个线程
WakeOne();
return true;
}
return false;
}
void Stop()
{
// 1. 将运行标志位置为false
LockGuard lockGuard(_mutex);
if (!_isrunning)
return;
_isrunning = false;
// 2. 唤醒休眠的线程,然后再HandlerTask中进行退出
WakeUpAllThread();
}
void Join()
{
for (auto &thread : _threads)
{
thread.Join();
LOG(LogLevel::INFO) << thread.GetName() << " 被Join";
}
}
~ThreadPool()
{
}
private:
std::vector<Thread> _threads;
int _num; // 线程数量
std::queue<T> _taskq; // 任务队列
Cond _cond;
Mutex _mutex;
bool _isrunning;
int _sleep_num;
static ThreadPool<T> *inc; // 单例指针
static Mutex _gmutex; // 用于多线程场景下保护单例不被多次创建
};
template <typename T>
ThreadPool<T> *ThreadPool<T>::inc = nullptr; // 静态成员变量需要在类外进行初始化
template <typename T>
Mutex ThreadPool<T>::_gmutex; // 自动调用Mutex的构造函数进行初始化
}
2.1 Common.hpp
该源文件中包含了整个项目所使用的通用的头文件,宏定义,结构体。
#pragma once
#include <iostream>
#include <memory>
#include <functional>
#include <unistd.h>
#include <string>
#include <cstring>
#include <signal.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/wait.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include "Log.hpp"
#include "ThreadPool.hpp"
using namespace LogModule;
using namespace ThreadPoolModule;
// 强转 struct sockaddr_in * 为 struct sockaddr * 的宏
#define CONV(addr) ((struct sockaddr*)&addr)
// 将各种错误的错误码用一个枚举类型表示
enum EixtCode
{
OK,
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;
};
2.2 InetAddr.hpp
该源文件定义了一个网络序列和主机序列存储及相互转换的类 InetAddr,主要用于主机序列和网络序列之间的相互转换。
#pragma once
#include "Common.hpp"
class InetAddr
{
public:
InetAddr(){};
// 使用套接字创建对象的构造函数
InetAddr(struct sockaddr_in &addr) : _addr(addr)
{
_port = ntohs(_addr.sin_port);
char ipbuffer[64];
inet_ntop(AF_INET, &_addr.sin_addr, ipbuffer, sizeof(_addr));
_ip = ipbuffer;
}
// 使用主机序列创建的构造函数
InetAddr(std::string &ip, uint16_t port) : _ip(ip), _port(port)
{
memset(&_addr, 0, sizeof(_addr));
_addr.sin_family = AF_INET;
_addr.sin_port = htons(_port);
inet_pton(AF_INET, _ip.c_str(), &_addr.sin_addr);
}
// 仅使用端口号创建,ip 设为 INADDR_ANY
InetAddr(uint16_t port) : _port(port), _ip()
{
memset(&_addr, 0, sizeof(_addr));
_addr.sin_family = AF_INET;
_addr.sin_port = htons(_port);
_addr.sin_addr.s_addr = INADDR_ANY;
}
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;
};
2.3 TcpClient.cc
该文件为项目中的客户端文件。
#include "Common.hpp"
#include "InetAddr.hpp"
void Usage(std::string proc)
{
std::cerr << "Usage: " << proc << " server_ip server_port" << std::endl;
}
// ./tcpclient server_ip server_port
int main(int argc, char *argv[])
{
if (argc != 3)
{
Usage(argv[0]);
exit(USAGE_ERR);
}
std::string server_ip = argv[1];
uint16_t server_port = std::stoi(argv[2]);
// 1. 创建套接字文件
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd < 0)
{
LOG(LogLevel::FATAL) << "socket error";
exit(SOCKET_ERR);
}
LOG(LogLevel::INFO) << "socket success";
// 2. 直接向目标服务器发起建立连接的请求
InetAddr serverAddr(server_ip, server_port);
int n = connect(sockfd, serverAddr.NetAddrPtr(), serverAddr.NetAddrLen());
if (n < 0)
{
LOG(LogLevel::FATAL) << "connect error";
exit(CONNECT_ERR);
}
LOG(LogLevel::INFO) << "connect success";
// 3. echo client
while (true)
{
// 3.1 发消息
std::string line;
std::cout << "Please Enter# ";
std::getline(std::cin, line);
if (line.empty())
continue;
write(sockfd, line.c_str(), line.size());
// 3.2 收消息
char buffer[1024];
ssize_t s = read(sockfd, buffer, sizeof(buffer) - 1);
if (s > 0)
{
buffer[s] = 0;
std::cout << buffer << std::endl;
}
}
return 0;
}
2.4 TcpServer.hpp
该源文件为回显服务器的封装文件,其中给出了回显服务器的多进程版本,多线程版本以及线程池版本。这里选择线程池版本进行测试。
#pragma once
#include "Common.hpp"
#include "InetAddr.hpp"
#include <iostream>
#include <functional>
using func_t = std::function<std::string(const std::string &, InetAddr &)>;
using task_t = std::function<void()>;
const static int defaultSockfd = -1;
const static int backlog = 8;
// 服务器往往是禁止拷贝的
class TcpServer
{
public:
// 短服务 -- 处理一次之后退出
// 长服务 -- 客户端不退出服务端不退出
void Service(int sockfd, InetAddr peer)
{
char buffer[1024];
while (true)
{
// 1. 读取数据
ssize_t n = read(sockfd, buffer, sizeof(buffer) - 1);
if (n > 0) // 读取成功
{
buffer[n] = 0;
LOG(LogLevel::DEBUG) << peer.StringAddr() << " # " << buffer;
std::string echo_string = "echo @ ";
echo_string += buffer;
// 2. 写回数据
write(sockfd, echo_string.c_str(), echo_string.size());
}
else if (n == 0) // 客户端把连接关闭了,读到文件的结尾,类似 pipe
{
LOG(LogLevel::DEBUG) << peer.StringAddr() << " 退出了...";
close(sockfd);
break;
}
else // 读取异常
{
LOG(LogLevel::DEBUG) << peer.StringAddr() << " 读取异常...";
close(sockfd);
break;
}
}
}
public:
TcpServer(uint16_t port)
: _port(port),
_listen_sockfd(defaultSockfd),
_isrunning(false)
{
}
void Init()
{
// signal(SIGCHLD, SIG_IGN); // 子进程退出,自动回收
// 1. 创建套接字文件
_listen_sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (_listen_sockfd < 0)
{
LOG(LogLevel::FATAL) << "socket error";
exit(SOCKET_ERR);
}
LOG(LogLevel::INFO) << "socket success: " << _listen_sockfd;
// 2. bind 端口号,服务器 ip 不显示绑定
InetAddr local(_port);
int n = bind(_listen_sockfd, local.NetAddrPtr(), local.NetAddrLen());
if (n < 0)
{
LOG(LogLevel::FATAL) << "bind error";
exit(BIND_ERR);
}
LOG(LogLevel::INFO) << "bind success: " << _listen_sockfd;
// 3. 设置 _listen_sockfd 为 listen 状态
n = listen(_listen_sockfd, backlog);
if (n < 0)
{
LOG(LogLevel::FATAL) << "listen error";
exit(LISTEN_ERR);
}
LOG(LogLevel::INFO) << "listen success: " << _listen_sockfd;
}
class ThreadData
{
public:
ThreadData(int sockfd, InetAddr addr, TcpServer *tsvr)
: _sockfd(sockfd),
_addr(addr),
_tsvr(tsvr)
{
}
public:
int _sockfd;
InetAddr _addr;
TcpServer *_tsvr;
};
static void *Routine(void *args)
{
// 分离线程,子线程退出自动回收
pthread_detach(pthread_self());
ThreadData* td = static_cast<ThreadData *>(args);
td->_tsvr->Service(td->_sockfd, td->_addr);
delete td;
return nullptr;
}
void Run()
{
_isrunning = true;
while (_isrunning)
{
// 1. 获取连接
struct sockaddr_in peer;
socklen_t len = sizeof(peer);
int sockfd = accept(_listen_sockfd, CONV(peer), &len); // 如果没有连接,accept 会阻塞
if (sockfd < 0)
{
LOG(LogLevel::WARNING) << "accept error";
continue;
}
InetAddr addr(peer);
LOG(LogLevel::INFO) << "accept success, peer addr : " << addr.StringAddr() << " sockfd: " << sockfd;
// 2.1 version0 -- test version -- 只能给一个客户端提供服务 -- 不会存在
// Service(sockfd, addr);
// 2.2 version1 -- 多进程版本
// pid_t id = fork();
// if (id < 0)
// {
// LOG(LogLevel::FATAL) << "fork error";
// exit(FORK_ERR);
// }
// else if (id == 0)
// {
// // 子进程
// close(_listen_sockfd);
// if (fork() > 0) // 子进程
// exit(OK);
// // 孙进程
// Service(sockfd, addr); // 当子进程退出时变成孤儿进程,服务结束系统进行回收
// exit(OK);
// }
// else
// {
// // 父进程
// close(sockfd);
// pid_t rid = waitpid(id, nullptr, 0);
// (void)rid;
// }
// 2.3 version2 -- 多线程版本
// ThreadData *td = new ThreadData(sockfd, addr, this);
// pthread_t tid;
// pthread_create(&tid, nullptr, Routine, td);
// 2.4 version3 -- 线程池版本 -- 线程池一般比较适合处理短服务
ThreadPool<task_t>::GetInstance()->Enqueue([this, sockfd, addr](){
// LOG(LogLevel::DEBUG) << "一个客户端进入线程池";
this->Service(sockfd, addr);
});
}
_isrunning = false;
}
~TcpServer() {}
private:
uint16_t _port;
int _listen_sockfd; // 监听socket
bool _isrunning;
// func_t _func; // 回调处理函数
};
2.5 TcpServer.cc
该源文件为服务端的文件。
#include "TcpServer.hpp"
#include "Common.hpp"
void Usage(std::string proc)
{
std::cerr << "Usage: " << proc << " port" << std::endl;
}
// ./tcpserver server_port
int main(int argc, char *argv[])
{
if (argc != 2)
{
Usage(argv[0]);
exit(USAGE_ERR);
}
uint16_t port = std::stoi(argv[1]);
Enable_Console_Log_Strategy();
// 1. 创建通信对象
std::unique_ptr<TcpServer> tsvr = std::make_unique<TcpServer>(port);
tsvr->Init();
tsvr->Run();
return 0;
}
2.6 demo 测试
如上图所示,启动服务端,绑定端口号 8888,先创建套接字,然后进行绑定,在将服务器设置为监听状态,使客户端能够进行连接。
当第一个客户端进行连接的时候,首次使用线程池,则创建线程池单例并唤醒一个线程给该客户端进行服务。当第二个客户端进行连接的时候,不用再创建线程池了,则唤醒另一个线程给该客户端提供服务。
当一个客户端退出之后,该线程结束服务,进入休眠状态。
3. TCP 翻译服务器
这里翻译的字典文件以及字典结构体的封装参考Linux 的 UDP 网络编程 -- 回显服务器,翻译服务器。
仅仅修改 TcpServer.cc 以及 TcpServer.hpp 文件即可,这里的服务器使用多线程版本,将回显服务从服务器中分层到应用层,并替换为翻译服务。
// TcpServer.hpp
#pragma once
#include "Common.hpp"
#include "InetAddr.hpp"
#include <iostream>
#include <functional>
using func_t = std::function<std::string(const std::string &, InetAddr &)>;
using task_t = std::function<void()>;
const static int defaultSockfd = -1;
const static int backlog = 8;
class TcpServer
{
public:
void Service(int sockfd, InetAddr peer)
{
char buffer[1024];
while (true)
{
// 1. 读取英文单词数据
ssize_t n = read(sockfd, buffer, sizeof(buffer) - 1);
if (n > 0) // 读取成功
{
buffer[n] = 0;
std::string echo_string = _func(buffer, peer);
LOG(LogLevel::DEBUG) << peer.StringAddr() << " # " << buffer;
// 2. 写回中文数据
write(sockfd, echo_string.c_str(), echo_string.size());
}
else if (n == 0)
{
LOG(LogLevel::DEBUG) << peer.StringAddr() << " 退出了...";
close(sockfd);
break;
}
else // 读取异常
{
LOG(LogLevel::DEBUG) << peer.StringAddr() << " 读取异常...";
close(sockfd);
break;
}
}
}
public:
TcpServer(uint16_t port, func_t func)
: _port(port),
_listen_sockfd(defaultSockfd),
_isrunning(false),
_func(func)
{
}
void Init()
{
// 1. 创建套接字文件
_listen_sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (_listen_sockfd < 0)
{
LOG(LogLevel::FATAL) << "socket error";
exit(SOCKET_ERR);
}
LOG(LogLevel::INFO) << "socket success: " << _listen_sockfd;
// 2. bind 端口号,服务器 ip 不显示绑定
InetAddr local(_port);
int n = bind(_listen_sockfd, local.NetAddrPtr(), local.NetAddrLen());
if (n < 0)
{
LOG(LogLevel::FATAL) << "bind error";
exit(BIND_ERR);
}
LOG(LogLevel::INFO) << "bind success: " << _listen_sockfd;
// 3. 设置 _listen_sockfd 为 listen 状态
n = listen(_listen_sockfd, backlog);
if (n < 0)
{
LOG(LogLevel::FATAL) << "listen error";
exit(LISTEN_ERR);
}
LOG(LogLevel::INFO) << "listen success: " << _listen_sockfd;
}
class ThreadData
{
public:
ThreadData(int sockfd, InetAddr addr, TcpServer *tsvr)
: _sockfd(sockfd),
_addr(addr),
_tsvr(tsvr)
{
}
public:
int _sockfd;
InetAddr _addr;
TcpServer *_tsvr;
};
static void *Routine(void *args)
{
// 分离线程,子线程退出自动回收
pthread_detach(pthread_self());
ThreadData* td = static_cast<ThreadData *>(args);
td->_tsvr->Service(td->_sockfd, td->_addr);
delete td;
return nullptr;
}
void Run()
{
_isrunning = true;
while (_isrunning)
{
// 1. 获取连接
struct sockaddr_in peer;
socklen_t len = sizeof(peer);
int sockfd = accept(_listen_sockfd, CONV(peer), &len); // 如果没有连接,accept 会阻塞
if (sockfd < 0)
{
LOG(LogLevel::WARNING) << "accept error";
continue;
}
InetAddr addr(peer);
LOG(LogLevel::INFO) << "accept success, peer addr : " << addr.StringAddr() << " sockfd: " << sockfd;
// 2.3 多线程版本
ThreadData *td = new ThreadData(sockfd, addr, this);
pthread_t tid;
pthread_create(&tid, nullptr, Routine, td);
}
_isrunning = false;
}
~TcpServer() {}
private:
uint16_t _port;
int _listen_sockfd; // 监听socket
bool _isrunning;
func_t _func; // 回调处理函数
};
// TcpServer.cc
#include "TcpServer.hpp"
#include "Common.hpp"
#include "Dict.hpp"
void Usage(std::string proc)
{
std::cerr << "Usage: " << proc << " port" << std::endl;
}
// ./tcpserver server_port
int main(int argc, char *argv[])
{
if (argc != 2)
{
Usage(argv[0]);
exit(USAGE_ERR);
}
uint16_t port = std::stoi(argv[1]);
Enable_Console_Log_Strategy();
// 1. 创建字典对象
Dict d;
d.LoadDict();
// 2. 创建通信对象
std::unique_ptr<TcpServer> tsvr = std::make_unique<TcpServer>(port, [&d](const std::string &word, InetAddr &client)->std::string{
return d.Translate(word, client);
});
tsvr->Init();
tsvr->Run();
return 0;
}
3.1 demo 测试
启动服务器并绑定 8888 号端口,并加载字典文件到内存中,将绑定套接字并设置为监听状态。
启动客户端进行连接,并输入字符串请求服务端服务。
这里再服务端可以看到服务器的运行信息。