🌈个人主页:秦jh__https://blog.csdn.net/qinjh_?spm=1010.2135.3001.5343
🔥 系列专栏:https://blog.csdn.net/qinjh_/category_12891150.html
目录
TCP socket API 详解
socket():
bind():
listen():
accept():
connect
V0 - Echo Server
TcpClientMain.cc
TcpServer.hpp
TcpServerMain.cc
V1 - Echo Server 多进程版本
TcpServer.hpp
V2 - 多线程版本
TcpServer.hpp
V3 -- 线程池版本
TcpServer.hpp
多线程远程命令执行
Command.hpp
TcpServer.hpp
前言
💬 hello! 各位铁子们大家好哇。
今日更新了Linux网络编程的内容
🎉 欢迎大家关注🔍点赞👍收藏⭐️留言📝
TCP socket API 详解
socket():
- socket()打开一个网络通讯端口,如果成功的话,就像 open()一样返回一个文件描述符;
- 应用程序可以像读写文件一样用 read/write 在网络上收发数据;
- 如果 socket()调用出错则返回-1;
- 对于 IPv4, family 参数指定为 AF_INET;
- 对于 TCP 协议,type 参数指定为 SOCK_STREAM, 表示面向流的传输协议
- protocol 参数的介绍从略,指定为 0 即可。
bind():
- 服务器程序所监听的网络地址和端口号通常是固定不变的,客户端程序得知服务器程序的地址和端口号后就可以向服务器发起连接; 服务器需要调用bind 绑定一个固定的网络地址和端口号;
- bind()成功返回 0,失败返回-1。
- bind()的作用是将参数 sockfd 和 myaddr 绑定在一起, 使 sockfd 这个用于网络通讯的文件描述符监听 myaddr 所描述的地址和端口号;
- 前面讲过,struct sockaddr *是一个通用指针类型,myaddr 参数实际上可以接受多种协议的 sockaddr 结构体,而它们的长度各不相同,所以需要第三个参数addrlen指定结构体的长度;
对 myaddr 参数的初始化通常是这样的:
- 将整个结构体清零;
- 设置地址类型为 AF_INET;
- 网络地址为 INADDR_ANY, 这个宏表示本地的任意 IP 地址,因为服务器可能有多个网卡,每个网卡也可能绑定多个 IP 地址, 这样设置可以在所有的IP 地址上监听, 直到与某个客户端建立了连接时才确定下来到底用哪个 IP 地址;
- 端口号 我们定义为 8888
listen():
- listen()声明 sockfd 处于监听状态, 并且最多允许有 backlog 个客户端处于连接等待状态, 如果接收到更多的连接请求就忽略, 这里设置不会太大(一般是5), 具体细节同学们课后深入研究;
- listen()成功返回 0,失败返回-1
accept():
- 如果服务器调用 accept()时还没有客户端的连接请求,就阻塞等待直到有客户端连接上来;
- addr 是一个传出参数,accept()返回时传出客户端的地址和端口号;
- 如果给 addr 参数传 NULL,表示不关心客户端的地址;
- addrlen 参数是一个传入传出参数(value-result argument), 传入的是调用者提供的, 缓冲区 addr 的长度以避免缓冲区溢出问题, 传出的是客户端地址结构体的实际长度(有可能没有占满调用者提供的缓冲区);
理解 accept 的返回值:
”拉客“例子:有一个饭店,张三的工作是在店门口拉客,顾客a路过,最终被张三说服进来吃饭,张三就向店内喊一声,来客人了,里面就会有服务员出来接客提供服务。而张三就继续在门口拉客。张三只负责拉客。这个饭店就相当于服务器,一个个的顾客就是新的连接,张三就是类里面的_listensockfd(监听套接字),而里面的服务员就相当于accept的返回值。 _listensockfd的作用是用来获取新连接,accept的返回值fd是用来给顾客提供服务的。
connect
- 客户端需要调用 connect()连接服务器;
- connect 和 bind 的参数形式一致, 区别在于 bind 的参数是自己的地址, 而connect 的参数是对方的地址;
- connect()成功返回 0,出错返回-1;
V0 - Echo Server
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-port
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);
}
//2.不需要显示的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);
::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;
}
TcpServer.hpp
#pragma once
#include<iostream>
#include<unistd.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<cstring>
#include"Log.hpp"
#include"InetAddr.hpp"
using namespace log_ns;
enum
{
SOCKET_ERROR=1,
BIND_ERROR,
LISTEN_ERR
};
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()
{
//1.创建socket
_listensockfd=::socket(AF_INET,SOCK_STREAM,0);
if(_listensockfd<0)
{
LOG(FATAL,"socket create error\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 error\n");
exit(BIND_ERROR);
}
LOG(INFO,"bind success\n");
//3.因为tcp是面向连接的,tcp需要未来不断的能够做到获取连接
if(::listen(_listensockfd,gblcklog)<0)
{
LOG(FATAL,"listen error\n");
exit(BIND_ERROR);
}
LOG(INFO,"listen success\n");
}
void Loop()
{
_isrunning=true;
while(_isrunning)
{
struct sockaddr_in client;
socklen_t len=sizeof(client);
//4.获取新连接
int sockfd=::accept(_listensockfd,(struct sockaddr*)&client,&len);
if(sockfd<0)
{
LOG(WARNING,"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;
}
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 ="[server 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;
};
TcpServerMain.cc
#include"TcpServer.hpp"
#include<memory>
// ./tcpserver 8888
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]);
std::unique_ptr<TcpServer> tsvr=std::make_unique<TcpServer>(port);
tsvr->InitServer();
tsvr->Loop();
return 0;
}
上面代码,只能处理一个连接。如果再启动一个客户端, 尝试连接服务器, 发现第二个客户端, 不能正确的和服务器进行通信. 因为我们 accecpt 了一个请求之后, 就在一直 while 循环尝试read, 没有继续调用到 accecpt, 导致不能接受新的请求
V1 - Echo Server 多进程版本
父子进程都会有独立的文件描述符表,但子进程会拷贝父进程的文件描述符表,因此他们最终都会指向同样的文件。为了防止误操作,需要把父子进程各自不需要的文件描述符关掉。如果父进程不需要的文件描述符一直不关,后面再有新的连接来,就会导致父进程的文件描述符一直被打开而没有关闭,文件描述符本质就是数组的下标,也就说明可用的文件描述符会越来越少,最终就会导致文件描述符泄漏。
TcpServer.hpp
#pragma once
#include<iostream>
#include<unistd.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<cstring>
#include<sys/wait.h>
#include"Log.hpp"
#include"InetAddr.hpp"
using namespace log_ns;
enum
{
SOCKET_ERROR=1,
BIND_ERROR,
LISTEN_ERR
};
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()
{
//1.创建socket
_listensockfd=::socket(AF_INET,SOCK_STREAM,0);
if(_listensockfd<0)
{
LOG(FATAL,"socket create error\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 error\n");
exit(BIND_ERROR);
}
LOG(INFO,"bind success\n");
//3.因为tcp是面向连接的,tcp需要未来不断的能够做到获取连接
if(::listen(_listensockfd,gblcklog)<0)
{
LOG(FATAL,"listen error\n");
exit(BIND_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);
//4.获取新连接
int sockfd=::accept(_listensockfd,(struct sockaddr*)&client,&len);
if(sockfd<0)
{
LOG(WARNING,"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);//此时有父子孙三个进程,子进程退出,孙子进程就变成孤儿进程,就被系统领养。父进程的wait就可以立即返回。
Service(sockfd,addr);
exit(0);
}
//father
::close(sockfd);
int n=waitpid(id,nullptr,0);
if(n>0)
{
LOG(INFO,"wait child success\n");
//父子进程都会有独立的文件描述符表,但子进程会拷贝父进程的文件描述符表,因此他们最终都会指向同样的文件
}
}
_isrunning=false;
}
void Service(int sockfd,InetAddr addr)
{
//长服务
while(true)
{
char inbuffer[1024];//当作字符串
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;
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;
};
V2 - 多线程版本
多进程版本效率太低,因为要创建进程,就要创建进程pcb,构建页表,还要把父进程数据拷贝给子进程,所以可以用多线程。
多线程中,大部分资源都是共享的。所以新老线程的文件描述符表也是共享的。而进程则是拷贝一个给新线程。所以在多线程中就不能关闭fd了。
TcpServer.hpp
#pragma once
#include<iostream>
#include<unistd.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<cstring>
#include<sys/wait.h>
#include<pthread.h>
#include"Log.hpp"
#include"InetAddr.hpp"
using namespace log_ns;
enum
{
SOCKET_ERROR=1,
BIND_ERROR,
LISTEN_ERR
};
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()
{
//1.创建socket
_listensockfd=::socket(AF_INET,SOCK_STREAM,0);
if(_listensockfd<0)
{
LOG(FATAL,"socket create error\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 error\n");
exit(BIND_ERROR);
}
LOG(INFO,"bind success\n");
//3.因为tcp是面向连接的,tcp需要未来不断的能够做到获取连接
if(::listen(_listensockfd,gblcklog)<0)
{
LOG(FATAL,"listen error\n");
exit(BIND_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);
//4.获取新连接
int sockfd=::accept(_listensockfd,(struct sockaddr*)&client,&len);
if(sockfd<0)
{
LOG(WARNING,"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);//此时有父子孙三个进程,子进程退出,孙子进程就变成孤儿进程,就被系统领养。父进程的wait就可以立即返回。
// Service(sockfd,addr);
// exit(0);
// }
// //father
// ::close(sockfd);
// int n=waitpid(id,nullptr,0);
// if(n>0)
// {
// LOG(INFO,"wait child success\n");
// //父子进程都会有独立的文件描述符表,但子进程会拷贝父进程的文件描述符表,因此他们最终都会指向同样的文件
// }
//version 2 --多线程版本
pthread_t tid;
ThreadData* td=new ThreadData(sockfd,this,addr);
pthread_create(&tid,nullptr,Execute,td);//新线程进行分离
}
_isrunning=false;
}
static void* Execute(void* args)
{
pthread_detach(pthread_self());
ThreadData* td=static_cast<ThreadData*>(args);
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[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;
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;
};
V3 -- 线程池版本
TcpServer.hpp
#pragma once
#include<iostream>
#include<cstring>
#include<functional>
#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=1,
BIND_ERROR,
LISTEN_ERR
};
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()
{
//1.创建socket
_listensockfd=::socket(AF_INET,SOCK_STREAM,0);
if(_listensockfd<0)
{
LOG(FATAL,"socket create error\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 error\n");
exit(BIND_ERROR);
}
LOG(INFO,"bind success\n");
//3.因为tcp是面向连接的,tcp需要未来不断的能够做到获取连接
if(::listen(_listensockfd,gblcklog)<0)
{
LOG(FATAL,"listen error\n");
exit(BIND_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);
//4.获取新连接
int sockfd=::accept(_listensockfd,(struct sockaddr*)&client,&len);
if(sockfd<0)
{
LOG(WARNING,"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);//此时有父子孙三个进程,子进程退出,孙子进程就变成孤儿进程,就被系统领养。父进程的wait就可以立即返回。
// Service(sockfd,addr);
// exit(0);
// }
// //father
// ::close(sockfd);
// int n=waitpid(id,nullptr,0);
// if(n>0)
// {
// LOG(INFO,"wait child success\n");
// //父子进程都会有独立的文件描述符表,但子进程会拷贝父进程的文件描述符表,因此他们最终都会指向同样的文件
// }
//version 2 --多线程版本
// pthread_t tid;
// ThreadData* td=new ThreadData(sockfd,this,addr);
// pthread_create(&tid,nullptr,Execute,td);//新线程进行分离
//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* Execute(void* args)
{
pthread_detach(pthread_self());
ThreadData* td=static_cast<ThreadData*>(args);
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[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;
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;
};
多线程远程命令执行
recv的前三个参数及返回值和read的使用一样。flag里面有阻塞读和非阻塞读,该选项其实我们也不用。
send的前三个参数及返回值也跟write一样,也只是多了个参数flag。
popen内部会先建立一个管道文件,然后帮我们创建子进程,之后执行exec*,执行对应的command,内部会帮我们进行命令解析。
过程如下图:子进程执行command,所有的执行结果会写到管道里,父进程就可以在管道里读,为了让我们从管道里读,就把管道的文件描述符包装成了FILE*。type表示打开命令的方式,
Command.hpp
#pragma once
#include <iostream>
#include <string>
#include <cstring>
#include<cstdio>
#include<set>
#include "Log.hpp"
#include "InetAddr.hpp"
class Command
{
public:
Command()
{
_safe_command.insert("ls");
_safe_command.insert("touch");//touch filename
_safe_command.insert("pwd");
_safe_command.insert("whoami");
_safe_command.insert("which");//which pwd
}
~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.empty()?"success":result;
}
return "execute error";
}
void HandlerCommand(int sockfd, InetAddr addr)
{
//我们把它当作长服务
while (true)
{
char commandbuf[1024]; // 当作字符串
ssize_t n = ::recv(sockfd, commandbuf, sizeof(commandbuf) - 1,0);//设为0,默认阻塞读取
if (n > 0)
{
commandbuf[n] = 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;
};
TcpServer.hpp
#pragma once
#include<iostream>
#include<cstring>
#include<functional>
#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,
BIND_ERROR,
LISTEN_ERR
};
const static int gport=8888;
const static int gsock=-1;
const static int gblcklog=8;
using command_service_t =std::function<void(int sockfd,InetAddr addr)>;
class TcpServer
{
public:
TcpServer(command_service_t service,uint16_t port=gport)
:_port(port),
_listensockfd(gsock),
_isrunning(false),
_service(service)
{
}
void InitServer()
{
//1.创建socket
_listensockfd=::socket(AF_INET,SOCK_STREAM,0);
if(_listensockfd<0)
{
LOG(FATAL,"socket create error\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 error\n");
exit(BIND_ERROR);
}
LOG(INFO,"bind success\n");
//3.因为tcp是面向连接的,tcp需要未来不断的能够做到获取连接
if(::listen(_listensockfd,gblcklog)<0)
{
LOG(FATAL,"listen error\n");
exit(BIND_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);
//4.获取新连接
int sockfd=::accept(_listensockfd,(struct sockaddr*)&client,&len);
if(sockfd<0)
{
LOG(WARNING,"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 2 --多线程版本
pthread_t tid;
ThreadData* td=new ThreadData(sockfd,this,addr);
pthread_create(&tid,nullptr,Execute,td);//新线程进行分离
}
_isrunning=false;
}
static void* Execute(void* args)
{
pthread_detach(pthread_self());
ThreadData* td=static_cast<ThreadData*>(args);
td->_self->_service(td->_sockfd,td->_addr);
::close(td->_sockfd);
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[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;
// 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;
// }
// }
// }
~TcpServer(){}
private:
uint16_t _port;
int _listensockfd;
bool _isrunning;
command_service_t _service;
};