?🌈個人主頁:秦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.創建socketint 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);//3struct 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 addrif(::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);//3struct 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 addrif(::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);//3struct 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 addrif(::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);//3struct 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 addrif(::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 addrtask_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);//3struct 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 addrif(::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;
};