目录
[Echo Server](#Echo Server)
有了前面的基础,讲解TCP更加简单了。
我们直接讲解代码。
Echo Server
我们先讲解基本框架和思路,再来改进版本。
还是和先前UDP一样,服务端只需要拿着需要绑定的端口号,直接将服务端跑起来即可,而客户端需要拿着目标服务端的ip和绑定的端口号访问即可。
TCP新增函数
我们只是简单讲,主要是代码为主。
listen函数:

- listen()声明sockfd处于监听状态,并且最多允许有backlog个客⼾端处于连接等待状态,如果接收 到更多的连接请求就忽略,这⾥设置不会太⼤(⼀般是5)
- listen()成功返回0,失败返回-1;
accept函数:

- 三次握⼿完成后,服务器调⽤accept()接受连接;
- 如果服务器调⽤accept()时还没有客⼾端的连接请求,就阻塞等待直到有客⼾端连接上来
- addr是⼀个传出参数,accept()返回时传出客⼾端的地址和端⼝号
- 如果给addr参数传NULL,表⽰不关⼼客⼾端的地址;
- addrlen参数是⼀个传⼊传出参数(value-resultargument),传⼊的是调⽤者提供的,缓冲区addr 的⻓度以避免缓冲区溢出问题,传出的是客⼾端地址结构体的实际⻓度(有可能没有占满调⽤者提 供的缓冲区);
connect函数:

- 客⼾端需要调⽤connect()连接服务器;
- connect和bind的参数形式⼀致,区别在于bind的参数是⾃⼰的地址,⽽connect的参数是对⽅的 地址;
- connect()成功返回0,出错返回-1;
基本框架
TcpServer.hpp:
1. 初始化需要做创建套接字,绑定端口号,以及将套接字设置listen状态。

2.服务端运行起来,接受连接,当有客户端连接上来时,执行相关服务,当然可以回调函数,执行我们自定义的函数。


3.服务端回调函数,是可以灵活的实现各种服务,比如我们依然写之前的翻译系统服务。

细节:
一个服务端,肯定是不允许拷贝的,也就是说有且只有一个服务端对象处理客户端信息,而不能有多个服务端对象,所以服务端类是不允许拷贝和赋值重载的,怎么解决?
直接将它的拷贝和赋值重载删除或者私有化,不优雅,我们可以这样:

这样子类要拷贝的时候,也需要拷贝父类,而父类是不允许拷贝的,所以这个服务端类不能进行拷贝和赋值重载。
单进程版本
单进程版它是有BUG的,它一次只能服务一个客户端,也就是说当其他客户端连接服务端,它只能服务第一个客户端。
我们来看看:
服务端跑起来,服务端进程就会不断地接收,当有一个客户端连接成功,服务端就会调用Service函数,然后死循环读,直到这个客户端不写了,退出了,服务端这个进程才会再次阻塞着接收,也就是说只要这个客户端一个不退出,那服务端就不会处理其他客户端信息。

所以,单进程是一定不会被允许存在的。
多进程版本
多进程可以解决这个问题。
父进程阻塞接收accept,当接收成功,创建子进程,父进程阻塞等待回收子进程,子进程创建孙子进程,然后子进程直接退出,父进程等待回收子进程成功,父进程再次循环等待接收accept,而孙子进程就会变成孤儿进程,这个孙子进程会去调用Service函数(也就是说我们让孙子进程跑服务),跑完退出,系统1号进程会回收这个孤儿进程。

当有多个客户端连接时,服务端就会有多个孙子进程跑服务(一个客户端对应一个孙子进程),父进程只负责接收,没有子进程。
一张图了解:

多线程版本
主线程不断地阻塞接收accept,当接收成功,创建线程,创建的线程跑Routine函数,将线程设置成分离状态,这样主线程就不用等待阻塞回收子线程而是执行完系统回收,子线程回调Service函数,也就是说子线程是来处理客户端信息的(做服务的),而主线程是来接收accept的。

当客户端越来越多时,服务端只会有一个主线程,多个子线程,一个子线程处理一个客户的信息。
线程池版本
主线程不断地阻塞接收accept,当接收成功,创建线程池,线程池中会创建固定多个子线程,然后将Service函数插入这些线程中,然后线程池底层唤醒子线程,转而这些线程就会处理这个Service服务。

当客户端越来越多时,服务端只会有一个主线程,固定的子线程,但依然是一个子线程对应一个客户端,按理来说当客户端达到一定数量,会不够线程去处理客户端信息,当然线程池中的线程多少可以自行设置。
我们可以结合一些服务来应用这些方案。
远程命令执行服务
客户端向服务端发送指令,服务端执行指令,然后将指令执行完成后的信息回显给客户端。


popen函数:
当前父进程poen读到某个指令,会创建子进程执行指令,然后将指令执行完后的回显信息会写到特定的FILE文件中(文件),我们只需要从这个特定的文件中拿数据就能拿到回显信息。

代码:
Command.hpp:
class Command
{
public:
// 严格匹配
Command()
{
_whiteListCommands.insert("ls");
_whiteListCommands.insert("which");
_whiteListCommands.insert("ls -l");
_whiteListCommands.insert("who");
}
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 who = addr.StringAddr();
// 执行命令
FILE *fp = popen(cmd.c_str(), "r");
if (fp == nullptr)
return "你的命令不存在" + 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;
return result;
}
private:
std::set<std::string> _whiteListCommands;
};
Common.hpp:
#define CONV(addr) ((struct sockaddr *)&addr)
enum ExitCode
{
OK = 0,
USAGE_ERR,
SOCKET_ERR,
BIND_ERR,
LISTEN_ERROR,
CONNECT_ERROR,
FORK_ERROR,
};
// 不能拷贝
class NoCopy
{
public:
NoCopy() = default;
// 拷贝
NoCopy(const NoCopy &) = delete;
// 赋值构造
const NoCopy &operator=(const NoCopy &) = delete;
};
TcpServer.hpp:
using task_t = std::function<void()>;
const static int backlog = 8;
using func_t = std::function<std::string(const std::string &, InetAddr &)>;
class TcpServer : public NoCopy
{
public:
TcpServer(uint16_t port, func_t func)
: _port(port), _isrunning(false),
_listensockfd(-1), _func(func) {}
void Init()
{
// 1.创建套接字
_listensockfd = socket(AF_INET, SOCK_STREAM, 0);
if (_listensockfd < 0)
{
LOG(LogLevel::FATAL) << "socket error";
exit(SOCKET_ERR);
}
LOG(LogLevel::INFO) << "socket success:" << _listensockfd; // 3
// 2.绑定端口号
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; // 3
// 3.设置socket状态为listen监听
n = listen(_listensockfd, backlog);
if (n < 0)
{
LOG(LogLevel::FATAL) << "listen error" << _listensockfd; // 3
exit(LISTEN_ERROR);
}
LOG(LogLevel::INFO) << "listen success:" << _listensockfd; // 3
}
void Service(int sockfd, InetAddr &peer)
{
char buffer[1024];
while (true)
{
ssize_t n = read(sockfd, buffer, sizeof(buffer) - 1);
if (n > 0) // 读取成功
{
buffer[n] = 0;
LOG(LogLevel::INFO) << 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 // n==0的情况 表示对端把链接关闭了,读到了文件结尾
{
LOG(LogLevel::DEBUG) << peer.StringAddr() << "异常...";
close(sockfd); // 关闭文件描述符
break;
}
}
}
class ThreadData
{
public:
ThreadData(int fd, InetAddr &ar, TcpServer *t)
: sockfd(fd), addr(ar), ts(t) {}
int sockfd;
InetAddr addr;
TcpServer *ts;
};
static void *Routine(void *args)
{
pthread_detach(pthread_self());
ThreadData *td = static_cast<ThreadData *>(args);
td->ts->Service(td->sockfd, td->addr);
delete td;
return nullptr;
}
void Run()
{
_isrunning = true;
while (_isrunning)
{
struct sockaddr_in peer;
socklen_t len = sizeof(peer);
// 如果没有连接,accept就会阻塞
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();
// // version0--单进程程序:不会存在
// Service(sockfd, addr); // 连接成功执行服务
// version1--多进程版本
// pid_t id = fork();
// if (id < 0) // 创建失败
// {
// LOG(LogLevel::FATAL) << "fork error";
// exit(FORK_ERROR);
// }
// else if (id == 0)
// {
// close(_listensockfd);
// if (fork() > 0)
// exit(OK);
// Service(sockfd, addr);//孤儿进程,系统1号进程回收我
// exit(OK);
// }
// else
// {
// close(sockfd);
// waitpid(id, nullptr, 0);//不会阻塞了
// }
// version2--多线程版本
ThreadData *td = new ThreadData(sockfd, addr, this);
pthread_t pid;
pthread_create(&pid, nullptr, Routine, td);
// //version3--线程池版本
// ThreadPool<task_t>::GetInstance()->Enqueue([this,sockfd,&addr]() {
// Service(sockfd,addr);
// });
}
_isrunning = false;
}
private:
uint16_t _port;
bool _isrunning;
int _listensockfd;
func_t _func;
};
//./TcpServer 8080
int main(int agrc, char *agrv[])
{
if (agrc != 2)
{
LOG(LogLevel::FATAL) << "Usage error";
exit(USAGE_ERR);
}
Enable_Console_Log_Strategy();
uint16_t port = std::stoi(agrv[1]);
//翻译服务
// Dict d;
// d.LoadDict();
// std::unique_ptr<TcpServer> up = std::make_unique<TcpServer>(port,
// [&d](const std::string & word, InetAddr & addr){
// return d.Translate(word,addr);
// });
Command d;
std::unique_ptr<TcpServer> up = std::make_unique<TcpServer>(port,
std::bind(&Command::Execute,&d,std::placeholders::_1,std::placeholders::_2)
);
//也可以这样
// std::unique_ptr<TcpServer> up = std::make_unique<TcpServer>(port,
// [&d](const std::string & cmd, InetAddr & addr){
// return d.Execute(cmd,addr);
// });
up->Init();
up->Run();
return 0;
}
// ./TcpClient 127.0.0.1 8080
int main(int agrc, char *agrv[])
{
if (agrc != 3)
{
std::cerr << "Usage error" << std::endl;
exit(USAGE_ERR);
}
std::string server_ip = agrv[1];
uint16_t server_port = std::stoi(agrv[2]);
// 1.创建套接字
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd < 0)
{
std::cout << "socket error" << std::endl;
exit(SOCKET_ERR);
}
// 2.发送连接请求
// 将服务端的ip和端口号转化成网络序列
InetAddr serveraddr(server_ip, server_port);
// 请求连接
int n = connect(sockfd, serveraddr.NetAddrPtr(), serveraddr.NetAddrLen());
if (n < 0)
{
std::cerr << "connect error" << std::endl;
exit(CONNECT_ERROR);
}
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 n = read(sockfd, buffer, sizeof(buffer));
if (n > 0)
{
buffer[n]=0;
std::cout<<"Server say#"<<buffer<<std::endl;
}
}
close(sockfd);
return 0;
}
好了,我们下期见!!!