Linux应用开发:socket

news2025/1/26 15:31:47

目录

1、TCP

1.1 TCP建立连接的流程图

1.2 TCP函数

1.2.1 socket

1.2.2 bind

1.2.3 listen

1.2.4 accept

1.2.5 recv

1.2.6 send

1.2.7 connnect

1.2.8 setsockopt、getsockopt

1.3 应用程序:服务器

1.4 应用程序:客户端

2、UDP

2.1 UDP建立连接的流程图

2.2 UDP函数

2.2.1 socket

2.2.2 bind

2.2.3 recvfrom

2.2.4 sendto

2.3 应用程序:服务器

2.4 应用程序:客户端


1、TCP

1.1 TCP建立连接的流程图

1.2 TCP函数

1.2.1 socket

int socket(int domain, int type, int protocol);
/*
功能:创建套接字
头文件:
       #include <sys/types.h>     
       #include <sys/socket.h>
参数:
    @domain:指定传输时候的地址族(协议族)
       Name                Purpose                          Man page
       AF_UNIX, AF_LOCAL   Local communication              unix(7)
       AF_INET             IPv4 Internet protocols          ip(7)
       AF_INET6            IPv6 Internet protocols          ipv6(7);

	@type:套接字类型
		SOCK_STREAM:字节流式套接字,流式套接字。--->TCP
        SOCK_DGRAM:数据报式套接字,报式套接字。---->UDP
		SOCK_RAW:原始套接字,传输协议需要自定义,在第三个参数中定义
            
	@protocol:传输协议,默认协议填0;
		IPPROTO_TCP IPPROTO_UDP	
返回值:
    成功,返回套接字的文件描述符
	失败,返回-1,更新errno             
*/

1.2.2 bind

int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
/*
功能:将IP地址和端口与套接字绑定;
头文件:
       #include <sys/types.h>       
       #include <sys/socket.h>
参数:
    @sockfd:socket的返回值;
	@addr:需要绑定到套接字上的地址信息;
        通用结构体,地址信息结构体。实际的结构体根据地址族的不同,指定不同类型;
		AF_INET: man 7 ip 查找;
           struct sockaddr_in {
               sa_family_t    sin_family; // address family: AF_INET 
               in_port_t      sin_port;   // 端口的网络字节序
               struct in_addr sin_addr;   // internet address 
           };
           
           struct in_addr {
               uint32_t       s_addr;      // IP的网络字节序
           };
		
		AF_INET6:man 7 ipv6;

	@addrlen:实际结构体的大小;
                sizeof(struct sockaddr_in);
返回值:
    成功,返回0;
	失败,返回-1,更新errno;
*/

1.2.3 listen

int listen(int sockfd, int backlog);
/*
功能:将套接字设置为被动监听状态;
头文件:
       #include <sys/types.h>       
       #include <sys/socket.h>
参数:
    @sockfd:socket函数返回的流式套接字;
	@backlog:允许同一时间连接的客户端个数;
			调用listen函数后,内核会维护两个队列:已完成连接队列,未完成连接队列;
           	backlog:未完成连接队列的容量;
返回值:
    成功,返回0;
	失败,返回-1,更新errno;
*/

1.2.4 accept

int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
/*
功能:阻塞函数,会从已完成连接的队列头中获取一个客户端信息,并生成一个新的文件描述符,用于通信;
头文件:
       #include <sys/types.h>       
       #include <sys/socket.h>
参数:
    @sockfd:被listen设置成监听状态的文件描述符;
	@addr:存储连接成功的客户端的地址信息;当不想获取的时候,填NULL;
        通用结构体,地址信息结构体。实际的结构体根据地址族的不同,指定不同类型;

	@addrlen:地址信息结构体的大小,注意是指针类型; 第二个参数填NULL,则当前参数填NULL;
返回值:
    成功,返回新的文件描述符,该文件描述符用于与客户端交互;
	失败,返回-1,更新errno;	
*/

1.2.5 recv

ssize_t recv(int sockfd, void *buf, size_t len, int flags);
/*
功能:从套接字中读取数据;
头文件:
       #include <sys/types.h>
       #include <sys/socket.h>
参数:
    @sockfd:accept函数的返回值;
	@buf:存储接收到的数据,可以接收任意类型,但是需要注意,收发格式一致;
	@len:指定要读取多少个字节;
	@flags:0,阻塞方式接收;
返回值:
    >0, 	成功,读取到的字节数;
	=-1,	函数运行失败;更新errno;
	=0; 	对方关闭;
*/

1.2.6 send

ssize_t send(int sockfd, const void *buf, size_t len, int flags);
/*
功能:发送数据;
头文件:
       #include <sys/types.h>
       #include <sys/socket.h>
参数:
    int sockfd:accept返回的新的文件描述符;
	void *buf:存储要发送的数据;可以是任意类型数据;
	size_t len:指定要发送多少个字节;
	int flags:发送标识
        0:阻塞方式发送,当接收缓冲区满的时候,该函数阻塞;
返回值:
   	>0, 成功发送的字节数;
	=-1, 函数调用出错;
*/

1.2.7 connnect

int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
/*
功能:连接服务器
头文件:
       #include <sys/types.h>      
       #include <sys/socket.h>      
参数:
    @sockfd:socket函数创建的文件描述符;
	@addr:指定要连接的服务器,填服务器的地址信息;
			AF_INET: man 7 ip
           struct sockaddr_in {
               sa_family_t    sin_family;   // address family: AF_INET
               in_port_t      sin_port;    	//端口号的网络字节序
               struct in_addr sin_addr;  	//ip地址的网络字节序
           };

           struct in_addr {
               uint32_t       s_addr;       //网络字节序
           };
	@addrlen:第二个地址信息结构体的大小;
返回值:
    成功,返回0;
	失败,返回-1,更新errno;
*/

1.2.8 setsockopt、getsockopt

int setsockopt(int sockfd, int level, int optname,
        const void *optval, socklen_t optlen);
int getsockopt(int sockfd, int level, int optname, 
              void *optval, socklen_t *optlen);
/*
功能:
头文件:    
    #include <sys/types.h>
    #include <sys/socket.h>
参数:
    @sockfd:套接字的文件描述符
    @level: 选项定义的层次,通常是SOL_SOCKET
    @optname:需设置的选项
    @optval:指向在其中指定所请求选项值的缓冲区的指针
    @optlen:指向的缓冲区的大小(以字节为单位)
返回值:成功返回0。 
       失败返回SOCKET_ERROR值,并且可以通过调用 WSAGetLastError 来检索特定的错误代码。
此处放上一些常用的:
    level:
		SOL_SOCKET: 通用套接字选项;
		IPPROTO_TCP  	TCP选项
        IPPROTO_UDP 	UDP选项
        IPPROTO_IP 		IP选项;
	optname:
	             SOL_SOCKET
        SO_REUSEADDR 	允许端口快速重用;
		SO_BROADCAST 	广播;
		SO_RCVTIMEO 	接收超时时间
        SO_SNDTIMEO 	发送超时时间
        SO_SNDBUF 		发送缓冲区大小
        SO_RCVBUF 		接收缓冲区大小;
*/

 选项太多,具体的参考文档:

使用此函数的文章:

        linux网络编程系列(五)--setsockopt的常用选项 - 知乎

为了防为此文章挂掉,因为我觉得写的很好,所以我抄了一份

        setsockopt的常用选项_凛冬将至__的博客-CSDN博客

setsockopt 的参考文档:

        setsockopt 函数 (winsock.h) - Win32 apps | Microsoft Learn

level 的参考文档:

        套接字选项 - Win32 apps | Microsoft Learn

optname 的参考文档:

        

1.3 应用程序:服务器

#define PORT  	6666
#define IP 		"192.168.1.12"

int main(int argc, const char *argv[])
{
	//创建流式套接字 socket
	int sfd = socket(AF_INET, SOCK_STREAM, 0);

	//允许端口快速重用
	int reuse = 1;
	setsockopt(sfd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse))

	//填充地址信息结构体
	struct sockaddr_in sin;
	sin.sin_family 		= AF_INET; 	 	//IPV4地址族
	sin.sin_port 		= htons(PORT); 	//端口号的网络字节序,1024~49151
	sin.sin_addr.s_addr = inet_addr(IP); 	//IP地址的网络字节序,终端输入ifconfig

	//绑定服务器的地址信息结构体
	bind(sfd, (struct sockaddr*)&sin, sizeof(sin))

	//将套接字设置为被动监听状态
	listen(sfd, 10);

	//获取新的文件描述符,当有客户端连接成功,解除阻塞;
	int newfd = accept(sfd, NULL, NULL);

	char buf[128] = "";
	ssize_t res = -1;
	while(1)
	{
		bzero(buf, sizeof(buf));
		//读取数据
		res = recv(newfd, buf, sizeof(buf), 0);
		if(res < 0) {
			return -1;
		} else if(0 == res) {
			break;
		}

		send(newfd, buf, sizeof(buf), 0);
	}

	//关闭文件描述符
	close(newfd);
	close(sfd);

	return 0;
}

1.4 应用程序:客户端

int main(int argc, const char *argv[])
{
	if(argc < 3) {
		fprintf(stderr, "请输入IP 及 端口\n");
		return -1;
	}

	//创建流式套接字
	int sfd = socket(AF_INET, SOCK_STREAM, 0);

	//绑定客户端的IP和端口---->非必须	

	//填充服务器的地址信息结构体
	struct sockaddr_in sin;
	sin.sin_family 		= AF_INET;
	sin.sin_port 		= htons(atoi(argv[2])); 	//外部传参输入端口号,需要转换成整型
	sin.sin_addr.s_addr = inet_addr(argv[1]); 		//外部传参输入IP 

	//连接服务器
	connect(sfd, (struct sockaddr*)&sin, sizeof(sin));
	
	char buf[128] = "";
	ssize_t res = -1;

	//循环接收发送
	while(1)
	{
		bzero(buf, sizeof(buf));

		printf("请输入>>>");
		fgets(buf, sizeof(buf), stdin);
		buf[strlen(buf)-1] = 0; 	//将结尾的'\n'修改成'\0'

		send(sfd, buf, sizeof(buf), 0);

		bzero(buf, sizeof(buf));
		res = recv(sfd, buf, sizeof(buf), 0);
		if(res < 0) {
			return -1;
		} else if(0 == res) {
			break;
		}
		printf(":%s\n", buf);
	}

	//关闭文件描述符
	close(sfd);

	return 0;
}

2、UDP

2.1 UDP建立连接的流程图

2.2 UDP函数

2.2.1 socket

int socket(int domain, int type, int protocol);
/*
功能:创建套接字
头文件:
       #include <sys/types.h>     
       #include <sys/socket.h>
参数:
    @domain:指定传输时候的地址族(协议族)
       Name                Purpose                          Man page
       AF_UNIX, AF_LOCAL   Local communication              unix(7)
       AF_INET             IPv4 Internet protocols          ip(7)
       AF_INET6            IPv6 Internet protocols          ipv6(7);

	@type:套接字类型
		SOCK_STREAM:字节流式套接字,流式套接字。--->TCP
        SOCK_DGRAM:数据报式套接字,报式套接字。---->UDP
		SOCK_RAW:原始套接字,传输协议需要自定义,在第三个参数中定义
            
	@protocol:传输协议,默认协议填0;
		IPPROTO_TCP IPPROTO_UDP	
返回值:
    成功,返回套接字的文件描述符
	失败,返回-1,更新errno             
*/

2.2.2 bind

int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
/*
功能:将IP地址和端口与套接字绑定;
头文件:
       #include <sys/types.h>       
       #include <sys/socket.h>
参数:
    @sockfd:socket的返回值;
	@addr:需要绑定到套接字上的地址信息;
        通用结构体,地址信息结构体。实际的结构体根据地址族的不同,指定不同类型;
		AF_INET: man 7 ip 查找;
           struct sockaddr_in {
               sa_family_t    sin_family; // address family: AF_INET 
               in_port_t      sin_port;   // 端口的网络字节序
               struct in_addr sin_addr;   // internet address 
           };
           
           struct in_addr {
               uint32_t       s_addr;      // IP的网络字节序
           };
		
		AF_INET6:man 7 ipv6;

	@addrlen:实际结构体的大小;
                sizeof(struct sockaddr_in);
返回值:
    成功,返回0;
	失败,返回-1,更新errno;
*/

2.2.3 recvfrom

ssize_t recv(int sockfd, void *buf, size_t len, int flags);
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,
                        struct sockaddr *src_addr, socklen_t *addrlen);
/*
功能:接收数据包,并且可以获取到该数据包是谁发送的,存储在地址信息结构体中;
头文件:
       #include <sys/types.h>
       #include <sys/socket.h>
参数:
    @sockfd:accept函数的返回值;
	@buf:存储接收到的数据,可以接收任意类型,但是需要注意,收发格式一致;
	@len:指定要读取多少个字节;
	@flags:0,阻塞方式接收;
	@src_addr:存储数据包发送端的IP和端口;
				如果不想接收,填NULL,最后一个参数也填NULL
	@addrlen:地址信息结构体的大小,注意是指针类型;
返回值:
    >0, 	成功,读取到的字节数;
	=-1,	函数运行失败;更新errno;
	=0; 	对方关闭;(只适用于tcp)   
*/

2.2.4 sendto

ssize_t send(int sockfd, const void *buf, size_t len, int flags);
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,
                      const struct sockaddr *dest_addr, socklen_t addrlen);
/*
功能:发送数据给指定的IP和端口;
头文件:
       #include <sys/types.h>
       #include <sys/socket.h>   
参数:
    @sockfd:accept返回的新的文件描述符;
	@buf:存储要发送的数据;可以是任意类型数据;
	@len:指定要发送多少个字节;
	@flags:发送标识
        0:阻塞方式发送,当接收缓冲区满的时候,该函数阻塞;
	@dest_addr:指定该数据包该发送给谁;
	@addrlen:地址信息结构体的大小;
返回值:
   	>0, 成功发送的字节数;
	=-1, 函数调用出错;
*/

2.3 应用程序:服务器

#define PORT 	8888
#define IP 		"192.168.1.12"

int main(int argc, const char *argv[])
{
	//创建报式套接字
	int sfd = socket(AF_INET, SOCK_DGRAM, 0);
	
	//填充服务器本身的地址信息
	struct sockaddr_in sin;
	sin.sin_family 		= AF_INET;
	sin.sin_port 		= htons(PORT);
	sin.sin_addr.s_addr = inet_addr(IP);

	//绑定服务的IP和端口
    bind(sfd, (struct sockaddr*)&sin, sizeof(sin));

	char buf[128] = "";
	ssize_t res = 0;

	struct sockaddr_in cin;
	socklen_t addrlen = sizeof(cin);

	while(1)
	{
		bzero(buf, sizeof(buf));
		//接收
		res = recvfrom(sfd, buf, sizeof(buf), 0, (struct sockaddr*)&cin, &addrlen);

		printf("[%s:%d]:%s\n", inet_ntoa(cin.sin_addr), ntohs(cin.sin_port), buf);
		
		//发送
		strcat(buf, "*_*");
		sendto(sfd, buf, sizeof(buf), 0, (struct sockaddr*)&cin, sizeof(cin));	
	}
	//关闭
	close(sfd);
	return 0;
}

2.4 应用程序:客户端

#define PORT 	8888
#define IP 		"192.168.1.12"

int main(int argc, const char *argv[])
{
	//创建报式套接字
	int sfd = socket(AF_INET, SOCK_DGRAM, 0);
	//绑定IP和端口---->非必须绑定

	//填充服务器的地址信息
	struct sockaddr_in sin;
	sin.sin_family 		= AF_INET;
	sin.sin_port 		= htons(PORT);
	sin.sin_addr.s_addr = inet_addr(IP);

	char buf[128] = "";
	ssize_t res = 0;

	struct sockaddr_in cin;
	socklen_t addrlen = sizeof(cin);

	while(1)
	{
		bzero(buf, sizeof(buf));
		//发送
		fgets(buf, sizeof(buf), stdin);
		buf[strlen(buf)-1] = 0;

		sendto(sfd, buf, sizeof(buf), 0, (struct sockaddr*)&sin, sizeof(sin));

		bzero(buf, sizeof(buf));
		//接收
		res = recvfrom(sfd, buf, sizeof(buf), 0, (struct sockaddr*)&cin, &addrlen);

		printf("[%s:%d]:%s\n", inet_ntoa(cin.sin_addr), ntohs(cin.sin_port), buf);
	}
	//关闭
	close(sfd);
	return 0;
}

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

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

相关文章

Github上传大于25M文件最简单方法!!!

Github上传大于25M文件最简单方法 方法&#xff1a;使用 GitHub 桌面应用程序1.下载 [Github](https://desktop.github.com/)应用程序到您的 Windows 或 Mac PC 上。2.单击“从互联网克隆存储库...”选项。3. 使用您的 Git 帐户登录。4. GitHub 应用程序将提示您使用电脑浏览器…

Docker安装Kong konga

一、安装Kong 1. 创建一个docker网络 docker network create kong-net2.拉取镜像 docker pull postgres:9.6 docker pull kong:2.6.03. 搭建pgsql数据库环境 docker run -d --name kong-database \--networkkong-net \-p 5432:5432 \-e "POSTGRES_USERkong" \-e …

Cisco Nexus 9000v Switch, NX-OS Release 10.3(3)F - 虚拟化的数据中心交换机

Cisco Nexus 9000v Switch, NX-OS Release 10.3(3)F - 虚拟化的数据中心交换机 请访问原文链接&#xff1a;https://sysin.org/blog/cisco-nexus-9000v/&#xff0c;查看最新版。原创作品&#xff0c;转载请保留出处。 作者主页&#xff1a;sysin.org NX-OS System SoftwareR…

Java接口介绍

Java接口介绍 接口&#xff08;Interface&#xff09;&#xff0c;在Java编程语言中是一个抽象类型&#xff0c;是抽象方法的集合&#xff0c;接口通常以interface关键字来声明。Java接口是用于描述类所具有的方法集合&#xff0c;但并不提供实现这些方法的代码。它们被用来定义…

Leetcode刷题日志5.0

目录 前言&#xff1a; 1.两数相加 2.无重复字符的最长子串 3.整数反转 4.删除链表的倒数第 N 个结点 前言&#xff1a; 今天我又来继续分享最近做的题了&#xff0c;现在开始进入我们快乐的刷题时间吧&#xff01;&#xff08;编程语言Python3.0&#xff0c;难度&#xf…

C++模板详解(函数模板、类模板)

hello,这里是bangbang&#xff0c;今天来讲下模板 目录 1. 泛型编程 2. 函数模板 2.1 函数模板概念 2.2 函数模板格式 2.3 函数模板的实例化 2.4 模板参数的匹配原则 3. 类模板 3.1 类模板定义格式 3.2 类模板实例化 4. 非类型模板参数 5. 模板特化 5.1 模板特化概念 5.2…

计算机视觉——day 90 基于级联卷积神经网络和对抗学习的显著目标检测

基于级联卷积神经网络和对抗学习的显著目标检测 I. INTRODUCTIONII. 网路架构A. 基于级联卷积神经网络的生成器G全局显著性估计器 E局部显著性精炼器 R B.鉴别器 DIv. 实验A. 数据集和评价标准B. 实验结果 V. 结论 I. INTRODUCTION 显著目标检测在过去的几年中受到了广泛的关注…

AcWing算法提高课-1.3.7货币系统

宣传一下算法提高课整理 <— CSDN个人主页&#xff1a;更好的阅读体验 <— 本题链接&#xff08;AcWing&#xff09; 点这里 题目描述 在网友的国度中共有  n n n 种不同面额的货币&#xff0c;第  i i i 种货币的面额为  a [ i ] a[i] a[i]&#xff0c;你可以假…

Linux知识点 -- Linux环境基础开发工具使用

Linux知识点 – Linux环境基础开发工具使用 文章目录 Linux知识点 -- Linux环境基础开发工具使用一、Linux编辑器 - vim1.vim的打开与关闭2.vim的三种模式3.命令模式常见命令4.底行模式命令5.设置vim的table键为4个字符 二、Linux编辑器 - gcc / g1.介绍2.gcc / g的使用3.gcc /…

docker笔记详解

Docker 官方文档地址:https://www.docker.com/get-started 中文参考手册:https://docker_practice.gitee.io/zh-cn/ 1.什么是 Docker 1.1 官方定义 最新官网首页 # 1.官方介绍 - We have a complete container solution for you - no matter who you are and where you are …

Linux | 将SpringBoot+Vue项目部署到服务器上

知识目录 一、写在前面二、后端部署2.1 项目打包2.2 项目运行 三、通过Shell脚本自动部署项目3.1 安装Git和Maven3.2 编写Shell脚本3.2 执行脚本 四、前端部署4.1 安装NGINX4.2 node.js安装4.3 npm打包项目4.4 运行项目 四、总结撒花 一、写在前面 大家好&#xff0c;我是初心…

MyBatis配置

配置结构 属性&#xff08;properties&#xff09; 属性不仅可以在内部直接修改&#xff0c;还可以在外部引入&#xff0c;外部引入需要在配置文件引入属性的文件&#xff0c; db.properties放在资源目录下面&#xff0c;然后在configuration标签下面导入外部配置的propertie…

Kali linux ssh Permission denied, please try again解决

新装的kali 没有ssh 装上ssh后root用户禁止登录所以要修改ssh配置&#xff0c;下面就这个过程解决全部演示。 默认情况下 SSH 不允许以 root 用户登录&#xff0c;因此将会出现下面的错误提示信息&#xff1a; Permission denied, please try again.kali linux 默认没有ssh,因此…

股票量价关系基础知识5

图解各阶段量价关系&#xff1a;价涨量增 价涨量增是指股价上涨的同时成交量也放大。它是最常见的多头进攻模式&#xff0c;说明价量配合良好&#xff0c;反映投资者买卖情绪高涨。成交量放大说明有资金流入&#xff0c;做多力量增强&#xff0c;后市看涨。 注意&#xff1a;1…

yolov5 用自己的数据集进行训练

在训练之前先要按照一定目录格式准备数据&#xff1a; VOC标签格式转yolo格式并划分训练集和测试集_爱钓鱼的歪猴的博客-CSDN博客 目录 1、修改数据配置文件 2、修改模型配置文件 3、训练 1、修改数据配置文件 coco.yaml 拷贝data/scripts/coco.yaml文件&#xff0c; pa…

requset页面的代码逻辑

基地址 &#xff1a; //引入axios import axios from axios; //vuex // import store from ../store/index; //配置基准地址 const Serve axios.create({baseURL: http://47.99.166.157:3000,// transformRequest: [function (data) {// try {// return jsonBig.…

【python】无限量PPT免费下载?找模板在不怕心仪得不能用啦

前言 大家早好、午好、晚好吖 ❤ ~欢迎光临本文章 开发环境: python 3.8 pycharm 不会安装的可以文末名片我获取哦 &#x1f60e; 模块使用: 第三方模块&#xff0c;需要安装 win R 输入cmd 输入安装命令 pip install 模块名 &#xff08;如果出现爆红 可能是因为 网络…

5分钟搞懂矩阵乘法的本质

大家好啊&#xff0c;我是董董灿。 很多与深度学习算法相关的面试&#xff0c;面试官可能都会问一类问题&#xff0c;那就是你是如何理解矩阵乘算法的。 更有甚者&#xff0c;会让你当场手写矩阵乘算法&#xff0c;然后问细节&#xff0c;问如何优化&#xff0c;面试现场&…

【MATLAB第31期】基于MATLAB的降维/全局敏感性分析/特征排序/数据处理方法MATLAB代码实现(持续更新)

【MATLAB第31期】基于MATLAB的降维/全局敏感性分析/特征排序/数据处理方法MATLAB代码实现(持续更新) 一、降维方法 常见的降维方法&#xff1a; 1.变量归类&#xff08;主成分分析PCA、核主成分分析KPCA&#xff09; 2.变量筛选&#xff08;临近成分分析NCA、皮尔逊系数PCC、…

签名预售活动圆满结束!各位敬等快递,第一个付款的兄弟来领取大礼!

我的新书经过千难万险终于上架&#xff0c; 为了感谢众多老铁的支持&#xff0c; 所以上周日搞了签名预售的活动&#xff0c;挂了300本&#xff0c; 一上线很快就被大家买光了&#xff0c; 留言需要单独写一些话的老铁&#xff0c;我也都尽量满足了&#xff0c; 如果一楼还…