Socket编程TCP
目录
一、接口介绍
1.1.socket函数
作用:创建一个通信的一端
参数1:域
- 本地通信:AF_UNIX
- 网络通信:AF_INET
参数2:套接字类型
- UDP:SOCK_DGRAM(面向数据报)
- TCP:SOCK_STREAM(面向字节流)
参数3:设置为0
返回值:
- 返回成功:文件描述符
- 返回失败:-1

1.2.bind函数
作用:给一个套接字(网络文件)绑定一个名字
参数1:文件描述符
参数2:sockaddr结构体(填充端口号和IP地址)
参数3:结构体大小

1.3.listen函数
作用:监听一个套接字的链接
参数1:文件描述符
参数2:全连接队列

1.4.telnet命令
作用:访问TCP协议的目标服务器




只要TCP服务器处于listen状态,就已经可以被连接
TCP是全双工,客户端和服务器在一台机器,所以会有两条链接
1.5.accept函数
作用:获取一个套接字的链接
参数1:文件描述符
参数2:获取客户端的addr结构(输出型参数)
参数3:获取客户端的addr结构大小(输出型参数)
返回值:
- 获取成功:文件描述符
- 获取失败:-1
链接从内核中直接获取,建立连接的过程与accept无关


1.6.read函数
作用:读文件

1.7.write函数
作用:写文件

1.8.connect函数
作用:与服务器进行连接
参数1:客户端文件描述符
参数2:目标服务器的addr结构
参数3:目标服务器的addr结构大小
返回值:
- 连接成功:0
- 连接失败:-1

二、V1单进程回显服务器
2.1.TcpServer.hpp
cpp
#pragma once
#include "Common.hpp"
#include "Log.hpp"
#include "InetAddr.hpp"
using namespace LogModule;
const static int defaultsockfd = -1;
const static int backlog = 8;
class TcpServer : public NoCopy
{
public:
TcpServer(uint16_t port)
: _port(port), _listensockfd(defaultsockfd), _isrunning(false)
{
}
// 服务器初始化
void Init()
{
// 创建套接字
_listensockfd = socket(AF_INET, SOCK_STREAM, 0);
if (_listensockfd < 0)
{
LOG(LogLevel::FATAL) << "socket error";
exit(SOCKET_ERR);
}
LOG(LogLevel::INFO) << "socket success: " << _listensockfd; // fd = 3
// 绑定IP地址和端口号
InetAddr local(_port);
int n = bind(_listensockfd, local.NetAddrPtr(), local.NetAddrLen());
if (n < 0)
{
LOG(LogLevel::FATAL) << "bind error";
exit(BIND_ERR);
}
LOG(LogLevel::INFO) << "bind success: " << _listensockfd; // fd = 3
// 设置监听状态
n = listen(_listensockfd, backlog);
if (n < 0)
{
LOG(LogLevel::FATAL) << "listen error";
exit(LISTEN_ERR);
}
LOG(LogLevel::INFO) << "listen success: " << _listensockfd; // fd = 3
}
// 单线程程序
void Service(int sockfd, InetAddr &peer)
{
char buffer[1024];
while (true)
{
// 读取数据
// n > 0: 读取成功
// n < 0: 读取失败
// n = 0: 客户端关闭链接, 服务器读到文件结尾
ssize_t n = read(sockfd, buffer, sizeof(buffer) - 1);
if(n > 0)
{
// 设置为C风格的字符串
// n(实际读取) ≤ sizeof(buffer) - 1
buffer[n] = 0;
LOG(LogLevel::DEBUG) << peer.StringAddr() << " say# " << buffer;
}
else if(n == 0)
{
LOG(LogLevel::DEBUG) << peer.StringAddr() << " 退出了...";
close(sockfd);
break;
}
else
{
LOG(LogLevel::DEBUG) << peer.StringAddr() << " 异常...";
close(sockfd);
break;
}
// 写回数据
std::string echo_string = "echo# ";
echo_string += buffer;
write(sockfd, echo_string.c_str(), echo_string.size());
}
}
void Run()
{
_isrunning = true;
while (_isrunning)
{
// 获取链接
struct sockaddr_in peer;
socklen_t len = sizeof(sockaddr_in);
// 没有链接时, 会发送阻塞
int sockfd = accept(_listensockfd, CONV(peer), &len);
if (sockfd < 0)
{
LOG(LogLevel::WARNING) << "accept error";
continue;
}
InetAddr addr(peer);
LOG(LogLevel::INFO) << "accept success, peer addr: " << addr.StringAddr();
// version 0
Service(sockfd, addr);
}
_isrunning = false;
}
~TcpServer()
{
}
private:
uint16_t _port; // 服务器端口号
int _listensockfd; // 监听文件描述符
bool _isrunning; // 服务器运行状态
};
2.2.TcpServer.cc
cpp
#include "TcpServer.hpp"
void Usage(std::string proc)
{
std::cerr << "Usage: " << proc << " port" << std::endl;
}
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();
std::unique_ptr<TcpServer> tsvr = std::make_unique<TcpServer>(port);
tsvr->Init();
tsvr->Run();
return 0;
}
2.3.TcpClient.cc
cpp
#include <iostream>
#include "Common.hpp"
#include "InetAddr.hpp"
void Usage(std::string proc)
{
std::cerr << "Usage: " << proc << " server_ip server_port" << std::endl;
}
int main(int argc, char* argv[])
{
if(argc != 3)
{
Usage(argv[0]);
exit(USAGE_ERR);
}
std::string serverip = argv[1];
uint16_t serverport = std::stoi(argv[2]);
// 创建套接字
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
if(sockfd < 0)
{
std::cerr << "socket error" << std::endl;
exit(SOCKET_ERR);
}
// 非显示绑定
// 向目标服务器发起建立链接的请求
InetAddr serveraddr(serverip, serverport);
int n = connect(sockfd, serveraddr.NetAddrPtr(), serveraddr.NetAddrLen());
if(n < 0)
{
std::cerr << "connect error" << std::endl;
exit(CONNECT_ERR);
}
while(true)
{
// 写入数据
std::string line;
std::cout << "Please Enter@ ";
std::getline(std::cin, line);
write(sockfd, line.c_str(), line.size());
// 读取数据
char buffer[1024];
ssize_t size = read(sockfd, buffer, sizeof(buffer) - 1);
if(size > 0)
{
buffer[size] = 0;
std::cout << "server echo# " << buffer << std::endl;
}
}
close(sockfd);
return 0;
}
2.4.Common.hpp
cpp
#pragma once
#include <iostream>
#include <unistd.h>
#include <string>
#include <cstring>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
enum ExitCode
{
OK = 0,
USAGE_ERR,
SOCKET_ERR,
BIND_ERR,
LISTEN_ERR,
CONNECT_ERR,
};
// 禁止拷贝
class NoCopy
{
public:
NoCopy()
{
}
~NoCopy()
{
}
NoCopy(const NoCopy &) = delete;
const NoCopy &operator=(const NoCopy &) = delete;
};
#define CONV(addr) ((struct sockaddr *)&addr)
2.5.InetAddr.hpp
cpp
#pragma once
#include <iostream>
#include <string>
#include <cstring>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include "Common.hpp"
// 网络地址 <=> 主机地址
class InetAddr
{
public:
InetAddr(struct sockaddr_in &addr)
: _addr(addr)
{
// 网络序列 → 主机序列(端口号)
_port = ntohs(_addr.sin_port);
// 网络序列 → 点分十进制(IP地址)
char ipbuffer[64];
inet_ntop(AF_INET, &_addr.sin_addr, ipbuffer, sizeof(_addr));
_ip = ipbuffer;
}
InetAddr(const 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);
// 点分十进制 → 网络序列(IP地址)
inet_pton(AF_INET, _ip.c_str(), &_addr.sin_addr);
}
InetAddr(uint16_t port)
: _port(port), _ip("0")
{
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;
};
三、V2多进程回显服务器
3.1.TcpServer.hpp
cpp
#pragma once
#include <sys/wait.h>
#include <signal.h>
#include "Common.hpp"
#include "Log.hpp"
#include "InetAddr.hpp"
using namespace LogModule;
const static int defaultsockfd = -1;
const static int backlog = 8;
class TcpServer : public NoCopy
{
public:
TcpServer(uint16_t port)
: _port(port), _listensockfd(defaultsockfd), _isrunning(false)
{
}
// 服务器初始化
void Init()
{
// 忽略SIG_IGN信号
// OS自动回收子进程, 但无法获取子进程信息
// 推荐写法: signal(SIGCHLD, SIG_IGN);
// 创建套接字
_listensockfd = socket(AF_INET, SOCK_STREAM, 0);
if (_listensockfd < 0)
{
LOG(LogLevel::FATAL) << "socket error";
exit(SOCKET_ERR);
}
LOG(LogLevel::INFO) << "socket success: " << _listensockfd; // fd = 3
// 绑定IP地址和端口号
InetAddr local(_port);
int n = bind(_listensockfd, local.NetAddrPtr(), local.NetAddrLen());
if (n < 0)
{
LOG(LogLevel::FATAL) << "bind error";
exit(BIND_ERR);
}
LOG(LogLevel::INFO) << "bind success: " << _listensockfd; // fd = 3
// 设置监听状态
n = listen(_listensockfd, backlog);
if (n < 0)
{
LOG(LogLevel::FATAL) << "listen error";
exit(LISTEN_ERR);
}
LOG(LogLevel::INFO) << "listen success: " << _listensockfd; // fd = 3
}
void Service(int sockfd, InetAddr &peer)
{
char buffer[1024];
while (true)
{
// 读取数据
// n > 0: 读取成功
// n < 0: 读取失败
// n = 0: 客户端关闭链接, 服务器读到文件结尾
ssize_t n = read(sockfd, buffer, sizeof(buffer) - 1);
if (n > 0)
{
// 设置为C风格的字符串
// n(实际读取) ≤ sizeof(buffer) - 1
buffer[n] = 0;
LOG(LogLevel::DEBUG) << peer.StringAddr() << " say# " << buffer;
}
else if (n == 0)
{
LOG(LogLevel::DEBUG) << peer.StringAddr() << " 退出了...";
close(sockfd);
break;
}
else
{
LOG(LogLevel::DEBUG) << peer.StringAddr() << " 异常...";
close(sockfd);
break;
}
// 写回数据
std::string echo_string = "echo# ";
echo_string += buffer;
write(sockfd, echo_string.c_str(), echo_string.size());
}
}
void Run()
{
_isrunning = true;
while (_isrunning)
{
// 获取链接
struct sockaddr_in peer;
socklen_t len = sizeof(sockaddr_in);
// 没有链接时, 会发送阻塞
int sockfd = accept(_listensockfd, CONV(peer), &len);
if (sockfd < 0)
{
LOG(LogLevel::WARNING) << "accept error";
continue;
}
InetAddr addr(peer);
LOG(LogLevel::INFO) << "accept success, peer addr: " << addr.StringAddr();
// version 1
pid_t id = fork();
if (id < 0)
{
LOG(LogLevel::FATAL) << "fork error";
exit(FORK_ERR);
}
else if (id == 0)
{
// 子进程
close(_listensockfd); // 子进程不能访问listensockfd
if(fork() > 0) // 子进程fork
{
// 子进程退出
exit(OK);
}
// 孙子进程(孤儿进程: 被1号进程领养, 由系统回收)
Service(sockfd, addr);
exit(OK);
}
else
{
// 父进程
close(sockfd); // 父进程不需要sockfd
pid_t rid = waitpid(id, nullptr, 0); // 父进程不会阻塞, 立即回收
(void)rid;
}
}
_isrunning = false;
}
~TcpServer()
{
}
private:
uint16_t _port; // 服务器端口号
int _listensockfd; // 监听文件描述符
bool _isrunning; // 服务器运行状态
};
3.2.Common.hpp
cpp
#pragma once
#include <iostream>
#include <unistd.h>
#include <string>
#include <cstring>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.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)
四、V3多线程回显服务器
4.1.TcpServer.hpp
cpp
#pragma once
#include <sys/wait.h>
#include <signal.h>
#include "Common.hpp"
#include "Log.hpp"
#include "InetAddr.hpp"
#include <pthread.h>
using namespace LogModule;
const static int defaultsockfd = -1;
const static int backlog = 8;
class TcpServer : public NoCopy
{
public:
TcpServer(uint16_t port)
: _port(port), _listensockfd(defaultsockfd), _isrunning(false)
{
}
// 服务器初始化
void Init()
{
// 创建套接字
_listensockfd = socket(AF_INET, SOCK_STREAM, 0);
if (_listensockfd < 0)
{
LOG(LogLevel::FATAL) << "socket error";
exit(SOCKET_ERR);
}
LOG(LogLevel::INFO) << "socket success: " << _listensockfd; // fd = 3
// 绑定IP地址和端口号
InetAddr local(_port);
int n = bind(_listensockfd, local.NetAddrPtr(), local.NetAddrLen());
if (n < 0)
{
LOG(LogLevel::FATAL) << "bind error";
exit(BIND_ERR);
}
LOG(LogLevel::INFO) << "bind success: " << _listensockfd; // fd = 3
// 设置监听状态
n = listen(_listensockfd, backlog);
if (n < 0)
{
LOG(LogLevel::FATAL) << "listen error";
exit(LISTEN_ERR);
}
LOG(LogLevel::INFO) << "listen success: " << _listensockfd; // fd = 3
}
void Service(int sockfd, InetAddr &peer)
{
char buffer[1024];
while (true)
{
// 读取数据
// n > 0: 读取成功
// n < 0: 读取失败
// n = 0: 客户端关闭链接, 服务器读到文件结尾
ssize_t n = read(sockfd, buffer, sizeof(buffer) - 1);
if (n > 0)
{
// 设置为C风格的字符串
// n(实际读取) ≤ sizeof(buffer) - 1
buffer[n] = 0;
LOG(LogLevel::DEBUG) << peer.StringAddr() << " say# " << buffer;
}
else if (n == 0)
{
LOG(LogLevel::DEBUG) << peer.StringAddr() << " 退出了...";
close(sockfd);
break;
}
else
{
LOG(LogLevel::DEBUG) << peer.StringAddr() << " 异常...";
close(sockfd);
break;
}
// 写回数据
std::string echo_string = "echo# ";
echo_string += buffer;
write(sockfd, echo_string.c_str(), echo_string.size());
}
}
class ThreadData
{
public:
ThreadData(int fd, InetAddr &ar, TcpServer *s)
: sockfd(fd), addr(ar), tsvr(s)
{
}
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)
{
// 获取链接
struct sockaddr_in peer;
socklen_t len = sizeof(sockaddr_in);
// 没有链接时, 会发生阻塞
int sockfd = accept(_listensockfd, CONV(peer), &len);
if (sockfd < 0)
{
LOG(LogLevel::WARNING) << "accept error";
continue;
}
InetAddr addr(peer);
LOG(LogLevel::INFO) << "accept success, peer addr: " << addr.StringAddr();
// version 2
ThreadData *td = new ThreadData(sockfd, addr, this);
pthread_t tid;
pthread_create(&tid, nullptr, Routine, td);
}
_isrunning = false;
}
~TcpServer()
{
}
private:
uint16_t _port; // 服务器端口号
int _listensockfd; // 监听文件描述符
bool _isrunning; // 服务器运行状态
};
五、V4线程池回显服务器
5.1.TcpServer.hpp
cpp
#pragma once
#include <sys/wait.h>
#include <signal.h>
#include "Common.hpp"
#include "Log.hpp"
#include "InetAddr.hpp"
#include "ThreadPool.hpp"
#include <functional>
using namespace LogModule;
using namespace ThreadPoolModule;
using task_t = std::function<void()>;
const static int defaultsockfd = -1;
const static int backlog = 8;
class TcpServer : public NoCopy
{
public:
TcpServer(uint16_t port)
: _port(port), _listensockfd(defaultsockfd), _isrunning(false)
{
}
// 服务器初始化
void Init()
{
// 创建套接字
_listensockfd = socket(AF_INET, SOCK_STREAM, 0);
if (_listensockfd < 0)
{
LOG(LogLevel::FATAL) << "socket error";
exit(SOCKET_ERR);
}
LOG(LogLevel::INFO) << "socket success: " << _listensockfd; // fd = 3
// 绑定IP地址和端口号
InetAddr local(_port);
int n = bind(_listensockfd, local.NetAddrPtr(), local.NetAddrLen());
if (n < 0)
{
LOG(LogLevel::FATAL) << "bind error";
exit(BIND_ERR);
}
LOG(LogLevel::INFO) << "bind success: " << _listensockfd; // fd = 3
// 设置监听状态
n = listen(_listensockfd, backlog);
if (n < 0)
{
LOG(LogLevel::FATAL) << "listen error";
exit(LISTEN_ERR);
}
LOG(LogLevel::INFO) << "listen success: " << _listensockfd; // fd = 3
}
void Service(int sockfd, InetAddr &peer)
{
char buffer[1024];
while (true)
{
// 读取数据
// n > 0: 读取成功
// n < 0: 读取失败
// n = 0: 客户端关闭链接, 服务器读到文件结尾
ssize_t n = read(sockfd, buffer, sizeof(buffer) - 1);
if (n > 0)
{
// 设置为C风格的字符串
// n(实际读取) ≤ sizeof(buffer) - 1
buffer[n] = 0;
LOG(LogLevel::DEBUG) << peer.StringAddr() << " say# " << buffer;
}
else if (n == 0)
{
LOG(LogLevel::DEBUG) << peer.StringAddr() << " 退出了...";
close(sockfd);
break;
}
else
{
LOG(LogLevel::DEBUG) << peer.StringAddr() << " 异常...";
close(sockfd);
break;
}
// 写回数据
std::string echo_string = "echo# ";
echo_string += buffer;
write(sockfd, echo_string.c_str(), echo_string.size());
}
}
void Run()
{
_isrunning = true;
while (_isrunning)
{
// 获取链接
struct sockaddr_in peer;
socklen_t len = sizeof(sockaddr_in);
// 没有链接时, 会发生阻塞
int sockfd = accept(_listensockfd, CONV(peer), &len);
if (sockfd < 0)
{
LOG(LogLevel::WARNING) << "accept error";
continue;
}
InetAddr addr(peer);
LOG(LogLevel::INFO) << "accept success, peer addr: " << addr.StringAddr();
// version 3
// 长服务: 多进程多线程
// 短服务: 线程池
// 将新链接和客户端构建一个新的任务交给线程池
ThreadPool<task_t>::GetInstance()->Enqueue(
[this, sockfd, &addr]()
{
this->Service(sockfd, addr);
});
}
_isrunning = false;
}
~TcpServer()
{
}
private:
uint16_t _port; // 服务器端口号
int _listensockfd; // 监听文件描述符
bool _isrunning; // 服务器运行状态
};
六、V5多线程字典服务器
6.1.TcpServer.hpp
cpp
#pragma once
#include "Common.hpp"
#include "Log.hpp"
#include "InetAddr.hpp"
#include <pthread.h>
#include <functional>
using namespace LogModule;
using func_t = std::function<std::string(const std::string &, InetAddr &)>;
const static int defaultsockfd = -1;
const static int backlog = 8;
class TcpServer : public NoCopy
{
public:
TcpServer(uint16_t port, func_t func)
: _port(port), _listensockfd(defaultsockfd), _isrunning(false), _func(func)
{
}
// 服务器初始化
void Init()
{
// 创建套接字
_listensockfd = socket(AF_INET, SOCK_STREAM, 0);
if (_listensockfd < 0)
{
LOG(LogLevel::FATAL) << "socket error";
exit(SOCKET_ERR);
}
LOG(LogLevel::INFO) << "socket success: " << _listensockfd; // fd = 3
// 绑定IP地址和端口号
InetAddr local(_port);
int n = bind(_listensockfd, local.NetAddrPtr(), local.NetAddrLen());
if (n < 0)
{
LOG(LogLevel::FATAL) << "bind error";
exit(BIND_ERR);
}
LOG(LogLevel::INFO) << "bind success: " << _listensockfd; // fd = 3
// 设置监听状态
n = listen(_listensockfd, backlog);
if (n < 0)
{
LOG(LogLevel::FATAL) << "listen error";
exit(LISTEN_ERR);
}
LOG(LogLevel::INFO) << "listen success: " << _listensockfd; // fd = 3
}
void Service(int sockfd, InetAddr &peer)
{
char buffer[1024];
while (true)
{
// 读取数据
// n > 0: 读取成功
// n < 0: 读取失败
// n = 0: 客户端关闭链接, 服务器读到文件结尾
ssize_t n = read(sockfd, buffer, sizeof(buffer) - 1);
if (n > 0)
{
// 设置为C风格的字符串
// n(实际读取) ≤ sizeof(buffer) - 1
buffer[n] = 0;
LOG(LogLevel::DEBUG) << peer.StringAddr() << " #" << buffer;
std::string echo_string = _func(buffer, peer);
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;
}
}
}
class ThreadData
{
public:
ThreadData(int fd, InetAddr &ar, TcpServer *s)
: sockfd(fd), addr(ar), tsvr(s)
{
}
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)
{
// 获取链接
struct sockaddr_in peer;
socklen_t len = sizeof(sockaddr_in);
// 没有链接时, 会发生阻塞
int sockfd = accept(_listensockfd, CONV(peer), &len);
if (sockfd < 0)
{
LOG(LogLevel::WARNING) << "accept error";
continue;
}
InetAddr addr(peer);
LOG(LogLevel::INFO) << "accept success, peer addr: " << addr.StringAddr();
ThreadData *td = new ThreadData(sockfd, addr, this);
pthread_t tid;
pthread_create(&tid, nullptr, Routine, td);
}
_isrunning = false;
}
~TcpServer()
{
}
private:
uint16_t _port; // 服务器端口号
int _listensockfd; // 监听文件描述符
bool _isrunning; // 服务器运行状态
func_t _func; // 服务器回调处理
};
6.2.TcpServer.cc
cpp
#include "TcpServer.hpp"
#include "Dict.hpp"
std::string defaulthandler(const std::string &word, InetAddr &addr)
{
LOG(LogLevel::DEBUG) << "发生回调";
std::string s = "haha, ";
s += word;
return s;
}
void Usage(std::string proc)
{
std::cerr << "Usage: " << proc << " port" << std::endl;
}
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();
// 翻译模块
Dict d;
d.LoadDict();
std::unique_ptr<TcpServer> tsvr = std::make_unique<TcpServer>(port,
[&d](const std::string &word, InetAddr &addr)
{
return d.Translate(word, addr);
});
tsvr->Init();
tsvr->Run();
return 0;
}
七、V6多线程远程命令服务器
7.1.popen函数
作用:创建管道和子进程,在子进程中执行命令,将子进程的标准输出重定向到管道写端
返回值:父进程接收管道的读端

7.2.Tcpserver.hpp
cpp
#pragma once
#include "Common.hpp"
#include "Log.hpp"
#include "InetAddr.hpp"
#include <pthread.h>
#include <functional>
using namespace LogModule;
using func_t = std::function<std::string(const std::string &, InetAddr &)>;
const static int defaultsockfd = -1;
const static int backlog = 8;
class TcpServer : public NoCopy
{
public:
TcpServer(uint16_t port, func_t func)
: _port(port), _listensockfd(defaultsockfd), _isrunning(false), _func(func)
{
}
// 服务器初始化
void Init()
{
// 创建套接字
_listensockfd = socket(AF_INET, SOCK_STREAM, 0);
if (_listensockfd < 0)
{
LOG(LogLevel::FATAL) << "socket error";
exit(SOCKET_ERR);
}
LOG(LogLevel::INFO) << "socket success: " << _listensockfd; // fd = 3
// 绑定IP地址和端口号
InetAddr local(_port);
int n = bind(_listensockfd, local.NetAddrPtr(), local.NetAddrLen());
if (n < 0)
{
LOG(LogLevel::FATAL) << "bind error";
exit(BIND_ERR);
}
LOG(LogLevel::INFO) << "bind success: " << _listensockfd; // fd = 3
// 设置监听状态
n = listen(_listensockfd, backlog);
if (n < 0)
{
LOG(LogLevel::FATAL) << "listen error";
exit(LISTEN_ERR);
}
LOG(LogLevel::INFO) << "listen success: " << _listensockfd; // fd = 3
}
void Service(int sockfd, InetAddr &peer)
{
char buffer[1024];
while (true)
{
// 读取数据
// n > 0: 读取成功
// n < 0: 读取失败
// n = 0: 客户端关闭链接, 服务器读到文件结尾
ssize_t n = read(sockfd, buffer, sizeof(buffer) - 1);
if (n > 0)
{
// 设置为C风格的字符串
// n(实际读取) ≤ sizeof(buffer) - 1
buffer[n] = 0;
LOG(LogLevel::DEBUG) << peer.StringAddr() << " #" << buffer;
std::string echo_string = _func(buffer, peer);
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;
}
}
}
class ThreadData
{
public:
ThreadData(int fd, InetAddr &ar, TcpServer *s)
: sockfd(fd), addr(ar), tsvr(s)
{
}
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)
{
// 获取链接
struct sockaddr_in peer;
socklen_t len = sizeof(sockaddr_in);
// 没有链接时, 会发生阻塞
int sockfd = accept(_listensockfd, CONV(peer), &len);
if (sockfd < 0)
{
LOG(LogLevel::WARNING) << "accept error";
continue;
}
InetAddr addr(peer);
LOG(LogLevel::INFO) << "accept success, peer addr: " << addr.StringAddr();
ThreadData *td = new ThreadData(sockfd, addr, this);
pthread_t tid;
pthread_create(&tid, nullptr, Routine, td);
}
_isrunning = false;
}
~TcpServer()
{
}
private:
uint16_t _port; // 服务器端口号
int _listensockfd; // 监听文件描述符
bool _isrunning; // 服务器运行状态
func_t _func; // 服务器回调处理
};
7.3.TcpServer.cc
cpp
#include "TcpServer.hpp"
#include "Command.hpp"
std::string defaulthandler(const std::string &word, InetAddr &addr)
{
LOG(LogLevel::DEBUG) << "发生回调";
std::string s = "haha, ";
s += word;
return s;
}
void Usage(std::string proc)
{
std::cerr << "Usage: " << proc << " port" << std::endl;
}
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();
// 命令执行
Command cmd;
func_t f = std::bind(&Command::Execute, &cmd, std::placeholders::_1, std::placeholders::_2);
std::unique_ptr<TcpServer> tsvr = std::make_unique<TcpServer>(port, f);
// 等价于:
// std::unique_ptr<TcpServer> tsvr = std::make_unique<TcpServer>(port,
// [&cmd](const std::string &cpmmand, InetAddr &addr)
// {
// return cmd.Execute(command, addr);
// });
tsvr->Init();
tsvr->Run();
return 0;
}
7.4.Command.hpp
cpp
#pragma once
#include <iostream>
#include <string>
#include <set>
#include "Common.hpp"
#include "InetAddr.hpp"
#include "Log.hpp"
using namespace LogModule;
class Command
{
public:
Command()
{
// 严格匹配
_WhiteListCommands.insert("ls");
_WhiteListCommands.insert("pwd");
_WhiteListCommands.insert("ls -l");
_WhiteListCommands.insert("touch haha.txt");
_WhiteListCommands.insert("who");
_WhiteListCommands.insert("whoami");
}
bool IsSafeCommand(const std::string &cmd)
{
auto iter = _WhiteListCommands.find(cmd);
return iter != _WhiteListCommands.end();
}
std::string Execute(const std::string &cmd, InetAddr &addr)
{
// 检查命令
if (!IsSafeCommand(cmd))
{
return std::string("你要执行的命令不安全");
}
std::string who = addr.StringAddr();
// 执行命令
FILE *fp = popen(cmd.c_str(), "r");
if (nullptr == fp)
{
return std::string("你要执行的命令不存在: ") + cmd;
}
std::string res;
char line[1024];
while (fgets(line, sizeof(line), fp))
{
res += line;
}
pclose(fp);
std::string result = who + " execute done, result is: \n" + res;
LOG(LogLevel::DEBUG) << result;
return result;
}
~Command()
{
}
private:
// 受限制的远程执行
std::set<std::string> _WhiteListCommands;
};