【Linux網絡】Socket 編程TCP

?🌈個人主頁:秦jh__https://blog.csdn.net/qinjh_?spm=1010.2135.3001.5343
🔥?系列專欄:https://blog.csdn.net/qinjh_/category_12891150.html

9efbcbc3d25747719da38c01b3fa9b4f.gif

目錄

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 參數的初始化通常是這樣的:

  1. 將整個結構體清零;
  2. 設置地址類型為 AF_INET;
  3. 網絡地址為 INADDR_ANY, 這個宏表示本地的任意 IP 地址,因為服務器可能有多個網卡,每個網卡也可能綁定多個 IP 地址, 這樣設置可以在所有的IP 地址上監聽, 直到與某個客戶端建立了連接時才確定下來到底用哪個 IP 地址;
  4. 端口號 我們定義為 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;
};

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/diannao/78929.shtml
繁體地址,請注明出處:http://hk.pswp.cn/diannao/78929.shtml
英文地址,請注明出處:http://en.pswp.cn/diannao/78929.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

記一次 .NET某固高運動卡測試 卡慢分析

一&#xff1a;背景 1. 講故事 年前有位朋友找到我&#xff0c;說他們的程序會偶發性卡慢 10s 鐘&#xff0c;在某些組合下會正常&#xff0c;某些組合下就會出現問題&#xff0c;解釋不了其中的原因&#xff0c;讓我幫忙看下怎么回事&#xff1f;截圖如下&#xff1a; priva…

硬件知識積累 單片機+ 光耦 + 繼電器需要注意的地方

1. 電路圖 與其數值描述 1.1 單片機引腳信號為 OPtoCoupler_control_4 PC817SB 為 光耦 繼電器 SRD-05VDC-SL-A 的線圈電壓為 67Ω。 2. 需注意的地方 1. 單片機的推挽輸出的電流最大為 25mA 2. 注意光耦的 CTR 參數 3. 注意繼電器線圈的 內阻 4. 繼電器的開啟電壓。 因為光耦…

IP組播技術與internet

1.MAC地址分為三類&#xff1a;廣播地址&#xff1b;組播地址&#xff1b;單播地址 2.由一個源向一組主機發送信息的傳輸方式稱為組播。 3.組播MAC地址&#xff0c;第一個字節的最后一位為1&#xff1b; 單播MAC地址&#xff0c;第一個字節的最后一位為0&#xff1b; 4.不能…

vue3+vite+ts使用daisyui/tailwindcss

vite創建vue3腳手架 npm init vitelatest myVue3 – --template vue cd .\myVue3\ npm i npm run dev 安裝tailwindcss/daisyui 依賴安裝 npm install -D tailwindcss postcss autoprefixer daisyui npx tailwindcss init -p 這條命令將生成postcss.config.js(因為加了…

大數據(7)Kafka核心原理揭秘:從入門到企業級實戰應用

目錄 一、大數據時代的技術革命1.1 消息中間件演進史1.2 Kafka核心設計哲學 二、架構深度解構2.1 核心組件拓撲2.1.1 副本同步機制&#xff08;ISR&#xff09; 2.2 生產者黑科技2.3 消費者演進路線 三、企業級應用實戰3.1 金融行業實時風控3.2 物聯網數據管道 四、生產環境優化…

spring boot大文件與多文件下載

一、簡單大文件下載&#xff1a; /*** 下載大文件* param path 路徑* param fileName 文件名* return* throws IOException*/ public static ResponseEntity<InputStreamResource> downloadFile(String path, String fileName) throws IOException {Path filePath Path…

第二節:React 基礎篇-受控組件 vs 非受控組件

一、場景題&#xff1a;設計一個實時搜索輸入框&#xff0c;說明選擇依據 受控組件 vs 非受控組件 核心區別 特征受控組件非受控組件數據管理由React狀態&#xff08;state&#xff09;控制通過DOM元素&#xff08;ref&#xff09;直接訪問更新時機每次輸入觸發onChange提交…

局部路由守衛

局部路由守衛為我們提供了更細粒度的路由控制&#xff0c;允許我們在特定的路由或組件級別添加鑒權和邏輯處理。局部路由守衛分為 path 守衛和 component 守衛&#xff0c;它們分別適用于不同的場景。 path 守衛&#xff08;路由守衛&#xff09; path 守衛用于在進入特定路由…

Android 16應用適配指南

Android 16版本特性介紹 https://developer.android.com/about/versions/16?hlzh-cn Android 16 所有功能和 API 概覽 https://developer.android.com/about/versions/16/features?hlzh-cn#language-switching Android 16 發布時間 Android 16 適配指南 Google開發平臺&…

android display 筆記(十二)CPU,GPU,DPU的區別

CPU&#xff08;Central Processing Unit&#xff09;通用計算&#xff1a;處理復雜邏輯、分支預測、多任務調度。 低延遲&#xff1a;優先快速響應單線程任務。 GPU&#xff08;Graphics Processing Unit&#xff09; 高吞吐量并行計算&#xff1a;適合大規模數據并行處理。…

音頻轉文本:如何識別音頻成文字

Python腳本:MP4轉MP3并語音識別為中文 以下是一個完整的Python腳本,可以將MP4視頻轉換為MP3音頻,然后使用語音識別模型將音頻轉換為中文文本。 準備工作 首先需要安裝必要的庫: pip install moviepy pydub SpeechRecognition openai-whisper完整腳本 import os from m…

理解 MCP 協議的數據傳遞:HTTP 之上的一層“殼子

以下是以 CSDN 博客的風格記錄你對 MCP 協議數據傳遞的理解和發現&#xff0c;內容涵蓋了 MCP 協議基于 HTTP 的本質、JSON-RPC 的“殼子”作用&#xff0c;以及為什么熟悉 HTTP 協議就足以理解 MCP 的數據傳遞。文章面向技術社區&#xff0c;結構清晰&#xff0c;適合分享。 理…

基于ssm網絡游戲推薦系統(源碼+lw+部署文檔+講解),源碼可白嫖!

摘要 當今社會進入了科技進步、經濟社會快速發展的新時代。國際信息和學術交流也不斷加強&#xff0c;計算機技術對經濟社會發展和人民生活改善的影響也日益突出&#xff0c;人類的生存和思考方式也產生了變化。傳統網絡游戲管理采取了人工的管理方法&#xff0c;但這種管理方…

vue入門:指令

文章目錄 vue的內置指令說明&#xff1a; 自定義指令 vue的內置指令 Vue 指令的本質是&#xff1a; 聲明式的 DOM 操作接口&#xff08;隱藏底層 JavaScript 代碼&#xff09;。響應式數據的綁定媒介&#xff08;連接數據和視圖&#xff09;。模板編譯的標記&#xff08;最終…

oracle 索引失效

在 Oracle 11g 中&#xff0c;索引失效的常見原因包括函數修改列、隱式類型轉換、統計信息過時等&#xff0c;解決方法需結合版本特性&#xff08;如虛擬列、索引跳躍掃描&#xff09;。通過執行計劃分析、統計信息維護和合理使用提示&#xff08;Hints&#xff09;&#xff0c…

k8s藍綠發布

k8s藍綠發布 什么是藍綠部署K8S中如何實現藍綠部署k8s藍綠部署流程圖 什么是藍綠部署 參考: https://youtu.be/CLq_hA0lAd0 https://help.coding.net/docs/cd/best-practice/blue-green.html 藍綠部署最早是由馬丁福勒 2010年在他的博客中提出. 藍綠部署是一種軟件部署策略,用…

stm32面試

數據結構相關問題 stm32面試 數據結構相關問題 目錄基礎數據結構樹與圖排序與查找算法 Linux相關問題Linux系統基礎Linux命令與腳本Linux網絡與服務 操作系統相關問題操作系統基礎概念操作系統調度算法操作系統同步與通信 STM32相關問題STM32硬件基礎STM32編程與開發STM32應用與…

Mybatis 中 mappers標簽 package的使用

MyBatis 的配置文件中&#xff0c;<mappers> 標簽用于指定 MyBatis 應該加載哪些映射器&#xff08;Mapper&#xff09;。其中 package 屬性是一種便捷的方式來批量注冊多個映射器接口 package 屬性允許你指定一個包名&#xff0c;MyBatis 會自動掃描該包下的所有映射器…

設計模式 --- 訪問者模式

訪問者模式是一種行為設計模式&#xff0c;它允許在不改變對象結構的前提下&#xff0c;定義作用于這些對象元素的新操作。 優點&#xff1a; 1.??符合開閉原則&#xff1a;新增操作只需添加新的訪問者類&#xff0c;無需修改現有對象結構。 ??2.操作邏輯集中管理??&am…

監控docker中的java應用

1)進入指定的容器 docker exec -it demo /bin/bash 2)下載curl root89a67e345354:/# apt install curl -y 3)下載arthas root89a67e345354:/# curl -O https://arthas.aliyun.com/arthas-boot.jar 4)運行 root89a67e345354:/# java -jar arthas-boot.jar 5)監控 […