云计算百科
云计算领域专业知识百科平台

【Linux网络编程】第六弹---构建TCP服务器:从基础到线程池版本的实现与测试详解

个人主页: 熬夜学编程的小林

💗系列专栏: 【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个线程

赞(0)
未经允许不得转载:网硕互联帮助中心 » 【Linux网络编程】第六弹---构建TCP服务器:从基础到线程池版本的实现与测试详解
分享到: 更多 (0)

评论 抢沙发

评论前必须登录!