Linux套接字编程详解

Linux套接字编程

  • 预备知识
    • IP地址和MAC地址
    • 套接字结构
    • 网络字节序
  • UDP套接字编程
      • 服务端代码
      • 客服端代码
    • TCP 套接字
      • 守护进程
  • 计算器
    • 模块1 日志头文件
    • 序列化和反序列化

预备知识

IP地址和MAC地址

MAC地址用来在局域网中标识唯一主机
Ip地址用于在广域网中标识唯一主机
(1)IP地址:

IP协议有两个版本 , IPv4 和IPv6. 我们凡是提到 IP 协议 , 没有特殊说明的 , 默认都是指 IPv4 IP 地址是在 IP
协议中 , 用来标识网络中不同主机的地址 ; 对于 IPv4 来说 , IP 地址是一个4字节, 32位的整数; 我们通常也使用
“点分十进制” 的字符串表示IP地址, 例如 192.168.0.1 ; 用点分割的每一个数字表示一个字节, 范围是 0 - 255;

(2)MAC地址:

MAC地址是物理网卡硬件地址 :用于识别相邻的两个物理硬件设备,它的大小为:6字节 ①长度为48位, 及6个字节 .
一般用16进制数字加上冒号的形式来表示(例如: 08:00:27:03:fb:19)
②在出厂时就会设定,不能修改,MAC地址通常是唯一的,它的大小是6字节,用于识别相邻设备,在链路层完成相邻设备之间的数据传输。
(虚拟机中的mac地址不是真实的mac地址, 可能会冲突; 也有些网卡支持用户配置mac地址). ③MAC地址与网络无关
④一台计算机可以有多个MAC地址:一台计算机可以绑定多个网卡,进而可以拥有多个MAC地址。
举例:IP数据报的收发方进行跨网投递时,发送方需利用ARP协议获取
发送方本网段路由器对应端口的MAC地址,因为当需要跨网络进行传递的时候,也就是意味着需要找到该数据包的下一跳的MAC地址,所以认为从发送方出来,首先先到到达本网段的路由器,所以获取本网段的路由器的MAC地址

套接字结构

套接字由IP地址和端口号组成,其中端口号标识唯一进程。
主机间在通信的本质是:在各自的主机上的两个进程在互相交互数据!
IP地址可以完成主机和主机的通信,而主机上各自的通信进程,才是发送和接受数据的一方

IP :确保主机的唯一性
端口号(port):确保该主机上某一个进程的唯一性(则一个进程只能占用一个端口号)
IP:PORT = 标识互联网中唯一的一个进程!——>这两个合起来叫 socket(套接字)(翻译是插座)
网络通信的本质:就是进程间通信! ! !
端口号(port)是传输层协议的内容:
端口号是一个 2字节16位的整数 ;类型是uint16_t,不过传uint32_t也可以,最终会截断成uint16_t。 因为一般1-1023端口属于系统保留端口,这些端口已经分配给一些应用了,所以我们只能使用1024及以上的端口。
端口号是进程的门,如果一个进程有多个门,那我可以接受多路信息。
如果一个端口可以去多个进程,那么就会出问题,端口就变成十字路口了。
注1: 一个端口号只能被一个进程占用(一个进程可以有多个端口号,但一个端口号不可以对应多个进程,只要保证从端口号到进程的数据链路是唯一的 )
注2: Socket客户端的端口是不固定的, Socket服务端的端口是固定的。
解释:客户端的端口我们推荐是不主动绑定策略,这样可以尽可能的避免端口冲突,让系统选择合适端口绑定,因此不固定;
服务端的端口必须是固定的,因为总是客户端先请求服务端,因此必须提前获知服务端地址端口信息,但是一旦服务器端端口改变,会造成之前的客户端的信息失效找不到服务端了。
思考一下 服务端为什么是固定的? 客服端为什么是动态的?
因为高铁站修好了就不动了,而你可以一直搬家。
理解 “端口号” 和 “进程ID”(端口号的意义)
端口号和进程ID的区别,端口号是进程在网络的户口,进程ID是在操作系统的户口。这样更加方便分层管理。

网络字节序

网络字节序在网络中是大端。可能大家已经忘了大小端,下面我们在介绍一下大小端。
在这里插入图片描述
大端存储在网络中是规定的。
网络和主机字节序的转换函数:
为使网络程序具有可移植性 , 使同样的 C 代码在大端和小端计算机上编译后都能正常运行 , 可以调用以下库函数做网络字节序和主机字节序的转换
在这里插入图片描述

uint32_t htonl (uint32_ t hostlong);——htonl(host to net 主机转网络)

下面是这四个函数的详细讲解:

htonl (host to network long)

功能:将32位无符号整数从主机字节序转换为网络字节序(大端字节序)。

参数:一个32位无符号整数(通常表示IPv4地址或端口号)。

返回值:转换后的32位无符号整数(网络字节序)。

htons (host to network short)

功能:将16位无符号整数从主机字节序转换为网络字节序(大端字节序)。

参数:一个16位无符号整数(通常表示端口号)。

返回值:转换后的16位无符号整数(网络字节序)。

ntohl (network to host long)

功能:将32位无符号整数从网络字节序(大端字节序)转换为主机字节序。

参数:一个32位无符号整数(网络字节序)。

返回值:转换后的32位无符号整数(主机字节序)。

ntohs (network to host short)

功能:将16位无符号整数从网络字节序(大端字节序)转换为主机字节序。

参数:一个16位无符号整数(网络字节序)。

返回值:转换后的16位无符号整数(主机字节序)。

①这些函数名很好记,h表示host,n表示network,l表示32位长整数,s表示16位短整数。例如htonl表示以32位的长整数为单位从主机字节序转换为网络字节序,例如将IP地址转换后准备发送。
②如果主机是小端字节序,这些函数将参数做相应的大小端转换然后返回;
③如果主机是大端字节序,这些函数不做转换,将参数原封不动地返回。

所以主机必须可具备大小端转换,并且保证发到网络中的数据是大端数据。

UDP套接字编程

接下来我们介绍套接字编程,首先介绍流程:
在这里插入图片描述
socket常见API

// 创建 socket 文件描述符 (TCP/UDP, 客户端 + 服务器)
int socket(int domain, int type, int protocol);
// 绑定端口号 (TCP/UDP, 服务器)
int bind(int socket, const struct sockaddr * address,socklen_t address_len);*
// 开始监听socket (TCP, 服务器)
int listen(int socket, int backlog);
// 接收请求 (TCP, 服务器)
int accept(int socket, struct sockaddr* address,socklen_t* address_len);
// 建立连接 (TCP, 客户端)
int connect(int sockfd, const struct sockaddr*addr,socklen_t addrlen);

socket的数据类型:sockaddr结构(套接字的地址结构类型定义)
在这里插入图片描述
前16位是标志,是数据结构的名字。通用数据类型sockaddr,sockaddr_in和sockaddr_un是sockaddr是他们的统一形式,为了方便传参。

struct sockaddr_ in——网络套接字,用于网络通信;
struct sockaddr_un——域间套接字,用于UNIX本地通信。

下面我们详细介绍struct sockaddr_in

struct sockaddr_in {  short            sin_family;   // 地址族,通常为 AF_INET  unsigned short   sin_port;     // 端口号,网络字节序  struct in_addr   sin_addr;     // IPv4 地址  char             sin_zero[8];  // 填充至 struct sockaddr 的大小,通常不用  
};

字符串风格的IP地址转为4字节地址 inet_addr
4字节转字符串 inet_ntoa

in_addr_t inet_addr(const char *cp); 

网络服务 recvfromsendto

 ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr *src_addr, socklen_t *addrlen);

从特定套接字 sockfd中读取数据到缓冲区buf中,buf大小为len,flags设为0——阻塞式读取

src_addr:(输出型参数)当服务器读取客户端发送的消息时——哪个客户端给你发的消息,就把这个客户端套接字信息存入src_addr中。(src_addr的类型是套接字类型指针struct
sockaddr*,传入的网络套接字类型struct sockaddr_in需要强转成此类型指针 struct sockaddr。)

addrlen:(输入输出型参数)客户端这个缓冲区大小。(socklen_t就是unsigned int)

返回值:返回读到的字节数,错误就返回-1错误码被设置

该接口为阻塞方式接口。接收端收到消息后,就已经知道发送方的套接字,并不需要再次接收。

socket编程三部曲: 1创 2绑 3发

部分细节解释+代码(udp套接字)
易错:1. port_ 端口号是一个 2字节16位的整数,主机转网络要用htons,不能用htonl.16位是短整型
server.sin_port=htons(server_port);

htonl 是转换四字节的,如果你传入一个两字节的数据,它就会自动进行补位,补位前面部分都是零,那这时候经过htonl置换之后,前16位就变成零了,相当于你的程序跑去绑定零端口去了,就会绑定失败。

(1)INADDR_ANY

 #define INADDR_ANY    ((in_addr_t) 0x00000000)
local.sin_addr.s_addr = ip_.empty() ? htonl(INADDR_ANY) : inet_addr(ip_.c_str());

①INADDR_ANY (这个宏的值就是0): 程序员不关心会bind到哪一个ip, 任意地址bind,强烈推荐的做法,所有服务器一般的做法(解释:一般服务器只有一个IP,会自动bind这个IP;如果服务器有多个IP,会自动bind这个服务器的所有的IP——因为如果有两个IP:IP1和IP2,只bind一个IP1,那么只有传给IP1的报文会交给程序,IP2就不会提交报文)

云服务器有一些特殊情况:禁止你bind云服务器上的任何确定IP, 所以这里只能使用INADDR_ANY,如果你是虚拟机就可以bind自己虚拟机的IP,用ifconfig查看IP。

注意:这里inet_addr(ip_.c_str()) 当ip_是"0"时 等价于INADDR_ANY,INADDR_ANY 这个宏的值就是0,0是字符串风格还是网络风格无所谓,并且inet_addr 还会自动给我们进行 h—>n 主机字节序转网络字节序,即 inet_addr(0)=inet_addr(INADDR_ANY)=htonl(INADDR_ANY) 作用是一样的
UDPsocke的创建
1.创 创建UDPsocket文件描述符:

sockfd_=socket(AF_INET,SOCK_DGRAM,0);
if(sockfd_<0)
{log(Fatal,"socket creat error, sockfd : %d",sockfd_);exit(SOCKET_ERR);
}

在这里插入图片描述
2 绑 绑定之前需要设定好需要被绑定的信息:
在这里插入图片描述
接下来我们详细介绍一下sockaddr_in结构体的内部:
在这里插入图片描述

服务端代码

这就是他的结构体类型,我开始依次绑定三个信息地址族,端口号,IPV4地址信息。

local.sin_family=AF_INET;//表示我使用IPV4协议族
local.sin_port=htons(port_);//字节序的转换,不管你是什么字节序,在发送时都必须转换为网络字节序
local.sin_addr.s_addr=inet_addr(ip_.c_str());
//字符串风格ip转转ip 

inet_addr有两个功能一个是字符串转ip 。
一个是主机序转网络序。
3发 发送消息,其实就是像文件中写入。

recvfrom
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr *src_addr, socklen_t *addrlen);

在这里插入图片描述

inet_ntoa(client.sin_addr)
32位IPV4转换为点分十进制IP
ntohs(client.sin_port)
网络序转

刚刚我们写完了服务器,现在我们来描述一下客服端。
客服端可以主动给服务器发消息,所以我们需要知道,客服端到底给谁发以及发什么。
所以我可以用我们之前学的命令行参数,直接给main函数传参。

int main(int argc, char *argv[])
{if (argc != 3){Usage(argv[0]);exit(0);}std::string serverip = argv[1];uint16_t serverport = std::stoi(argv[2]);

我们可以直接将通信的ip 以及端口号 传进去。组成基本套接字。

客服端代码

客服端 也是1创 2绑 3发
接下来我们继续创建客服端:

1 创 创建端口

   int sockfd = socket(AF_INET, SOCK_DGRAM, 0);if (sockfd < 0){cout << "socker error" << endl;return 1;}

2 绑 绑定之前先写入IP信息

struct sockaddr_in client;client.sin_family=AF_INET; 
client.sin_port=htons(serverport);//端口号 转换 
client.sin_addr.s_addr=inet_addr(serverip.c_str()); // 字符串风格IP转网络字节序整数
socklen_t len =sizeof(client);// client 要bind吗?要!只不过不需要用户显示的bind!一般有OS自由随机选择!// 一个端口号只能被一个进程bind,对server是如此,对于client,也是如此!// 其实client的port是多少,其实不重要,只要能保证主机上的唯一性就可以!// 系统什么时候给我bind呢?首次发送数据的时候

注意: 服务器需要固定的端口号和ip地址 客服端不需要!旅客可以变,旅馆不能变。

小贴士: 可以通过netstat -naup 查看端口号
在这里插入图片描述
2. 云服务禁止绑定公网IP
3. 0-1023系统内定了,不能使用。都被固定应用层用了。

  1. 注意 客服端的端口号并不需要固定,服务器的端口号是固定的,由你选择的协议而定

我们知道,服务器 IP和端口号固定。但是用户端 ip和端口都不需要固定 。我们测试这个服务器的端口号。
在这里插入图片描述
我们通过测试发现,系统每次绑定的并不是同一个。
接下来我们附上udp代码:

#pragma once#include <iostream>
#include <string>
#include <strings.h>
#include <cstring>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <functional>
#include "Log.hpp"
using namespace std;
// using func_t = std::function<std::string(const std::string&)>;
typedef std::function<std::string(const std::string&)> func_t;extern Log lg;enum{SOCKET_ERR=1,BIND_ERR
};uint16_t defaultport = 8080;
std::string defaultip = "0.0.0.0";
const int size = 1024;class UdpServer{
public:UdpServer(const uint16_t &port = defaultport, const std::string &ip = defaultip):sockfd_(0), port_(port), ip_(ip),isrunning_(false){}void Init(){// 1. 创建udp socketsockfd_ = socket(AF_INET, SOCK_DGRAM, 0); // PF_INETif(sockfd_ < 0){lg(Fatal, "socket create error, sockfd: %d", sockfd_);exit(SOCKET_ERR);}lg(Info, "socket create success, sockfd: %d", sockfd_);// 2. bind socketstruct sockaddr_in local;bzero(&local, sizeof(local));local.sin_family = AF_INET;local.sin_port = htons(port_); //需要保证我的端口号是网络字节序列,因为该端口号是要给对方发送的local.sin_addr.s_addr = inet_addr(ip_.c_str()); //1. string -> uint32_t 2. uint32_t必须是网络序列的 // ??// local.sin_addr.s_addr = htonl(INADDR_ANY);if(bind(sockfd_, (const struct sockaddr *)&local, sizeof(local)) < 0){lg(Fatal, "bind error, errno: %d, err string: %s", errno, strerror(errno));exit(BIND_ERR);}lg(Info, "bind success, errno: %d, err string: %s", errno, strerror(errno));}void Run() // 对代码进行分层{isrunning_ = true;char inbuffer[size];while(isrunning_){struct sockaddr_in client;socklen_t len = sizeof(client);ssize_t n = recvfrom(sockfd_, inbuffer, sizeof(inbuffer) - 1, 0,(struct sockaddr*)&client, &len);sockaddr_in clientTmp=(sockaddr_in)client;string IP(inet_ntoa(clientTmp.sin_addr));uint16_t p=ntohs(client.sin_port);if(n < 0){lg(Warning, "recvfrom error, errno: %d, err string: %s", errno, strerror(errno));continue;}inbuffer[n] = 0;string buffer=inbuffer;std::string info ;info="client@:"+buffer;cout<<"test:"<<p<<endl;cout<<"test:"<<info<<" client ip:"<<IP<<" client port:"<<(uint16_t)p<<endl;sendto(sockfd_, info .c_str(), info .size(), 0, (const sockaddr*)&client, len);}}~UdpServer(){if(sockfd_>0) close(sockfd_);}
private:int sockfd_;     // 网路文件描述符std::string ip_; // 任意地址bind 0uint16_t port_;  // 表明服务器进程的端口号bool isrunning_;
};
#pragma once
#include <iostream>
#include <pthread.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <functional>
#include <vector>
#include <string>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <functional>
#include <time.h>
#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>
#include <mutex> 
#include<netinet/in.h>
#include<string.h>
#include"Log.hpp"
#include<semaphore.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
using namespace std;void Usage(std::string proc)
{std::cout << "\n\rUsage: " << proc << " serverip serverport\n"<< std::endl;
}int main(int argc, char *argv[])//启动客服端必须告知你要访问的ip 端口等信息
{if (argc != 3){Usage(argv[0]);exit(0);}std::string serverip = argv[1];uint16_t serverport = std::stoi(argv[2]); //端口号字符串int sockfd =socket(AF_INET,SOCK_DGRAM,0);if(sockfd<0){cout<<"socker error"<<endl;return 1;}struct sockaddr_in client;client.sin_family=AF_INET; client.sin_port=htons(serverport);//端口号 转换 client.sin_addr.s_addr=inet_addr(serverip.c_str()); // 字符串风格IP转网络字节序整数socklen_t len =sizeof(client);// client 要bind吗?要!只不过不需要用户显示的bind!一般有OS自由随机选择!// 一个端口号只能被一个进程bind,对server是如此,对于client,也是如此!// 其实client的port是多少,其实不重要,只要能保证主机上的唯一性就可以!// 系统什么时候给我bind呢?首次发送数据的时候string message;char buffer[1024];while (true){cout << "Please Enter@ ";getline(cin, message);//std::cout << message << std::endl;// 1. 数据 2. 给谁发sendto(sockfd, message.c_str(), message.size(), 0, (struct sockaddr *)&client, len);struct sockaddr_in temp;socklen_t lent = sizeof(temp);ssize_t s = recvfrom(sockfd, buffer, 1023, 0, (struct sockaddr*)&temp, &lent);if(s > 0){buffer[s] = '\0';    cout << buffer << endl;}}close(sockfd);return 0;
}

TCP 套接字

再次介绍地址族和套接字族:
地址族(Address Family)

地址族是指网络中主机的地址类型。在网络编程中,地址族决定了套接字所使用的网络通信协议和地址格式。

AF_INET:这是一个常用的地址族,表示使用IPv4协议。IPv4是互联网上使用最广泛的协议之一,它使用32位的IP地址。当你创建一个使用IPv4地址的套接字时,你会使用AF_INET作为地址族参数。
AF_INET6:这个地址族用于IPv6协议。IPv6是IPv4的下一代协议,它使用128位的IP地址,提供了更多的地址空间和其他一些改进。

套接字类型(Socket Type)

套接字类型决定了套接字的工作方式和特性。不同的套接字类型适用于不同的应用场景。

SOCK_STREAM:这是一个面向连接的套接字类型,通常用于TCP协议。它提供了可靠、有序的、基于字节流的通信。

SOCK_DGRAM:这是一个无连接的套接字类型,通常用于UDP协议。它提供了不可靠的、基于数据报的通信。

介绍完之后,我们依然在初始化的时候对,TCP服务器开始三步走

//1 创
sockfd_=socket(AF_INET, SOCK_STREAM,0);if(sockfd_<0){
log(Fatal,"socket is worring");
exit(-1);}log(Info,"sockfd is %d",sockfd_);
//2 写struct sockaddr_in server;memset(&server,0,sizeof(server));server.sin_family=AF_INET;server.sin_port=htons(port_);server.sin_addr.s_addr=inet_addr(ip_.c_str());//3 绑
if(bind(sockfd_, (const struct sockaddr *)&server, sizeof(server))<0)
{log(Fatal,"bind errno");exit(-1);
}
if(listen(sockfd_,backlog)<0)
{log(Fatal,"listen errno");exit(-1);
}

TCP多了一步 听。TCP是面向链接的,建立链接了才能发。

void Run()
{log(Info,"Tcp is running");
while(1)
{//创建新链接 sockfd
struct sockaddr_in client;
socklen_t len =sizeof(client);int sockfd=accept(sockfd_,(sockaddr*)&client,&len);
// 根据新链接通信if(sockfd<0)
{log(Warning,"accecpt is waitting");continue;
} 
char* ipstr=new char[32];uint16_t clientport =ntohs(client.sin_port);inet_ntop(AF_INET,&(client.sin_addr),ipstr,32);log(Info,"get a new link..., sockfd:%d, client ip: %s\n ",sockfd,ipstr);}
}
inet_ntop(AF_INET,&(client.sin_addr),ipstr,32);

在本接口中输入型参数。用来获取转换后的ip。该函数没有线程不安全的问题。

查看TCP网络服务器情况和端口使用情况 netstat -nltp
在这里插入图片描述
注意:TCP是流式套接字,我们用wirte写入,read读取。
在这里插入图片描述
TCP服务端的创建
1创 2写 3绑

class Tcpserver
{
public:
Tcpserver(const uint16_t port=DEAFLITPORT,const string ip="0.0.0.0")
:port_(port),sockfd_(0),ip_(ip)
{}void Init()//Tcp初始化
{
//1 创
sockfd_=socket(AF_INET, SOCK_STREAM,0);if(sockfd_<0){
log(Fatal,"socket is worring");
exit(-1);}log(Info,"sockfd is %d",sockfd_);
//2 写struct sockaddr_in server;memset(&server,0,sizeof(server));server.sin_family=AF_INET;server.sin_port=htons(port_);server.sin_addr.s_addr=inet_addr(ip_.c_str());//3 绑
if(bind(sockfd_, (const struct sockaddr *)&server, sizeof(server))<0)
{log(Fatal,"bind errno");exit(-1);
}
if(listen(sockfd_,backlog)<0)
{log(Fatal,"listen errno");exit(-1);
}}void Run()
{int sockfd;log(Info,"Tcp is running");
while(1)
{//创建新链接 sockfd
struct sockaddr_in client;
socklen_t len =sizeof(client);sockfd=accept(sockfd_,(sockaddr*)&client,&len);
// 根据新链接通信if(sockfd<0)
{log(Warning,"accecpt is waitting");continue;
} 
char* ipstr=new char[32];uint16_t clientport =ntohs(client.sin_port);inet_ntop(AF_INET,&(client.sin_addr),ipstr,32);log(Info,"get a new link..., sockfd:%d, client ip: %s\n ",sockfd,ipstr);
break;
}
string infomassage ;while(1)
{infomassage.clear();char *str=new char[1024];ssize_t s = read(sockfd, str, strlen(str));infomassage=str;if(s>0){string tmp("revice:");infomassage+=tmp;size_t num=write(sockfd,infomassage.c_str(),infomassage.size());std::cout << "Server Echo>>> " <<infomassage << std::endl;}else{break;}}}~Tcpserver()
{}
private:int sockfd_;     // 网路文件描述符std::string ip_; // 任意地址bind 0uint16_t port_;  // 表明服务器进程的端口号};

后面我们会用一个计算机串联一切知识点。

守护进程

有一种进程他会残留信息造成进程信息,称之为僵尸进程。有一种暖心的进程叫做守护进程。
这两种是同一种进程的不同翻译,是特殊的孤儿进程,不但运行在后台,最主要的是脱离了与终端和登录会话的所有联系,也就是默默的运行在后台不想受到任何影响,并且退出后不会成为僵尸进程。
进程关系图
在这里插入图片描述
前后台进程
用户登录时会建立一个会话,会话内部会构建一个前台进程组 和 0个或者多个后台进程组,linux下客户端登录时 会给我们加载bash,bash就是前台进程组。(windows下的注销就是新建立一个会话)前台进程组必须有一个,而且任何时刻只能有一个。
在这里插入图片描述
2. 守护进程的创建
守护进程的创建分两步:

  1. fork创建子进程。
  2. 父进程退出,并且调用setsid()函数接口。

必做:fork+setsid()——让自己不成为进程组组长+设置自己是一个独立的会话
那我如何不成为组长以便调用setsid呢?——bash中新启动第一个进程一定成为组长,所以你可以成为进程组内的第二个进程。即:常规做法:fork()子进程,子进程就不再是组长进程了,它就可以成功调用setsid(); ————

if(fork() > 0) exit(0) ;
setsid() ;        

改守护进程的工作目录,如何更改进程的工作目录?—chdir()

(3)一般守护进程都要做的(必做):

  1. (不常用做法一)因为守护进程与标准输入,标准输出,标准错误已经没关系了,所以close(0, 1,2) 守护进程获取输入或写入都是和网络有关,不会从键盘获取,不会往显示器输出。(很少有人这样做,因为兼容性不好,会导致代码中的打印代码报错)

守护进程作为后台进程,就不能把自己的输出排放到显示器上。所以,他应该把产生的一切信息写入垃圾箱。
类似于所有Linux下的一个”垃圾桶(文件黑洞)“,凡是从 /dev/null 里面读/写一概被丢弃

推荐做法:打开/dev/null, 并且对 0,1,2 进行重定向!

总结:1.忽略SIGPIPE
2.更改进程的工作目录
3.让自己不要成为进程组组长(必做)
4.设置自己是一个独立的会话(必做)
5.重定向0,1,2(必做)

计算器

该计算器具有

  1. 多线程(生成消费者模型) 2. 守护进程 3. 打印日志 4. 计算数据四个功能。并且该计算机的协议自己手动实现。使用TCP/IP协议。

模块1 日志头文件

#include <iostream>
#include <time.h>
#include <stdarg.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>
#include <fstream> // 如果您打算使用 C++ 的文件流  
#include <cstring> // 如果您使用 C 风格的字符串操作 
#include <errno.h>
#include <cerrno>
#define Info 0
#define Debug 1
#define Warning 2
#define Error 3
#define Fatal 4#define Screen 1
#define Onefile 2
#define Classfile 3
using namespace std;
#define FILENAME "log.txt"#define SIZE 1024
class Log
{
public:
Log(int printMethod=Screen,std::string path="./log/")//构造函数
:_path(path),_printMethod(printMethod)
{}
//错误等级 输入错误等级返回字符串
std::string LevelTostring(int level)
{
switch (level){case Info:return "Info";case Debug:return "Debug";case Warning:return "Warning";case Error:return "Error";case Fatal:return "Fatal";default:return "None";}
}
void printLog(int level, const std::string &logtxt)  
{  // 根据 printMethod 的值选择不同的日志输出方式  switch (_printMethod)  {  // 如果 printMethod 为 Screen,则将日志内容输出到屏幕  case Screen:  std::cout << logtxt << std::endl;  break;  // 如果 printMethod 为 Onefile,则将日志内容输出到指定的日志文件中  case Onefile:  printOneFile(FILENAME,logtxt);  break;  // 如果 printMethod 为 Classfile,则根据日志级别和日志内容输出到分类的日志文件中  case Classfile:  printClassFile(level, logtxt);  break;  // 如果 printMethod 的值不是上述任何一种,则不执行任何操作  default:  break;  }  
}void printOneFile(const std::string &Filepanth,const std::string &Filetxt ) {
//文件在系统中的路径std::string _Filepanth=_path+Filepanth;//可读可写方式创建log.txt
int fd = open(_Filepanth.c_str(), O_WRONLY | O_CREAT | O_APPEND, 0666); // "log.txt"
if(fd<0)
{return ;
}
write(fd,Filetxt.c_str(),Filetxt.size());}
void printClassFile(int level,const std::string Filetxt)
{std::string _Filepanth =_path+ LevelTostring(level)+"/"+FILENAME;// cout<<_Filepanth<<endl;int fd = open(_Filepanth.c_str(), O_WRONLY | O_CREAT | O_APPEND, 0666); // "log.txt"
//cout<<fd<<endl;
if(fd<0)
{perror("Error opening file"); // 这将打印一个描述错误的消息 return ;
}
std:: string tmp("\n");
std::string Filetxttmp=Filetxt+tmp;
write(fd,Filetxttmp.c_str(),Filetxttmp.size());
} void operator()(int level, const char *format, ...){time_t t = time(nullptr);struct tm *ctime = localtime(&t);char leftbuffer[SIZE];snprintf(leftbuffer, sizeof(leftbuffer), "[%s][%d-%d-%d %d:%d:%d]", LevelTostring(level).c_str(),ctime->tm_year + 1900, ctime->tm_mon + 1, ctime->tm_mday,ctime->tm_hour, ctime->tm_min, ctime->tm_sec);va_list s;va_start(s, format);char rightbuffer[SIZE];vsnprintf(rightbuffer, sizeof(rightbuffer), format, s);int len=strlen(rightbuffer);rightbuffer[len]='\n';va_end(s);// 格式:默认部分+自定义部分char logtxt[SIZE * 2];snprintf(logtxt, sizeof(logtxt), "%s %s", leftbuffer, rightbuffer);// printf("%s", logtxt); // 暂时打印printLog(level, logtxt);}
~Log()
{}private:std::string _path;//文件所在路径
int _printMethod;//打印方法
};

序列化和反序列化

序列化和反序列化 序列化和反序列化是保证数据的完整性的工作。
直接发送同样的结构体对象,是不可取的,虽然在某些情况下,它确实行,但是我们需要进行序列化和反序列化

定义:定义结构体来表示我们需要交互的信息 ; 发送数据时将这个结构体按照一个规则转换成字符串, 接收到数据的时候再按照相同的规则把字符串转化回结构体; 这个过程叫做 " 序列化 " 和 " 反序列化 "
序列化,反序列化的操作是在用户发送和网络发送中间加了一层软件层,
———————————————————————
序列化和反序列化的示意图:
在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/pingmian/2804.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

2767. 将字符串分割为最少的美丽子字符串

2767. 将字符串分割为最少的美丽子字符串 Java: class Solution {int cnt;int ans;int[] arr;Set<Integer> set;private void dfs(String s, String s1, int pos, int len) {if (pos len) {ans Math.min(ans, cnt);return;}for(int i pos; i < len; i) {String s2…

软件游戏缺失d3dcompiler_43.dll怎么修复?分享多种靠谱的解决方法

在我们日常频繁地操作和使用电脑的过程中&#xff0c;时常会遇到一些突发的技术问题。其中一种常见的情况是&#xff0c;在尝试启动或运行某个应用程序时&#xff0c;系统会弹出一个错误提示窗口&#xff0c;明确指出当前电脑环境中缺少了一个至关重要的动态链接库文件——d3dc…

Linux配置腾讯云yum源(保姆级教学)

1. 备份原有的 yum 源配置文件 例如&#xff1a; mv /etc/yum.repos.d/CentOS-Base.repo /etc/yum.repos.d/CentOS-Base.repo.backup 2. 下载腾讯云的 yum 源配置文件 例如&#xff1a; wget -O /etc/yum.repos.d/CentOS-Base.repo http://mirrors.cloud.tencent.com/repo/…

将数字状态码在后台转换为中文状态

这是我们的实体类 可以看出我们的状态status是2如果返回到前端我们根本不知道2代表的是什么&#xff0c;所以我们需要再这里将数字转换成能看懂的中文状态&#xff0c;首先我们创建一个枚举类 先将我们状态码所对应的中文状态枚举出来&#xff0c;然后创建一个静态方法&#…

基于OpenCV+QT的人脸识别打卡项目

1.基本概念 基于OpenCV的人脸识别是一个多步骤的过程&#xff0c;通常涉及以下步骤&#xff1a; 人脸检测&#xff1a;使用Haar级联或深度学习模型来检测图像中的面部区域。OpenCV提供了预训练的Haar级联分类器&#xff0c;可以用于快速检测。 特征提取&#xff1a;一旦检测到…

eCharts 折线图 一段是实线,一段是虚线的实现效果

在lineStyle里写了不生效的话&#xff0c;可以尝试数据拼接 option {xAxis: {type: category,data: [Mon, Tue, Wed, Thu, Fri, Sat, Sun]},yAxis: {type: value},series: [{data: [150, 230, 224,218 ,,,],type: line},{data: [,,, 218, 135, 147, 260],type: line,lineStyl…

【御控工业物联网】JAVA JSON结构转换、JSON结构重构、JSON结构互换(5):对象To对象——转换映射方式

御控官网&#xff1a;https://www.yu-con.com/ 文章目录 御控官网&#xff1a;[https://www.yu-con.com/](https://www.yu-con.com/)一、JSON结构转换是什么&#xff1f;二、术语解释三、案例之《JSON对象 To JSON对象》四、代码实现五、在线转换工具六、技术资料 一、JSON结构…

JavaScript实现代码雨

一、功能描述 使用canvas实现一个代码雨的功能&#xff0c;炫一个~~~ 二、上码 html <canvas id"canvas"></canvas> js let canvas document.querySelector(canvas);let ctx canvas.getContext(2d);// screen.availWidth:可视区域的宽度canvas.width…

深度学习框架pytorch:tensor.data和tensor.detach()的区别

本文重点 本文我们区别一下tensor.data和tensor.detach(),我们所讲解的都是pytorch的1.0版本的情况 官方解释 返回一个新的张量,它与当前图形分离。结果永远不需要梯度。返回的张量与原始张量共享相同的存储空间。将看到对其中任何一个的就地修改,并且可能在正确性检查中…

测试用例设计方法-探索性测试

生活犹如骑单车&#xff0c;唯有前进才能保持平衡。大家好&#xff0c;今天给大家分享一下关于探索性测试的方法&#xff0c;在探索性测试中更加考验测试人员的经验&#xff0c;所以我们在平时的测试工作中一定要多记录、多总结、多复盘&#xff0c;对于经常出现的bug深究其根本…

【论文解析】笔触渲染生成 前沿工作梳理

最近的一些工作梳理 2023年 Stroke-based Neural Painting and Stylization with Dynamically Predicted Painting Region 2022年Im2Oil: Stroke-Based Oil Painting Rendering with Linearly Controllable Fineness Via Adaptive Sampling 文章目录 1 Stroke-based Neural P…

Python实现“黑猫投诉平台,舆论监控系统”

黑猫投诉平台&#xff0c;舆论监控系统 BuzzMonitor https://github.com/nangongchengfeng/BuzzMonitor.git 简介 "黑猫投诉"舆论监控系统是一款专为快速识别和响应网络投诉而设计的应用&#xff0c;旨在帮助企业或机构第一时间掌握公众意见和反馈。通过实时监控网…

Echarts异步数据与动画加载

目录 简介 头部代码 这段代码是使用 Echarts 绘制图表的关键部分。首先&#xff0c;初始化了一个 Echarts 实例。然后&#xff0c;通过 Ajax 请求获取数据&#xff0c;并基于此设置图表选项。其中包括颜色、背景色、标题、提示框、图例以及饼图的具体配置。 具体解释如下&a…

excel相同行不同列查询

EXCEL中e列和f列是每一行对应的&#xff0c;我想在d列中找和e列一样的元素&#xff0c;然后获取同一行中f列的值 IFERROR(VLOOKUP(D1, E:F, 2, FALSE), "")

MySQL索引为什么选择B+树,而不是二叉树、红黑树、B树?

12.1.为什么没有选择二叉树? 二叉树是一种二分查找树,有很好的查找性能,相当于二分查找。 二叉树的非叶子节值大于左边子节点、小于右边子节点。 原因: 但是当N比较大的时候,树的深度比较高。数据查询的时间主要依赖于磁盘IO的次数,二叉树深度越大,查找的次数越多,性能…

python爬虫 - 爬取 json 格式数据(股票行情信息:雪球网,自选股)

文章目录 1. 第一步&#xff1a;安装requests库2. 第二步&#xff1a;获取爬虫所需的header和cookie3. 第三步&#xff1a;获取网页4. 第四步&#xff1a;解析网页5. 第五步&#xff1a;解析 json 结构数据体6. 代码实例以及结果展示 python爬虫五部曲&#xff1a; 第一步&…

157平新中式复式装修,双客厅设计+开放式客餐厅。福州中宅装饰,福州装修

设计亮点 这个户型改造案例的亮点在于户型空间结构的优化。与传统复式户型不同&#xff0c;一层被重新规划为家庭入户门厅及会客厅茶室&#xff1b;而二楼则作为传统的生活区&#xff0c;突破了原有空间限制&#xff0c;为居住者提供更多自由活动空间。 改造亮点&#xff1a; ①…

江开2024年春《大学英语(B)(2) 060052》过程性考核作业4参考答案

答案&#xff1a;更多答案&#xff0c;请关注【电大搜题】微信公众号 答案&#xff1a;更多答案&#xff0c;请关注【电大搜题】微信公众号 答案&#xff1a;更多答案&#xff0c;请关注【电大搜题】微信公众号 单选题 1阅读Passage One&#xff0c;回答C-1C-4个问题。请…

QT中对于QPushButton样式的调整

文章目录 前言1.QPushButton1.1 新建项目导入资源1.2 添加Push Button并定义样式1.3 调整样式1.4 实际需求情况1.5 背景色和边框 2. 一些概念理解2.1 图片2.2 边距 总结 前言 前段时间在调软件的样式&#xff0c;学到了些新的东西&#xff0c;也碰到了些问题&#xff0c;这里做…

4.26日学习记录

[湖湘杯 2021 final]Penetratable SUID提权 SUID是一种对二进制程序进行设置的特殊权限&#xff0c;可以让二进制程序的执行者临时拥有属主的权限 SUID具有一定的限制&#xff1a; 1.仅对于二进制有效&#xff1b; 2.执行者在程序中有可以执行的权限&#xff1b; 3.权限仅在程序…