✨个人主页: 熬夜学编程的小林
????系列专栏: 【C语言详解】 【数据结构详解】【C++详解】【Linux系统编程】【Linux网络编程】
目录
1、TcpServerMain.cc
2、TcpServer.hpp
2.1、TcpServer类基本结构
2.2、构造析构函数
2.3、InitServer()
2.4、Loop()
2.4.1、Server 0(不靠谱版本)
2.4.2、Server 1(多进程版本)
2.4.3、Server 2(多线程版本)
2.4.4、Server 3(线程池版本)
3、TcpClientMain.cc
4、测试结果
4.1、不靠谱版本
4.2、多进程版本
4.3、多线程版本
4.4、线程池版本
5、完整代码
5.1、Makefile
5.2、TcpClientMain.cc
5.3、TcpServer.hpp
5.4、TcpServerMain.cc
前面几弹使用UDP协议实现了相关功能,此弹使用TCP协议实现客户端与服务端的通信,相比与UDP协议,TCP协议更加可靠,也更加复杂!与UDP类似,我们先写主函数,然后实现相关函数!
1、TcpServerMain.cc
服务端主函数使用智能指针构造Server对象,然后调用初始化与执行函数,调用主函数使用该可执行程序 + 端口号!
// ./tcpserver 8888
int main(int argc,char* argv[])
{
if(argc != 2)
{
std::cerr << "Usage: " << argv[0] << " local-post" << std::endl;
exit(0);
}
uint16_t port = std::stoi(argv[1]);
std::unique_ptr<TcpServer> tsvr = std::make_unique<TcpServer>(port);
tsvr->InitServer();
tsvr->Loop();
return 0;
}
2、TcpServer.hpp
TcpServer.hpp封装TcpServer类!
枚举常量:
enum
{
SOCKET_ERROR,
BIND_ERROR,
LISTEN_ERROR
};
全局静态变量:
const static uint16_t gport = 8888;
const static int gsockfd = -1;
const static int gblcklog = 8;
2.1、TcpServer类基本结构
TcpServer类的基本成员有端口号,文件描述符,与运行状态!
// 面向字节流
class TcpServer
{
public:
TcpServer(uint16_t port = gport);
void InitServer();
void Loop();
~TcpServer();
private:
uint16_t _port;
int _sockfd; // TODO
bool _isrunning;
};
2.2、构造析构函数
构造函数初始化成员变量,析构函数无需处理!
注意:此处需要用到两个全局静态变量!
TcpServer(uint16_t port = gport)
:_port(port),_sockfd(gsockfd),_isrunning(false)
{}
~TcpServer()
{}
2.3、InitServer()
InitServer() 初始化服务端!
初始化函数主要分为三步:
- 1、创建socket(类型与UDP不同)
类型需要使用 SOCK_STREAM
- 2、bind sockfd 和 socket addr
- 3、获取连接(与UDP不同)
获取连接需要使用listen函数(将套接字设置为监听模式,以便能够接受进入的连接请求)
listen()
#include <sys/types.h>
#include <sys/socket.h>
int listen(int sockfd, int backlog);
参数
-
sockfd
:这是一个已创建的套接字文件描述符,它应该是一个绑定到某个地址和端口的套接字。 -
backlog
:这个参数定义了内核应该为相应套接字排队的最大连接数(此处暂时使用8)。如果队列已满,新的连接请求可能会被拒绝。需要注意的是,这个值只是内核用于优化性能的一个提示,实际实现可能会有所不同。
返回值
- 成功时,
listen
函数返回 0。 - 失败时,返回 -1,并设置
errno
以指示错误类型。
注意:此处需要用到全局静态变量和枚举常量!
// _sockfd 版本
void InitServer()
{
// 1.创建socket
_sockfd = ::socket(AF_INET,SOCK_STREAM,0);
if(_sockfd < 0)
{
LOG(FATAL,"socket create eror\n");
exit(SOCKET_ERROR);
}
LOG(INFO,"socket create success,sockfd: %d\n",_sockfd); // 3
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;
// 2.bind sockfd 和 socket addr
if(::bind(_sockfd,(struct sockaddr*)&local,sizeof(local)) < 0)
{
LOG(FATAL,"bind eror\n");
exit(BIND_ERROR);
}
LOG(INFO,"bind success\n");
// 3.因为tcp是面向连接的,tcp需要未来不短地获取连接
// 老板模式,随时等待被连接
if(::listen(_sockfd,gblcklog) < 0)
{
LOG(FATAL,"listen eror\n");
exit(LISTEN_ERROR);
}
LOG(INFO,"listen success\n");
}
为了测试该函数,先将Loop函数设计成死循环!
Loop()
// 测试
void Loop()
{
_isrunning = true;
while(_isrunning)
{
sleep(1);
}
_isrunning = false;
}
2.4、Loop()
Loop() 函数一直执行服务!
执行服务函数主要分为两步:
- 1、获取新连接(accept函数[从已完成连接队列的头部返回下一个已完成连接,如果队列为空,则阻塞调用进程])
accept()
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
参数
-
sockfd
:这是一个监听套接字的文件描述符,它应该是一个已经通过socket
函数创建,并通过bind
函数绑定到特定地址和端口,以及通过listen
函数设置为监听模式的套接字。 -
addr
:这是一个指向sockaddr
结构的指针,该结构用于存储接受连接的客户端的地址信息。如果不需要这个信息,可以传递NULL
。 -
addrlen
:这是一个指向socklen_t
类型的变量的指针,用于存储addr
结构的大小。在调用accept
之前,应该将该变量的值设置为addr
结构的大小。在调用返回后,该变量将包含实际返回的地址信息的长度。如果addr
是NULL
,则这个参数也可以是NULL
。
返回值
-
成功时,
accept
函数返回一个新的套接字文件描述符,用于与接受的连接进行通信。这个新的套接字是原始监听套接字的子套接字,它继承了许多属性(如套接字选项),但与原始套接字是独立的。 - 失败时,返回 -1,并设置
errno
以指示错误类型。
因此TcpServer类的_sockfd应该改为_listensockfd!!!
TcpServer类
// 面向字节流
class TcpServer
{
public:
TcpServer(uint16_t port = gport):_port(port),_listensockfd(gsockfd),_isrunning(false)
{}
void InitServer()
{
// 1.创建socket
_listensockfd = ::socket(AF_INET,SOCK_STREAM,0);
if(_listensockfd < 0)
{
LOG(FATAL,"socket create eror\n");
exit(SOCKET_ERROR);
}
LOG(INFO,"socket create success,sockfd: %d\n",_listensockfd); // 3
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;
// 2.bind sockfd 和 socket addr
if(::bind(_listensockfd,(struct sockaddr*)&local,sizeof(local)) < 0)
{
LOG(FATAL,"bind eror\n");
exit(BIND_ERROR);
}
LOG(INFO,"bind success\n");
// 3.因为tcp是面向连接的,tcp需要未来不短地获取连接
// 老板模式,随时等待被连接
if(::listen(_listensockfd,gblcklog) < 0)
{
LOG(FATAL,"listen eror\n");
exit(LISTEN_ERROR);
}
LOG(INFO,"listen success\n");
}
~TcpServer()
{}
private:
uint16_t _port;
int _listensockfd;
bool _isrunning;
};
- 2、执行服务(前提是获取到新连接)
执行服务总共有四个版本!
2.4.1、Server 0(不靠谱版本)
Server 0版本直接执行长服务!
Loop()
Loop()函数先获取新连接,获取成功则执行服务函数!
void Loop()
{
_isrunning = true;
while(_isrunning)
{
struct sockaddr_in client;
socklen_t len = sizeof(client);
// 1.获取新连接
int sockfd = ::accept(_listensockfd,(struct sockaddr*)&client,&len);
// 获取失败继续获取
if(sockfd < 0)
{
LOG(WARNING,"sccept reeor\n");
continue;
}
InetAddr addr(client);
LOG(INFO,"get a new link,client info: %s,sockfd:%d\n",addr.AddrStr().c_str(),sockfd); // 4
// 获取成功
// version 0 -- 不靠谱版本
Server(sockfd,addr);
}
_isrunning = false;
}
Server()
注意:tcp协议可以直接使用read,write函数读写文件描述符的内容(因为tcp是面向字节流的)!
Server()执行服务,先从文件描述符中读数据,再写数据到文件描述符中!
void Server(int sockfd,InetAddr addr)
{
// 长服务
while(true)
{
char inbuffer[1024]; // 当做字符串
// 1.读文件
ssize_t n = ::read(sockfd,inbuffer,sizeof(inbuffer) - 1);
if(n > 0)
{
inbuffer[n] = 0;
LOG(INFO,"get message from client [%s],message: %s\n",addr.AddrStr().c_str(),inbuffer);
std::string echo_string = "[server echo]# ";
echo_string += inbuffer;
// 2.写文件
write(sockfd,echo_string.c_str(),echo_string.size());
}
// 读到文件结尾
else if(n == 0)
{
LOG(INFO,"client %s quit\n",addr.AddrStr().c_str());
break;
}
else
{
LOG(ERROR,"read error\n",addr.AddrStr().c_str());
break;
}
}
::close(sockfd);
}
2.4.2、Server 1(多进程版本)
多进程版本即创建子进程,让子进程执行服务函数,父进程回收子进程,但是如果以阻塞等待回收子进程会有一个问题,如果子进程一直没有退出,那么父进程会一直阻塞!为了解决这个问题,我们可以让子进程再创建一个孙子进程,让孙子进程去执行服务函数,子进程直接退出,父进程回收子进程,孙子进程此时会成为孤儿进程,孤儿进程退出OS会自动回收!
void Loop()
{
_isrunning = true;
while(_isrunning)
{
struct sockaddr_in client;
socklen_t len = sizeof(client);
// 1.获取新连接
int sockfd = ::accept(_listensockfd,(struct sockaddr*)&client,&len);
// 获取失败继续获取
if(sockfd < 0)
{
LOG(WARNING,"sccept reeor\n");
continue;
}
InetAddr addr(client);
LOG(INFO,"get a new link,client info: %s,sockfd:%d\n",addr.AddrStr().c_str(),sockfd); // 4
// 获取成功
// version 1 -- 多进程版本
pid_t id = fork();
if(id == 0)
{
// child
::close(_listensockfd); // 建议!
if(fork() > 0) exit(0); // 让孙子进程执行服务,保证能不阻塞
Server(sockfd,addr);
exit(0);
}
// father
::close(sockfd); // 防止文件描述符泄漏(打开的不关闭)
int n = waitpid(id,nullptr,0); // 0阻塞等待
if(n > 0)
{
LOG(INFO,"wait child success\n");
}
}
_isrunning = false;
}
2.4.3、Server 2(多线程版本)
多线程版本即让新线程去执行服务函数,但是主线程需要回收新线程,为了做到主线程无需回收新线程,可以让新线程分离,此时无需回收新线程!还有一个问题,类内的成员函数有this指针,而新线程的函数只能有一个参数,此时需要使用静态成员函数,但是使用静态之后还有一个问题,不能看到类内的成员,此处可以使用地址传参,将一个包含sockfd,TcpServer类的指针和InetAddr类的 成员变量的地址传入!
内部类
// 内部类
class ThreadData
{
public:
int _sockfd;
TcpServer* _self;
InetAddr _addr;
public:
ThreadData(int sockfd,TcpServer* self,const InetAddr &addr)
:_sockfd(sockfd),_self(self),_addr(addr)
{}
};
Loop()
void Loop()
{
_isrunning = true;
while(_isrunning)
{
struct sockaddr_in client;
socklen_t len = sizeof(client);
// 1.获取新连接
int sockfd = ::accept(_listensockfd,(struct sockaddr*)&client,&len);
// 获取失败继续获取
if(sockfd < 0)
{
LOG(WARNING,"sccept reeor\n");
continue;
}
InetAddr addr(client);
LOG(INFO,"get a new link,client info: %s,sockfd:%d\n",addr.AddrStr().c_str(),sockfd); // 4
// 获取成功
// version 2 -- 多线程版 -- 不能关闭fd了,也不需要
pthread_t tid;
ThreadData *td = new ThreadData(sockfd, this,addr);
pthread_create(&tid,nullptr,Execute,td); // 新线程分离
}
_isrunning = false;
}
新线程执行函数
// 无法调用类内成员 无法看到sockfd
static void *Execute(void *args)
{
ThreadData *td = static_cast<ThreadData *>(args);
pthread_detach(pthread_self()); // 分离新线程,无需主线程回收
td->_self->Server(td->_sockfd,td->_addr);
delete td;
return nullptr;
}
2.4.4、Server 3(线程池版本)
线程池版本即 将执行服务的函数入线程池队列,该函数需要是参数为空和返回值为void的函数,因此需要bind绑定函数!
声明函数类型
using task_t = std::function<void()>;
Loop()
void Loop()
{
_isrunning = true;
while(_isrunning)
{
struct sockaddr_in client;
socklen_t len = sizeof(client);
// 1.获取新连接
int sockfd = ::accept(_listensockfd,(struct sockaddr*)&client,&len);
// 获取失败继续获取
if(sockfd < 0)
{
LOG(WARNING,"sccept reeor\n");
continue;
}
InetAddr addr(client);
LOG(INFO,"get a new link,client info: %s,sockfd:%d\n",addr.AddrStr().c_str(),sockfd); // 4
// 获取成功
// version 3 -- 线程池版本
task_t t = std::bind(&TcpServer::Server,this,sockfd,addr);
ThreadPool<task_t>::GetInstance()->Equeue(t);
}
_isrunning = false;
}
3、TcpClientMain.cc
客户端主函数主要实现向服务端发送消息的功能,调用主函数使用该可执行程序 + IP + 端口号!
主函数主要分为四步:
- 1、创建socket(与服务端一样)
- 2、与服务端建立连接(使用connect[客户端与服务器建立TCP连接])
connect()
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
参数
-
sockfd
:这是一个由socket
函数返回的套接字描述符。 -
addr
:这是一个指向sockaddr
结构的指针,它包含了目标服务器的地址和端口信息。对于IPv4地址,通常使用sockaddr_in
结构;对于IPv6地址,使用sockaddr_in6
结构。 -
addrlen
:这是addr
参数的长度,以字节为单位。对于sockaddr_in
,它通常是sizeof(struct sockaddr_in)
;对于sockaddr_in6
,它通常是sizeof(struct sockaddr_in6)
。
返回值
- 成功时,
connect
返回0。 - 失败时,返回-1,并设置
errno
以指示错误类型。
- 3、发送消息
- 4、关闭socket
// ./tcpclient server-ip server-ip
int main(int argc,char* argv[])
{
if(argc != 3)
{
std::cerr << "Usage: " << argv[0] << " server-ip server-port" << std::endl;
exit(0);
}
std::string serverip = argv[1];
uint16_t serverport = std::stoi(argv[2]);
// 1.创建socket
int sockfd = ::socket(AF_INET,SOCK_STREAM,0);
if(sockfd < 0)
{
std::cerr << "create socket error" << std::endl;
exit(1);
}
// 不需要显示的bind,但是一定要有自己的IP和port,需要隐式的绑定(OS用自己的IP和随机端口号)
// 什么时候进行bind? If the connection or binding succeeds
struct sockaddr_in server;
memset(&server,0,sizeof(server));
server.sin_family = AF_INET;
server.sin_port = htons(serverport);
// server.sin_addr.s_addr =
::inet_pton(AF_INET,serverip.c_str(),&server.sin_addr);
// 2.与服务端建立连接
int n = ::connect(sockfd,(struct sockaddr*)&server,sizeof(server));
// 也可以重连
if(n < 0)
{
std::cerr << "connect socket error" << std::endl;
exit(2);
}
// 3.发送消息
while(true)
{
std::string message;
std::cout << "Enter#";
std::getline(std::cin,message);
write(sockfd,message.c_str(),message.size());
char echo_buffer[1024];
n = read(sockfd,echo_buffer,sizeof(echo_buffer));
if(n > 0)
{
echo_buffer[n] = 0;
std::cout << echo_buffer << std::endl;
}
else
{
break;
}
}
// 4.关闭socket
::close(sockfd);
return 0;
}
4、测试结果
4.1、不靠谱版本
该版本是一个只能执行一个客户端的版本,因此称为不靠谱版本!
4.2、多进程版本
该版本是一个能执行多客户端的版本,但是创建进程的开销比较大,也不是很完美!
4.3、多线程版本
该版本是一个能执行多客户端的版本,相比与多进程版本效果会更好,因为创建线程的开销比进程更少!
4.4、线程池版本
该版本是一个能执行多客户端的版本,与线程池版本差不太多,此处只是使用以前实现的线程池!
5、完整代码
前面一弹就有且没有修改的代码此处就没有再放上来了!
5.1、Makefile
.PHONY:all
all:tcpserver tcpclient
tcpserver:TcpServerMain.cc
g++ -o $@ $^ -std=c++14
tcpclient:TcpClientMain.cc
g++ -o $@ $^ -std=c++14
.PHONY:clean
clean:
rm -rf tcpserver tcpclient
5.2、TcpClientMain.cc
#include <iostream>
#include <cstring>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
// ./tcpclient server-ip server-ip
int main(int argc,char* argv[])
{
if(argc != 3)
{
std::cerr << "Usage: " << argv[0] << " server-ip server-port" << std::endl;
exit(0);
}
std::string serverip = argv[1];
uint16_t serverport = std::stoi(argv[2]);
// 1.创建socket
int sockfd = ::socket(AF_INET,SOCK_STREAM,0);
if(sockfd < 0)
{
std::cerr << "create socket error" << std::endl;
exit(1);
}
// 不需要显示的bind,但是一定要有自己的IP和port,需要隐式的绑定(OS用自己的IP和随机端口号)
// 什么时候进行bind? If the connection or binding succeeds
struct sockaddr_in server;
memset(&server,0,sizeof(server));
server.sin_family = AF_INET;
server.sin_port = htons(serverport);
// server.sin_addr.s_addr =
::inet_pton(AF_INET,serverip.c_str(),&server.sin_addr);
// 2.与服务端建立连接
int n = ::connect(sockfd,(struct sockaddr*)&server,sizeof(server));
// 也可以重连
if(n < 0)
{
std::cerr << "connect socket error" << std::endl;
exit(2);
}
// 3.发送消息
while(true)
{
std::string message;
std::cout << "Enter#";
std::getline(std::cin,message);
write(sockfd,message.c_str(),message.size());
char echo_buffer[1024];
n = read(sockfd,echo_buffer,sizeof(echo_buffer));
if(n > 0)
{
echo_buffer[n] = 0;
std::cout << echo_buffer << std::endl;
}
else
{
break;
}
}
// 4.关闭socket
::close(sockfd);
return 0;
}
5.3、TcpServer.hpp
#pragma once
#include <iostream>
#include <functional>
#include <cstring>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/wait.h>
#include <pthread.h>
#include "Log.hpp"
#include "InetAddr.hpp"
#include "ThreadPool.hpp"
using namespace log_ns;
enum
{
SOCKET_ERROR,
BIND_ERROR,
LISTEN_ERROR
};
const static uint16_t gport = 8888;
const static int gsockfd = -1;
const static int gblcklog = 8;
using task_t = std::function<void()>;
// 面向字节流
class TcpServer
{
public:
// _sockfd 版本
// TcpServer(uint16_t port = gport):_port(port),_sockfd(gsockfd),_isrunning(false)
// {}
TcpServer(uint16_t port = gport):_port(port),_listensockfd(gsockfd),_isrunning(false)
{}
// _sockfd 版本
// void InitServer()
// {
// // 1.创建socket
// _sockfd = ::socket(AF_INET,SOCK_STREAM,0);
// if(_sockfd < 0)
// {
// LOG(FATAL,"socket create eror\n");
// exit(SOCKET_ERROR);
// }
// LOG(INFO,"socket create success,sockfd: %d\n",_sockfd); // 3
// 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;
// // 2.bind sockfd 和 socket addr
// if(::bind(_sockfd,(struct sockaddr*)&local,sizeof(local)) < 0)
// {
// LOG(FATAL,"bind eror\n");
// exit(BIND_ERROR);
// }
// LOG(INFO,"bind success\n");
// // 3.因为tcp是面向连接的,tcp需要未来不短地获取连接
// // 老板模式,随时等待被连接
// if(::listen(_sockfd,gblcklog) < 0)
// {
// LOG(FATAL,"listen eror\n");
// exit(LISTEN_ERROR);
// }
// LOG(INFO,"listen success\n");
// }
void InitServer()
{
// 1.创建socket
_listensockfd = ::socket(AF_INET,SOCK_STREAM,0);
if(_listensockfd < 0)
{
LOG(FATAL,"socket create eror\n");
exit(SOCKET_ERROR);
}
LOG(INFO,"socket create success,sockfd: %d\n",_listensockfd); // 3
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;
// 2.bind sockfd 和 socket addr
if(::bind(_listensockfd,(struct sockaddr*)&local,sizeof(local)) < 0)
{
LOG(FATAL,"bind eror\n");
exit(BIND_ERROR);
}
LOG(INFO,"bind success\n");
// 3.因为tcp是面向连接的,tcp需要未来不短地获取连接
// 老板模式,随时等待被连接
if(::listen(_listensockfd,gblcklog) < 0)
{
LOG(FATAL,"listen eror\n");
exit(LISTEN_ERROR);
}
LOG(INFO,"listen success\n");
}
// 内部类
class ThreadData
{
public:
int _sockfd;
TcpServer* _self;
InetAddr _addr;
public:
ThreadData(int sockfd,TcpServer* self,const InetAddr &addr)
:_sockfd(sockfd),_self(self),_addr(addr)
{}
};
// 测试
// void Loop()
// {
// _isrunning = true;
// while(_isrunning)
// {
// sleep(1);
// }
// _isrunning = false;
// }
void Loop()
{
_isrunning = true;
while(_isrunning)
{
struct sockaddr_in client;
socklen_t len = sizeof(client);
// 1.获取新连接
int sockfd = ::accept(_listensockfd,(struct sockaddr*)&client,&len);
// 获取失败继续获取
if(sockfd < 0)
{
LOG(WARNING,"sccept reeor\n");
continue;
}
InetAddr addr(client);
LOG(INFO,"get a new link,client info: %s,sockfd:%d\n",addr.AddrStr().c_str(),sockfd); // 4
// 获取成功
// version 0 -- 不靠谱版本
// Server(sockfd,addr);
// version 1 -- 多进程版本
// pid_t id = fork();
// if(id == 0)
// {
// // child
// ::close(_listensockfd); // 建议!
// if(fork() > 0) exit(0); // 让孙子进程执行服务,保证能不阻塞
// Server(sockfd,addr);
// exit(0);
// }
// // father
// ::close(sockfd); // 防止文件描述符泄漏(打开的不关闭)
// int n = waitpid(id,nullptr,0); // 0阻塞等待
// if(n > 0)
// {
// LOG(INFO,"wait child success\n");
// }
// version 2 -- 多线程版 -- 不能关闭fd了,也不需要
// pthread_t tid;
// ThreadData *td = new ThreadData(sockfd, this,addr);
// pthread_create(&tid,nullptr,Execute,td); // 新线程分离
// version 3 -- 线程池版本
task_t t = std::bind(&TcpServer::Server,this,sockfd,addr);
ThreadPool<task_t>::GetInstance()->Equeue(t);
}
_isrunning = false;
}
// 无法调用类内成员 无法看到sockfd
static void *Execute(void *args)
{
ThreadData *td = static_cast<ThreadData *>(args);
pthread_detach(pthread_self()); // 分离新线程,无需主线程回收
td->_self->Server(td->_sockfd,td->_addr);
delete td;
return nullptr;
}
void Server(int sockfd,InetAddr addr)
{
// 长服务
while(true)
{
char inbuffer[1024]; // 当做字符串
// 1.读文件
ssize_t n = ::read(sockfd,inbuffer,sizeof(inbuffer) - 1);
if(n > 0)
{
inbuffer[n] = 0;
LOG(INFO,"get message from client [%s],message: %s\n",addr.AddrStr().c_str(),inbuffer);
std::string echo_string = "[server echo]# ";
echo_string += inbuffer;
// 2.写文件
write(sockfd,echo_string.c_str(),echo_string.size());
}
// 读到文件结尾
else if(n == 0)
{
LOG(INFO,"client %s quit\n",addr.AddrStr().c_str());
break;
}
else
{
LOG(ERROR,"read error\n",addr.AddrStr().c_str());
break;
}
}
::close(sockfd);
}
~TcpServer()
{}
private:
uint16_t _port;
// int _sockfd; // TODO
int _listensockfd;
bool _isrunning;
};
5.4、TcpServerMain.cc
#include "TcpServer.hpp"
#include <memory>
// ./tcpserver 8888
int main(int argc,char* argv[])
{
if(argc != 2)
{
std::cerr << "Usage: " << argv[0] << " local-post" << std::endl;
exit(0);
}
uint16_t port = std::stoi(argv[1]);
std::unique_ptr<TcpServer> tsvr = std::make_unique<TcpServer>(port);
tsvr->InitServer();
tsvr->Loop();
return 0;
}
注意:线程池只需将全局变量gdefaultnum改为10即可!
static const int gdefaultnum = 10; // 默认创建10个线程