【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.mzph.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)监控 […