day33练习:


客户端 与 服务器实现一个点对点聊天

tcp

客户端

clifd = socket

connect

//收 --父进程 //发 --子进程 tcp

服务器 listenfd = socket

bind

listen

connfd = accept()

//收 -- 父进程 //发 -- 子进程

client.c

#include "../head.h"int res_fd[1]; // 只需要存储客户端socket文件描述符int tcp_client_connect(char const *ip,char const * port)
{int fd = socket(AF_INET,SOCK_STREAM,0);if (fd < 0){perror("socket fail");return -1;}res_fd[0] = fd; // 保存文件描述符用于清理printf("fd = %d\n",fd);struct sockaddr_in seraddr;//ip 192.168.0.150//port 50000bzero(&seraddr,sizeof(seraddr)); // 使用bzero清零seraddr.sin_family = AF_INET;seraddr.sin_port = htons(atoi(port)); //atoiseraddr.sin_addr.s_addr = inet_addr(ip);if ( connect(fd,(const struct sockaddr *)&seraddr,sizeof(seraddr)) < 0){perror("connect fail");return -1;}return fd;
}// 信号处理函数
void do_handler(int signo)
{exit(0);
}// 清理函数
void cleanup(void)
{close(res_fd[0]);printf("pid = %d ---cleanup ---exit---\n",getpid());
}//./cli 192.168.0.130 50000 
int main(int argc, char const *argv[])
{if (argc != 3){printf("Usage: %s <ip> <port>\n",argv[0]); return -1;       }int clifd = tcp_client_connect(argv[1],argv[2]);if (clifd < 0){printf("tcp_client_connect fail\n");return -1;}// 注册信号处理函数signal(SIGUSR1,do_handler);pid_t pid = fork();if (pid < 0){perror("fork fail");return -1;}// 注册退出清理函数if (atexit(cleanup) != 0){perror("atexit fail");return -1;}char buf[1024];if (pid > 0){printf("---f -- pid = %d\n",getpid());while (1){printf(">");fgets(buf,sizeof(buf),stdin);write(clifd,buf,strlen(buf)+1);if (strncmp(buf,"quit",4) == 0){kill(pid,SIGKILL);wait(NULL);exit(0);}}}else if (pid == 0){printf("---c -- pid = %d\n",getpid());while (1){read(clifd,buf,sizeof(buf));if (strncmp(buf,"quit",4) == 0){//kill(getppid(),SIGKILL);kill(getppid(),SIGUSR1); // 使用SIGUSR1信号通知父进程退出exit(0);}printf("cli buf: %s\n",buf);}}// 程序不会执行到这里,但为了完整性保留close(clifd);return 0;
}

sever.c

#include "head.h"int res_fd[2];int tcp_accept(char const*ip,char const * port )
{//step1 socket int fd = socket(AF_INET,SOCK_STREAM,0);if (fd < 0){perror("socket fail");return -1;}res_fd[0] = fd;printf("fd = %d\n",fd);struct sockaddr_in seraddr;//ip 192.168.0.150//port 50000bzero(&seraddr,sizeof(seraddr));seraddr.sin_family = AF_INET;seraddr.sin_port = htons(atoi(port));seraddr.sin_addr.s_addr = inet_addr(ip);if (bind(fd,(const struct sockaddr *)&seraddr,sizeof(seraddr)) < 0){perror("connect fail");return -1;}if (listen(fd,5) < 0){perror("listen fail");return -1;}int connfd = accept(fd,NULL,NULL);if (connfd < 0){perror("accept fail");return -1;}res_fd[1] = connfd;return connfd;
}void do_handler(int signo)
{exit(0);
}void cleanup(void)
{close(res_fd[0]);close(res_fd[1]);printf("pid = %d ---cleanup ---exit---\n",getpid());
}int main(int argc, char const *argv[])
{if (argc != 3){printf("Usage: %s <ip> <port>\n",argv[0]);return -1;}int connfd = tcp_accept(argv[1],argv[2]);if (connfd < 0){printf("tcp_accept fail");return -1;}signal(SIGUSR1,do_handler);pid_t pid = fork();if (pid < 0){perror("fork fail");return -1;}//退出资源的管理 if (atexit(cleanup) != 0) // 注册 一个 清理函数 {perror("atexit fail");return -1;}char buf[1024];if (pid > 0){printf("---f -- pid = %d\n",getpid());while (1){printf(">");fgets(buf,sizeof(buf),stdin);write(connfd,buf,strlen(buf)+1);if (strncmp(buf,"quit",4) == 0){kill(pid,SIGKILL);wait(NULL);exit(0);}}}else if (pid == 0){printf("---c -- pid = %d\n",getpid());while (1){read(connfd,buf,sizeof(buf));if (strncmp(buf,"quit",4) == 0){//kill(getppid(),SIGKILL);kill(getppid(),SIGUSR1);exit(0);}printf("cli buf: %s\n",buf);}}close(connfd);return 0;
}

1. 什么是粘包?

粘包(TCP 粘包问题)是指 发送方 分多次发送的数据包,在 接收方 接收到时,数据会被“粘”在一起,导致接收方无法正确区分数据包的边界。

2. 粘包的原因

粘包问题本质上是因为 TCP 是面向流的协议,它没有明确的消息边界。具体原因如下:

1. TCP 是流式协议

  • TCP 协议把数据看作是一条字节流,不关心应用层的数据是否被完整地发送或接收,接收方只能收到一个“字节流”。

  • 所以,发送方分多次发送的数据可能会被合并在一起发送,也可能是一次发送的数据被拆分成多个小块接收。

2. 操作系统的缓冲机制

  • TCP 发送方的数据会存入发送缓冲区,直到缓冲区数据达到一定量或操作系统觉得合适时,数据才会被发送出去。

  • 同样,接收方的操作系统会将收到的网络数据包缓存到接收缓冲区,等待应用程序读取。

3. 粘包与拆包

除了粘包,另一个常见的问题是 拆包。拆包是指应用程序发送的数据包在传输过程中被拆成多个小包,导致接收方无法正确地拼接成原本的消息。

4. 解决粘包问题

为了避免粘包和拆包的问题,应用层需要通过某种机制来明确 数据包的边界。常见的解决方法有以下几种:

1. 固定长度的消息
  • 每个发送的消息都固定长度。接收方一接收到数据,就知道消息的长度。

  • 例如,假设我们每次发送 10 字节 的消息,接收方就可以按照 10 字节 来读取数据,保证数据不被粘连。

缺点:如果数据量小于固定长度,可能会浪费空间;如果数据量大于固定长度,则需要分段处理。

2. 包头加包体方式
  • 通过在每个消息前加一个 固定长度的头部,头部指定消息的 长度。接收方根据头部的长度信息来确定如何读取数据。

  • 例如,发送一个消息时,先发送一个 4 字节的包头,告诉接收方后续数据的长度。

优点:通过包头可以解决粘包问题,灵活性高。
缺点:需要处理包头和包体的分离。

3. 特定的分隔符
  • 采用特定的分隔符(例如 \n## 等)来标识消息的边界。接收方根据分隔符来区分不同的数据包。

优点:实现简单,适用于文本数据。
缺点:对于二进制数据不太适用,容易导致数据误解析。

4. 使用高级协议
  • 一些协议(如 HTTPWebSocketMQTT)已经为数据包边界做了设计,使用这些协议时,粘包问题会被协议本身解决。

练习:

client.c

#include "../head.h"int tcp_client_connect(char const *ip,char const * port)
{int fd = socket(AF_INET,SOCK_STREAM,0);if (fd < 0){perror("socket fail");return -1;}//printf("fd = %d\n",fd);struct sockaddr_in seraddr;//ip 192.168.0.150//port 50000seraddr.sin_family = AF_INET;seraddr.sin_port = htons(atoi(port)); //atoiseraddr.sin_addr.s_addr = inet_addr(ip);if ( connect(fd,(const struct sockaddr *)&seraddr,sizeof(seraddr)) < 0){perror("connect fail");return -1;}return fd;
}//./cli 192.168.0.130 50000 
int main(int argc, char const *argv[])
{if (argc != 3){printf("Usage: %s <ip> <port>\n",argv[0]); return -1;       }int clifd = tcp_client_connect(argv[1],argv[2]);if (clifd < 0){printf("tcp_client_connect fail\n");return -1;}msg_t msg;printf("Input file name:");msg.type = -1;fgets(msg.buf,sizeof(msg.buf),stdin);msg.buf[strlen(msg.buf)-1] = '\0';write(clifd,&msg,sizeof(msg));int fd_s = open(msg.buf,O_RDONLY);if (fd_s < 0){perror("open fail");return -1;}while (1){int ret = read(fd_s,msg.buf,sizeof(msg.buf));msg.type = ret;write(clifd,&msg,sizeof(msg));if (ret == 0)break;}close(fd_s);close(clifd);return 0;
}

server.c

#include "../head.h"int tcp_accept(char const*ip,char const * port )
{//step1 socket int fd = socket(AF_INET,SOCK_STREAM,0);if (fd < 0){perror("socket fail");return -1;}printf("fd = %d\n",fd);struct sockaddr_in seraddr;//ip 192.168.0.150//port 50000bzero(&seraddr,sizeof(seraddr));seraddr.sin_family = AF_INET;seraddr.sin_port = htons(atoi(port));seraddr.sin_addr.s_addr = inet_addr(ip);if (bind(fd,(const struct sockaddr *)&seraddr,sizeof(seraddr)) < 0){perror("connect fail");return -1;}if (listen(fd,5) < 0){perror("listen fail");return -1;}int connfd = accept(fd,NULL,NULL);if (connfd < 0){perror("accept fail");return -1;}return connfd;
}int main(int argc, char const *argv[])
{if (argc != 3){printf("Usage: %s <ip> <port>\n",argv[0]);return -1;}int connfd = tcp_accept(argv[1],argv[2]);if (connfd < 0){printf("tcp_accept fail");return -1;}msg_t msg;read(connfd,&msg,sizeof(msg));printf("buf = %s\n",msg.buf);int fd_d = open(msg.buf,O_WRONLY|O_TRUNC|O_CREAT,0666);if (fd_d < 0){perror("open fail");return -1;}while (1){int ret = read(connfd,&msg,sizeof(msg));if (msg.type == 0)break;write(fd_d,msg.buf,msg.type);}close(connfd);close(fd_d);return 0;
}

recv

ssize_t recv(int sockfd, void *buf, size_t len, int flags);

参数说明

  1. sockfd

    • 要接收数据的套接字描述符。它是通过 socket() 创建,并且已经通过 connect()accept() 建立连接的套接字。

  2. buf

    • 用于接收数据的缓冲区(指针)。接收到的数据会存放在这个缓冲区中。

  3. len

    • 缓冲区的大小,即最多接收的字节数。

  4. flags

    • 控制选项,一般使用 0,表示没有特殊的控制。也可以使用一些标志,例如:

      • MSG_PEEK:查看数据但不从缓冲区移除。

      • MSG_DONTWAIT:非阻塞模式下调用。

返回值

  • 成功:返回实际接收到的字节数。

    • 如果返回 0,表示对方已经关闭连接。

    • 如果返回大于 0,表示接收到的数据的字节数。

  • 失败:返回 -1,并设置 errno。常见错误包括:

    • EAGAIN / EWOULDBLOCK:非阻塞模式下,接收缓冲区没有数据。

    • ECONNRESET:连接被对方重置。

recv()read() 的区别

在 Linux 中,recv()read() 函数在套接字上基本上是等价的,都会从套接字中读取数据。但是 recv() 提供了一些额外的功能:

  • recv() 可以使用 flags 参数,控制接收操作的行为。

  • recv() 更常用于网络编程,因为它可以在接收数据时设置额外的标志(比如非阻塞接收、查看数据等)。


send

ssize_t send(int sockfd, const void *buf, size_t len, int flags);

参数说明

  1. sockfd

    • 发送数据的套接字描述符,通常是通过 socket() 创建并且已经连接(使用 connect()accept())的套接字。

  2. buf

    • 发送数据的缓冲区(指向内存的指针)。你希望通过 send() 发送的数据应该存放在这个缓冲区中。

  3. len

    • 要发送的字节数(数据的长度)。len 不能超过缓冲区 buf 的大小。

  4. flags

    • 控制发送行为的标志,通常设置为 0,但也可以设置为一些特定的标志,例如:

      • MSG_DONTWAIT:非阻塞模式,立即返回(如果不能发送数据)。

      • MSG_NOSIGNAL:不触发 SIGPIPE 信号(通常用在当对方关闭连接时)。

返回值

  • 成功:返回实际发送的字节数(即已写入套接字的字节数)。这个值可能小于你请求发送的字节数(即发送过程可能被中断,部分数据发送成功)。你需要处理这种情况,确保数据完全发送。

  • 失败:返回 -1,并设置 errno。常见的错误包括:

    • EAGAIN / EWOULDBLOCK:非阻塞模式下,缓冲区没有空间或者网络忙。

    • ECONNRESET:连接被对方重置。

send()write() 的区别

Linux 中,send()write() 在行为上是非常相似的。两者都可以用于套接字,发送数据时的行为几乎一致。区别在于:

  • send() 函数支持 flags 参数,可以控制发送行为(例如是否阻塞、是否触发 SIGPIPE 等)。

  • write() 通常用于文件描述符,不具有像 send() 那样的灵活控制。

所以,send() 更常用于网络编程,因为它具有更高的控制灵活性。


udb编程

UDP(User Datagram Protocol,用户数据报协议) 是一个 无连接 的传输层协议,它是 TCP 的一个对立面。与 TCP 不同,UDP 不保证数据的可靠性和顺序,它简单、快速、适用于对速度要求高且能容忍一定丢包的场景。

特点:

  • 无连接:UDP 不需要建立连接,发送数据时不进行握手,发送方直接将数据发送给接收方。

  • 不保证顺序:接收方可能会收到乱序的包,也可能丢失一些包。

  • 轻量级:UDP 头部开销小,适用于对实时性要求较高的应用(例如视频、语音等流媒体)。

  • 无重传机制:如果数据丢失,UDP 不会重传丢失的部分,应用层需要自行处理丢包。

  • 面向数据报:每次发送的数据都是一个独立的包,且有明确的边界。

sendto

ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,
const struct sockaddr *dest_addr, socklen_t addrlen);

参数说明

  1. sockfd

    • 类型int

    • 描述:由 socket() 函数创建的套接字描述符。

    • 用途:该套接字用于指定数据发送的目标套接字。对于 UDP,通常是使用 SOCK_DGRAM 类型的套接字。

  2. buf

    • 类型const void *

    • 描述:一个指向数据缓冲区的指针,这个缓冲区包含了要发送的数据。

    • 用途:将要发送的消息数据存放在 buf 中,可以是任何类型的数据(通常是字符串或二进制数据)。

  3. len

    • 类型size_t

    • 描述:缓冲区中要发送的数据的字节数。

    • 用途:指定从 buf 中发送的字节数。例如,如果 buf 是一个字符串,len 就是字符串的长度。

  4. flags

    • 类型int

    • 描述:控制发送操作的标志位。

    • 用途:通常设为 0,但可以使用特定的标志:

      • MSG_DONTWAIT:非阻塞模式。如果套接字的缓冲区没有足够的空间,sendto() 会立即返回,而不是阻塞。

      • MSG_NOSIGNAL:防止发送数据时触发 SIGPIPE 信号(通常在连接被关闭时)。

  5. dest_addr

    • 类型const struct sockaddr *

    • 描述:指向目标地址的指针。

    • 用途:指定接收数据的目标地址,通常是一个 struct sockaddr_in(IPv4 地址)或者 struct sockaddr_in6(IPv6 地址)结构体。

  6. addrlen

    • 类型socklen_t

    • 描述:目标地址结构体的长度。

    • 用途:指定 dest_addr 的长度,通常使用 sizeof(struct sockaddr_in)sizeof(struct sockaddr_in6) 来获取。

返回值

  • 成功:返回实际发送的字节数(即 len 字节)。

  • 失败:返回 -1,并设置 errno。常见的错误包括:

    • EAGAIN / EWOULDBLOCK:非阻塞模式下,缓冲区没有足够的空间。

    • ENOTCONN:套接字未连接(对于某些协议需要连接)。


recvfrom

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

参数说明

  1. sockfd

    • 类型int

    • 描述:由 socket() 创建的 UDP 套接字描述符。

    • 用途:用于指定接收数据的套接字。

  2. buf

    • 类型void *

    • 描述:接收数据的缓冲区。

    • 用途:存放从 UDP 套接字接收到的数据。这个缓冲区的大小应足够存放可能接收到的最大数据。

  3. len

    • 类型size_t

    • 描述:缓冲区的大小,即最多接收的字节数。

    • 用途:告知 recvfrom() 函数最多接收多少字节数据。

  4. flags

    • 类型int

    • 描述:接收的控制选项,通常设置为 0,但可以使用一些标志,例如:

      • MSG_PEEK:查看数据但不从接收缓冲区移除数据。

      • MSG_DONTWAIT:非阻塞模式,立即返回(如果没有数据可接收)。

  5. src_addr

    • 类型struct sockaddr *

    • 描述:指向发送方地址的结构体指针。

    • 用途:接收数据时,会将发送方的地址信息(如 IP 地址和端口)存放在这里。

    • 通常使用 struct sockaddr_in(IPv4)或 struct sockaddr_in6(IPv6)来表示。

  6. addrlen

    • 类型socklen_t *

    • 描述:发送方地址结构体的长度,接收时会更新为实际的地址长度。

    • 用途:传递给 recvfrom() 时,表示地址结构的大小,返回时更新为实际接收的数据源地址的长度。

返回值

  • 成功:返回接收到的字节数。如果接收到的数据是完整的消息,recvfrom() 将返回实际接收到的数据字节数。

  • 失败:返回 -1,并设置 errno。常见错误包括:

    • EAGAIN / EWOULDBLOCK:非阻塞模式下,没有数据可以接收。

    • ECONNRESET:连接被重置。

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

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

相关文章

零知开源——基于STM32F103RBT6与ADXL362三轴加速度计的体感迷宫游戏设计与实现

✔零知IDE 是一个真正属于国人自己的开源软件平台&#xff0c;在开发效率上超越了Arduino平台并且更加容易上手&#xff0c;大大降低了开发难度。零知开源在软件方面提供了完整的学习教程和丰富示例代码&#xff0c;让不懂程序的工程师也能非常轻而易举的搭建电路来创作产品&am…

《Linux 网络编程一:网络编程导论及UDP 服务器的创建与数据接收》

Linux下的网络编程1. 目的实现不同主机之间进程的通信。2. 问题主机之间在物理层面必须互联互通。进程之间在软件层面必须互联互通。IP地址&#xff1a;计算机的软件地址&#xff0c;用于标识计算机设备。MAC地址&#xff1a;计算机的硬件地址&#xff08;固定&#xff09;。网…

排序(数据结构)

比较排序 插入排序&#xff08;斗地主摸牌就是一个插入排序&#xff09; 单纯的插入排序也叫直接插入排序 时间复杂度&#xff1a; 最好O(n)最坏O(n^2) 过程 先写单趟&#xff0c;再写整体 依次比较&#xff0c;如果大于就往后挪动&#xff0c;否则就退出循环&#xff0c;插入数…

【C++组件】Elasticsearch 安装及使用

&#x1f308; 个人主页&#xff1a;Zfox_ &#x1f525; 系列专栏&#xff1a;C框架/库 目录&#x1f525; 介绍 &#x1f525; ES 安装 &#x1f98b; 安装 kibana&#x1f98b; ES 客户端的安装&#x1f525; ES 核心概念 &#x1f98b; 索引&#xff08;Index&#xff09;&…

项目:电动车报警器

1.项目需求 点击遥控器A按键&#xff0c;系统进入警戒模式&#xff0c;一旦检测到震动(小偷偷车)&#xff0c;则喇叭发出声响报警&#xff0c;吓退小偷。 点击遥控器B按键&#xff0c;系统退出警戒模式&#xff0c;再怎么摇晃系统都不会报警&#xff0c;否则系统一直发出尖叫&a…

GDSFactory环境配置(PyCharm+Git+KLayout)

1、安装 PyCharm 和 KLayout 安装 PyCharm&#xff08;官网社区版即可&#xff09;和 KLayout&#xff08;官网最新版&#xff09;&#xff0c;这两款软件均开源&#xff0c;安装操作简单&#xff0c;这里不再赘述。&#xff08;注意&#xff1a;PyCharm软件是否安装成功以能否…

STM32 定时器(输出模式)

⚙️ ​一、输出模式总览​STM32定时器的输出比较模式通过比较计数器&#xff08;CNT&#xff09;与捕获/比较寄存器&#xff08;CCRx&#xff09;的值&#xff0c;控制输出引脚&#xff08;OCx&#xff09;的电平状态。六种模式定义如下&#xff1a;​模式宏​​触发动作​&am…

嵌入式硬件篇---手柄

手柄原理&#xff1a;手柄遥控的原理其实可以简单理解为 “信号的发送与接收”&#xff0c;就像两个人用对讲机聊天&#xff0c;一方说话&#xff08;发送信号&#xff09;&#xff0c;另一方听话&#xff08;接收信号&#xff09;&#xff0c;然后根据内容行动。下面用通俗的方…

数据库架构开发知识库体系

摘要面向初创与企业团队&#xff0c;系统梳理数据库与数据平台从采集、传输、存储、处理、服务化到治理与安全的全链路。覆盖 OLTP/OLAP/HTAP、湖仓一体与实时数据栈&#xff0c;结合国内外工具与方法论&#xff0c;给出架构选型、性能优化、可靠性与合规要点&#xff0c;以及可…

在Excel和WPS表格中合并多个单元格这样最快

如果要把WPS表格和Excel中多个单元格的数据合成到一个单元格中&#xff0c;不用函数&#xff0c;只需要先写输入公式&#xff0c;然后在各个单元格之间输入&符号即可。&#xff08;当然&#xff0c;&符号不只是连接单元格的数据&#xff0c;也可以直接输入内容连接&…

在嵌入式上用 C++14实现简单HSM状态机

文章目录概述为什么要迁移到 C&#xff0c;以及 C 的陷阱目标与挑战为什么不能直接使用 std::function&#xff1f;解决方案&#xff1a;POD 回调与模板 Trampoline核心设计模板 trampoline 实现两种成员函数绑定策略1. **Per-Transition Context&#xff08;每个状态转移绑定一…

【unity】Obfuz加固混淆日志还原解析方案ObfuzResolver

Github | Gitee ObfuzResolver是基于obfuz-tools针对Obfuz的一项辅助工具&#xff0c;方便开发者在unity编辑器中或者运行时快捷将使用Obfuz混淆加固后的日志信息还原为原始信息&#xff0c;以辅助开发者快速定位Bug。 特性 支持unity编辑器模式下还原被加固混淆的日志信息&a…

2025DevOps平台趋势解读:哪些DevOps工具正在引领行业变革?

DevOps平台已成为企业提升研发效能、实现敏捷交付的核心支柱。2025年DevOps领域正经历深刻变革&#xff0c;平台能力正从“工具链整合”向“价值流智能中枢”跃升。01. 2025Devops平台趋势解读“全栈式”与“模块化/可组合”的平衡&#xff1a;企业既需要能覆盖开发、测试、部署…

第二阶段Winform-4:MDI窗口,布局控件,分页

1_MDI窗口 &#xff08;1&#xff09;MDI是指将多控件窗体在同一窗体中打开,可以设置重叠打开&#xff0c;平捕打开等&#xff0c;MDI窗体&#xff08;Multiple-Document Interface&#xff0c;多文档界面&#xff09;用于同时显示多个文档。在项目中使用MDI窗体时&#xff0c…

实用R语言机器学习指南:从数据预处理到模型实战(附配套学习资源)

一、为什么需要掌握机器学习建模&#xff1f;在科研与项目实践中&#xff0c;机器学习已成为数据挖掘的核心工具。本文手把手带你在R语言中实现7大常用模型&#xff1a;逻辑回归/正则化回归决策树/随机森林SVM支持向量机XGBoost梯度提升神经网络全程包含数据标准化→模型训练→…

go.uber.org/zap 日志库高性能写入

使用 go.uber.org/zap 实现日志分割功能 实现按照单个文件最大MB自动分割,最多保留多少天的文件,是否启用压缩,按天自动分割日志 核心依赖 go.uber.org/zap:核心日志库 lumberjack.v2:日志轮转工具(实现按大小/时间分割) 时间处理依赖标准库 time 实现步骤 1. 初始化…

电脑端完全免费的动态壁纸和屏保软件(真正免费、无广告、无会员)

✅ 1. Lively Wallpaper&#xff08;强烈推荐&#xff09; 特点&#xff1a;完全免费、开源、无广告&#xff0c;支持本地视频/GIF/网页作为动态壁纸内置资源&#xff1a;12个高质量动态壁纸&#xff08;可自定义&#xff09;屏保功能&#xff1a;支持将动态壁纸一键设为屏保系…

C#_组合优于继承的实际应用

2.2 Composition over Inheritance&#xff1a;组合优于继承的实际应用 继承&#xff08;Inheritance&#xff09;是面向对象编程中最容易被过度使用和误用的特性之一。传统的教学往往让人们优先选择继承来实现代码复用和建立“是一个&#xff08;is-a&#xff09;”的关系。然…

Kafka消息丢失的场景有哪些

生产者在生产过程中的消息丢失 broker在故障后的消息丢失 消费者在消费过程中的消息丢失ACK机制 ack有3个可选值&#xff0c;分别是1&#xff0c;0&#xff0c;-1。 ack0&#xff1a;生产者在生产过程中的消息丢失 简单来说就是&#xff0c;producer发送一次就不再发送了&#…

盼之代售 231滑块 csessionid 分析

声明 本文章中所有内容仅供学习交流使用&#xff0c;不用于其他任何目的&#xff0c;抓包内容、敏感网址、数据接口等均已做脱敏处理&#xff0c;严禁用于商业用途和非法用途&#xff0c;否则由此产生的一切后果均与作者无关&#xff01; 逆向分析 部分python代码 url "…