网络字节序——TCP接口及其实现简单TCP服务器
网络字节序——TCP接口及其实现简单TCP服务器
文章目录
- 网络字节序——TCP接口及其实现简单TCP服务器
- 简单TCP服务器的实现
- 1. 单进程版:客户端串行版
- 2. 多进程版:客户端并行版
- netstat查看网络信息
- 3.多线程版:并行执行
- log.hpp
- 守护进程
- fg、bg
- setsid
简单TCP服务器的实现
- TCP区别于UDP在于要设置套接字为监控状态,即TCP是面向链接,因此TCP套接字需要设置为监听状态
void initserver()
{
//1.创建套接字
_listensock=socket(AF_INET,SOCK_STREAM,0);
if(_listensock<0)
{logMessage(FATAL,"create listensocket error");exit(SOCK_ERR);
}logMessage(NORMAL, "create socket success: %d", _listensock);
//2.bind ip和port
struct sockaddr_in local;
local.sin_family=AF_INET;
local.sin_port=htons(_port);
local.sin_addr.s_addr=INADDR_ANY;
if(bind(_listensock,(struct sockaddr*)&local,sizeof(local))<0)//绑定失败
{logMessage(FATAL,"bind error");exit(BIND_ERR);
}logMessage(NORMAL,"bind success");
//3.将套接字设置为监听模式
if(listen(_listensock,0)<0)
{logMessage(FATAL,"listen error");exit(LISTEN_ERR);
}
logMessage(NORMAL,"listen success");
}
socket函数原型
#include <sys/types.h>
#include <sys/socket.h>int socket(int domain, int type, int protocol);
-
domain
表示协议族,常用的有AF_INET
(IPv4)和AF_INET6
(IPv6)。 -
type
表示Socket类型,常用的有SOCK_STREAM
(TCP)和SOCK_DGRAM
(UDP)。 -
protocol
通常可以设置为 0,让系统根据domain
和type
来选择合适的协议。 -
socket()打开一个网络通讯端口,如果成功的话,就像open()一样返回一个文件描述符
-
应用程序可以像读写文件一样通过socket函数用read/write在网络上收发数据
bind函数原型
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
-
sockfd
是socket描述符。 -
addr
是一个struct sockaddr
结构体,包含要绑定的IP地址和端口信息。 -
addrlen
是addr
结构体的长度。因为addr结构体可以接受多种协议的sockaddr结构体,因此要传其结构体的长度 -
bind()成功返回0,失败返回-1。
-
bind()的作用是将参数sockfd和myaddr绑定在一起, 使sockfd这个用于网络通讯的文件描述符监听addr所描述的地址和端口号;
listen函数原型
int listen(int sockfd, int backlog);
sockfd
是socket描述符,指用于进行网络监听的文件描述符backlog
表示等待连接队列的最大长度。- listen成功返回0,失败返回-1
- listen函数将使得sockfd处于监听状态,并且允许backlog个客户端处于连接等待状态,当收到多于backlog个客户端的的连接请求则选择忽略。
- 实际上listen函数告诉操作系统指定的套接字sockfd处于监听状态,该套接字开始等待其他计算机通过网络与其建立连接,一旦有连接请求到达,操作系统会将连接请求放入连接队列中,连接队列的最大长度为backlog,连接队列是一个存放连接请求的缓冲区,如果队列已满新的连接请求将会被拒绝。即当一个套接字处于监听状态时,它不直接处理数据传输,而是等待其他计算机发起连接。
总的来说initserver函数作用是先创建套接字,然后填充指定的端口号和ip,并将套接字设置为监听状态
void start()
{while(true){struct sockaddr_in cli;socklen_t len=sizeof(cli);bzero(&cli,len);int sock=accept(_listensock,(struct sockaddr*)&cli,&len);if(sock<0){logMessage(FATAL,"accept client error");continue;}logMessage(NORMAL,"accept client success");cout<<"accept sock: "<<sock<<endl;}
accept函数原型
#include <sys/types.h>
#include <sys/socket.h>int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
-
sockfd
:是一个已经通过socket
函数创建的套接字描述符,并且是已经处于监听状态,用于监听传入的连接请求。 -
addr
:是一个指向struct sockaddr
结构的指针,用于接收连接请求的客户端的地址信息。 -
addrlen
:是一个指向socklen_t
类型的指针,用于指定addr
缓冲区的长度,同时也用于返回实际客户端地址结构的大小。 -
accept函数作用是接受传入的连接请求,他会阻塞程序的执行,直到有一个连接请求到达。一旦有连接请求到达,将会创建一个新的套接字,并返回这个新套接字的文件描述符,这个新套接字用于与客户端进行通信,同时
addr
和addrlen
会填充上客户端的地址信息。 -
在服务器程序中,accept函数会被用在一个循环中,以接受多个客户端的连接请求
start函数作用是阻塞接受客户端发送来的连接请求,使得服务器与客户端建立通信
tcpclient.cc
#include<iostream>
#include<string>
#include<memory>
#include"tcpclient.hpp"
using namespace std;
using namespace client;
static void Usage(string proc)
{cout<<"\nUsage :\n\t"<<proc<<" serverip serverport\n"<<endl;
}
int main(int argc, char* argv[])
{if(argc!=3){Usage(argv[0]);exit(1);}string serverip=argv[1];
uint16_t serverport=atoi(argv[2]);unique_ptr<tcpclient> tc(new tcpclient(serverip,serverport));tc->initclient();
tc->start();return 0;
}
tcpclient.hpp
#pragma once
#include <iostream>
#include <string>
#include <cstring>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
using namespace std;
#define NUM 1024
namespace client
{class tcpclient
{public:
tcpclient(const string& ip,const uint16_t& port)
:_sock(-1)
,_port(port)
,_ip(ip)
{}void initclient()
{
//1.创建sockfd
_sock=socket(AF_INET,SOCK_STREAM,0);
if(_sock<0)
{cerr<<"socket create error"<<endl;exit(2);
}
//2.绑定 ip port,不显示绑定,OS自动绑定
}void start()
{
struct sockaddr_in ser;
bzero(&ser,sizeof(ser));
socklen_t len=sizeof(ser);
ser.sin_family=AF_INET;
ser.sin_port=htons(_port);
ser.sin_addr.s_addr=inet_addr(_ip.c_str());
if(connect(_sock,(struct sockaddr *)&ser,len)!=0)
{cerr<<"connect error"<<endl;
}else
{string msg;while(true){cout<<"Enter# ";getline(cin,msg);write(_sock,msg.c_str(),msg.size());char inbuffer[NUM];int n=read(_sock,inbuffer,sizeof(inbuffer)-1);if(n>0){cout<<"server return :"<<inbuffer<<endl;}else{break;}}
}
}~tcpclient()
{if(_sock>=0) close(_sock);
}private:
int _sock;
uint16_t _port;
string _ip;};
}
tcpserver.cc
#include"tcpserver.hpp"
#include"log.hpp"
#include<iostream>
#include<stdlib.h>
#include<memory>
using namespace Server;
using namespace std;static void Usage(string proc)
{cout<<"\nUsage:\n\t"<<proc<<" local_port\n\n"<<endl;
}int main(int argc,char* argv[])
{
if(argc!=2)
{Usage(argv[0]);exit(USAGE_ERR);
}uint16_t port=atoi(argv[1]);//将字符串转化为整数unique_ptr<tcpserver> ts(new tcpserver(port));
ts->initserver();
ts->start();return 0;
}
tcpserver.hpp
#pragma once
#include <iostream>
#include <string>
#include <cstring>
#include <cstdlib>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/wait.h>
#include <signal.h>
#include <pthread.h>
#include"log.hpp"
#define NUM 1024using namespace std;
namespace Server
{enum{USAGE_ERR=1,SOCK_ERR,BIND_ERR,LISTEN_ERR};
class tcpserver;class ThreadData{
public:
ThreadData( tcpserver* self,int psock):_this(self),_psock(psock){}
tcpserver* _this;
int _psock;};class tcpserver
{public:tcpserver(const uint16_t& port):_port(port),_listensock(-1){}void initserver()
{
//1.创建套接字
_listensock=socket(AF_INET,SOCK_STREAM,0);
if(_listensock<0)
{logMessage(FATAL,"create listensocket error");exit(SOCK_ERR);
}logMessage(NORMAL, "create socket success: %d", _listensock);
//2.bind ip和port
struct sockaddr_in local;
local.sin_family=AF_INET;
local.sin_port=htons(_port);
local.sin_addr.s_addr=INADDR_ANY;
if(bind(_listensock,(struct sockaddr*)&local,sizeof(local))<0)//绑定失败
{logMessage(FATAL,"bind error");exit(BIND_ERR);
}logMessage(NORMAL,"bind success");
//3.将套接字设置为监听模式
if(listen(_listensock,0)<0)
{logMessage(FATAL,"listen error");exit(LISTEN_ERR);
}
logMessage(NORMAL,"listen success");
}void start()
{// signal(SIGCHLD, SIG_IGN);threadPool<Task>::getthpptr()->run();while(true){struct sockaddr_in cli;socklen_t len=sizeof(cli);bzero(&cli,len);int sock=accept(_listensock,(struct sockaddr*)&cli,&len);if(sock<0){logMessage(FATAL,"accept client error");continue;}logMessage(NORMAL,"accept client success");cout<<"accept sock: "<<sock<<endl;// serviceIO(sock);//客户端串行版// close(sock);//多进程版---//一个客户端占用一个文件描述符,原因在于孙子进程执行IO任务需要占用独立的文件描述符,而文件描述符是继承父进程的,而每次客户端进来都要占用新的文件描述符//因此若接收多个客户端不退出的话文件描述符会越来越少。
// pid_t id=fork();//创建子进程
// if(id==0)//子进程进入
// {
// close(_listensock);//子进程不需要用于监听因此关闭该文件描述符
// if(fork()>0) exit(0);
// //子进程创建孙子进程,子进程直接退出,让孙子进程担任IO任务,且孙子进程成为孤儿进程被OS领养,
// //除非客户端退出IO任务结束否则该孤儿进程一直运行下去不会相互干扰,即并行完成服务器和客户端的通信// //孙子进程
// serviceIO(sock);
// close(sock);
// exit(0);
// }
// //父进程
// pid_t ret=waitpid(id,nullptr,0);
// if(ret<0)
// {
// cout << "waitsuccess: " << ret << endl;
// }//多线程版
// pthread_t pid;
// ThreadData* th=new ThreadData(this,sock);
// pthread_create(&pid,nullptr,start_routine,th);threadPool<Task>::getthpptr()->push(Task(sock,serviceIO));}
}// static void* start_routine(void* args)
// {
// pthread_detach(pthread_self());
// ThreadData* ret=static_cast<ThreadData*>(args);
// ret->_this->serviceIO(ret->_psock);
// close(ret->_psock);
// delete ret;
// return nullptr;
// } // void serviceIO(int sock)
// {
// char inbuffer[NUM];
// while(true)
// {
// ssize_t n=read(sock,inbuffer,sizeof(inbuffer)-1);
// if(n>0)
// {
// inbuffer[n]=0;
// cout<<"recv message: "<<inbuffer<<endl;
// string outb=inbuffer;
// string outbuffer=outb+"[server echo]";// write(sock,outbuffer.c_str(),outbuffer.size());// }
// else
// {
// logMessage(NORMAL,"client quit,i quit yep");
// break;
// }
// }// }~tcpserver(){}private:
int _listensock;//用于监听服务器的sock文件描述符
uint16_t _port;//端口号
};}
1. 单进程版:客户端串行版
#pragma once
#include <iostream>
#include <string>
#include <cstring>
#include <cstdlib>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/wait.h>
#include <signal.h>
#include <pthread.h>
#include"log.hpp"
#define NUM 1024using namespace std;
namespace Server
{enum{USAGE_ERR=1,SOCK_ERR,BIND_ERR,LISTEN_ERR};class tcpserver
{public:tcpserver(const uint16_t& port):_port(port),_listensock(-1){}void initserver()
{
//1.创建套接字
_listensock=socket(AF_INET,SOCK_STREAM,0);
if(_listensock<0)
{logMessage(FATAL,"create listensocket error");exit(SOCK_ERR);
}logMessage(NORMAL, "create socket success: %d", _listensock);
//2.bind ip和port
struct sockaddr_in local;
local.sin_family=AF_INET;
local.sin_port=htons(_port);
local.sin_addr.s_addr=INADDR_ANY;
if(bind(_listensock,(struct sockaddr*)&local,sizeof(local))<0)//绑定失败
{logMessage(FATAL,"bind error");exit(BIND_ERR);
}logMessage(NORMAL,"bind success");
//3.将套接字设置为监听模式
if(listen(_listensock,0)<0)
{logMessage(FATAL,"listen error");exit(LISTEN_ERR);
}
logMessage(NORMAL,"listen success");
}void start()
{while(true){struct sockaddr_in cli;socklen_t len=sizeof(cli);bzero(&cli,len);int sock=accept(_listensock,(struct sockaddr*)&cli,&len);if(sock<0){logMessage(FATAL,"accept client error");continue;}logMessage(NORMAL,"accept client success");cout<<"accept sock: "<<sock<<endl;serviceIO(sock);//客户端串行版close(sock);}
}void serviceIO(int sock)
{char inbuffer[NUM];while(true){ssize_t n=read(sock,inbuffer,sizeof(inbuffer)-1);if(n>0){inbuffer[n]=0;cout<<"recv message: "<<inbuffer<<endl;string outb=inbuffer;string outbuffer=outb+"[server echo]";write(sock,outbuffer.c_str(),outbuffer.size());}
else
{logMessage(NORMAL,"client quit,i quit yep");break;
}}}~tcpserver(){}private:
int _listensock;//用于监听服务器的sock文件描述符
uint16_t _port;//端口号
};}
注意:客户端串行給服务器发数据是在哪里堵塞?由于阻塞在accept函数处,即accept等待客户端接入是阻塞式等待。accept函数接收了一个连接请求后,后来的客户端连接请求需要在accept函数处等待,当上一个客户端退出后,服务器才能accept当前客户端发送来的连接请求成功,才能接收当前客户端的数据。即服务器串行接收处理客户端发送来的数据
2. 多进程版:客户端并行版
tcpserver.hpp
#pragma once
#include <iostream>
#include <string>
#include <cstring>
#include <cstdlib>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/wait.h>
#include <signal.h>
#include <pthread.h>
#include"log.hpp"
#define NUM 1024using namespace std;
namespace Server
{enum{USAGE_ERR=1,SOCK_ERR,BIND_ERR,LISTEN_ERR};
class tcpserver
{public:tcpserver(const uint16_t& port):_port(port),_listensock(-1){}void initserver()
{
//1.创建套接字
_listensock=socket(AF_INET,SOCK_STREAM,0);
if(_listensock<0)
{logMessage(FATAL,"create listensocket error");exit(SOCK_ERR);
}logMessage(NORMAL, "create socket success: %d", _listensock);
//2.bind ip和port
struct sockaddr_in local;
local.sin_family=AF_INET;
local.sin_port=htons(_port);
local.sin_addr.s_addr=INADDR_ANY;
if(bind(_listensock,(struct sockaddr*)&local,sizeof(local))<0)//绑定失败
{logMessage(FATAL,"bind error");exit(BIND_ERR);
}logMessage(NORMAL,"bind success");
//3.将套接字设置为监听模式
if(listen(_listensock,0)<0)
{logMessage(FATAL,"listen error");exit(LISTEN_ERR);
}
logMessage(NORMAL,"listen success");
}void start()
{// signal(SIGCHLD, SIG_IGN);while(true){struct sockaddr_in cli;socklen_t len=sizeof(cli);bzero(&cli,len);int sock=accept(_listensock,(struct sockaddr*)&cli,&len);if(sock<0){logMessage(FATAL,"accept client error");continue;}logMessage(NORMAL,"accept client success");cout<<"accept sock: "<<sock<<endl;//多进程版---//一个客户端占用一个文件描述符,原因在于孙子进程执行IO任务需要占用独立的文件描述符,而文件描述符是继承父进程的,而每次客户端进来都要占用新的文件描述符//因此若接收多个客户端不退出的话文件描述符会越来越少。pid_t id=fork();//创建子进程if(id==0)//子进程进入{close(_listensock);//子进程不需要用于监听因此关闭该文件描述符if(fork()>0) exit(0);
// //子进程创建孙子进程,子进程直接退出,让孙子进程担任IO任务,且孙子进程成为孤儿进程被OS领养,
// //除非客户端退出IO任务结束否则该孤儿进程一直运行下去不会相互干扰,即并行完成服务器和客户端的通信// //孙子进程
serviceIO(sock);
close(sock);
exit(0);}//父进程// close(sock);//父进程不使用文件描述符就关闭pid_t ret=waitpid(id,nullptr,0);if(ret<0){cout << "waitsuccess: " << ret << endl;}}
}
void serviceIO(int sock)
{char inbuffer[NUM];while(true){ssize_t n=read(sock,inbuffer,sizeof(inbuffer)-1);if(n>0){inbuffer[n]=0;cout<<"recv message: "<<inbuffer<<endl;string outb=inbuffer;string outbuffer=outb+"[server echo]";write(sock,outbuffer.c_str(),outbuffer.size());}
else
{logMessage(NORMAL,"client quit,i quit yep");break;
}}}
~tcpserver(){}
private:
int _listensock;//用于监听服务器的sock文件描述符
uint16_t _port;//端口号
};}
-
父进程fork创建子进程,创建完后waitpid等待回收子进程。子进程fork创建孙子进程,创建完后直接退出。导致孙子进程成为孤儿进程,进而被OS领养。因此除非客户端退出IO任务,否则孤儿进程将一直运行下去不会干扰到其他进程,即并行完成服务器和客户端的通信
-
注意的是服务器accept一次客户端的连接请求,就需要申请一个文件描述符,而文件描述符是有上限的,如果大量的客户端请求连接成功并且不结束的话,会造成文件描述符泄露。
因此在父进程那里需要关闭不使用的文件描述符
- 父进程这里回收子进程,不能使用非阻塞等待,原因在于非阻塞等待的本质是轮询,而这里使用后会导致父进程会在accept函数处阻塞等待客户端发送连接请求,那么父进程就无法回收子进程了。因此waitpid的返回值用ret接收,等待回收成功就打印日志,失败则跳过
- 当子进程图退出或者被中止时子进程会发送17号信号SIGCHILD给父进程,父进程可以通过忽略17号信号SIGCHILD的方式来不阻塞等待回收子进程(这种方法对于linux环境可用,其余不保证)
signal(SIGCHLD, SIG_IGN);
netstat查看网络信息
netstat
是一个用于查看网络连接和网络统计信息的命令行工具。它可以用来显示当前系统上的网络连接、路由表、接口统计信息等等。在 Linux 系统中,netstat
命令的用法如下:
netstat [options]
一些常用的选项包括:
-a
:显示所有的连接,包括监听中和已建立的连接。-t
:显示 TCP 协议的连接。-u
:显示 UDP 协议的连接。-n
:以数字形式显示 IP 地址和端口号,而不是尝试进行 DNS 解析。-p
:显示与连接关联的进程信息。-r
:显示路由表。-l
:仅显示监听中的连接。-atun
:显示所有的TCP和UDP连接
注意一下:这里出现了两个连接,原因在于服务器和客户端在同一台主机上,即服务器和客户端完成了本地环回,因此能看到两个连接。
3.多线程版:并行执行
tcpserver.hpp
#pragma once
#include <iostream>
#include <string>
#include <cstring>
#include <cstdlib>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/wait.h>
#include <signal.h>
#include <pthread.h>
#include"log.hpp"
#define NUM 1024using namespace std;
namespace Server
{enum{USAGE_ERR=1,SOCK_ERR,BIND_ERR,LISTEN_ERR};
class tcpserver;class ThreadData{
public:
ThreadData( tcpserver* self,int psock):_this(self),_psock(psock){}
tcpserver* _this;
int _psock;};class tcpserver
{public:tcpserver(const uint16_t& port):_port(port),_listensock(-1){}void initserver()
{
//1.创建套接字
_listensock=socket(AF_INET,SOCK_STREAM,0);
if(_listensock<0)
{logMessage(FATAL,"create listensocket error");exit(SOCK_ERR);
}logMessage(NORMAL, "create socket success: %d", _listensock);
//2.bind ip和port
struct sockaddr_in local;
local.sin_family=AF_INET;
local.sin_port=htons(_port);
local.sin_addr.s_addr=INADDR_ANY;
if(bind(_listensock,(struct sockaddr*)&local,sizeof(local))<0)//绑定失败
{logMessage(FATAL,"bind error");exit(BIND_ERR);
}logMessage(NORMAL,"bind success");
//3.将套接字设置为监听模式
if(listen(_listensock,0)<0)
{logMessage(FATAL,"listen error");exit(LISTEN_ERR);
}
logMessage(NORMAL,"listen success");
}void start()
{while(true){struct sockaddr_in cli;socklen_t len=sizeof(cli);bzero(&cli,len);int sock=accept(_listensock,(struct sockaddr*)&cli,&len);if(sock<0){logMessage(FATAL,"accept client error");continue;}logMessage(NORMAL,"accept client success");cout<<"accept sock: "<<sock<<endl;//多线程版
pthread_t pid;
ThreadData* th=new ThreadData(this,sock);
pthread_create(&pid,nullptr,start_routine,th);}
}static void* start_routine(void* args)
{pthread_detach(pthread_self());//线程分离后让OS自动回收新线程ThreadData* ret=static_cast<ThreadData*>(args);ret->_this->serviceIO(ret->_psock);close(ret->_psock);delete ret;return nullptr;
} void serviceIO(int sock)
{char inbuffer[NUM];while(true){ssize_t n=read(sock,inbuffer,sizeof(inbuffer)-1);if(n>0){inbuffer[n]=0;cout<<"recv message: "<<inbuffer<<endl;string outb=inbuffer;string outbuffer=outb+"[server echo]";write(sock,outbuffer.c_str(),outbuffer.size());}
else
{logMessage(NORMAL,"client quit,i quit yep");break;
}}}
~tcpserver(){}
private:
int _listensock;//用于监听服务器的sock文件描述符
uint16_t _port;//端口号
};}
- 服务器接收一个客户端的连接请求,就申请一个新线程,多线程下可以让服务器接收多个线程
log.hpp
#pragma once#include <iostream>
#include <string>
#include<ctime>
#include <sys/types.h>#include <unistd.h>#include <stdio.h>
#include <stdarg.h>
using namespace std;
#define DEBUG 0
#define NORMAL 1
#define WARNING 2
#define ERROR 3
#define FATAL 4#define NUM 1024
#define LOG_STR "./logstr.txt"
#define LOG_ERR "./log.err"
const char* to_str(int level)
{switch(level){case DEBUG: return "DEBUG";case NORMAL: return "NORMAL";case WARNING: return "WARNING";case ERROR: return "ERROR";case FATAL: return "FATAL";default: return nullptr;}
}void logMessage(int level, const char* format,...)
{// [日志等级] [时间戳/时间] [pid] [messge]// [WARNING] [2023-05-11 18:09:08] [123] [创建socket失败]// 暂定// std::cout << message << std::endl;char logprestr[NUM];
snprintf(logprestr,sizeof(logprestr),"[%s][%ld][%d]",to_str(level),(long int)time(nullptr),getpid());//把后面的内容打印进logprestr缓存区中char logeldstr[NUM];
va_list arg;
va_start(arg,format);
vsnprintf(logeldstr,sizeof(logeldstr),format,arg);//arg是logmessage函数列表中的...cout<<logprestr<<logeldstr<<endl;// FILE* str=fopen(LOG_STR,"a");
// FILE* err=fopen(LOG_ERR,"a");//以追加方式打开文件,若文件不存在则创建// if(str!=nullptr||err!=nullptr)//两个文件指针都不为空则创建文件成功
// {
// FILE* ptr=nullptr;
// if(level==DEBUG||level==NORMAL||level==WARNING)
// {
// ptr=str;
// }
// if(level==ERROR||level==FATAL)
// {
// ptr=err;
// }
// if(ptr!=nullptr)
// {
// fprintf(ptr,"%s-%s\n",logprestr,logeldstr);
// }
// fclose(str);
// fclose(err);//}}
可变参数列表
va_list是(char*)重命名的类型,定义可以访问可变参数的变量。
va_start(ap, v) ap是定义的可变参数变量,v是形参中可变参数前第一个参数名,其作用是使ap指向可变参数部分。
va_arg(ap, t) ap是定义的可变参数变量,t是可变参数的类型,根据类型,访问可变参数列表中的数据。
va_end(ap) ap是定义的可变参数变量,使ap变量置空,作为结束使用。
vsnprintf函数原型
#include <stdio.h>
int vsnprintf(char *str, size_t size, const char *format, va_list ap);
-
str是一个指向字符数组(缓冲区)的指针,用于存储格式化后的数据
-
size是缓冲区的大小,限制了写入的最大字符数,包括终止的 null 字符
-
format格式化字符串,类似于
printf
函数中的格式化字符串 -
ap是一个
va_list
类型的变量,用于存储可变参数列表的信息,并且要注意OS对参数压栈的顺序是从右向左 -
vsnprintf
函数根据指定的format
格式化字符串将数据写入str
缓冲区,但不会超出指定的缓冲区大小。它会在写入数据后自动在缓冲区末尾添加一个 null 终止字符,确保结果是一个合法的 C 字符串。
守护进程
守护进程(Daemon)是在计算机系统中以后台方式运行的一类特殊进程。它通常在操作系统启动时被初始化,并在整个系统运行期间保持活动状态,不需要与用户交互。守护进程通常用于执行系统任务、服务管理以及提供后台服务,如网络服务、定时任务等。
守护进程特点如下:
- 后台运行,守护进程在后台运行,不与用户交互,没有控制终端。
- 独立性:它通常独立于用户会话,即使用户注销或关闭终端,守护进程也会继续运行。
- 没有标准输入输出:守护进程通常没有标准输入和输出,因为它们不与用户交互。它们通常将输出写入日志文件。
- 分离自身:守护进程会通过一系列操作来与终端、会话和控制组脱离连接,以确保它不会意外地被控制终端关闭。
一个服务器中可以具有多个会话,例如一个服务器上有一个root用户和多个普通用户,当普通用户登录上服务器时即成为一个会话。
一个会话具有多个后台任务,但只能具有一个前台任务(bash)。
- jobs查看任务可以看到任务1是./tcpserver,任务2是sleep 1000 | sleep 2000 | sleep 3000 &,任务3是sleep 4000 | sleep 5000 &,且三个任务后面都带&,在进程或任务后带&作用是将该任务放到后台运行
- sleep 1000 、sleep 2000 、sleep 3000 、sleep 4000、sleep 5000的父进程都是16853即bash;而 sleep 1000 、sleep 2000 、sleep 3000的PGID相同,都是sleep 1000的pid,即 sleep 1000 、sleep 2000 、sleep 3000属于同一组,同一个组要协同起来完成同一个作业。第一个任务的pid是组长的pid即sleep 1000的pid;而小组16858和小组17070的SID都是16853,即这两个小组属于同一个会话(bash),要完成的是同一个任务;
fg、bg
fg 作业号:将作业放到前台
bg 作业号:将作业放到后台,或者继续执行后台作业
ctrl+Z将前台任务暂停并把作业放到后台
- 用户登录时服务器就需要为此创建一些后台作业和前台作业(命令行)来服务用户,而用户注销或退出服务器也会影响其前台作业和后台作业。而服务器程序不能受到用户登录和注销的影响。
- 我们可以使得服务器程序自成会话,自成进程组,那么该程序就与终端设备无关,不能再收到用户登录和注销的影响了。该类进程被称为守护进程
setsid
在Unix和类Unix系统中,
setsid
是一个用于创建新会话的系统调用函数。会话(Session)是一组相关的进程组合,通常由一个控制终端和一些子进程组成。setsid
函数的主要作用是将调用它的进程从当前会话中分离出来,并创建一个新的会话。
#include <unistd.h>
pid_t setsid(void);
- 创建新会话:调用
setsid
的进程会成为一个新的会话的组长(Session Leader)。新会话不再与之前的控制终端相关联。但该进程在调用setsid函数之前不能是组长。 - 分离终端:调用
setsid
的进程不再与任何控制终端关联,无法重新获得控制终端。 - 成为新进程组的组长:新会话中的第一个进程(调用
setsid
的进程)会成为新的进程组的组长。
daemon.hpp
#pragma once#include <unistd.h>
#include <signal.h>
#include <cstdlib>
#include <cassert>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#define DEV "/dev/null"
void daemonSelf(const char *currPath = nullptr)
{// 1. 让调用进程忽略掉异常的信号
signal(SIGPIPE,SIG_IGN);//选择忽略SIGPIPE信号// 2. 如何让自己不是组长,setsid
if(fork()>0)
exit(0);//父进程退出// 子进程 -- 守护进程,精灵进程,本质就是孤儿进程的一种!
pid_t ret=setsid();
assert(ret!=-1);// 3. 守护进程是脱离终端的,关闭或者重定向以前进程默认打开的文件
int fd=open(DEV,O_RDWR);
if(fd>=0)
{//dup2(oldfd,newfd):将oldfd的内容填充到newfd中,这样输入到newfd的内容被重定向到oldfddup2(fd,0);dup2(fd,1);dup2(fd,2);
}else
{close(0);close(1);close(2);
}// 4. 可选:进程执行路径发生更改
if(currPath) chdir(currPath);//更改currPath的路径
}
/dev/null
是一个特殊的设备文件,它被用作数据丢弃点,向它写入的数据会被丢弃,从它读取数据会立即返回EOF(End of File)- SIGPIPE的触发场景:当一个进程向一个已经关闭写端的管道(或者套接字)写数据时、当进程向一个已经收到
RST
包(连接重置)的套接字发送数据时,该进程就会向父进程发送SIGPIPE信号来进行进程终止。对SIGPIPE进行忽略行为避免了进程向/dev/null中写入数据并出现错误导致的进程终止 - 父进程创建子进程,父进程作为组长,父进程退出后,子进程能够自己成为组长即能够成为守护进程
- dup2(oldfd,newfd):将oldfd的内容填充到newfd中,这样输入到newfd的内容被重定向到oldfd。在代码中是将输入文件描述符012的内容重定向到fd即/dev/null中
tcpserver.cc
#include"tcpserver.hpp"
#include"log.hpp"
#include"daemon.hpp"
#include<iostream>
#include<stdlib.h>
#include<memory>
using namespace Server;
using namespace std;static void Usage(string proc)
{cout<<"\nUsage:\n\t"<<proc<<" local_port\n\n"<<endl;
}int main(int argc,char* argv[])
{
if(argc!=2)
{Usage(argv[0]);exit(USAGE_ERR);
}uint16_t port=atoi(argv[1]);//将字符串转化为整数unique_ptr<tcpserver> ts(new tcpserver(port));
ts->initserver();
daemonSelf();
ts->start();return 0;
}
相关文章:

网络字节序——TCP接口及其实现简单TCP服务器
网络字节序——TCP接口及其实现简单TCP服务器 文章目录 网络字节序——TCP接口及其实现简单TCP服务器简单TCP服务器的实现1. 单进程版:客户端串行版2. 多进程版:客户端并行版netstat查看网络信息3.多线程版:并行执行log.hpp 守护进程fg、bg s…...

RxJS如何根据事件创建Observable对象?
RxJS提供了一些用来创建Observable对象的函数,我们可以根据事件、定时器、Promise,AJAX等来创建Observable对象。 下面是根据事件创建Observable对象的几个例子。 this.outsideClick$ fromEvent(document, click).subscribe((event: MouseEvent) > …...

网站常见安全漏洞 | 青训营
Powered by:NEFU AB-IN 文章目录 网站常见安全漏洞 | 青训营 网站基本组成及漏洞定义服务端漏洞SQL注入命令执行越权漏洞SSRF文件上传漏洞 客户端漏洞开放重定向XSSCSRF点击劫持CORS跨域配置错误WebSocket 网站常见安全漏洞 | 青训营 网站常见安全漏洞-网站基本组成及漏洞定义…...

vue2使用 vis-network 和 vue-vis-network 插件封装一个公用的关联关系图
效果图: vis组件库:vis.js vis-network中文文档:vis-network 安装组件库: npm install vis-network vue-vis-network 或 yarn add vis-network vue-vis-network 新建RelationGraph.vue文件: <template><…...

给定一个 m x n 的矩阵,如果一个元素为 0 ,则将其所在行和列的所有元素都设为 0 。
LeetCode第73题矩阵置零 1.思路: 想到一个开辟一点空间来解决方法,使用哈希集。就是使用一个哈希集(row和col)来储存数组中的元素为0的下标。然后再遍历,整个二维数组,在哈希集中存在对应的下标,…...

java-初识Servlet,Tomcat,JDBC
文章目录 前言一、ServletServlet 生命周期Servlet 实例Servlet 过滤器 二、TomcatJDBCJDBC连接数据库实例 总结 前言 java入门须知的重要概念/名词/技术 等 一、Servlet Servlet是Java Web开发中的一个核心组件,它是基于Java语言编写的服务器端程序,…...

SpringBoot+mybatis+pgsql多个数据源配置
一、配置文件 jdk环境:1.8 配置了双数据源springbootdruidpgsql,application.properties配置修改如下: #当前入库主数据库 spring.primary.datasource.typecom.alibaba.druid.pool.DruidDataSource spring.primary.datasource.driver-class…...

视频汇聚/视频监控管理平台EasyCVR接入海康SDK协议后无法播放该如何解决?
开源EasyDarwin视频监控/安防监控/视频汇聚EasyCVR能在复杂的网络环境中,将分散的各类视频资源进行统一汇聚、整合、集中管理,在视频监控播放上,视频安防监控汇聚平台可支持1、4、9、16个画面窗口播放,可同时播放多路视频流&#…...

MQ消息队列(主要介绍RabbitMQ)
消息队列概念:是在消息的传输过程中保存消息的容器。 作用:异步处理、应用解耦、流量控制..... RabbitMQ: SpringBoot继承RabbitMQ步骤: 1.加入依赖 <dependency><groupId>org.springframework.boot</groupId&g…...

2023年7月天猫糕点市场数据分析(天猫数据怎么看)
烘焙食品行业是近几年食品领域比较火热的赛道之一,随着居民饮食结构的变化,人均消费水平的上升,蛋糕、面包等烘焙糕点越发成为消费者饮食的重要组成部分。同时,在烘焙糕点市场中,老品牌不断推新迭变,新品牌…...

开源双语对话语言模型 ChatGLM-6B 本地私有化部署
本文首发于:https://www.licorne.ink/2023/08/llm-chatglm-6b-local-deploy/ ChatGLM-6B 是一个开源的、支持中英双语的对话语言模型,基于 General Language Model (GLM) 架构,具有 62 亿参数。结合模型量化技术,用户可以在消费级…...

Zabbix 5.0 媒体介质 邮箱配置例子
QQ企业邮箱 参考:zabbix 腾讯企业邮箱配置图_harveymomo的博客-CSDN博客...

基于Red Hat Enterprise Linux 7操作系统的PostgresSql15的备份恢复(实践笔记)
零、前言 本文是基于阿里云ECS服务器进行的实践操作,操作系统版本:Red Hat Enterprise Linux 7 PG数据库版本:PostgresSql 15 PG安装方式:yum 由于本人新接触pg数据,本次也是出于好奇,就对pg数据库的pg_du…...

AMEYA360:类比半导体推出小尺寸低功耗仪表放大器INA103和INA104
致力于提供高品质芯片的国内优秀模拟及数模混合芯片设计商上海类比半导体技术有限公司(下称“类比半导体”或“类比”)宣布推出小尺寸、低功耗、高性能、零漂移仪表放大器INA103和INA104。该系列产品仅需要一个外部电阻即可设置1到10000的增益,静态电流仅为1.3mA并具…...

【Ubuntu20.04】安装gcc11 g++11, Ubuntu18.04
#查看当前使用的gcc版本命令: gcc -v #更新软件源指令: sudo apt-get update #更新软件指令: sudo app-get upgrade# 添加相应的源 sudo add-apt-repository ppa:ubuntu-toolchain-r/test #更新软件源指令: sudo apt-get update# 卸载已有gcc…...

vim系列之常用命令
一.欢迎来到我的酒馆 在本章节介绍vim编辑器常用命令。 目录 一.欢迎来到我的酒馆二.vim常用命令 二.vim常用命令 2.1vim编辑器常用命令: i: 在光标位置处插入字符。o: 在下一行开始位置插入一行。yy: 复制光标所在的行p: 在光标位置粘贴剪切板内容。...

Scikit-Learn中的特征选择和特征提取详解
概要 机器学习在现代技术中扮演着越来越重要的角色。不论是在商业界还是科学领域,机器学习都被广泛地应用。在机器学习的过程中,我们需要从原始数据中提取出有用的特征,以便训练出好的模型。但是,如何选择最佳的特征是一个关键问…...

Python之动态规划
序言 最近在学习python语言,语言有通用性,此文记录复习动态规划并练习python语言。 动态规划(Dynamic Programming) 动态规划是运筹学的一个分支,是求解决策过程最优化的过程。20世纪50年代初,美国数学家…...

[ES]二基础 |
一、索引库操作 1、mapping属性 mapping是对索引库中文档的约束,常见的mapping属性包括: 1)type:字段数据类型,常见的简单类型有: ①字符串:text(可分词的文本)、keyword(精确值,…...

vscode vue3自定义自动补全
敲代码多了,发现重发动作很多,于是还是定义自动补全代码吧——懒是第一生产力! 1,Ctrl Shift P打开快捷命令行:找到下面这个 2,然后找到ts: 里面给了demo照着写就行 // "Print to conso…...

Spring Cloud + Spring Boot 项目搭建结构层次示例讲解
Spring Cloud Spring Boot 项目搭建结构层次示例讲解 Spring Cloud 项目搭建结构层次示例Spring Cloud示例: Spring Boot 项目搭建结构层次讲解Spring Boot 项目通常按照一种常见的架构模式组织,可以分为以下几个主要层次:当构建一个 Spring…...

使用cgroup工具对服务器某些/全部用户进行计算资源限制
使用cgroup工具对服务器某些/全部用户进行计算资源限制 主要介绍,如何对指定/所有用户进行资源限定(这里主要介绍cpu和内存占用限制),防止某些用户大量占用服务器计算资源,影响和挤占他人正常使用服务器。 安装cgrou…...

C#获取DataTable的前N行数据然后按指定字段排序
获取DataTable的前N行数据然后按指定字段排序 可以使用以下三种代码: 第一种:使用Linq DataTable dtLast dataTable.AsEnumerable().Take(count).OrderBy(dataRow > Convert.ToInt32(dataRow["Sequence"])).CopyToDataTable(); 第二种…...

Swift 中的动态成员查找
文章目录 前言基础介绍基础示例1. 定义一个动态成员访问类:2. 访问嵌套动态成员: 使用 KeyPath 的编译时安全性KeyPath 用法示例KeyPath 进阶使用示例1. 动态访问属性:2. 结合可选属性和 KeyPath:3. 动态 KeyPath 和字典ÿ…...

leetcode做题笔记102. 二叉树的层序遍历
给你二叉树的根节点 root ,返回其节点值的 层序遍历 。 (即逐层地,从左到右访问所有节点)。 思路一:递归 int** levelOrder(struct TreeNode* root, int* returnSize, int** returnColumnSizes){int** ans(int**)mal…...

python编写四画面同时播放swap视频
当代技术让我们能够创建各种有趣和实用的应用程序。在本篇博客中,我们将探索一个基于wxPython和OpenCV的四路视频播放器应用程序。这个应用程序可以同时播放四个视频文件,并将它们显示在一个GUI界面中。 C:\pythoncode\new\smetimeplaymp4.py 准备工作…...

用XSIBackup为VMware ESXi打造完美备份方案
文章目录 VMware ESXi 备份方案引言XSIBackup安装步骤1. XSIBackup软件安装2. SSH连接3. 定位到xsibackup目录4. 修改文件权限5. 安装cron查看crontab列表6. 配置备份任务结论VMware ESXi 备份方案 引言 数据就像是我们的生命线,一旦丢失,可能会带来无法挽回的损失。对于那…...

React 项目中引入msal验证以及部分报错处理
功能实现 如何在React 项目中引入msal身份验证, 微软在官网有提供文档支持,文档包含示例和具体使用的教程,地址如下: https://learn.microsoft.com/zh-cn/azure/active-directory/develop/tutorial-v2-nodejs-webapp-msal 照着文…...

Unity3D 2021 使用 SharpZipLib 遇到的安卓打包 I18N 相关问题
在 Unity3D 中,使用 ICSharpCode.SharpZipLib.dll 来做压缩和解压缩,但打包安卓后遇到问题,原因是字符编码程序集被裁减掉了导致。 根据网上搜索,将 UnityEditor 对应目录下的 I18N开头的,比如 I18N.CJK.dll 等系列文…...

软件工程(十五) 行为型设计模式(一)
1、责任链模式 简要说明 通过多个对象处理的请求,减少请求的发送者与接收者之间的耦合。将接受对象链接起来,在链中传递请求,直到有一个对象处理这个请求。 速记关键字 传递职责 类图如下 由类图可以比较容易的看出来,其实就是自己关联自己,形成了一个链,并且自己有…...