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

【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…...

C++指针与内存管理深度解析

前言&#xff1a; 在C开发的道路上&#xff0c;指针和内存管理就像是两个既强大又危险的朋友。掌握它们就如同学会驾驭一辆高性能跑车&#xff0c;稍有不慎可能导致灾难&#xff0c;但一旦熟练掌握&#xff0c;便能发挥出惊人的性能和灵活性。今天就让我们一起深入探讨C中的指…...

ESP32-idf学习(二)esp32C3作服务端与电脑蓝牙数据交互

一、当前需求 目前是想利用蓝牙来传输命令&#xff0c;或者一些数据&#xff0c;包括电脑、手机与板子的数据传输&#xff0c;板子与板子之间的数据传输。构思是一个板子是数据接收终端&#xff0c;在电脑或手机下发指令后&#xff0c;再给其他板子相应指令&#xff0c;也需要…...

NHANES指标推荐:CMI

文章题目&#xff1a;Association between cardiometabolic index and biological ageing among adults: a population-based study DOI&#xff1a;10.1186/s12889-025-22053-3 中文标题&#xff1a;成年人心脏代谢指数与生物衰老之间的关系&#xff1a;一项基于人群的研究 发…...

前端单元测试实战:如何开始?

实战&#xff1a;如何开始单元测试 1.安装依赖 npm install --save-dev jest2.简单的例子 首先&#xff0c;创建一个 sum.js 文件 ./sum.js function sum(a, b) {return a b; }module.exports sum;创建一个名为 sum.test.js 的文件&#xff0c;这个文件包含了实际测试内…...

react-native搭建开发环境过程记录

主要参考&#xff1a;官网的教程 https://reactnative.cn/docs/environment-setup 环境介绍&#xff1a;macos ios npm - 已装node18 - 已装&#xff0c;通过nvm进行版本控制Homebrew- 已装yarn - 已装ruby - macos系统自带的2.2版本。watchman - 正常安装Xcode - 正常安装和…...

【数据库系统概论】第3章 SQL(四)视图(超详细)

视图&#xff08;View&#xff09;是数据库中的虚拟表 通过执行查询定义并存储在数据库中&#xff0c;可以像普通表一样被查询和使用。 视图本身并不存储数据&#xff0c;而是基于一个或多个表的查询结果动态生成。 视图的概念 视图( View )是由其它表或视图上的查询所定义…...

观察者模式详解与C++实现

1. 模式定义 观察者模式&#xff08;Observer Pattern&#xff09;是一种行为型设计模式&#xff0c;定义了对象间的一对多依赖关系。当一个对象&#xff08;被观察者/主题&#xff09;状态改变时&#xff0c;所有依赖它的对象&#xff08;观察者&#xff09;都会自动收到通知…...

空调制冷量和功率有什么关系?

空调的制冷量和功率是衡量空调性能的两个核心参数,二者既有区别又紧密相关,以下是具体解析: 1. 基本定义 制冷量(Cooling Capacity)指空调在单位时间内从室内环境中移除的热量,单位为 瓦特(W) 或 千卡/小时(kcal/h)。它直接反映空调的制冷能力,数值越大,制冷效果越…...

【python报错解决训练】

在编程开发中&#xff0c;正确解读报错信息是解决问题的关键技能。以下是系统学习解读报错信息的方法指南&#xff1a; 一、理解报错信息的核心结构 典型的报错信息包含以下要素&#xff08;以Python为例&#xff09;&#xff1a; Traceback (most recent call last):File &q…...

UE5 关卡序列

文章目录 介绍创建一个关卡序列编辑动画添加一个物体编辑动画时间轴显示秒而不是帧时间轴跳转到一个确定的时间时间轴的显示范围更改关键帧的动画插值方式操作多个关键帧 播放动画 介绍 类似于Unity的Animation动画&#xff0c;可以用来录制场景中物体的动画 创建一个关卡序列…...

AI测试用例生成平台

AI测试用例生成平台 项目背景技术栈业务描述项目展示项目重难点 项目背景 针对传统接口测试用例设计高度依赖人工经验、重复工作量大、覆盖场景有限等行业痛点&#xff0c;基于大语言模型技术实现接口测试用例智能生成系统。 技术栈 LangChain框架GLM-4模型Prompt Engineeri…...

C#中扩展方法和钩子机制使用

1.扩展方法&#xff1a; 扩展方法允许向现有类型 “添加” 方法&#xff0c;而无需创建新的派生类型、重新编译或以其他方式修改原始类型。扩展方法是一种特殊的静态方法&#xff0c;但可以像实例方法一样进行调用。 使用场景&#xff1a; 1.当无法修改某个类的源代码&#…...

大语言模型减少幻觉的常见方案

什么是大语言模型的幻觉 大语言模型的幻觉&#xff08;Hallucination&#xff09;是指模型在生成文本时&#xff0c;输出与输入无关、不符合事实、逻辑错误或完全虚构的内容。这种现象主要源于模型基于概率生成文本的本质&#xff0c;其目标是生成语法合理、上下文连贯的文本&…...

YOLOv5、YOLOv6、YOLOv7、YOLOv8、YOLOv9、YOLOv10、YOLOv11、YOLOv12的网络结构图

文章目录 一、YOLOv5二、YOLOv6三、YOLOv7四、YOLOv8五、YOLOv9六、YOLOv10七、YOLOv11八、YOLOv12九、目标检测系列文章 本文将给出YOLO各版本&#xff08;YOLOv5、YOLOv6、YOLOv7、YOLOv8、YOLOv9、YOLOv10、YOLOv11、YOLOv12&#xff09;网络结构图的绘制方法及图。本文所展…...

03 UV

04 Display工具栏_哔哩哔哩_bilibili 讲的很棒 ctrlMMB 移动点 s 打针 ss 批量打针...

AIGC-几款本地生活服务智能体完整指令直接用(DeepSeek,豆包,千问,Kimi,GPT)

Unity3D特效百例案例项目实战源码Android-Unity实战问题汇总游戏脚本-辅助自动化Android控件全解手册再战Android系列Scratch编程案例软考全系列Unity3D学习专栏蓝桥系列AIGC(GPT、DeepSeek、豆包、千问、Kimi)👉关于作者 专注于Android/Unity和各种游戏开发技巧,以及各种资…...

Django ORM 定义模型

提示&#xff1a;定义模型字段的类型 文章目录 一、字段类型二、字段属性三、元信息 一、字段类型 常用字段 字段名描述备注AutoFieldint 自增必填参数 primary_keyTrue&#xff0c;无该字段时&#xff0c;django自动创建一个 BigAutoField&#xff0c;一个model不能有两个Au…...

4.18---缓存相关问题(操作原子性,击穿,穿透,雪崩,redis优势)

为什么要用redis做一层缓存&#xff0c;相比直接查mysql有什么优势&#xff1f; 首先介绍Mysql自带缓存机制的问题&#xff1a; MySQL 的缓存机制存在一些限制和问题,它自身带的缓存功能Query Cache只能缓存完全相同的查询语句&#xff0c;对于稍有不同的查询语句&#xff0c…...

java八股之并发编程

1.java线程和操作系统线程之间的区别&#xff1f; 现在java线程本质上是操作系统线程&#xff0c;java中采用的是一对一的线程模型&#xff08;一个用户线程对应一个内核进程&#xff09; 2.什么是进程和线程&#xff1f; 1.进程是操作系统一次执行&#xff0c;资源分配和调度的…...

C#/.NET/.NET Core拾遗补漏合集(25年4月更新)

前言 在这个快速发展的技术世界中&#xff0c;时常会有一些重要的知识点、信息或细节被忽略或遗漏。《C#/.NET/.NET Core拾遗补漏》专栏我们将探讨一些可能被忽略或遗漏的重要知识点、信息或细节&#xff0c;以帮助大家更全面地了解这些技术栈的特性和发展方向。 ✍C#/.NET/.N…...

层次式架构核心:中间层的功能、优势与技术选型全解析

层次式架构中的中间层是整个架构的核心枢纽&#xff0c;承担着多种重要职责&#xff0c;在功能实现、优势体现以及技术选型等方面都有丰富的内容&#xff0c;以下为你详细介绍&#xff1a; 一、功能 1.业务逻辑处理 复杂规则运算&#xff1a;在许多企业级应用中&#xff0c;…...

PDF.js 生态中如何处理“添加注释\添加批注”以及 annotations.contents 属性

我们来详细解释一下在 PDF.js 生态中如何处理“添加注释”以及 annotations.contents 属性。 核心要点&#xff1a;PDF.js 本身主要是阅读器&#xff0c;不是编辑器 首先&#xff0c;最重要的一点是&#xff1a;PDF.js 的核心库 (pdfjs-dist) 主要设计用于解析和渲染&#xf…...

MySQL性能调优(三):MySQL中的系统库(简介、performance_schema)

文章目录 MySQL性能调优数据库设计优化查询优化配置参数调整硬件优化 1.MySQL中的系统库1.1.系统库简介1.2.performance_schema1.2.1.什么是performance_schema1.2.2.performance_schema使用1.2.3.检查当前数据库版本是否支持1.2.4.performance_schema表的分类1.2.5.performanc…...

【Python语言基础】22、异常处理

文章目录 1. 异常1.1 简介1.2 为什么需要异常处理 2. 基本语法2.1 各部分详解 3. 异常处理流程3.1 执行try代码块3.2 异常发生检查3.3 异常捕获与匹配3.4 执行匹配的 except 代码块3.5 执行 else 代码块&#xff08;可选&#xff09;3.6 执行 finally 代码块&#xff08;可选&a…...

印度zj游戏出海代投本土网盟广告核心优势

印度游戏出海代投本土网盟广告的核心优势包括&#xff1a; 本土化广告策略&#xff1a;针对印度市场的特点&#xff0c;定制本土化的广告策略&#xff0c;吸引更多印度用户的关注和参与。 深度了解印度市场&#xff1a;对印度文化、消费习惯、网络使用习惯等有深入了解&#x…...

NO.97十六届蓝桥杯备战|数论板块-最大公约数和最小公倍数|欧几里得算法|秦九韶算法|小红的gcd(C++)

约数和倍数 如果a 除以b 没有余数&#xff0c;那么a 就是b 的倍数&#xff0c;b 就是a 的约数&#xff0c;记作b ∣ a 。 约数&#xff0c;也称因数。 最⼤公约数和最⼩公倍数 最⼤公约数Greatest Common Divisor&#xff0c;常缩写为gcd。 ⼀组整数的公约数&#xff0c;是…...

《软件设计师》复习笔记(11.6)——系统转换、系统维护、系统评价

目录 一、遗留系统&#xff08;Legacy System&#xff09; 定义&#xff1a; 特点&#xff1a; 演化策略&#xff08;基于价值与技术评估&#xff09;&#xff1a; 高水平 - 低价值&#xff1a; 高水平 - 高价值&#xff1a; 低水平 - 低价值&#xff1a; 低水平 - 高价…...

ROS机器人一般用哪些传感器?

以下是ROS机器人常用传感器的分层详解及思维导图总结,涵盖传感器分类、核心参数、ROS支持及典型应用: 一、环境感知传感器 1. 视觉传感器 类型 原理 ROS支持 数据类型 典型型号/驱动 优缺点及应用场景 单目摄像头 单镜头成像,通过透视变换获取2D图像,依赖算法推断深度 驱…...

嵌入式linux架构理解(宏观理解)6ull学习心得---从架构理解到自写程序运行及自写程序开机自启动

一、linux系统的三个组成部分 U-Boot、Linux kernel 和 rootfs 这三者一起构成了一个完整的 Linux 系 统,一个可以正常使用、功能完善的 Linux 系统。 1.在移植 Linux之前我们需要先移植一个 bootloader 代码,这个 bootloader 代码用于启动 Linux 内核,bootloader有很多,常…...