🌈C++专栏: 南桥谈C++
🌈C语言专栏: C语言学习系列
🌈Linux学习专栏: 南桥谈Linux
🌈数据结构学习专栏: 数据结构杂谈
🌈数据库学习专栏: 南桥谈MySQL
🌈Qt学习专栏: 南桥谈Qt
🌈菜鸡代码练习: 练习随想记录
🌈git学习: 南桥谈Git
前言
在学习本章之前,先看【Linux网络编程】Socket编程–UDP:实现服务器接收客服端的消息 | DictServer简单的英译汉的网络字典 | 简单聊天室】,里面详细介绍函数的使用方法,小编在这篇文章不再具体介绍。
TCP echo server
服务端
创建套接字 | 绑定套接字
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
int socket(int domain, int type, int protocol);
在TCP中,第二个参数,指定套接字类型应该为SOCK_STREAM
,其余的和UDP中一样。
绑定套接字:
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
将创建的套接字sockfd
与struct addr
绑定。
//创建
_listensockfd=::socket(AF_INET,SOCK_STREAM,0);
if(_listensockfd<0)
{
exit(SOCKET_ERROR);
LOG(FATAL,"socket create error\n");
}
LOG(INFO,"socket create sussedd,listensockfd: %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;
//绑定:sockfd 和 socket addr
if(::bind(_listensockfd,(struct sockaddr*)&local,sizeof(local))<0)
{
LOG(FATAL,"bind error\n");
exit(BAND_ERROR);
}
listen–设置监听状态
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
int listen(int sockfd, int backlog);
int sockfd
创建的套接字int backlog
是连接队列的长度
由于TCP是面向连接的,因此TCP需要不断地能够做到获取连接,所以设置成监听状态,让套接字准备好,随时准备等待别人来连网。
const static int gblcklog=8;
if(::listen(_sockfd,gblcklog)<0)
{
LOG(FATAL,"listen error\n");
exit(LISTEN_ERROR);
}
accetp–获取连接
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
int sockfd
设置为listen
状态的套接字- 后面俩输出型参数用来获取 client 端的套接字信息
- 返回值:返回值是一个文件描述符
如何理解这里的文件描述符和_sockfd
?
-
_sockfd
套接字是用来获取新的连接,accept
返回的文件描述符套接字是用来给客户提供服务的,随这_sockfd
套接字获取的连接增多,accept
返回的文件描述符套接字会越来越多。 -
_sockfd
称之为监听套接字
while(_isrunning)
{
struct sockaddr_in client;
socklen_t len=sizeof(client);
// 获取新连接
int sockfd=::accept(_listensockfd,(struct sockaddr*)&client,&len);
if(sockfd<0)
{
//获取连接失败
LOG(WARING,"accept error\n");
continue;
}
InetAddr addr(client);
LOG(INFO,"get a new link,client info: %s\n",addr.AddrStr().c_str());
//提供服务
Service(sockfd,addr);
read–读取数据 | write–读取数据
TCP是面向字节流的,符合流式的特性,在Linux以及C++中,学过文件流等流式特性,这些都属于文件。UDP无法直接做文件读取,需要使用sento
、recvdrom
。
#include <unistd.h>
ssize_t read(int fd, void *buf, size_t count);
#include <unistd.h>
ssize_t write(int fd, const void *buf, size_t count);
服务端提供信息version–0(单进程版)
#pragma once
#include<iostream>
#include<cstring>
#include<unistd.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include"Log.hpp"
#include"InetAddr.hpp"
using namespace log_ns;
enum
{
SOCKET_ERROR=1,
BAND_ERROR,
LISTEN_ERROR
};
const static int gport=8888;
const static int gsock=-1;
const static int gblcklog=8;
class TcpServer
{
public:
TcpServer(uint16_t port=gport)
:_port(port)
,_listensockfd(gsock)
,_isrunning(false)
{}
void InitServer()
{
//创建
_listensockfd=::socket(AF_INET,SOCK_STREAM,0);
if(_listensockfd<0)
{
exit(SOCKET_ERROR);
LOG(FATAL,"socket create error\n");
}
LOG(INFO,"socket create sussess,listensockfd: %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;
//绑定:sockfd 和 socket addr
if(::bind(_listensockfd,(struct sockaddr*)&local,sizeof(local))<0)
{
LOG(FATAL,"bind error\n");
exit(BAND_ERROR);
}
LOG(INFO,"bind success\n");
// 设置监听状态 listen
if(::listen(_listensockfd,gblcklog)<0)
{
LOG(FATAL,"listen error\n");
exit(LISTEN_ERROR);
}
LOG(INFO,"listen success\n");
}
void Loop()
{
_isrunning=true;
while(_isrunning)
{
struct sockaddr_in client;
socklen_t len=sizeof(client);
// 获取新连接
int sockfd=::accept(_listensockfd,(struct sockaddr*)&client,&len);
if(sockfd<0)
{
//获取连接失败
LOG(WARING,"accept error\n");
continue;
}
InetAddr addr(client);
LOG(INFO,"get a new link,client info: %s\n",addr.AddrStr().c_str());
//提供服务--version 0
Service(sockfd,addr);
}
_isrunning=false;
}
//提供服务--version 0
void Service(int sockfd,InetAddr addr)
{
//长服务
while(true)
{
char inbuffer[1024];
ssize_t n=::read(sockfd,inbuffer,sizeof(inbuffer)-1);
if(n>0)
{
std::string echo_string="[sever echo] # ";
echo_string+=inbuffer;
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: %s\n",addr.AddrStr().c_str());
break;
}
}
::close(sockfd);
}
~TcpServer()
{}
private:
uint16_t _port;
int _listensockfd;
bool _isrunning;
};
服务端提供信息version–1(多进程版)
在上一个版本中,服务器只能接受一个客户端发来的连接,无法接受多个客户端发来的请求,因此称之为单进程版。在这个版本中,使用多进程,实现多个客户端都可以向服务器发起连接。
上述父进程在创建子进程后,将父进程的数据结构以及文件描述符表都拷贝给了子进程,此时父子进程都指向同样的文件。
如果想让子进程单独处理新获取的连接,对于子进程来说,不关心之前创建的其他连接,只关心子进程本身继承下来的文件描述符,比如4号fd。对于曾经已经打开的文件建议关闭。如果不关,万一将来子进程误对3号fd进行操作。父进程对新获取的连接进行关闭,父进程不需要这个文件,新连接已经交给了子进程,让子进程进行处理。
对于不使用的文件描述符,需要及时关闭,避免文件描述符泄漏。
因此,在多进程版本中,父进程在完成任务后,继续返回到获取连接那里,子进程完成获取到的新连接的任务。这样服务器实现了多进程并发式获取连接。
子进程创建后未来会结束,一旦结束就会先进入僵尸状态,等待父进程进行wait
读取,一旦被读取,僵尸进程才会被释放掉,如果不读取,子进程将会一直处于僵尸状态。当前父进程采用的是阻塞式wait
,当子进程被创建,子进程在处理任务时,父进程会一直阻塞等待,如果子进程一直不死,父进程就一直在阻塞等待。此时依旧是等子进程结束,父进程才开始工作,简单来说这不是并发,依然还是一次只能处理一个连接。
解决方案:
- 使用信号量,这是最推荐的做法,但是过于简单
signal(SIGCHLD,SIG_IGN)
- 在子进程中再
fork
一次,让子进程直接退出,父进程的wait
可以立即返回,僵尸状态就可以立即被处理。此时孙子进程的父进程退出了,变成了孤儿进程,孤儿进程被系统领养,退出后,OS会对他进程回收,爷爷进程就不用关心了。
完整代码
#pragma once
#include<iostream>
#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"Log.hpp"
#include"InetAddr.hpp"
using namespace log_ns;
enum
{
SOCKET_ERROR=1,
BAND_ERROR,
LISTEN_ERROR
};
const static int gport=8888;
const static int gsock=-1;
const static int gblcklog=8;
class TcpServer
{
public:
TcpServer(uint16_t port=gport)
:_port(port)
,_listensockfd(gsock)
,_isrunning(false)
{}
void InitServer()
{
//创建
_listensockfd=::socket(AF_INET,SOCK_STREAM,0);
if(_listensockfd<0)
{
exit(SOCKET_ERROR);
LOG(FATAL,"socket create error\n");
}
LOG(INFO,"socket create sussess,listensockfd: %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;
//绑定:sockfd 和 socket addr
if(::bind(_listensockfd,(struct sockaddr*)&local,sizeof(local))<0)
{
LOG(FATAL,"bind error\n");
exit(BAND_ERROR);
}
LOG(INFO,"bind success\n");
// 设置监听状态 listen
if(::listen(_listensockfd,gblcklog)<0)
{
LOG(FATAL,"listen error\n");
exit(LISTEN_ERROR);
}
LOG(INFO,"listen success\n");
}
void Loop()
{
//signal(SIGCHLD,SIG_IGN);
_isrunning=true;
while(_isrunning)
{
struct sockaddr_in client;
socklen_t len=sizeof(client);
// 获取新连接
int sockfd=::accept(_listensockfd,(struct sockaddr*)&client,&len);
if(sockfd<0)
{
//获取连接失败
LOG(WARING,"accept error\n");
continue;
}
InetAddr addr(client);
LOG(INFO,"get a new link,client info: %s, sockfd is: %d\n",addr.AddrStr().c_str(),sockfd);
//提供服务--version 0
//Service(sockfd,addr);
//提供服务--version 1(多进程版)
pid_t id=fork();
if(id==0)
{
//child
::close(_listensockfd);
if(fork()>0) exit(0);
Service(sockfd,addr);
exit(0);
}
//fathre
::close(sockfd);
int n=waitpid(id,nullptr,0);
if(n>0)
{
LOG(INFO,"wait child success\n");
}
}
_isrunning=false;
}
//提供服务--version 0
void Service(int sockfd,InetAddr addr)
{
//长服务
while(true)
{
char inbuffer[1024];
ssize_t n=::read(sockfd,inbuffer,sizeof(inbuffer)-1);
if(n>0)
{
std::string echo_string="[sever echo] # ";
echo_string+=inbuffer;
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: %s\n",addr.AddrStr().c_str());
break;
}
}
::close(sockfd);
}
~TcpServer()
{}
private:
uint16_t _port;
int _listensockfd;
bool _isrunning;
};
为什么两次都是4号fd?
父进程获取第一次连接后,将4号fd关闭了,此时4号fd是空的,第二次进程连接的时候,依然用这个4号fd。
服务端提供信息version–2(多线程版)
需要让新线程分离,如果在创建一个新线程后,让主线程等待,这不是并发式。
这里需要让新线程分离,这样新线程执行完毕后,会直接被回收。
在多线程中,所有的文件描述符表都是共享的,因此不能对不需要的文件描述符进行关闭。
#pragma once
#include<iostream>
#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"
using namespace log_ns;
enum
{
SOCKET_ERROR=1,
BAND_ERROR,
LISTEN_ERROR
};
const static int gport=8888;
const static int gsock=-1;
const static int gblcklog=8;
class TcpServer
{
public:
TcpServer(uint16_t port=gport)
:_port(port)
,_listensockfd(gsock)
,_isrunning(false)
{}
void InitServer()
{
//创建
_listensockfd=::socket(AF_INET,SOCK_STREAM,0);
if(_listensockfd<0)
{
exit(SOCKET_ERROR);
LOG(FATAL,"socket create error\n");
}
LOG(INFO,"socket create sussess,listensockfd: %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;
//绑定:sockfd 和 socket addr
if(::bind(_listensockfd,(struct sockaddr*)&local,sizeof(local))<0)
{
LOG(FATAL,"bind error\n");
exit(BAND_ERROR);
}
LOG(INFO,"bind success\n");
// 设置监听状态 listen
if(::listen(_listensockfd,gblcklog)<0)
{
LOG(FATAL,"listen error\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()
{
//signal(SIGCHLD,SIG_IGN);
_isrunning=true;
while(_isrunning)
{
struct sockaddr_in client;
socklen_t len=sizeof(client);
// 获取新连接
int sockfd=::accept(_listensockfd,(struct sockaddr*)&client,&len);
if(sockfd<0)
{
//获取连接失败
LOG(WARING,"accept error\n");
continue;
}
InetAddr addr(client);
LOG(INFO,"get a new link,client info: %s, sockfd is: %d\n",addr.AddrStr().c_str(),sockfd); //4
//提供服务--version 2(多线程版)
pthread_t tid;
ThreadData *td=new ThreadData(sockfd,this,addr);
pthread_create(&tid,nullptr,Excute,td); //新线程分离
}
_isrunning=false;
}
static void *Excute(void *argc)
{
ThreadData *td=static_cast<ThreadData *>(argc);
pthread_detach(pthread_self());
td->_self->Service(td->_sockfd,td->_addr);
delete td;
return nullptr;
}
//提供服务
void Service(int sockfd,InetAddr addr)
{
//长服务
while(true)
{
char inbuffer[1024];
ssize_t n=::read(sockfd,inbuffer,sizeof(inbuffer)-1);
if(n>0)
{
inbuffer[1024]=0;
LOG(INFO,"get from client %s, message: %s\n",addr.AddrStr().c_str(),inbuffer);
std::string echo_string="[sever echo] # ";
echo_string+=inbuffer;
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: %s\n",addr.AddrStr().c_str());
break;
}
}
::close(sockfd);
}
~TcpServer()
{}
private:
uint16_t _port;
int _listensockfd;
bool _isrunning;
};
服务端提供信息version–3(线程池版)
线程池中的线程数量是有限的,因此需要对现有的长时间运行的服务进行修改,以避免某个服务长时间占用线程池中的线程,这样可能导致资源浪费和性能问题。
在这里,依然使用的是长服务,在未来可以使用短的服务,避免这种问题
#pragma once
#include<iostream>
#include<cstring>
#include<unistd.h>
#include<functional>
#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"Thread.hpp"
#include"ThreadPool.hpp"
using namespace log_ns;
enum
{
SOCKET_ERROR=1,
BAND_ERROR,
LISTEN_ERROR
};
const static int gport=8888;
const static int gsock=-1;
const static int gblcklog=8;
using task_t=std::function<void()>;
class TcpServer
{
public:
TcpServer(uint16_t port=gport)
:_port(port)
,_listensockfd(gsock)
,_isrunning(false)
{}
void InitServer()
{
//创建
_listensockfd=::socket(AF_INET,SOCK_STREAM,0);
if(_listensockfd<0)
{
exit(SOCKET_ERROR);
LOG(FATAL,"socket create error\n");
}
LOG(INFO,"socket create sussess,listensockfd: %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;
//绑定:sockfd 和 socket addr
if(::bind(_listensockfd,(struct sockaddr*)&local,sizeof(local))<0)
{
LOG(FATAL,"bind error\n");
exit(BAND_ERROR);
}
LOG(INFO,"bind success\n");
// 设置监听状态 listen
if(::listen(_listensockfd,gblcklog)<0)
{
LOG(FATAL,"listen error\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()
{
//signal(SIGCHLD,SIG_IGN);
_isrunning=true;
while(_isrunning)
{
struct sockaddr_in client;
socklen_t len=sizeof(client);
// 获取新连接
int sockfd=::accept(_listensockfd,(struct sockaddr*)&client,&len);
if(sockfd<0)
{
//获取连接失败
LOG(WARING,"accept error\n");
continue;
}
InetAddr addr(client);
LOG(INFO,"get a new link,client info: %s, sockfd is: %d\n",addr.AddrStr().c_str(),sockfd); //4
//提供服务--version 3(线程池版) int sockfd,InetAddr addr
task_t t=std::bind(&TcpServer::Service,this,sockfd,addr);
ThreadPool<task_t>::GetInstance()->Equeue(t);
}
_isrunning=false;
}
static void *Excute(void *argc)
{
ThreadData *td=static_cast<ThreadData *>(argc);
pthread_detach(pthread_self());
td->_self->Service(td->_sockfd,td->_addr);
delete td;
return nullptr;
}
//提供服务
void Service(int sockfd,InetAddr addr)
{
//长服务
while(true)
{
char inbuffer[1024];
ssize_t n=::read(sockfd,inbuffer,sizeof(inbuffer)-1);
if(n>0)
{
inbuffer[1024]=0;
LOG(INFO,"get from client %s, message: %s\n",addr.AddrStr().c_str(),inbuffer);
std::string echo_string="[sever echo] # ";
echo_string+=inbuffer;
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: %s\n",addr.AddrStr().c_str());
break;
}
}
::close(sockfd);
}
~TcpServer()
{}
private:
uint16_t _port;
int _listensockfd;
bool _isrunning;
};
客户端
connect–连接
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
返回值:成功为1,失败为0
connect
函数用于客户端与服务器之间的连接建立。客户端使用它向指定的服务器地址和端口发起连接请求。如果目标地址和端口的服务器正在监听并接受连接,connect
会成功,建立一个套接字连接。如果服务器拒绝连接,或者发生其他网络错误,则 connect
会失败,并返回错误代码。
通过命令行参数指定服务器地址和端口,连接到服务器后,可以输入并发送消息,接收服务器返回的回显消息。程序使用基本的套接字操作(如 socket()
, connect()
, write()
, read()
, 和close()
)进行通信。
客户端完整代码
/*
在使用的时候必须遵循的格式为:./tcpclient server_ip server_port
例如:./tcpclient 127.0.0.1
*/
#include<iostream>
#include<cstring>
#include<unistd.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include"Log.hpp"
#include"InetAddr.hpp"
using namespace log_ns;
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]);
//创建socket
int sockfd=::socket(AF_INET,SOCK_STREAM,0);
if(sockfd<0)
{
std::cerr<<"create socket error"<<std::endl;
exit(1);
}
//注意:客户端不需要显示的bind,但是一定要有自己的ip和port,所以需要隐式的bind,OS会自动bind sockfd,用自己的IP和随机端口号
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);
int n=::connect(sockfd,(struct sockaddr*)&server,sizeof(server));
if(n<0)
{
std::cerr<<"connect socket error"<<std::endl;
exit(2);
}
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;
}
}
::close(sockfd);
return 0;
}
多线程远程命令执行
popen–命令交互
#include <stdio.h>
FILE *popen(const char *command, const char *type);
参数:
-
command
:要执行的外部命令(通常是一个 shell 命令)。这个命令将被传递给系统的命令解释器(比如 /bin/sh 或类似的 shell)。 -
type
:指定文件流的打开方式。它决定了你如何与命令的标准输入、标准输出进行交互。
返回值:
- 如果调用成功,popen 返回一个 FILE * 类型的指针,该指针指向一个文件流,这个流可以用来与执行的命令交互。
- 如果调用失败,返回 NULL,并且可以通过 errno 获取错误原因。
完整代码
Command.hpp
#pragma once
#include<iostream>
#include<string>
#include<set>
#include<cstdio>
#include<cstring>
#include"Log.hpp"
#include"InetAddr.hpp"
class Command
{
public:
Command()
{
_safe_command.insert("ls");
_safe_command.insert("touch");
_safe_command.insert("pwd");
_safe_command.insert("whoami");
_safe_command.insert("which");
}
~Command() {}
bool SafeCheck(const std::string &cmdstr)
{
for(auto &cmd:_safe_command)
{
if(strncmp(cmd.c_str(),cmdstr.c_str(),cmd.size())==0)
{
return true;
}
}
return false;
}
std::string Excute(const std::string &cmdstr)
{
if(!SafeCheck(cmdstr)) return "unsafe";
std::string result;
FILE *fp=popen(cmdstr.c_str(),"r");
if(fp)
{
char line[1024];
while(fgets(line,sizeof(line),fp))
{
result+=line;
}
return result;
}
else
{
return "execute error";
}
}
void HandlerCommand(int sockfd,InetAddr addr)
{
//长服务
while(true)
{
char commandbuf[1024];
ssize_t n=::recv(sockfd,commandbuf,sizeof(commandbuf)-1,0);
if(n>0)
{
commandbuf[1024]=0;
LOG(INFO,"get command from client %s, command: %s\n",addr.AddrStr().c_str(),commandbuf);
std::string result=Excute(commandbuf);
send(sockfd,result.c_str(),result.size(),0);
}
else if(n==0)
{
LOG(INFO,"client %s quit\n",addr.AddrStr().c_str());
break;
}
else
{
LOG(ERROR,"read error: %s\n",addr.AddrStr().c_str());
break;
}
}
}
private:
std::set<std::string> _safe_command;
};
TcpServerMain.cc
#include "TcpServer.hpp"
#include "Command.hpp"
#include <memory>
int main(int argc, char *argv[])
{
if (argc != 2)
{
std::cerr << "Usage: " << argv[0] << " local-port" << std::endl;
exit(0);
}
uint16_t port = std::stoi(argv[1]);
Command cmdservice;
std::unique_ptr<TcpServer> tsvr = std::make_unique<TcpServer>(
std::bind(&Command::HandlerCommand,
&cmdservice, std::placeholders::_1,
std::placeholders::_2),
port);
tsvr->InitServer();
tsvr->Loop();
return 0;
}
TcpServer.hpp
#pragma once
#include<iostream>
#include<cstring>
#include<unistd.h>
#include<functional>
#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"
using namespace log_ns;
using command_service_t=std::function<void(int sockfd,InetAddr addr)>;
enum
{
SOCKET_ERROR=1,
BAND_ERROR,
LISTEN_ERROR
};
const static int gport=8888;
const static int gsock=-1;
const static int gblcklog=8;
class TcpServer
{
public:
TcpServer(command_service_t service,uint16_t port=gport)
:_port(port)
,_listensockfd(gsock)
,_isrunning(false)
,_service(service)
{}
void InitServer()
{
//创建
_listensockfd=::socket(AF_INET,SOCK_STREAM,0);
if(_listensockfd<0)
{
exit(SOCKET_ERROR);
LOG(FATAL,"socket create error\n");
}
LOG(INFO,"socket create sussess,listensockfd: %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;
//绑定:sockfd 和 socket addr
if(::bind(_listensockfd,(struct sockaddr*)&local,sizeof(local))<0)
{
LOG(FATAL,"bind error\n");
exit(BAND_ERROR);
}
LOG(INFO,"bind success\n");
// 设置监听状态 listen
if(::listen(_listensockfd,gblcklog)<0)
{
LOG(FATAL,"listen error\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()
{
//signal(SIGCHLD,SIG_IGN);
_isrunning=true;
while(_isrunning)
{
struct sockaddr_in client;
socklen_t len=sizeof(client);
// 获取新连接
int sockfd=::accept(_listensockfd,(struct sockaddr*)&client,&len);
if(sockfd<0)
{
//获取连接失败
LOG(WARING,"accept error\n");
continue;
}
InetAddr addr(client);
LOG(INFO,"get a new link,client info: %s, sockfd is: %d\n",addr.AddrStr().c_str(),sockfd); //4
//提供服务--version 2(多线程版)
pthread_t tid;
ThreadData *td=new ThreadData(sockfd,this,addr);
pthread_create(&tid,nullptr,Excute,td); //新线程分离
}
_isrunning=false;
}
static void *Excute(void *argc)
{
ThreadData *td=static_cast<ThreadData *>(argc);
pthread_detach(pthread_self());
td->_self->_service(td->_sockfd,td->_addr);
::close(td->_sockfd);
delete td;
return nullptr;
}
~TcpServer()
{}
private:
uint16_t _port;
int _listensockfd;
bool _isrunning;
command_service_t _service;
};