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 socket
        sockfd_ = socket(AF_INET, SOCK_DGRAM, 0); // PF_INET
        if(sockfd_ < 0)
        {
            lg(Fatal, "socket create error, sockfd: %d", sockfd_);
            exit(SOCKET_ERR);
        }
        lg(Info, "socket create success, sockfd: %d", sockfd_);
        // 2. bind socket
        struct 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 0
    uint16_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 0
    uint16_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.mfbz.cn/a/570987.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

李廉洋:4.24-4.25现货黄金,WTI原油区间震荡,走势分析。

黄金消息面分析&#xff1a;金银近日回调。随着伊朗方面淡化以色列最新反击&#xff0c;中东地区局势没有进一步发酵下&#xff0c;风险溢价下降金银出现较大幅度调整。由于近期高于预期的通胀数据&#xff0c;降息预期持续降温。昨日疲软的美国PMI以及以色列在加沙攻击的加剧支…

【Unity】AssetBundle加载与卸载

unity官方apiAssetBundle-LoadFromFileAsync - Unity 脚本 API 异步加载AB包 using UnityEngine; using System.Collections; using System.IO;public class LoadFromFileAsyncExample : MonoBehaviour {IEnumerator Start(){var bundleLoadRequest AssetBundle.LoadFromFil…

消息服务应用1——java项目使用websocket

在当前微服务项目中&#xff0c;由于业务模块众多&#xff0c;消息服务的使用场景变得异常活跃。而WebSocket由于其自身的可靠性强&#xff0c;实时性好&#xff0c;带宽占用更小的优势&#xff0c;在实时通讯应用场景中独占鳌头&#xff0c;加上HTML5标准的普及流行&#xff0…

OpenCompass 大模型评测实战——笔记

OpenCompass 大模型评测实战——笔记 一、评测1.1、为什么要做评测1.2、如何通过能力评测促进模型发展1.2.1、面向未来拓展能力维度1.2.2、扎根通用能力1.2.3、高质量1.2.4、性能评测 1.3、评测的挑战1.3.1、全面性1.3.2、评测成本1.3.3、数据污染1.3.4、鲁棒性 二、OpenCompas…

java-junit单元测试

问题 Junit框架 代码 工具类 // 工具类 public class StringUtils {// 获取字符串的最大下标public static int getMaxIndex(String str){// 这个地方是有问题的&#xff0c;应该是str.length() - 1 也没有进行str是否为空的判断return str.length() ;} }测试类 测试类类名&…

vcontact2:病毒聚类(失败)

Bitbucket 安装 mamba create --name vContact2 biopython1.78 mamba install -c bioconda vcontact20.11.3vim ~/envs/vContact2/lib/python3.9/site-packages/vcontact2/exports/summaries.py 把 np.warnings.filterwarnings(ignore) 改成 import warnings warnings.filte…

递归、搜索与回溯算法:FloodFill 算法

例题一 算法思路&#xff1a; 可以利⽤「深搜」或者「宽搜」&#xff0c;遍历到与该点相连的所有「像素相同的点」&#xff0c;然后将其修改成指定的像素即可。 全局变量&#xff1a; int dx[4] { 0,0,1,-1 }, dy[4] { 1,-1,0,0 }; int m, n; int precolor;//记录原先的颜色…

【Linux】日志分析与管理

作为一个运维&#xff0c;如果不会看日志&#xff0c;就好比是冬天刚刚用热水泡完了脚&#xff0c;接着就立马让人把水喝掉。 目录 一、Inode介绍 1.1 什么是inode 1.2 inode表内容 1.3 查看inode号的方式 二、日志分析 2.1 日志的用途 2.2 日志的分类 2.3 日志级别 2…

Flink学习(七)-单词统计

前言 Flink是流批一体的框架。因此既可以处理以流的方式处理&#xff0c;也可以按批次处理。 一、代码基础格式 //1st 设置执行环境 xxxEnvironment env xxxEnvironment.getEnvironment;//2nd 设置流 DataSource xxxDSenv.xxxx();//3rd 设置转换 Xxx transformation xxxDS.…

简述MASM宏汇编

Hello , 我是小恒不会java。今天写写x86相关底层的东西 寄存器 8086由BIU和EU组成 8088/8086寄存器有14个。8通用&#xff0c;4段&#xff0c;1指针&#xff0c;1标志 8个通用寄存器&#xff1a;这些寄存器可以用来存储任意类型的数据&#xff0c;包括整数、地址等。8086有8个…

Modbus转Profinet网关接电表与工控机通讯

Modbus转Profinet网关&#xff08;XD-MDPN100/300&#xff09;的主要功能是实现Modbus协议和Profinet协议之间的转换和通信。Modbus转Profinet网关集成了Modbus和Profinet两种协议&#xff0c;支持Modbus RTU主站/从站&#xff0c;并可以与RS485接口的设备&#xff0c;如变频器…

找对方法,单位信息宣传工作向媒体投稿其实也简单

曾经,作为一名肩负单位信息宣传重任的我,每当面对那堆叠如山的稿件与闪烁不定的电脑屏幕,心中总会涌起一股无尽的焦虑与疲惫。尤其在向媒体投稿这个环节,我仿佛陷入了一个难以挣脱的漩涡,邮箱投稿的艰辛、审核的严苛、出稿的迟缓以及成功发表的少之又少,如同一座座无形的大山压…

力扣面试 150二叉搜索树迭代器 中序遍历 栈模拟递归 步骤拆分

Problem: 173. 二叉搜索树迭代器 思路 &#x1f469;‍&#x1f3eb; 三叶 复杂度 时间复杂度: O ( 1 ) O(1) O(1) 空间复杂度: O ( h ) O(h) O(h) Code class BSTIterator { Stack<TreeNode> d new Stack<>();public BSTIterator(TreeNode root){dfsLe…

书生·浦语大模型第二期实战营第七节-OpenCompass 大模型评测实战 笔记和作业

来源&#xff1a; 视频教程&#xff1a;https://www.bilibili.com/video/BV1Pm41127jU/?spm_id_from333.788&vd_sourcef4a51f7f5a63e756f73ad0dff318c1a3 文字教程&#xff1a;https://github.com/InternLM/Tutorial/blob/camp2/opencompass/readme.md 作业来源&#x…

day12 过一遍Nestjs框架(java转ts全栈/3R教室)

介绍&#xff1a;NestJS是Ts技术栈的后端框架&#xff0c;相当于Java中的springboot。 学习方法&#xff1a;与java技术体系进行对比学习。学习目标&#xff1a;nest相关知识也是挺多&#xff0c;但对比学spring的时候&#xff0c;大部分在项目生产中都是套路化的&#xff0c;大…

SpringMVC基础篇(二)

文章目录 1.Postman1.基本介绍Postman是什么&#xff1f; 2.Postman快速入门1.Postman下载点击安装自动安装在系统盘 2.基本操作1.修改字体大小2.ctrl “” 放大页面3.进入创建请求界面 2.需求分析3.具体操作4.保存请求到文件夹中1.点击保存2.创建新的文件夹3.保存成功 3.使用…

Linux系统IO

Linux系统中的IO函数主要包括两大类&#xff1a;标准C库中的函数和Linux系统调用。这些函数可以用于文件操作、网络通信、设备控制等多种IO任务。以下是Linux系统中常用的IO函数和系统调用的概述&#xff1a; 标准C库IO函数 这些函数是高级的、封装好的&#xff0c;并且与操作…

一些好听且有心意的英文全名Burwood新南威尔士州伯伍德喝酒上脸就是乙醛中毒1. 康奈尔大学官宣恢复标化要求2. 香港城市大学(东莞)正式设立!

目录 一些好听且有心意的英文全名 Burwood新南威尔士州伯伍德 喝酒上脸就是乙醛中毒 1. 康奈尔大学官宣恢复标化要求 2. 香港城市大学&#xff08;东莞&#xff09;正式设立&#xff01; 一些好听且有心意的英文全名 在选择好听且有意义的英文全名时&#xff0c;我们可…

[MoeCTF-2022]Sqlmap_boy

title:[MoeCTF 2022]Sqlmap_boy 查看网页源代码&#xff0c;得到提示 <!-- $sql select username,password from users where username".$username." && password".$password.";; --> 用万能密码绕过&#xff0c;用’"闭合 爆数据库…

【NLP】大语言模型基础之GPT

大语言模型基础之GPT GPT简介1. 无监督预训练2. 有监督下游任务微调 GPT-4体系结构1. GPT-4的模型结构2. GPT-4并行策略3. GPT-4中的专家并行GPT-4的特点 参考连接 以ELMo为代表的动态词向量模型开启了语言模型预训练的大门&#xff0c;此后&#xff0c;出现了以GPT和BERT为代表…
最新文章