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

网络字节序——TCP接口及其实现简单TCP服务器

网络字节序——TCP接口及其实现简单TCP服务器

森格 (2)

文章目录

  • 网络字节序——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,让系统根据 domaintype 来选择合适的协议。

  • socket()打开一个网络通讯端口,如果成功的话,就像open()一样返回一个文件描述符

  • 应用程序可以像读写文件一样通过socket函数用read/write在网络上收发数据

bind函数原型

int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
  • sockfd 是socket描述符。

  • addr 是一个 struct sockaddr 结构体,包含要绑定的IP地址和端口信息。

  • addrlenaddr 结构体的长度。因为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函数作用是接受传入的连接请求,他会阻塞程序的执行,直到有一个连接请求到达。一旦有连接请求到达,将会创建一个新的套接字,并返回这个新套接字的文件描述符,这个新套接字用于与客户端进行通信,同时addraddrlen会填充上客户端的地址信息。

  • 在服务器程序中,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一次客户端的连接请求,就需要申请一个文件描述符,而文件描述符是有上限的,如果大量的客户端请求连接成功并且不结束的话,会造成文件描述符泄露。

image-20230825163937980

因此在父进程那里需要关闭不使用的文件描述符

image-20230825192206974

  • 父进程这里回收子进程,不能使用非阻塞等待,原因在于非阻塞等待的本质是轮询,而这里使用后会导致父进程会在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连接

image-20230825190401543

注意一下:这里出现了两个连接,原因在于服务器和客户端在同一台主机上,即服务器和客户端完成了本地环回,因此能看到两个连接。

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 字符串。

image-20230825224540368

守护进程

守护进程(Daemon)是在计算机系统中以后台方式运行的一类特殊进程。它通常在操作系统启动时被初始化,并在整个系统运行期间保持活动状态,不需要与用户交互。守护进程通常用于执行系统任务、服务管理以及提供后台服务,如网络服务、定时任务等。

守护进程特点如下:

  1. 后台运行,守护进程在后台运行,不与用户交互,没有控制终端。
  2. 独立性:它通常独立于用户会话,即使用户注销或关闭终端,守护进程也会继续运行。
  3. 没有标准输入输出:守护进程通常没有标准输入和输出,因为它们不与用户交互。它们通常将输出写入日志文件。
  4. 分离自身:守护进程会通过一系列操作来与终端、会话和控制组脱离连接,以确保它不会意外地被控制终端关闭。

一个服务器中可以具有多个会话,例如一个服务器上有一个root用户和多个普通用户,当普通用户登录上服务器时即成为一个会话。

一个会话具有多个后台任务,但只能具有一个前台任务(bash)。

image-20230825231612464

  • 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
  1. fg 作业号:将作业放到前台

  2. bg 作业号:将作业放到后台,或者继续执行后台作业

  3. ctrl+Z将前台任务暂停并把作业放到后台

image-20230825233513024

  • 用户登录时服务器就需要为此创建一些后台作业和前台作业(命令行)来服务用户,而用户注销或退出服务器也会影响其前台作业和后台作业。而服务器程序不能受到用户登录和注销的影响。
  • 我们可以使得服务器程序自成会话,自成进程组,那么该程序就与终端设备无关,不能再收到用户登录和注销的影响了。该类进程被称为守护进程

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

image-20230826112937206

相关文章:

网络字节序——TCP接口及其实现简单TCP服务器

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

RxJS如何根据事件创建Observable对象?

RxJS提供了一些用来创建Observable对象的函数&#xff0c;我们可以根据事件、定时器、Promise&#xff0c;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 插件封装一个公用的关联关系图

效果图&#xff1a; vis组件库&#xff1a;vis.js vis-network中文文档&#xff1a;vis-network 安装组件库&#xff1a; npm install vis-network vue-vis-network 或 yarn add vis-network vue-vis-network 新建RelationGraph.vue文件&#xff1a; <template><…...

给定一个 m x n 的矩阵,如果一个元素为 0 ,则将其所在行和列的所有元素都设为 0 。

LeetCode第73题矩阵置零 1.思路&#xff1a; 想到一个开辟一点空间来解决方法&#xff0c;使用哈希集。就是使用一个哈希集&#xff08;row和col&#xff09;来储存数组中的元素为0的下标。然后再遍历&#xff0c;整个二维数组&#xff0c;在哈希集中存在对应的下标&#xff0c…...

java-初识Servlet,Tomcat,JDBC

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

SpringBoot+mybatis+pgsql多个数据源配置

一、配置文件 jdk环境&#xff1a;1.8 配置了双数据源springbootdruidpgsql&#xff0c;application.properties配置修改如下&#xff1a; #当前入库主数据库 spring.primary.datasource.typecom.alibaba.druid.pool.DruidDataSource spring.primary.datasource.driver-class…...

视频汇聚/视频监控管理平台EasyCVR接入海康SDK协议后无法播放该如何解决?

开源EasyDarwin视频监控/安防监控/视频汇聚EasyCVR能在复杂的网络环境中&#xff0c;将分散的各类视频资源进行统一汇聚、整合、集中管理&#xff0c;在视频监控播放上&#xff0c;视频安防监控汇聚平台可支持1、4、9、16个画面窗口播放&#xff0c;可同时播放多路视频流&#…...

MQ消息队列(主要介绍RabbitMQ)

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

2023年7月天猫糕点市场数据分析(天猫数据怎么看)

烘焙食品行业是近几年食品领域比较火热的赛道之一&#xff0c;随着居民饮食结构的变化&#xff0c;人均消费水平的上升&#xff0c;蛋糕、面包等烘焙糕点越发成为消费者饮食的重要组成部分。同时&#xff0c;在烘焙糕点市场中&#xff0c;老品牌不断推新迭变&#xff0c;新品牌…...

开源双语对话语言模型 ChatGLM-6B 本地私有化部署

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

Zabbix 5.0 媒体介质 邮箱配置例子

QQ企业邮箱 参考&#xff1a;zabbix 腾讯企业邮箱配置图_harveymomo的博客-CSDN博客...

基于Red Hat Enterprise Linux 7操作系统的PostgresSql15的备份恢复(实践笔记)

零、前言 本文是基于阿里云ECS服务器进行的实践操作&#xff0c;操作系统版本&#xff1a;Red Hat Enterprise Linux 7 PG数据库版本&#xff1a;PostgresSql 15 PG安装方式&#xff1a;yum 由于本人新接触pg数据&#xff0c;本次也是出于好奇&#xff0c;就对pg数据库的pg_du…...

AMEYA360:类比半导体推出小尺寸低功耗仪表放大器INA103和INA104

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

【Ubuntu20.04】安装gcc11 g++11, Ubuntu18.04

#查看当前使用的gcc版本命令: gcc -v #更新软件源指令&#xff1a; sudo apt-get update #更新软件指令&#xff1a; sudo app-get upgrade# 添加相应的源 sudo add-apt-repository ppa:ubuntu-toolchain-r/test #更新软件源指令&#xff1a; sudo apt-get update# 卸载已有gcc…...

vim系列之常用命令

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

Scikit-Learn中的特征选择和特征提取详解

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

Python之动态规划

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

[ES]二基础 |

一、索引库操作 1、mapping属性 mapping是对索引库中文档的约束&#xff0c;常见的mapping属性包括&#xff1a; 1)type&#xff1a;字段数据类型&#xff0c;常见的简单类型有&#xff1a; ①字符串&#xff1a;text(可分词的文本)、keyword&#xff08;精确值&#xff0c…...

vscode vue3自定义自动补全

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

【网络】每天掌握一个Linux命令 - iftop

在Linux系统中&#xff0c;iftop是网络管理的得力助手&#xff0c;能实时监控网络流量、连接情况等&#xff0c;帮助排查网络异常。接下来从多方面详细介绍它。 目录 【网络】每天掌握一个Linux命令 - iftop工具概述安装方式核心功能基础用法进阶操作实战案例面试题场景生产场景…...

SpringBoot+uniapp 的 Champion 俱乐部微信小程序设计与实现,论文初版实现

摘要 本论文旨在设计并实现基于 SpringBoot 和 uniapp 的 Champion 俱乐部微信小程序&#xff0c;以满足俱乐部线上活动推广、会员管理、社交互动等需求。通过 SpringBoot 搭建后端服务&#xff0c;提供稳定高效的数据处理与业务逻辑支持&#xff1b;利用 uniapp 实现跨平台前…...

用docker来安装部署freeswitch记录

今天刚才测试一个callcenter的项目&#xff0c;所以尝试安装freeswitch 1、使用轩辕镜像 - 中国开发者首选的专业 Docker 镜像加速服务平台 编辑下面/etc/docker/daemon.json文件为 {"registry-mirrors": ["https://docker.xuanyuan.me"] }同时可以进入轩…...

Caliper 配置文件解析:config.yaml

Caliper 是一个区块链性能基准测试工具,用于评估不同区块链平台的性能。下面我将详细解释你提供的 fisco-bcos.json 文件结构,并说明它与 config.yaml 文件的关系。 fisco-bcos.json 文件解析 这个文件是针对 FISCO-BCOS 区块链网络的 Caliper 配置文件,主要包含以下几个部…...

Linux --进程控制

本文从以下五个方面来初步认识进程控制&#xff1a; 目录 进程创建 进程终止 进程等待 进程替换 模拟实现一个微型shell 进程创建 在Linux系统中我们可以在一个进程使用系统调用fork()来创建子进程&#xff0c;创建出来的进程就是子进程&#xff0c;原来的进程为父进程。…...

A2A JS SDK 完整教程:快速入门指南

目录 什么是 A2A JS SDK?A2A JS 安装与设置A2A JS 核心概念创建你的第一个 A2A JS 代理A2A JS 服务端开发A2A JS 客户端使用A2A JS 高级特性A2A JS 最佳实践A2A JS 故障排除 什么是 A2A JS SDK? A2A JS SDK 是一个专为 JavaScript/TypeScript 开发者设计的强大库&#xff…...

08. C#入门系列【类的基本概念】:开启编程世界的奇妙冒险

C#入门系列【类的基本概念】&#xff1a;开启编程世界的奇妙冒险 嘿&#xff0c;各位编程小白探险家&#xff01;欢迎来到 C# 的奇幻大陆&#xff01;今天咱们要深入探索这片大陆上至关重要的 “建筑”—— 类&#xff01;别害怕&#xff0c;跟着我&#xff0c;保准让你轻松搞…...

【Linux】Linux 系统默认的目录及作用说明

博主介绍&#xff1a;✌全网粉丝23W&#xff0c;CSDN博客专家、Java领域优质创作者&#xff0c;掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域✌ 技术范围&#xff1a;SpringBoot、SpringCloud、Vue、SSM、HTML、Nodejs、Python、MySQL、PostgreSQL、大数据、物…...

自然语言处理——文本分类

文本分类 传统机器学习方法文本表示向量空间模型 特征选择文档频率互信息信息增益&#xff08;IG&#xff09; 分类器设计贝叶斯理论&#xff1a;线性判别函数 文本分类性能评估P-R曲线ROC曲线 将文本文档或句子分类为预定义的类或类别&#xff0c; 有单标签多类别文本分类和多…...

数据结构第5章:树和二叉树完全指南(自整理详细图文笔记)

名人说&#xff1a;莫道桑榆晚&#xff0c;为霞尚满天。——刘禹锡&#xff08;刘梦得&#xff0c;诗豪&#xff09; 原创笔记&#xff1a;Code_流苏(CSDN)&#xff08;一个喜欢古诗词和编程的Coder&#x1f60a;&#xff09; 上一篇&#xff1a;《数据结构第4章 数组和广义表》…...