SOCKET REUSE_ADDR RESUE_PORT FW FROM stackoverflow

NTP服务报错:unable to bind to wildcard address another precess may be running 公司有很多远古机器,其上有很多远古故障,今天记录一台NTP时间一直拉不回来的故障服务器老了之后机器时间很容易越来越慢,如果没有NTP时间会愈发离谱,相应一些依赖时间的服务也会运行不正常。 阅读详情

Welcome to the wonderful world of portability... or rather the lack of it. Before we start analyzing these two options in detail and take a deeper look how different operating systems handle them, it should be noted that the BSD socket implementation is the mother of all socket implementations. Basically all other systems copied the BSD socket implementation at some point of time (or at least its interfaces) and then started to evolving it on their own. Of course the BSD socket implementation was evolved as well at the same time and thus systems that copied it later got features that were lacking in systems that copied it earlier. Understanding the BSD socket implementation is the key to understanding all other socket implementations, so you should read about it even if you don't care to ever write code for a BSD system.

There are a couple of basics you should know before we look at these two options. A TCP/UDP connection is identified by a tuple of five values:

{<protocol>, <src addr>, <src port>, <dest addr>, <dest port>}

Any unique combination of these values identifies a connection. As a result, no two connections can have the same five values, otherwise the system would not be able to distinguish these connections any longer.

The protocol of a socket is set when a socket is created with the socket() function. The source address and port are set with the bind() function. The destination address and port are set with the connect() function. Since UDP is a connectionless protocol, UDP sockets can be used without connecting them. Yet it is allowed to connect them and in some cases very advantageous for your code and general application design. In connectionless mode, UDP sockets that were not explicitly bound when data is sent over them for the first time are usually automatically bound by the system, as an unbound UDP socket cannot receive any (reply) data. Same is true for an unbound TCP socket, it is automatically bound before it will be connected.

If you explicitly bind a socket, it is possible to bind it to port 0, which means "any port". Since a socket cannot really be bound to all existing ports, the system will have to choose a specific port itself in that case (usually from a predefined, OS specific range of source ports). A similar wildcard exists for the source address, which can be "any address" (0.0.0.0 in case of IPv4 and :: in case of IPv6). Unlike in case of ports, a socket can really be bound to "any address" which means "all source IP addresses of all local interfaces". If the socket is connected later on, the system has to choose a specific source IP address, since a socket cannot be connected and at the same time be bound to any local IP address. Depending on the destination address and the content of the routing table, the system will pick an appropriate source address and replace the "any" binding with a binding to the chosen source IP address.

By default, no two sockets can be bound to the same combination of source address and source port. As long as the source port is different, the source address is actually irrelevant. Binding socketA to A:X and socketB to B:Y, where A and B are addresses and X and Y are ports, is always possible as long as X != Y holds true. However, even if X == Y, the binding is still possible as long as A != B holds true. E.g. socketA belongs to a FTP server program and is bound to 192.168.0.1:21 and socketB belongs to another FTP server program and is bound to 10.0.0.1:21, both bindings will succeed. Keep in mind, though, that a socket may be locally bound to "any address". If a socket is bound to 0.0.0.0:21, it is bound to all existing local addresses at the same time and in that case no other socket can be bound to port 21, regardless which specific IP address it tries to bind to, as 0.0.0.0 conflicts with all existing local IP addresses.

Anything said so far is pretty much equal for all major operating system. Things start to get OS specific when address reuse comes into play. We start with BSD, since as I said above, it is the mother of all socket implementations.

BSD

SO_REUSEADDR

If SO_REUSEADDR is enabled on a socket prior to binding it, the socket can be successfully bound unless there is a conflict with another socket bound to exactly the same combination of source address and port. Now you may wonder how is that any different than before? The keyword is "exactly". SO_REUSEADDR mainly changes the way how wildcard addresses ("any IP address") are treated when searching for conflicts.

Without SO_REUSEADDR, binding socketA to 0.0.0.0:21 and then binding socketB to 192.168.0.1:21 will fail (with error EADDRINUSE), since 0.0.0.0 means "any local IP address", thus all local IP addresses are considered in use by this socket and this includes 192.168.0.1, too. With SO_REUSEADDR it will succeed, since 0.0.0.0 and 192.168.0.1 are not exactly the same address, one is a wildcard for all local addresses and the other one is a very specific local address. Note that the statement above is true regardless in which order socketA and socketBare bound; without SO_REUSEADDR it will always fail, with SO_REUSEADDR it will always succeed.

To give you a better overview, let's make a table here and list all possible combinations:

SO_REUSEADDR       socketA        socketB       Result
---------------------------------------------------------------------
  ON/OFF       192.168.0.1:21   192.168.0.1:21    Error (EADDRINUSE)
  ON/OFF       192.168.0.1:21      10.0.0.1:21    OK
  ON/OFF          10.0.0.1:21   192.168.0.1:21    OK
   OFF             0.0.0.0:21   192.168.1.0:21    Error (EADDRINUSE)
   OFF         192.168.1.0:21       0.0.0.0:21    Error (EADDRINUSE)
   ON              0.0.0.0:21   192.168.1.0:21    OK
   ON          192.168.1.0:21       0.0.0.0:21    OK
  ON/OFF           0.0.0.0:21       0.0.0.0:21    Error (EADDRINUSE)

The table above assumes that socketA has already been successfully bound to the address given for socketA, then socketB is created, either gets SO_REUSEADDR set or not, and finally is bound to the address given for socketBResult is the result of the bind operation for socketB. If the first column says ON/OFF, the value of SO_REUSEADDR is irrelevant to the result.

Okay, SO_REUSEADDR has an effect on wildcard addresses, good to know. Yet that isn't it's only effect it has. There is another well known effect which is also the reason why most people use SO_REUSEADDR in server programs in the first place. For the other important use of this option we have to take a deeper look on how the TCP protocol works.

A socket has a send buffer and if a call to the send() function succeeds, it does not mean that the requested data has actually really been sent out, it only means the data has been added to the send buffer. For UDP sockets, the data is usually sent pretty soon, if not immediately, but for TCP sockets, there can be a relatively long delay between adding data to the send buffer and having the TCP implementation really send that data. As a result, when you close a TCP socket, there may still be pending data in the send buffer, which has not been sent yet but your code considers it as sent, since the send() call succeeded. If the TCP implementation was closing the socket immediately on your request, all of this data would be lost and your code wouldn't even know about that. TCP is said to be a reliable protocol and losing data just like that is not very reliable. That's why a socket that still has data to send will go into a state called TIME_WAIT when you close it. In that state it will wait until all pending data has been successfully sent or until a timeout is hit, in which case the socket is closed forcefully.

The amount of time the kernel will wait before it closes the socket, regardless if it still has pending send data or not, is called the Linger Time. The Linger Time is globally configurable on most systems and by default rather long (two minutes is a common value you will find on many systems). It is also configurable per socket using the socket option SO_LINGER which can be used to make the timeout shorter or longer, and even to disable it completely. Disabling it completely is a very bad idea, though, since closing a TCP socket gracefully is a slightly complex process and involves sending forth and back a couple of packets (as well as resending those packets in case they got lost) and this whole close process is also limited by the Linger Time. If you disable lingering, your socket may not only lose pending data, it is also always closed forcefully instead of gracefully, which is usually not recommended. The details about how a TCP connection is closed gracefully are beyond the scope of this answer, if you want to learn more about, I recommend you have a look at this page. And even if you disabled lingering with SO_LINGER, if your process dies without explicitly closing the socket, BSD (and possibly other systems) will linger nonetheless, ignoring what you have configured. This will happen for example if your code just calls exit() (pretty common for tiny, simple server programs) or the process is killed by a signal (which includes the possibility that it simply crashes because of an illegal memory access). So there is nothing you can do to make sure a socket will never linger under all circumstances.

The question is, how does the system treat a socket in state TIME_WAIT? If SO_REUSEADDR is not set, a socket in state TIME_WAIT is considered to still be bound to the source address and port and any attempt to bind a new socket to the same address and port will fail until the socket has really been closed, which may take as long as the configured Linger Time. So don't expect that you can rebind the source address of a socket immediately after closing it. In most cases this will fail. However, if SO_REUSEADDR is set for the socket you are trying to bind, another socket bound to the same address and port in state TIME_WAIT is simply ignored, after all its already "half dead", and your socket can bind to exactly the same address without any problem. In that case it plays no role that the other socket may have exactly the same address and port. Note that binding a socket to exactly the same address and port as a dying socket in TIME_WAIT state can have unexpected, and usually undesired, side effects in case the other socket is still "at work", but that is beyond the scope of this answer and fortunately those side effects are rather rare in practice.

There is one final thing you should know about SO_REUSEADDR. Everything written above will work as long as the socket you want to bind to has address reuse enabled. It is not necessary that the other socket, the one which is already bound or is in a TIME_WAIT state, also had this flag set when it was bound. The code that decides if the bind will succeed or fail only inspects the SO_REUSEADDR flag of the socket fed into the bind() call, for all other sockets inspected, this flag is not even looked at.

SO_REUSEPORT

SO_REUSEPORT is what most people would expect SO_REUSEADDR to be. Basically, SO_REUSEPORTallows you to bind an arbitrary number of sockets to exactly the same source address and port as long as all prior bound sockets also had SO_REUSEPORT set before they were bound. If the first socket that is bound to an address and port does not have SO_REUSEPORT set, no other socket can be bound to exactly the same address and port, regardless if this other socket has SO_REUSEPORTset or not, until the first socket releases its binding again. Unlike in case of SO_REUESADDR the code handling SO_REUSEPORT will not only verify that the currently bound socket has SO_REUSEPORT set but it will also verify that the socket with a conflicting address and port had SO_REUSEADDR set when it was bound.

SO_REUSEPORT does not imply SO_REUSEADDR. This means if a socket did not have SO_REUSEPORT set when it was bound and another socket has SO_REUSEPORT set when it is bound to exactly the same address and port, the bind fails, which is expected, but it also fails if the other socket is already dying and is in TIME_WAIT state. To be able bind a socket to the same addresses and port as another socket in TIME_WAIT state requires either SO_REUSEADDR to be set on that socket or SO_REUSEPORT must have been set on both sockets prior to binding them. Of course it is allowed to set both, SO_REUSEPORT and SO_REUSEADDR, on a socket.

There is not much more to say about SO_REUSEPORT other than that it was added later than SO_REUSEADDR, that's why you will not find it in many socket implementations of other systems, which "forked" the BSD code before this option was added, and that there was no way to bind two sockets to exactly the same socket address in BSD prior to this option.

Connect() Returning EADDRINUSE?

Most people know that bind() may fail with the error EADDRINUSE, however, when you start playing around with address reuse, you may run into the strange situation that connect() fails with that error as well. How can this be? How can a remote address, after all that's what connect adds to a socket, be already in use? Connecting multiple sockets to exactly the same remote address has never been a problem before, so what's going wrong here?

As I said on the very top of my reply, a connection is defined by a tuple of five values, remember? And I also said, that these five values must be unique otherwise the system cannot distinguish two connections any longer, right? Well, with address reuse, you can bind two sockets of the same protocol to the same source address and port. That means three of those five values are already the same for these two sockets. If you now try to connect both of these sockets also to the same destination address and port, you would create two connected sockets, whose tuples are absolutely identical. This cannot work, at least not for TCP connections (UDP connections are no real connections anyway). If data arrived for either one of the two connections, the system could not tell which connection the data belongs to. At least the destination address or destination port must be different for either connection, so that the system has no problem to identify to which connection incoming data belongs to.

So if you bind two sockets of the same protocol to the same source address and port and try to connect them both to the same destination address and port, connect() will actually fail with the error EADDRINUSE for the second socket you try to connect, which means that a socket with an identical tuple of five values is already connected.

Multicast Addresses

Most people ignore the fact that multicast addresses exist, but they do exist. While unicast addresses are used for one-to-one communication, multicast addresses are used for one-to-many communication. Most people got aware of multicast addresses when they learned about IPv6 but multicast addresses also existed in IPv4, even though this feature was never widely used on the public Internet.

The meaning of SO_REUSEADDR changes for multicast addresses as it allows multiple sockets to be bound to exactly the same combination of source multicast address and port. In other words, for multicast addresses SO_REUSEADDR behaves exactly as SO_REUSEPORT for unicast addresses. Actually the code treats SO_REUSEADDR and SO_REUSEPORT identically for multicast addresses, that means you could say that SO_REUSEADDR implies SO_REUSEPORT for all multicast addresses and the other way round.


FreeBSD/OpenBSD/NetBSD

All these are rather late forks of the original BSD code, that's why they all three offer the same options as BSD and they also behave the same way as in BSD.


MacOS X

At its very core, MacOS X is simply a BSD-style UNIX, based on a rather late fork of the BSD code, which was even synchronized with FreeBSD 5 for the Mac OS 10.3 release. That's why MacOS X offers the same options as BSD and they also behave the same way as in BSD.


iOS

iOS is just modified MacOS X at its core, so everything that applies to MacOS X also applies to iOS.


Linux

Prior to Linux 3.9, only the option SO_REUSEADDR existed. This option behaves generally the same as in BSD with two important exceptions. One exception is that if a listening (server) TCP socket is already bound to a wildcard IP address and a specific port, no other TCP socket can be bound to the same port, regardless whether either one or both sockets have this flag set. Not even if it would use a more specific address (as is allowed in case of BSD). This restriction does not apply to non-listening (client) TCP sockets and it is also possible to first bind a listening TCP socket to a specific IP address and port combination and later on bind another one to a wildcard IP address and the same port. The second exception is that for UDP sockets this option behaves exactly like SO_REUSEPORT in BSD, so two UDP sockets can be bound to exactly the same address and port combination as long as both had this flag set before they were bound.

Linux 3.9 added the option SO_REUSEPORT to Linux as well. This option allows two (or more) sockets, TCP or UDP, listening (server) or non-listening (client), to be bound to exactly the same address and port combination as long as all sockets (including the very first one) had this flag set prior to binding them. To prevent "port hijacking", there is one special limitation, though: All sockets that want to share the same address and port combination must belong to processes that share the same effective user ID! So one user cannot "steal" ports of another user. Additionally the kernel performs some "special magic" for SO_REUSEPORT sockets that isn't found in any other operating system so far: For UDP sockets, it tries to distribute datagrams evenly, for TCP listening sockets, it tries to distribute incoming connect requests (those accepted by calling accept()) evenly across all the sockets that share the same address and port combination. That means while it is more or less random which socket receives a datagram or connect request in other operating systems that allow full address reuse, Linux tries to optimize distribution so that, for example, multiple instances of a simple server process can easily use SO_REUSEPORT sockets to achieve a kind of simple load balancing and that absolutely for free as the kernel is doing "all the hard work" for them.


Android

Even though the whole Android system is somewhat different from most Linux distributions, at its core works a slightly modified Linux kernel, thus everything that applies to Linux applies to Android as well.


Windows

Windows only knows the SO_REUSEADDR option, there is no SO_REUSEPORT. Setting SO_REUSEADDR on a socket in Windows behaves like setting SO_REUSEPORT and SO_REUSEADDRon a socket in BSD, with one exception: A socket with SO_REUSEADDR can always bind to exactly the same source address and port as an already bound socket, even if the other socket did not have this option set when it was bound. This behavior is somewhat dangerous because it allows an application "to steal" the connected port of another application. Needless to say, this can have major security implications. Microsoft realized that this might be a problem and thus added another socket option SO_EXCLUSIVEADDRUSE. Setting SO_EXCLUSIVEADDRUSE on a socket makes sure that if the binding succeeds, the combination of source address and port is owned exclusively by this socket and no other socket can bind to them, not even if it has SO_REUSEADDR set.


Solaris

Solaris is the successor of SunOS. SunOS was originally based on a fork of BSD, SunOS 5 and later was based on a fork of SVR4, however SVR4 is a merge of BSD, System V, and Xenix, so up to some degree Solaris is also a BSD fork, and a rather early one. As a result Solaris only knows SO_REUSEADDR, there is no SO_REUSEPORT. The SO_REUSEADDR behaves pretty much the same as it does in BSD. As far as I know there is no way to get the same behavior as SO_REUSEPORT in Solaris, that means it is not possible to bind two sockets to exactly the same address and port.

Similar to Windows, Solaris has an option to give a socket an exclusive binding. This option is named SO_EXCLBIND. If this option is set on a socket prior to binding it, setting SO_REUSEADDR on another socket has no effect if the two sockets are tested for an address conflict. E.g. if socketA is bound to a wildcard address and socketB has SO_REUSEADDR enabled and is bound to a non-wildcard address and the same port as socketA, this bind will normally succeed, unless socketAhad SO_EXCLBIND enabled, in which case it will fail regardless the SO_REUSEADDR flag of socketB.

tcp 端口复用与惊群效应(REUSEADDRREUSEPORT 我在之前的一篇文章中,介绍了我在之前的项目中遇到的端口复用,windows 的udp里端口复用导致了一个bug,具体的链接参考如下: 关于Socket中端口复用_zhc的博客-CSDN博客_socket端口复用 而我现在是想总结一下,linux下的情况,想从头再梳理下,REUSEADDRREUSEPORT的作用。 1.SO_REUSEADDR 是为了解决time_wait问题而产生的 SO_REUSEADDR 它的作用是当服务器端主动断开链接的时候,处在timewait阶段大约60秒钟,服 阅读详情

相关推荐

如何理解反步法设计控制器?

假设以二阶系统为例,被控对象的系统状态方程为: 基于反步控制的具体方法如下: 1.1步骤一 定义期望状态为xd,状态误差e1,为: e1=x1-xd; 则e1的导数为: 1.2步骤二 定义虚拟控制量为: e2定义为: 1.3步骤三 定义Lyapunov能量函数为: 1.4步骤四 定义总Lyapunov能量函数为: 因此设计的控制律为: 设计c>0,则: 满足Lyapunov的稳定性判定条件...

weixin_42736130的博客 2万+

【愚公系列】2023年04月 wireshark系列-数据抓包分析之FTP协议

FTP协议是一种用于文件传输的协议。它允许多位用户在Internet上共享文件和文件夹,并提供连接到远程计算机的高效途径。FTP协议使用TCP协议作为传输协议。

愚公智库 5万+

Linux服务器 离线安装oracle

Linux服务器 离线安装oracle

xie_bro的博客 4210

网络基础4(TCP三次握手,四次握手,TCP流量控制,TCP状态转换 , TCP异常断开,设置TCP属性,端口复用)

TCP协议 TCP通信时序 下图是一次TCP通讯的时序图。TCP连接建立断开。包含大家熟知的三次握手和四次握手。 TCP通讯时序 在这个例子中,首先客户端主动发起连接、发送请求,然后服务器端响应请求,然后客户端主动关闭连接。 两条竖线表示通讯的两端,从上到下表示时间的先后顺序, 注意,数据从一端传到网络的另一端也需要时间,所以图中的箭头都是斜的。 双方发送的段按时间顺序编号为1-10,各段中的主要...

LXY的博客 1194

搭建ntp服务,启动报错,解决办法

搭建ntp服务,启动报这个错误,又因为是内网,缺个包 解决方法:下载autogen-libopts-5.18-5.el7.x86_64.rpm,在启动解决

qq_39128254的博客 2268

apache2不能启动的解决办法(提示could not bind to address 0.0.0.0:80)

安装apache 2.22 出现的问题: “(OS 10048)通常每个套接字地址 (协议/网络地址/端口) 只允许使用一次: make_sock: could not bind to address 0.0.0.0:80...” 一般是IIS或其他程序占用了80端口引起的,引起的原因,可以参考: http://wiki.apache.org/httpd/CouldNotBind

smartDMer的专栏 4万+

so_reuseaddr & so_reuseport

http://stackoverflow.com/questions/14388706/socket-options-so-reuseaddr-and-so-reuseport-how-do-they-differ-do-they-mean-t Welcome to the wonderful world of portability... or rather the lac

ly402609921的专栏 954

linux 修改date、ntp、时钟同步(时间同步)、时钟回拨等

linux 修改date、ntp、时钟同步(时间同步)、时钟回拨等

enthan809882的博客 487

Windows & Unix IPC Introduction (Part.2)

3. Sockets A socket is a software abstraction that is used to create a channel-like interface between processes. The channel allows bi-directional communication between processes, but does little

Ant Ren的专栏 1958

Linux下启动httpd服务提示not bind to address

这几天在装Apache httpd服务后,启动httpd服务,报错如下: Starting httpd: httpd: Could not reliably determine the server's fully qualified domain name, using 0.0.0.0 for ServerName (13)Permission denied: make_sock: could ...

火麒马 652

解决TCP中Bind failed烦恼

在实际使用中,如果您尝试启动一个服务端程序并且遇到了 “Bind failed” 的错误信息,这通常意味着尝试绑定(bind)的端口已经被占用。端口占用:如果之前启动的服务端实例没有正确关闭,或者其他程序正在使用相同的端口,那么操作系统将不允许您再次绑定到该端口。未正确释放端口:在服务端程序异常退出或者没有正确关闭套接字的情况下,操作系统可能仍然认为端口正在被使用。确保正确关闭套接字:在您的代码中,确保在服务端关闭时,您调用了套接字的关闭方法。检查权限:确保您的程序有足够的权限来绑定所需的端口。

如果想成为中心,那么就到中心去吧。 4982

tomcat的Server port端口一样,也会出现地址已在使用 (Bind failed)

场景:同事新测试两个tomcat,但是只有一个能启动,让帮忙看看 启动tomcat,查看日志:错误如下 分析:这种错误一般就是端口占用,但是我查看,并没有发现在使用的端口 这里就搞不通了,想了一下,tomcat其实有三个端口:sever port ,业务端口,AJP端口。因为AJP端口已经关闭,所以剩下的可能性就是Server port一样。 查看server.xml,果然两个都设置为了8225,然后修改其中一个就行。问题解决 PS:server.xml文件中有三个端口设置: :关闭时使用 : 一

qq_39677803的博客 1906

日常工作问题解决:配置NTP服务器以及一些常见错误解决

日常工作问题解决:配置NTP服务器以及一些常见错误解决

asdfgh0077的博客 1176

linux时间同步ntp服务的安装与配置

当我们需要管理多台服务器的时间时,一台一台的修改未免太麻烦了,NTP服务就很好的为我们解决了这个问题! 1.首先安装NTP [root@localhost /]# yum install ntp -y 2.修改NTP配置文件,添加NTP服务器的网络位置 /etc/ntp.conf # For more information about this file, see th...

weixin_30765505的博客 1483

多线程

【】一个小例子,用到了c++11的线程库、优雅的断开套接字 #include <iostream> #include <unistd.h> #include <sys/socket.h> #include <string.h> #include <arpa/inet.h> #include <thread> using nam...

dustnn的博客 226

socket_bind():无法绑定地址[99] (socket_bind(): unable to bind address [99]

socket_bind():无法绑定地址[99] (socket_bind(): unable to bind address [99] PHP Warning: socket_bind(): unable to bind address [99]: Cannot assign requested address insockets_test.php on line 45 这个错误: 网上查了很多...

傅恒的博客 7388

网络编程中的SO_REUSEADDR和SO_REUSEPORT参数详解

在BSD中,SO_REUSEADDR选项有两个用户:setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR,(const void *)&reuse , sizeof(int));目的:当服务端出现timewait状态的链接时,确保server能够重启成功。注意:SO_REUSEADDR只有针对time-wait链接(linux系统time-wait连接持续时间为1min),确保server重启成功的这一个作用。举个time_wait的连接通过so_reuseaddr选项

赶路人儿 6295

socket中的reuse addrreuse port

SO_REUSEADDR (1)首先对于一个server服务进程来说,它的创建流程是 socket->bind ->listen->accept 创建监听套接字,bind一个指定端口,listen监听端口,为每个连接提供服务(一般会创建子进程)。当服务进程重启时,那么会重新执行一遍上面的流程,如果在创建socket后没有使用SO_REUSEADDR选项进行设置,再次进行bind就会失败。那么原因是什么? 首先需要了解一下TCP协议的4次挥手动作,如文章开头的图片所示,当一个连接在关闭时.

程序猿Ricky的日常干货 7008

linux ntp排错,NTP 部署思路及排错

NTP 部署:这里我准备了4台虚拟机,servera:192.168.1.15 serverc: 192.168.1.16serverc: 192.168.1.17 serverd: 192.168.1.18servera作服务端,安装NTP服务其他三台虚拟机作客户端,不需要安装NTPservera 安装完NTP 后,去修改配置文件 /etc/ntp.conf第 8行: restri...

weixin_32204241的博客 398

zj70电动驱动钻机绞车设计(sw10+说明书+cad).rar

zj70电动驱动钻机绞车设计(sw10+说明书+cad).rar

上一篇: MySQL query record
下一篇: DNS record limititions
BlueSy2008
博客等级 码龄19年 26粉丝 26原创
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值