大家好,又见面了,我是全栈君,祝每个程序员都可以多学几门语言。
int setsockopt(
SOCKET s,
int level,
int optname,
const char* optval,
int optlen
);
s(套接字): 指向一个打开的套接口描写叙述字
level:(级别): 指定选项代码的类型。
SOL_SOCKET: 基本套接口
IPPROTO_IP: IPv4套接口
IPPROTO_IPV6: IPv6套接口
IPPROTO_TCP: TCP套接口
optname(选项名): 选项名称
optval(选项值): 是一个指向变量的指针 类型:整形,套接口结构, 其它结构类型:linger{}, timeval{ }
optlen(选项长度) :optval 的大小
返回值:标志打开或关闭某个特征的二进制选项
[/code:1:59df4ce128]
========================================================================
SOL_SOCKET
————————————————————————
SO_BROADCAST 同意发送广播数据 int
适用於 UDP socket。其意义是同意 UDP socket 「广播」(broadcast)讯息到网路上。
SO_DEBUG 同意调试 int
SO_DONTROUTE 不查找路由 int
SO_ERROR 获得套接字错误 int
SO_KEEPALIVE 保持连接 int
检 測对方主机是否崩溃,避免(server)永远堵塞于TCP连接的输入。 设置该选项后,假设2小时内在此套接口的任一方向都没有数据交换,TCP就自己主动给对方 发一个保持存活探測分节(keepalive probe)。这是一个对方必须响应的TCP分节.它会导致下面三种情况: 对方接收一切正常:以期望的 ACK响应。2小时后,TCP将发出还有一个探測分节。 对方已崩溃且已又一次启动:以RST响应。套接口的待处理错误被置为ECONNRESET,套接 口本身则被关闭。 对方无不论什么响应:源自berkeley的TCP发送另外8个探測分节,相隔75秒一个,试图得到 一个响应。在发出第一个探測分节11分钟15秒后若仍无响应就放弃。套接口的待处理错 误被置为ETIMEOUT,套接口本身则被关闭。如ICMP错误是“host unreachable (主机不 可达)”,说明对方主机并没有崩溃,可是不可达,这样的情况下待处理错误被置为 EHOSTUNREACH。
SO_DONTLINGER 若为真,则SO_LINGER选项被禁止。
SO_LINGER 延迟关闭连接 struct linger
上面这两个选项影响close行为
选项 间隔 关闭方式 等待关闭与否
SO_DONTLINGER 不关心 优雅 否
SO_LINGER 零 强制 否
SO_LINGER 非零 优雅 是
若 设置了SO_LINGER(亦即linger结构中的l_onoff域设为非零,參见2.4,4.1.7和4.1.21各节),并设置了零超时间隔,则 closesocket()不被堵塞马上运行,不论是否有排队数据未发送或未被确认。这样的关闭方式称为“强制”或“失效”关闭,由于套接口的虚电路马上被 复位,且丢失了未发送的数据。在远端的recv()调用将以WSAECONNRESET出错。
若设置了SO_LINGER并确定了非零的超时间 隔,则closesocket()调用堵塞进程,直到所剩数据发送完成或超时。这样的关闭称为“优雅的”关闭。请注意假设套接口置为非堵塞且 SO_LINGER设为非零超时,则closesocket()调用将以WSAEWOULDBLOCK错误返回。
若在一个流类套接口上设置了 SO_DONTLINGER(也就是说将linger结构的l_onoff域设为零;參见2.4,4.1.7,4.1.21节),则 closesocket()调用马上返回。可是,假设可能,排队的数据将在套接口关闭前发送。请注意,在这样的情况下WINDOWS套接口实现将在一段不确 定的时间内保留套接口以及其它资源,这对于想用所以套接口的应用程序来说有一定影响。
SO_OOBINLINE 带外数据放入正常数据流,在普通数据流中接收带外数据 int
SO_RCVBUF 接收缓冲区大小 int
设置接收缓冲区的保留大小
与 SO_MAX_MSG_SIZE 或TCP滑动窗体无关,假设一般发送的包非常大非常频繁,那么使用这个选项
SO_SNDBUF 发送缓冲区大小 int
设置发送缓冲区的保留大小
与 SO_MAX_MSG_SIZE 或TCP滑动窗体无关,假设一般发送的包非常大非常频繁,那么使用这个选项
每 个套接口都有一个发送缓冲区和一个接收缓冲区。 接收缓冲区被TCP和UDP用来将接收到的数据一直保存到由应用进程来读。 TCP:TCP通告还有一端的窗体大小。 TCP套接口接收缓冲区不可能溢出,由于对方不同意发出超过所通告窗体大小的数据。 这就是TCP的流量控制,假设对方无视窗体大小而发出了超过宙口大小的数据,则接 收方TCP将丢弃它。 UDP:当接收到的数据报装不进套接口接收缓冲区时,此数据报就被丢弃。UDP是没有 流量控制的;快的发送者能够非常easy地就淹没慢的接收者,导致接收方的UDP丢弃数据报。
SO_RCVLOWAT 接收缓冲区下限 int
SO_SNDLOWAT 发送缓冲区下限 int
每 个套接口都有一个接收低潮限度和一个发送低潮限度。它们是函数selectt使用的, 接收低潮限度是让select返回“可读”而在套接口接收缓冲区中必须有的数据总量。 ——对于一个TCP或UDP套接口,此值缺省为1。发送低潮限度是让select返回“可写” 而在套接口发送缓冲区中必须有的可用空间。对于TCP套接口,此值常缺省为2048。 对于UDP使用低潮限度, 因为其发送缓冲区中可用空间的字节数是从不变化的,仅仅要 UDP套接口发送缓冲区大小大于套接口的低潮限度,这种UDP套接口就总是可写的。 UDP没有发送缓冲区,仅仅有发送缓冲区的大小。
SO_RCVTIMEO 接收超时 struct timeval
SO_SNDTIMEO 发送超时 struct timeval
SO_REUSERADDR 同意重用本地地址和port int
充许绑定已被使用的地址(或port号),能够參考bind的man
SO_EXCLUSIVEADDRUSE
独占模式使用port,就是不充许和其他程序使用SO_REUSEADDR共享的使用某一port。
在确定多重绑定使用谁的时候,依据一条原则是谁的指定最明白则将包递交给谁,并且没有权限之分,也就是说低级权限的用户是能够重绑定在高级权限如服务启动的port上的,这是很重大的一个安全隐患,
假设不想让自己程序被监听,那么使用这个选项
SO_TYPE 获得套接字类型 int
SO_BSDCOMPAT 与BSD系统兼容 int
==========================================================================
IPPROTO_IP
————————————————————————–
IP_HDRINCL 在数据包中包括IP首部 int
这个选项经常使用于黑客技术中,隐藏自己的IP地址
IP_OPTINOS IP首部选项 int
IP_TOS 服务类型
IP_TTL 生存时间 int
下面IPV4选项用于组播
IPv4 选项 数据类型 描 述
IP_ADD_MEMBERSHIP struct ip_mreq 添�到组播组中
IP_ROP_MEMBERSHIP struct ip_mreq 从组播组中退出
IP_MULTICAST_IF struct ip_mreq 指定提交组播报文的接口
IP_MULTICAST_TTL u_char 指定提交组播报文的TTL
IP_MULTICAST_LOOP u_char 使组播报文环路有效或无效
在头文件里定义了ip_mreq结构:
[code:1:63724de67f]
struct ip_mreq {
struct in_addr imr_multiaddr; /* IP multicast address of group */
struct in_addr imr_interface; /* local IP address of interface */
};
[/code:1:63724de67f]
若进程要添�到一个组播组中,用soket的setsockopt()函数发送该选项。该选项类型是ip_mreq结构,它的第一个字段imr_multiaddr指定了组播组的地址,第二个字段imr_interface指定了接口的IPv4地址。
IP_DROP_MEMBERSHIP
该选项用来从某个组播组中退出。数据结构ip_mreq的用法与上面同样。
IP_MULTICAST_IF
该选项能够改动网络接口,在结构ip_mreq中定义新的接口。
IP_MULTICAST_TTL
设置组播报文的数据包的TTL(生存时间)。默认值是1,表示数据包仅仅能在本地的子网中传送。
IP_MULTICAST_LOOP
组播组中的成员自己也会收到它向本组发送的报文。这个选项用于选择是否激活这样的状态。
无双 回复于:2003-05-08 21:21:52
IPPRO_TCP
————————————————————————–
TCP_MAXSEG TCP最大数据段的大小 int
获 取或设置TCP连接的最大分节大小(MSS)。返回值是我们的TCP发送给还有一端的最大 数据量,它经常就是由还有一端用SYN分节通告的MSS,除非我们的TCP选择使用一个比 对方通告的MSS小些的值。假设此值在套接口连接之前取得,则返回值为未从另·—端 收到Mss选项的情况下所用的缺省值。小于此返回值的信可能真正用在连接上,由于譬 如说使用时间戳选项的话,它在每一个分节上占用12字节的TCP选项容量。我们的TcP将 发送的每一个分节的最大数据量也可在连接存活期内改变,但前提是TCP要支持路径MTU 发现功能。假设到对方的路径改变了,此值可上下调整。
TCP_NODELAY 不使用Nagle算法 int
指定TCP開始发送保持存活探測分节前以秒为单位的连接空暇时间。缺省值至少必须为7200秒,即2小时。此选项仅在SO_KEPALIVEE套接口选项打开时才有效。
TCP_NODELAY 和 TCP_CORK,
这 两个选项都对网络连接的行为具有关键的数据。很多UNIX系统都实现了TCP_NODELAY选项,可是,TCP_CORK则是Linux系统所独有的而 且相对较新;它首先在内核版本号2.4上得以实现。此外,其它UNIX系统版本号也有功能相似的选项,值得注意的是,在某种由BSD派生的系统上的 TCP_NOPUSH选项事实上就是TCP_CORK的一部分详细实现。
TCP_NODELAY和TCP_CORK基本上控制了包的 “Nagle化”,Nagle化在这里的含义是採用Nagle算法把较小的包组装为更大的帧。John Nagle是Nagle算法的发明人,后者就是用他的名字来命名的,他在1984年首次用这样的方法来尝试解决福特汽车公司的网络拥塞问题(欲了解详情请參 看IETF RFC 896)。他解决的问题就是所谓的silly window syndrome ,中文称“愚蠢窗体症候群”,详细含义是,由于普遍终端应用程序每产生一次击键操作就会发送一个包,而典型情况下一个包会拥有一个字节的数据载荷以及40 个字节长的包头,于是产生4000%的过载,非常轻易地就能令网络发生拥塞,。 Nagle化后来成了一种标准并且马上在因特网上得以实现。它如今已经成为缺省配置了,但在我们看来,有些场合下把这一选项关掉也是合乎须要的。
现 在让我们假设某个应用程序发出了一个请求,希望发送小块数据。我们能够选择马上发送数据或者等待产生很多其它的数据然后再一次发送两种策略。假设我们马上发送 数据,那么交互性的以及客户/server型的应用程序将极大地受益。比如,当我们正在发送一个较短的请求并且等候较大的响应时,相关过载与传输的数据总量相比 就会比較低,并且,假设请求马上发出那么响应时间也会快一些。以上操作能够通过设置套接字的TCP_NODELAY选项来完毕,这样就禁用了Nagle算 法。
第二种情况则须要我们等到数据量达到最大时才通过网络一次发送所有数据,这样的传输数据方式故意于大量数据的通信性能,典型的应用就是文件服 务器。应用 Nagle算法在这样的情况下就会产生问题。可是,假设你正在发送大量数据,你能够设置TCP_CORK选项禁用Nagle化,其方式正好同 TCP_NODELAY相反(TCP_CORK 和 TCP_NODELAY 是互相排斥的)。以下就让我们细致分析下其工作原理。
假设应用程序 使用sendfile()函数来转移大量数据。应用协议通常要求发送某些信息来预先解释数据,这些信息事实上就是报头内容。典型情况下报头非常小,并且套接字 上设置了TCP_NODELAY。有报头的包将被马上传输,在某些情况下(取决于内部的包计数器),由于这个包成功地被对方收到后须要请求对方确认。这 样,大量数据的传输就会被推迟并且产生了不必要的网络流量交换。
可是,假设我们在套接字上设置了TCP_CORK(能够比喻为在管道上插入 “塞子”)选项,具有报头的包就会填补大量的数据,所有的数据都依据大小自己主动地通过包传输出去。当传输数据完毕时,最好取消TCP_CORK 选项设置给连接“拔去塞子”以便任一部分的帧都能发送出去。这同“塞住”网络连接同等重要。
总而言之,假设你肯定能一起发送多个数据集合(比如HTTP响应的头和正文),那么我们建议你设置TCP_CORK选项,这样在这些数据之间不存在延迟。能极大地故意于WWW、FTP以及文件server的性能,同一时候也简化了你的工作。演示样例代码例如以下:
intfd, on = 1;
…
/* 此处是创建套接字等操作,出于篇幅的考虑省略*/
…
setsockopt (fd, SOL_TCP, TCP_CORK, &on, sizeof (on)); /* cork */
write (fd, …);
fprintf (fd, …);
sendfile (fd, …);
write (fd, …);
sendfile (fd, …);
…
on = 0;
setsockopt (fd, SOL_TCP, TCP_CORK, &on, sizeof (on)); /* 拔去塞子 */
不幸的是,很多经常使用的程序并没有考虑到以上问题。比如,Eric Allman编写的sendmail就没有对其套接字设置不论什么选项。
Apache HTTPD 是因特网上最流行的Webserver,它的所有套接字就都设置了TCP_NODELAY选项,并且其性能也深受大多数用户的惬意。这是为什么呢?答案就在于实 现的区别之上。由BSD衍生的TCP/IP协议栈(值得注意的是FreeBSD)在这样的状况下的操作就不同。当在TCP_NODELAY 模式下提交大量小数据块传输时,大量信息将依照一次write()函数调用发送一块数据的方式发送出去。然而,由于负责请求交付确认的记数器是面向字节而 非面向包(在 Linux上)的,所以引入延迟的概率就减少了非常多。结果只和所有数据的大小有关系。而 Linux 在第一包到达之后就要求确认,FreeBSD则在进行如此操作之前会等待好几百个包。
在Linux系统上,TCP_NODELAY的效果同习惯于BSD TCP/IP协议栈的开发人员所期望的效果有非常大不同,并且在Linux上的Apache性能表现也会更差些。其它在Linux上频繁採用TCP_NODELAY的应用程序也有相同的问题。
TCP_DEFER_ACCEPT
我 们首先考虑的第1个选项是TCP_DEFER_ACCEPT(这是Linux系统上的叫法,其它一些操作系统上也有相同的选项但使用不同的名字)。为了理 解TCP_DEFER_ACCEPT选项的详细思想,我们有必要大致阐述一下典型的HTTP客户/server交互过程。请回忆下TCP是怎样与数据传输的目标 建立连接的。在网络上,在分离的单元之间传输的信息称为IP包(或IP 数据报)。一个包总有一个携带服务信息的包头,包头用于内部协议的处理,并且它也能够携带数据负载。服务信息的典型样例就是一套所谓的标志,它把包标记代 表TCP/IP协议栈内的特殊含义,比如收到包的成功确认等等。通常,在经过“标记”的包里携带负载是全然可能的,但有时,内部逻辑迫使TCP/IP协议 栈发出仅仅有包头的IP包。这些包常常会引发讨厌的网络延迟并且还添加�了系统的负载,结果导致网络性能在总体上减少。
如今server创建了一个套接字同 时等待连接。TCP/IP式的连接过程就是所谓“3次握手”。首先,客户程序发送一个设置SYN标志并且不带数据负载的TCP包(一个SYN包)。server 则以发出带SYN/ACK标志的数据包(一个SYN/ACK包)作为刚才收到包的确认响应。客户随后发送一个ACK包确认收到了第2个包从而结束连接过 程。在收到客户发来的这个SYN/ACK包之后,server会唤醒一个接收进程等待数据到达。当3次握手完毕后,客户程序即開始把“实用的”的数据发送给服务 器。通常,一个HTTP请求的量是非常小的并且全然能够装到一个包里。可是,在以上的情况下,至少有4个包将用来进行双向传输,这样就添加�了可观的延迟时 间。此外,你还得注意到,在“实用的”数据被发送之前,接收方已经開始在等待信息了。
为了减轻这些问题所带来的影响,Linux(以及其它的一些 操作系统)在其TCP实现中包含了TCP_DEFER_ACCEPT选项。它们设置在侦听套接字的server方,该选项命令内核不等待最后的ACK包并且在第 1个真正有数据的包到达才初始化侦听进程。在发送SYN/ACK包之后,server就会等待客户程序发送含数据的IP包。如今,仅仅须要在网络上传送3个包了, 并且还显著减少了连接建立的延迟,对HTTP通信而言尤其如此。
这一选项在好些操作系统上都有对应的对等物。比如,在FreeBSD上,相同的行为能够用下面代码实现:
/* 为明晰起见,此处略去无关代码 */
struct accept_filter_arg af = { “dataready”, “” };
setsockopt(s, SOL_SOCKET, SO_ACCEPTFILTER, &af, sizeof(af));
这 个特征在FreeBSD上叫做“接受过滤器”,并且具有多种使用方法。只是,在差点儿全部的情况下其效果与TCP_DEFER_ACCEPT是一样的:server不 等待最后的ACK包而只等待携带数据负载的包。要了解该选项及其对高性能Webserver的重要意义的很多其它信息请參考Apache文档上的有关内容。
就HTTP 客户/server交互而言,有可能须要改变客户程序的行为。客户程序为什么要发送这样的“没用的”ACK包呢?这是由于,TCP协议栈无法知道ACK包的状态。 假设採用FTP而非HTTP,那么客户程序直到接收了FTPserver提示的数据包之后才发送数据。在这样的情况下,延迟的ACK将导致客户/server交互出现延 迟。为了确定ACK是否必要,客户程序必须知道应用程序协议及其当前状态。这样,改动客户行为就成为必要了。
对Linux客户程序来说,我们还可 以採用还有一个选项,它也被叫做TCP_DEFER_ACCEPT。我们知道,套接字分成两种类型,侦听套接字和连接套接字,所以它们也各自具有对应的 TCP选项集合。因此,常常同一时候採用的这两类选项却具有相同的名字也是全然可能的。在连接套接字上设置该选项以后,客户在收到一个SYN/ACK包之后就 不再发送ACK包,而是等待用户程序的下一个发送数据请求;因此,server发送的包也就对应降低了。
TCP_QUICKACK
阻 止因发送无用包而引发延迟的还有一个方法是使用TCP_QUICKACK选项。这一选项与 TCP_DEFER_ACCEPT不同,它不但能用作管理连接建立过程并且在正常传输数据过程期间也能够使用。另外,它能在客户/server连接的不论什么一方设 置。假设知道数据不久即将发送,那么推迟ACK包的发送就会派上用场,并且最好在那个携带数据的数据包上设置ACK 标志以便把网络负载减到最小。当发送方肯定数据将被马上发送(多个包)时,TCP_QUICKACK 选项能够设置为0。对处于“连接”状态下的套接字该选项的缺省值是1,首次使用以后内核将把该选项马上复位为1(这是个一次性的选项)。
在某些情形下,发出ACK包则非常实用。ACK包将确认数据块的接收,并且,当下一块被处理时不至于引入延迟。这样的传输数据模式对交互过程是相当典型的,由于此类情况下用户的输入时刻无法预測。在Linux系统上这就是缺省的套接字行为。
在 上述情况下,客户程序在向server发送HTTP请求,而预先就知道请求包非常短所以在连接建立之后就应该马上发送,这可谓HTTP的典型工作方式。既然没有必 要发送一个纯粹的ACK包,所以设置TCP_QUICKACK为0以提高性能是全然可能的。在server方,这两种选项都仅仅能在侦听套接字上设置一次。全部的 套接字,也就是被接受呼叫间接创建的套接字则会继承原有套接字的全部选项。
通过TCP_CORK、TCP_DEFER_ACCEPT和 TCP_QUICKACK选项的组合,參与每一HTTP交互的数据包数量将被减少到最小的可接受水平(依据TCP协议的要求和安全方面的考虑)。结果不仅 是获得更快的传输数据和请求处理速度并且还使客户/server双向延迟实现了最小化。
二、使用样例说明
BOOL bReuseaddr=TRUE;
setsockopt(s,SOL_SOCKET ,SO_REUSEADDR,(const char*)&bReuseaddr,sizeof(BOOL));
2. 假设要已经处于连接状态的soket在调用closesocket后强制关闭,不经历
TIME_WAIT的过程:
BOOL bDontLinger = FALSE;
setsockopt(s,SOL_SOCKET,SO_DONTLINGER,(const char*)&bDontLinger,sizeof(BOOL));
3.在send(),recv()过程中有时因为网络状况等原因,发收不能预期进行,而设置收发时限:
int nNetTimeout=1000;//1秒
//发送时限
setsockopt(socket,SOL_S0CKET,SO_SNDTIMEO,(char *)&nNetTimeout,sizeof(int));
//接收时限
setsockopt(socket,SOL_S0CKET,SO_RCVTIMEO,(char *)&nNetTimeout,sizeof(int));
4.在send()的时候,返回的是实际发送出去的字节(同步)或发送到socket缓冲区的字节
(异步);系统默认的状态发送和接收一次为8688字节(约为8.5K);在实际的过程中发送数据
和接收数据量比較大,能够设置socket缓冲区,而避免了send(),recv()不断的循环收发:
// 接收缓冲区
int nRecvBuf=32*1024;//设置为32K
setsockopt(s,SOL_SOCKET,SO_RCVBUF,(const char*)&nRecvBuf,sizeof(int));
//发送缓冲区
int nSendBuf=32*1024;//设置为32K
setsockopt(s,SOL_SOCKET,SO_SNDBUF,(const char*)&nSendBuf,sizeof(int));
5. 假设在发送数据的时,希望不经历由系统缓冲区到socket缓冲区的拷贝而影响
程序的性能:
int nZero=0;
setsockopt(socket,SOL_S0CKET,SO_SNDBUF,(char *)&nZero,sizeof(nZero));
6.同上在recv()完毕上述功能(默认情况是将socket缓冲区的内容复制到系统缓冲区):
int nZero=0;
setsockopt(socket,SOL_S0CKET,SO_RCVBUF,(char *)&nZero,sizeof(int));
7.一般在发送UDP数据报的时候,希望该socket发送的数据具有广播特性:
BOOL bBroadcast=TRUE;
setsockopt(s,SOL_SOCKET,SO_BROADCAST,(const char*)&bBroadcast,sizeof(BOOL));
8.在client连接server过程中,假设处于非堵塞模式下的socket在connect()的过程中可
以设置connect()延时,直到accpet()被呼叫(本函数设置仅仅有在非堵塞的过程中有显著的
作用,在堵塞的函数调用中作用不大)
BOOL bConditionalAccept=TRUE;
setsockopt(s,SOL_SOCKET,SO_CONDITIONAL_ACCEPT,(const char*)&bConditionalAccept,sizeof(BOOL));
9.假设在发送数据的过程中(send()没有完毕,还有数据没发送)而调用了closesocket(),曾经我们
一般採取的措施是”从容关闭”shutdown(s,SD_BOTH),可是数据是肯定丢失了,怎样设置让程序满足详细
应用的要求(即让没发完的数据发送出去后在关闭socket)?
struct linger {
u_short l_onoff;
u_short l_linger;
};
linger m_sLinger;
m_sLinger.l_onoff=1;//(在closesocket()调用,可是还有数据没发送完毕的时候容许逗留)
// 假设m_sLinger.l_onoff=0;则功能和2.)作用同样;
m_sLinger.l_linger=5;//(容许逗留的时间为5秒)
setsockopt(s,SOL_SOCKET,SO_LINGER,(const char*)&m_sLinger,sizeof(linger));
一下来源于互联网: 1.closesocket(一般不会马上关闭而经历TIME_WAIT的过程)后想继续重用该socket: 2. 假设要已经处于连接状态的soket在调用closesocket后强制关闭,不经历 3.在send(),recv()过程中有时因为网络状况等原因,发收不能预期进行,而设置收发时限: 4.在send()的时候,返回的是实际发送出去的字节(同步)或发送到socket缓冲区的字节 5. 假设在发送数据的时,希望不经历由系统缓冲区到socket缓冲区的拷贝而影响 6.同上在recv()完毕上述功能(默认情况是将socket缓冲区的内容复制到系统缓冲区): 7.一般在发送UDP数据报的时候,希望该socket发送的数据具有广播特性: 8.在client连接server过程中,假设处于非堵塞模式下的socket在connect()的过程中可 9.假设在发送数据的过程中(send()没有完毕,还有数据没发送)而调用了closesocket(),曾经我们 |
发布者:全栈程序员-用户IM,转载请注明出处:https://javaforall.cn/118338.html原文链接:https://javaforall.cn
【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛
【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...