当前位置: 首页 > news >正文

【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;
};

相关文章:

【Linux网络】Socket 编程TCP

&#x1f308;个人主页&#xff1a;秦jh__https://blog.csdn.net/qinjh_?spm1010.2135.3001.5343 &#x1f525; 系列专栏&#xff1a;https://blog.csdn.net/qinjh_/category_12891150.html 目录 TCP socket API 详解 socket(): bind(): listen(): accept(): connect V0…...

代码训练day27贪心算法p1

贪心的本质是选择每一阶段的局部最优&#xff0c;从而达到全局最优 贪心算法一般分为如下四步&#xff1a; 将问题分解为若干个子问题找出适合的贪心策略求解每一个子问题的最优解将局部最优解堆叠成全局最优解 1.分发饼干 先将饼干数组和小孩数组排序。 然后从后向前遍历…...

基于RV1126开发板的车辆检测算法开发

1. 车辆检测简介 车辆检测是一种基于深度学习的对人进行检测定位的目标检测&#xff0c;能广泛的用于园区管理、交通分析等多种场景&#xff0c;是违停识别、堵车识别、车流统计等多种算法的基石算法。 人脸检测算法mAP0.5CAR0.78029 基于EASY-EAI-Nano硬件主板的运行效率&…...

Leetcode——137 260找出只出现一次的数

文章目录 找出只出现一次的数引入Leetcode 260Leetcode 137 找出只出现一次的数 对于数组中有一类题&#xff0c;即某些数据在数组中只出现一遍&#xff0c;需要我们找出&#xff0c;今天我们来看看这个类型的题。 引入 想必大家应该见过这么一道题&#xff1a; 现给定一个数…...

【多线程-第四天-自己模拟SDWebImage的下载图片功能-看SDWebImage的Demo Objective-C语言】

一、我们打开之前我们写的异步下载网络图片的项目,把刚刚我们写好的分类拖进来 1.我们这个分类包含哪些文件: 1)HMDownloaderOperation类, 2)HMDownloaderOperationManager类, 3)NSString+Sandbox分类, 4)UIImageView+WebCache分类, 这四个文件吧,把它们拖过来…...

【5G学习】基本概念之多频资源以及子载波和信道

在5G通信中&#xff0c;子载波、信道以及时域、频域、码域、空域是构建无线传输系统的核心概念。它们共同定义了信号的传输方式、资源分配和多维复用技术。以下是详细解释及其相互关系&#xff1a; 一、核心概念定义 1. 子载波&#xff08;Subcarrier&#xff09; 定义&#…...

鸿蒙动画与交互设计:ArkUI 3D变换与手势事件详解

大家好&#xff0c;我是 V 哥。 在鸿蒙 NEXT 开发中&#xff0c;ArkUI 提供了丰富的 3D 变换和手势事件功能&#xff0c;可用于创建生动且交互性强的用户界面。下面详细介绍 ArkUI 的 3D 变换和手势事件&#xff0c;并给出相应的 ArkTS 案例代码。 1. ArkUI 3D 变换 ArkUI 支…...

敏感数据触发后怎么保障安全?

当敏感数据被触发或泄露时&#xff0c;需立即采取系统化措施控制风险。以下为分阶段应对策略&#xff0c;结合技术与管理手段&#xff1a; 一、即时响应阶段 阻断扩散 隔离受影响系统&#xff1a;立即断开网络连接、暂停服务或关闭相关端口。 终止可疑进程&#xff1a;通过华…...

【CVE-2024-10929】ARM CPU漏洞安全通告

安全之安全(security)博客目录导读 目录 一、概述 二、CVE详情 三、受影响产品 四、建议措施 五、致谢 六、版本历史 一、概述 在部分基于Arm架构的CPU中发现了一个潜在安全问题&#xff0c;称为Spectre-BSE&#xff08;Branch Status Eviction&#xff0c;分支状态驱逐…...

高级java每日一道面试题-2025年4月06日-微服务篇[Nacos篇]-如何诊断和解决Nacos中的常见问题?

如果有遗漏,评论区告诉我进行补充 面试官: 如何诊断和解决Nacos中的常见问题&#xff1f; 我回答: 在Java高级面试中诊断和解决Nacos常见问题的综合回答 在Java高级面试中&#xff0c;当被问及如何诊断和解决Nacos中的常见问题时&#xff0c;可以从以下几个方面进行详细阐述…...

【模块化拆解与多视角信息3】教育背景:学历通胀时代的生存法则

教育背景:学历通胀时代的生存法则 写在最前 作为一个中古程序猿,我有很多自己想做的事情,比如埋头苦干手搓一个低代码数据库设计平台(目前只针对写java的朋友),比如很喜欢帮身边的朋友看看简历,讲讲面试技巧,毕竟工作这么多年,也做到过高管,有很多面人经历,意见还算…...

无人机3S与4S电池技术对比!

一、基础参数对比 1. 电芯与电压 3S电池&#xff1a;由3节锂电芯串联组成&#xff0c;标称电压为11.1V&#xff08;单节3.7V3&#xff09;&#xff0c;满电电压约12.6V。 4S电池&#xff1a;由4节电芯串联&#xff0c;标称电压14.8V&#xff08;3.7V4&#xff09;&#…...

linux电源管理(二),内核的CPUFreq(DVFS)和ARM的SCPI

更多linux系统电源管理相关的内容请看&#xff1a;https://blog.csdn.net/u010936265/article/details/146436725?spm1011.2415.3001.5331 1 简介 CPUFreq子系统位于drivers/cpufreq目录下&#xff0c;负责进行运行过程中CPU频率和电压的动态调整&#xff0c;即DVFS (Dynami…...

短波红外高光谱相机:高光谱成像在塑料分选中的应用

随着塑料工业的迅猛发展&#xff0c;塑料包装制品需求量增长迅速&#xff0c;消耗量不断上升&#xff0c;废塑料产生量也急剧增加。由于塑料化学结构稳定&#xff0c;难以自然降解&#xff0c;不当使用和处置及累积会造成严重的环境污染和资源浪费。因此&#xff0c;快速、精准…...

通过OBD部署OceanBase社区版集群v4.3.5

以下内容结合OceanBase官方文档进行安装部署测试 官方文档地址&#xff1a;https://www.oceanbase.com/docs/common-oceanbase-database-cn-1000000002016072 一.环境准备 准备三台虚拟机&#xff0c;配置信息如下 192.168.232.8 centos7.9 4c16g 硬盘100g 192.168.232.9 …...

【Java学习笔记】注释

注释 为什么要写注释&#xff1f; 养成良好的编程习惯&#xff0c;方便后续阅读和查看&#xff0c;理顺思路&#xff0c;增加可读性 对自己的代码负责&#xff0c;对别人负责 说明 1. 被注释的文字&#xff0c;不会被 JVM&#xff08;虚拟机&#xff09;解释执行 2. 多行注…...

Python 调用 YOLO ONNX

Python 调用 YOLO ONNX 1 下载ONNX文件2 Python代码 1 下载ONNX文件 ONNX下载地址 2 Python代码 import cv2 from ultralytics import YOLO# 加载 YOLOv11 model YOLO(./yolo11n.pt)# 读取图片 image_path ./11.png img cv2.imread(image_path)# 推理&#xff08;可以传…...

Linux 下 Module 工具的介绍与使用

参考&#xff1a; https://www.fasteda.cn/post/22.html https://modules.readthedocs.io/en/latest/module.html Linux 下 Module 工具的介绍与使用 一、前言 在 Linux 中&#xff0c;当同一款编辑器、运行库、软件存在多个版本且多个版本都需要在不同的场景或人员使用时&a…...

批量归一化(Batch Normalization)原理与PyTorch实现

批量归一化&#xff08;Batch Normalization&#xff09;是加速深度神经网络训练的常用技术。本文通过Fashion-MNIST数据集&#xff0c;演示如何从零实现批量归一化&#xff0c;并对比PyTorch内置API的简洁实现方式。 1. 从零实现批量归一化 1.1 批量归一化函数实现 import t…...

Flutter 文本组件深度剖析:从基础到高级应用

引言 在 Flutter 应用开发中&#xff0c;文本是向用户传达信息的重要媒介。Flutter 提供了丰富且强大的文本组件和相关属性&#xff0c;使开发者能够轻松实现多样化的文本展示效果。无论是简单的静态文本显示&#xff0c;还是复杂的富文本渲染&#xff0c;Flutter 都能满足需求…...

FABC是什么?

在销售和品牌营销领域&#xff0c;FABC 是一种用于构建销售话术和营销信息的框架&#xff0c;其全称为 Features&#xff08;特点&#xff09;、Advantages&#xff08;优势&#xff09;、Benefits&#xff08;利益&#xff09;、Case&#xff08;案例&#xff09;。该模型帮助…...

【MySQL】MVCC工作原理、事务隔离机制、undo log回滚日志、间隙锁

一、什么是MVCC&#xff1f; MVCC&#xff0c;即 Multiversion Concurrency Control&#xff08;多版本并发控制&#xff09;&#xff0c;它是数据库实现并发控制的一种方式。 MVCC 的核心思想是&#xff1a; 为每个事务提供数据的“快照”版本&#xff0c;从而避免加锁&…...

Spring Boot 集成 RocketMQ 全流程指南:从依赖引入到消息收发

前言 在分布式系统中&#xff0c;消息中间件是解耦服务、实现异步通信的核心组件。RocketMQ 作为阿里巴巴开源的高性能分布式消息中间件&#xff0c;凭借其高吞吐、低延迟、高可靠等特性&#xff0c;成为企业级应用的首选。而 Spring Boot 通过其“约定优于配置”的设计理念&a…...

PCL 点云RANSAC提取平面(非内置函数)

文章目录 一、算法实现1.1实现步骤二、实现代码三、实现效果参考资料一、算法实现 1.1实现步骤 1、确定模型。三个点确定一个平面,方程式为 a x + b y + c z + 1 = 0 ax+by+cz+1=0...

中介者模式:理论、实践与 Spring 源码解析

摘要 本论文以中介者模式为核心,系统阐述其设计原理、应用场景及在 Spring 框架中的实现机制。通过机票预订系统、银行交易系统等典型案例,具象化展示模式如何解耦复杂对象交互;结合 Spring 5.3.29 源码,深入剖析事件驱动模型中ApplicationEventPublisher与ApplicationLis…...

2025.04.14【Table】| 生信数据表图技巧

Custom title A set of examples showing how to customize the titles of a table made with GT Custom footer How to customize the footer and the references section of a gt table 文章目录 Custom titleCustom footer 生信数据可视化&#xff1a;Table图表详解1. R语…...

Unified Modeling Language,统一建模语言

UML&#xff08;Unified Modeling Language&#xff0c;统一建模语言&#xff09;是一种标准化的图形化建模语言&#xff0c;用于可视化、规范和文档化软件系统的设计。UML 提供了一套通用的符号和规则&#xff0c;帮助开发者、架构师和团队成员更好地理解和沟通软件系统的结构…...

OCP证书有效期是永久,但需要更新

在数据库管理领域&#xff0c;OCP证书作为Oracle认证体系中的重要组成部分&#xff0c;一直是数据库专业人士追求的目标。许多考证者会有疑惑:OCP证书是永久有效的吗&#xff1f;需要更新吗&#xff1f; Oracle官方明确规定&#xff1a;OCP证书一经获得&#xff0c;终身有效。无…...

服务器本地搭建

socket函数 它用于创建一个新的套接字&#xff08;socket&#xff09;。 函数原型 #include <sys/socket.h> int socket(int domain, int type, int protocol);参数解释 domain&#xff1a;它指定了通信所使用的协议族&#xff0c;常见的取值如下&#xff1a; AF_INET…...

调节磁盘和CPU的矛盾——InnoDB的Buffer Pool

缓存的重要性 无论是用于存储用户数据的索引【聚簇索引、二级索引】还是各种系统数据&#xff0c;都是以页的形式存放在表空间中【对一个/几个实际文件的抽象&#xff0c;存储在磁盘上】如果需要访问某页的数据&#xff0c;就会把完整的页数据加载到内存中【即使只访问页中的一…...

[dp12_回文子串] 最长回文子串 | 分割回文串 IV

目录 1.回文子串 题解 2.最长回文子串 题解 3.分割回文串 IV 题解 dp[i][j] 表示 s 字符串 [i, j] 的子串&#xff0c;是否是回文串( 建始末表&#xff09; 将两个 for 循环的结果&#xff0c;借助二维 dp 来存 1.回文子串 链接&#xff1a;647. 回文子串 给你一个字符…...

分布式应用架构的演变

整体演变过程 第一阶段&#xff1a;单一应用架构 单一应用架构&#xff0c;是把所有服务都放在一个项目中&#xff0c;进行打包部署到服务器上&#xff0c;如果流量特别大的话&#xff0c;就在另外的服务器上部署相同的功能模块用来分摊流量。但是这样的话&#xff0c;一旦有某…...

zephyr RTOS 中 bt_le_adv_start函数的功能应用

目录 概述 1 功能 1.1 功能介绍 1.2 函数原型 2 参数说明 2.1 广播参数&#xff08;bt_le_adv_param&#xff09; 2.2 常用广播选项&#xff08;options&#xff09; 2.3 广播数据&#xff08;bt_data&#xff09; 3 示例代码 3.1 启动可连接广播&#xff08;带设备名…...

双按键控制LED(中断优先级)

1.启动时&#xff0c;两个LED灯熄灭&#xff0c;1秒钟后&#xff08;定时器实现&#xff09;&#xff0c;LED自动点亮&#xff1b; 2.按键1按下后&#xff0c;通过中断int0把两个LED熄灭5s时间&#xff0c;int0优先级设置为最高&#xff08;优先级必须设置&#xff0c;设置后才…...

美团即时零售大动作,将独立的闪购将会改变什么?

4月12日上午&#xff0c;美团核心本地商业CEO王莆中在社交媒体上发文&#xff0c;宣布美团将在下周正式发布即时零售品牌&#xff0c;标志着美团将进一步发展即时零售业务。 首先&#xff0c;从市场格局角度来看&#xff0c;美团将独立的闪购品牌推出&#xff0c;会进一步加剧…...

如何安装git?

以下是 Windows、macOS 和 Linux 系统安装 Git 的详细步骤&#xff1a; 一、Windows 系统安装 Git 下载安装包 访问 Git 官网下载页&#xff0c;点击下载 Windows 版安装程序&#xff08;如 Git-2.45.1-64-bit.exe&#xff09;。 运行安装程序 安装选项&#xff1a; 选择安装路…...

Ubuntu上docker、docker-compose的安装

今天来实践下Ubuntu上面安装docker跟docker-compose&#xff0c;为后面安装dify、fastgpt做准备。 一、安装docker sudo apt-get updatesudo apt-get install docker.io 然后系统输入 docker --version 出现下图即为docker安装成功。 二、安装docker-compose 我先看下系统…...

ubuntu如何设置静态ip

服务器有时是通过dhcp动态获取ip的&#xff0c;有时出于远程登录方便的考虑&#xff0c;会将其设置为静态ip&#xff0c;以下是设置静态ip的方法 在 Ubuntu 中设置静态 IP 的方法取决于你使用的网络管理工具&#xff08;如 netplan、NetworkManager 或 ifconfig&#xff09;。…...

js原型和原型链

js原型&#xff1a; 1、原型诞生的目的是什么呢&#xff1f; js原型的产生是为了解决在js对象实例之间共享属性和方法&#xff0c;并把他们很好聚集在一起&#xff08;原型对象上&#xff09;。每个函数都会创建一个prototype属性&#xff0c;这个属性指向的就是原型对象。 …...

大数据 - 2. Hadoop - HDFS

前言 HDFS&#xff1a;分布式文件系统 为什么海量数据需要分布式存储技术&#xff1f; 文件过大时&#xff0c;单台服务器无法承担&#xff0c;要靠数量来解决。数量的提升带来的是网络传输、磁盘读写、CPU、内存等各方面的提升。 众多的服务器一起工作&#xff0c;如何保证…...

嵌入式硬件常用总线接口知识体系总结和对比

0.前言 在嵌入式工程实现中,多多少少我们都使用过总线,各种各样的总线应用于不同场合,不同场景有不同的优势,但是我们在作为工程师过程中在如何选择项目合适的总线,根据什么来选?需要我们对项目全局和总线特征有所了解,本文目的就是对比多种总线的关键特征 我们在聊到…...

prime 1 靶场笔记(渗透测试)

环境说明&#xff1a; 靶机prime1和kali都使用的是NAT模式&#xff0c;网段在192.168.144.0/24。 Download (Mirror): https://download.vulnhub.com/prime/Prime_Series_Level-1.rar 一.信息收集 1.主机探测&#xff1a; 使用nmap进行全面扫描扫描&#xff0c;找到目标地址及…...

(二十四)安卓开发中的AppCompatActivity详解

在安卓开发中&#xff0c;AppCompatActivity 是一个非常核心的类&#xff0c;它继承自 Activity&#xff0c;并通过 Android Support Library&#xff08;现已迁移至 AndroidX&#xff09;提供了对 ActionBar 和 Material Design 的支持。它的主要作用是帮助开发者在不同版本的…...

AI大模型+全渠道整合:容联七陌智能客服赋能制造业升级

自《中国制造2025》战略提出以来&#xff0c;制造业的智能化发展进入快车道&#xff0c;但行业仍面临劳动力成本上升、供应链不透明、客户需求碎片化等挑战。企业亟需通过技术手段实现降本增效&#xff0c;而智能化客户服务成为关键突破口。 与此同时&#xff0c;客服行业正经历…...

Vue 技术解析:从核心概念到实战应用

Vue.js 是一款流行的渐进式前端框架&#xff0c;以其简洁的 API、灵活的组件化结构和高效的响应式数据绑定而受到开发者的广泛欢迎。本文将深入解析 Vue 技术的核心概念、原理和应用场景&#xff0c;帮助开发者更好地理解和使用 Vue.js。 一、Vue 的设计哲学与核心概念 &…...

中英文提示词对AI IDE编程能力影响有多大?

深度剖析 &#x1f9e0;&#xff1a;中英文提示词对AI IDE编程能力影响有多大&#xff1f;&#xff08;附实战建议&#xff09; 作者&#xff1a;AI助手 | 日期&#xff1a;2023-10-27 | 标签&#xff1a;AI, IDE, Prompt Engineering, LLM, 编程效率 摘要&#xff1a;随着 AI…...

ARM处理器程序烧写方式

一、烧写原理 无论是jtag还是串口烧写&#xff0c;本质都是先通过上位机&#xff08;keil 或者flymcu或者芯片官方上位机等烧写bin的上位机&#xff09;往mcu的ram里烧写一段代码即.FLM文件&#xff0c;这段代码在上位机&#xff08;keil体现在配置项里&#xff0c;flymcu应该…...

AI 项目详细开发步骤指南

AI 项目详细开发步骤指南 一、环境搭建详解 1. JDK 17 安装与配置 Windows 系统安装步骤&#xff1a; 访问 Oracle 官网下载 JDK 17 安装包&#xff1a;https://www.oracle.com/java/technologies/downloads/#java17下载 Windows x64 Installer 版本双击安装包&#xff0c;…...

文本纠错WPS插件:提升文档质量的利器

文本纠错WPS插件&#xff1a;提升文档质量的利器 引言 在数字化办公日益普及的今天&#xff0c;文档的质量直接影响到我们的工作效率和形象。一个错别字或标点错误&#xff0c;可能就会让我们的专业形象大打折扣。今天&#xff0c;我要向大家介绍一款强大的WPS插件——文本纠…...

Node.js 模块包的管理和使用是

一、模块包的概念 1.模块分类&#xff1a; 核心模块&#xff1a;Node.js 内置模块&#xff08;如 fs, http, path&#xff09;&#xff0c;无需安装直接引用。 本地模块&#xff1a;开发者自己编写的模块文件&#xff0c;通过相对路径引入。 第三方模块&#xff1a;通过 npm…...