TCP全连接和半连接的问题探讨
个人博客: https://rebootcat.com/2020/11/14/tcp_accept/
从何说起
说起 tcp 的连接过程,想必 “3次握手4次挥手”是大家广为熟知的知识,那么关于更细节更底层的连接过程也许就很少人能讲清楚了。
所以本文会先简单回顾一下 tcp 的 3次握手过程,然后重点聊一下 tcp accept 的过程,涉及到 tcp 半连接队列、全连接队列等的内容。
回顾一下
3 次握手
要了解 3 次握手的过程,可能需要先熟悉一下 tcp 协议的格式:
- tcp segment 的头部有两个 2字节的字段
source port
和dest port
,分别表示本机端口以及目标端口,在 tcp 传输层是没有 IP 的概念的,那是 IP 层 的概念,IP 层协议会在 IP 协议的头部加上src ip
和dest ip
; - 4 个字节的 seq,表示序列号,tcp 是可靠连接,不会乱序;
- 4 个字节的 ack,表示确认号,表示对接收到的上一个报文的确认,值为 seq + 1;
- 几个标志位:ACK,RST,SYN,FIN 这些是我们常用的,比较熟悉的。其中 ACK 简写为 “.”; RST 简写为 “R”; SYN 简写为 “S”; FIN 简写为 “F”;
注意: ack 和 ACK 是不一样的意思,一个是确认号,一个是标志位
了解了 tcp 协议的头部格式,那么再来讲一下 3 次握手的过程:
- 客户端对服务端发起建立连接的请求,发送一个 SYN 包(也就是 SYN 标志位设置为 1),同时随机生成一个 seq 值 x,然后客户端就处于 SYN_SENT 状态;
- 服务端收到客户端的连接请求,回复一个 SYN+ACK包(也就是设置 SYN 和 ACK 标志位为 1),同时随机生成一个 seq 值 y,然后确认号 ack = x + 1,也就是 client 的 seq +1,服务端进入 SYN_RECV 阶段;
- 客户端收到服务端的 SYN+ACK 包,会回复一个 ACK 包(也就是设置 ACK 标志位为 1),设置 seq = x + 1,ack 等于 服务端的 seq +1,也就是 ack = y+1,然后连接建立成功;
tcpdump 抓包
开一个终端执行以下命令作为服务端:
# 服务端
$ nc -l 10000
然后打开新的终端用 tcpdump 抓包:
# -i 表示监听所有网卡;# -t 表示不打印 timestamp;# -S 表示打印绝对的 seq 而不是相对的 seq number;# port 10000 表示对 10000 端口进行抓包$ tcpdump -i any -t -S port 10000
然后再打开一个终端模拟客户端:
$ nc 127.0.0.1 10000
观察 tcpdump 的输出如下:
IP Jia.22921 > 192.168.1.7.ndmp: Flags [S], seq 614247470, win 29200, options [mss 1460,sackOK,TS val 159627770 ecr 0], length 0
IP 192.168.1.7.ndmp > Jia.22921: Flags [S.], seq 1720434034, ack 614247471, win 65160, options [mss 1460,sackOK,TS val 3002840224 ecr 159627770], length 0
IP Jia.22921 > 192.168.1.7.ndmp: Flags [.], ack 1720434035, win 29200, options [nop,nop,TS val 159627770 ecr 3002840224], length 0
分析以下上面的结果可以看到:
第一个包 Flags [S] 表示 SYN 包,seq 为随机值 614247470;
然后服务端回复了一个 Falgs [S.],也就是 SYN+ACK 包,同时设置 seq 为随机值 1720434034,设置 ack 为 614247470 + 1 = 614247471;
客户端收到之后,回复一个 Flags [.],也就是 ACK 包,同时设置 ack 为 1720434034 + 1 = 1720434035;
假如3次握手丢包了?
上面是正常情况的握手情况,假如握手过程中的任何一个包出现丢包呢会怎么样?比如受到了攻击,比如服务端宕机,服务端超时,客户端掉线,网络波动等。
所以接下来我们分析下 3 次握手过程中涉及到的连接队列。
tcp 内核参数
backlog 参数
https://linux.die.net/man/3/listen
The backlog argument provides a hint to the implementation which the implementation shall use to limit the number of outstanding connections in the socket’s listen queue. Implementations may impose a limit on backlog and silently reduce the specified value. Normally, a larger backlog argument value shall result in a larger or equal length of the listen queue. Implementations shall support values of backlog up to SOMAXCONN, defined in <sys/socket.h>.
int listen(int socket, int backlog);
backlog 参数是用来限制 tcp listen queue 的大小的,真实的 listen queue 大小其实也是跟内核参数 somaxconn 有关系,somaxconn 是内核用来限制同一个端口上的连接队列长度。
全连接队列
完成 3 次握手的连接,也就是服务端收到了客户端发送的最后一个 ACK 报文后,这个连接会被放到这个端口的全连接队列里,然后等待应用程序来处理,对于 epoll 来说就是内核触发 EPOLLIN 事件,然后应用层使用 epoll_wait
来处理 accept 事件,为连接分配创建 socket 结构,分配 file descriptor 等;
那么假如应用层没有来处理这些就绪的连接呢?那么这个全连接队列有可能就满了,导致后续的连接被丢弃,发生全连接队列溢出,丢弃这个连接,对客户端来说就无法成功建立连接。
所以为了性能的考虑,我们有必要尽可能的把这个队列的大小调大一点。
查看全连接队列大小
可以通过一下命令来查看当前端口的全连接队列大小:
$ ss -antl
State Recv-Q Send-Q Local Address:Port Peer Address:Port Process
LISTEN 0 5 192.168.1.7:10000 0.0.0.0:*
在 ss 输出中:
LISTEN 状态:Recv-Q 表示当前 listen backlog 队列中的连接数目(等待用户调用 accept() 获取的、已完成 3 次握手的 socket 连接数量),而 Send-Q 表示了 listen socket 最大能容纳的 backlog。
非 LISTEN 状态:Recv-Q 表示了 receive queue 中存在的字节数目;Send-Q 表示 send queue 中存在的字节数;
压测观察全连接队列溢出
接下来我们实际测试一下,使用项目:mux。
我们先修改一下 backlog 参数为 5:
# 把backlog 调小一点listen(listenfd, 5);
根据编译文档,编译后得到两个二进制:
$ ls
bench_server bench_client_accept
bench_server
用来作为服务端,底层使用 epoll 实现bench_client_accept
作为压测客户端,并发创建大量连接,这里只会与服务端建立连接,不会发送其他任何消息(当然可以用其他的压测工具)
选择两台机器进行测试,192.168.1.7 作为服务端, 192.168.1.4 作为压测客户端,开始压测前,可能需要设置一下:
$ ulimit -n 65535
- 启动服务端
# 192.168.1.7 作为服务端,监听 10000 端口$ ./bench_server 192.168.1.7 10000
注意到上图执行 ss -antl
看到 10000 端口的 listen queue size 为 5,这里是故意调小一点,为了验证全连接队列溢出的场景。
- 先观察一下服务端全连接队列的情况以及溢出的情况
$ ss -natl |grep 10000
LISTEN 0 5 192.168.1.7:10000 0.0.0.0:*
$ netstat -s |grep -i overflowed2283 times the listen queue of a socket overflowed
上述表明 10000 端口的 listen queue size 为 5,并且全连接队列中没有等待应用层处理的连接;
netstat -s |grep -i overflowed 表示全连接队列溢出的情况,2683 是一个累加值。
- 启动 tcpdump 对客户端行为抓包,分析 3次握手连接情况
# 运行在 client: 192.168.1.4 上$ tcpdump -i any port 10000 and tcp -nn > tcpdump.log
3)启动压测客户端
# 192.168.1.4 作为压测客户端
# 30000 表示连接数
# 100 表示 100 个并发线程
# 1 表示执行 1 轮$ ./bench_client_accept 192.168.1.7 10000 30000 100 1
压测过程中,可以不断执行命令观察服务端全连接队列溢出的情况,压测完毕之后再观察一下全连接队列溢出的情况:
$ ss -natl |grep 10000
LISTEN 0 5 192.168.1.7:10000 0.0.0.0:*
$ ss -natl |grep 10000
LISTEN 5 5 192.168.1.7:10000 0.0.0.0:*
$ ss -natl |grep 10000
LISTEN 0 5 192.168.1.7:10000 0.0.0.0:*
$ ss -natl |grep 10000
LISTEN 1 5 192.168.1.7:10000 0.0.0.0:*
$ ss -natl |grep 10000
LISTEN 0 5 192.168.1.7:10000 0.0.0.0:*
$ ss -natl |grep 10000
LISTEN 0 5 192.168.1.7:10000 0.0.0.0:*
$ ss -natl |grep 10000
LISTEN 1 5 192.168.1.7:10000 0.0.0.0:*
$ ss -natl |grep 10000
LISTEN 0 5 192.168.1.7:10000 0.0.0.0:*
$ netstat -s |grep -i overflowed2930 times the listen queue of a socket overflowed
可以看到,压测过程中的 Recv-Q 出现了5,1 的值,表示全连接队列中等待被处理的连接,而且有 2930 - 2283 = 647 次连接由于全连接队列溢出而被丢弃。
我们再来观察一下 bench_client_accept
的日志情况:
$ grep -a 'Start OK' log/bench_client_accept.log |wc -l
29736
$ grep -a 'start failed' log/bench_client_accept.log |wc -l
264
可以看到最终有 264 个 client 由于服务端丢弃建立连接时 3 次握手的包而造成连接失败。
如果你细心的话会发现,全连接队列溢出发生了 647 次,但是最终只有 264 个 client 建立失败,why?其实原因很简单,因为客户端有重试机制,具体参数是 net.ipv4.tcp_syn_retries
,这个暂且不详说。
那再来看一下 tcpdump 抓包的结果,这里要用到一个 python 脚本 tcpdump_analyze.py
来处理一下 tcpdump.log 这个日志:
import os# tcpdump -i any port 10000 and tcp -nn > tcpdump.logserver_ip_port = "192.168.1.7.10000"
client_map = {}with open('./tcpdump.log', 'r') as fin:for line in fin:sp = line.split()if len(sp) < 3:print("invalid line:{0}".format(line))continueclient_ip_port = sp[2]if client_ip_port == server_ip_port:client_ip_port = sp[4].split(':')[0]if client_ip_port not in client_map:client_map[client_ip_port] = [line]else:client_map[client_ip_port].append(line)connect_fail_client = []
connect_succ_client = []
connect_succ_client_normal = []
connect_succ_client_try = []total_size = len(client_map)for k,v in client_map.items():print("{0}$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$begin".format(k))for ll in v:print(ll)connect_fail = Truefor i in v:# ack 1 is the last packet of tcp handshake from serverif i.find('ack 1,') != -1:connect_fail = Falsebreakif connect_fail:connect_fail_client.append(v)print("fail");else:connect_succ_client.append(v)if len(v) == 3:connect_succ_client_normal.append(v)print("succ no retry");else:connect_succ_client_try.append(v)print("succ with retry")print("{0}$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$end\n\n".format(k))print("\ntotal client:{0} connect success client size:{1}".format(total_size, len(connect_succ_client)))
print("\ntotal client:{0} connect success client normal handshake size:{1}".format(total_size, len(connect_succ_client_normal)))
print("\ntotal client:{0} connect success client after retry handshake size:{1}".format(total_size, len(connect_succ_client_try)))
print("\ntotal client:{0} connect fail client size:{1}".format(total_size, len(connect_fail_client)))
运行后得到结果:
$ python tcpdump_analyze.py(省略部分输出)192.168.1.4.20409$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$begin
15:43:12.030247 IP 192.168.1.4.20409 > 192.168.1.7.10000: Flags [S], seq 2040611302, win 29200, options [mss 1460,sackOK,TS val 166083457 ecr 0], length 015:43:13.033419 IP 192.168.1.4.20409 > 192.168.1.7.10000: Flags [S], seq 2040611302, win 29200, options [mss 1460,sackOK,TS val 166084460 ecr 0], length 015:43:13.033661 IP 192.168.1.7.10000 > 192.168.1.4.20409: Flags [S.], seq 3015149333, ack 2040611303, win 65160, options [mss 1460,sackOK,TS val 3009296915 ecr 166084460], length 015:43:13.033667 IP 192.168.1.4.20409 > 192.168.1.7.10000: Flags [.], ack 1, win 29200, options [nop,nop,TS val 166084460 ecr 3009296915], length 0succ with retry
192.168.1.4.20409$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$end192.168.1.4.54379$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$begin
15:43:21.047376 IP 192.168.1.4.54379 > 192.168.1.7.10000: Flags [S], seq 2685382859, win 29200, options [mss 1460,sackOK,TS val 166092474 ecr 0], length 015:43:21.047514 IP 192.168.1.7.10000 > 192.168.1.4.54379: Flags [S.], seq 1736229374, ack 2685382860, win 65160, options [mss 1460,sackOK,TS val 3009304929 ecr 166092474], length 015:43:21.047528 IP 192.168.1.4.54379 > 192.168.1.7.10000: Flags [.], ack 1, win 29200, options [nop,nop,TS val 166092474 ecr 3009304929], length 0succ no retry
192.168.1.4.54379$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$endtotal client:30000 connect success client size:29736total client:30000 connect success client normal handshake size:29195total client:30000 connect success client after retry handshake size:541total client:30000 connect fail client size:264
上面的意思是总共有 29736 个 client 成功建立连接,而有 264 个 client 建立失败;连接成功的 client 里有 29195 个是通过了正常的 3 次握手成功建立,没有发生重试;而有 541 个 client 是发生了重试的情况下才建立连接成功。
可以看到上面的输出,发生重试 “succ with retry” 的部分,client 发送一个 SYN 之后,由于 server 全连接队列溢出导致连接被丢弃,client 超时后重新发送 SYN 包,然后建立连接;
而上面连接失败的客户端,错误原因都是: errno = 110,也就是 “Connection timed out”。
Ok,到现在应该明白全连接队列大小对于 tcp 3 次握手的影响,如果全连接队列过小,一旦发生溢出,就会影响后续的连接。
调整内核参数,避免全连接队列溢出
那我们修改一下 backlog 的大小,改大一些:
listen(listenfd, 100000);
然后我们修改内核参数:
net.core.netdev_max_backlog = 400000
net.core.somaxconn = 100000
可以通过打开 /etc/sysctl.conf
直接修改,或是通过命令修改:
$ sysctl -w net.core.netdev_max_backlog=400000
重新编译运行,执行上述的压测,观察结果。
压测前:
$ ss -natl |grep 10000
LISTEN 0 100000 192.168.1.7:10000 0.0.0.0:*
$ netstat -s |grep -i overflowed3118 times the listen queue of a socket overflowed
压测后:
$ netstat -s |grep -i overflowed3118 times the listen queue of a socket overflowed$ python tcpdump_analyze.py
(省略部分输出)
total client:30000 connect success client size:30000total client:30000 connect success client normal handshake size:30000total client:30000 connect success client after retry handshake size:0total client:30000 connect fail client size:0
可以看到,当我们把内核参数以及 backlog 调大之后,30000 个 client 全部建立连接成功且没有发生重试,服务端的 listen queue 没有发生溢出。
半连接队列
全连接队列存放的是已经完成 3次握手,等待应用层调用 accept()
处理这些连接;其实还有一个半连接队列,当服务端收到客户端的 SYN 包后,并且回复 SYN+ACK包后,服务端进入 SYN_RECV 状态,此时这种连接称为半连接,会被存放到半连接队列,当完成 3 次握手之后,tcp 会把这个连接从半连接队列中移到全连接队列,然后等待应用层处理。
那么怎么查看半连接队列的大小呢?没有直接的 linux command 来查询半连接队列的长度,但是根据上面的定义,服务端处于 SYN_RECV 状态的数量就表示半连接的数量。所以采用一定的方式增大半连接的数量,看服务端 SYN_RECV 的数量最大值有多少,那就是半连接队列的大小。
那问题就来了,如何增大半连接的数量呢?这里采用到的就是 SYN-FLOOD 攻击,通过发送大量的 SYN 包而不进行回应,造成服务端创建了大量的半连接,但是这些半连接不会被确认,最终把 tcp 半连接队列占满造成溢出,并影响正常的连接。
半连接队列溢出
采用的工具是: hping3,一款很强大的工具。
启动服务端:
# 192.168.1.7 作为服务端,监听 10000 端口$ ./bench_server 192.168.1.7 10000
开始攻击:
$ hping3 -S --flood --rand-source -p 10000 192.168.1.7
观察半连接数量:
$ netstat -ant |grep SYN
(省略)
tcp 0 0 192.168.1.7:10000 152.66.128.1:48581 SYN_RECV
tcp 0 0 192.168.1.7:10000 208.220.119.30:57972 SYN_RECV
tcp 0 0 192.168.1.7:10000 3.104.166.109:25975 SYN_RECV $ netstat -ant |grep SYN |wc -l
256
持续观察,可以看到处于 SYN_RECV
状态的连接基本保持在 256,说明半连接队列的大小是 256。而此时,10000 端口已经比较难连接上了。
查看一下半连接队列的丢弃情况:
$ netstat -s |grep dropped26055883 SYNs to LISTEN sockets dropped
注意: 26055883 是一个累加值,可以持续观察
那怎么增大半连接队列大小呢?
增大半连接队列,防止溢出
直接修改内核参数:
# 直接修改文件 /etc/sysctl.confnet.ipv4.tcp_max_syn_backlog = 100000
或者使用命令:
$ sysctl -w net.ipv4.tcp_max_syn_backlog=100000
据说半连接队列并非只由这个参数决定,不同的系统的计算方式不一致,还会和全连接队列大小有关
当然这个应对 SYN-Flood 攻击只是轻微降低影响而已。
还可以设置 net.ipv4.tcp_syncookies = 0
来一定程度防范 SYN 攻击。
syncookies 的原理就是当服务端收到客户端 SYN 包后,不会放到半连接队列里,而是通过 {src_ip, src_port, timestamp} 等计算一个 cookie(也就是一个哈希值),通过 SYN+ACK包返回给客户端,客户端返回一个 ACK 包,携带上这个 cookie,服务端通过校验可以直接把这个连接放入全连接队列。整个过程不需要半连接队列的参与。
SYN 重试
上面压测验证全连接队列溢出的场景下,通过 tcpdump 抓包分析到有些连接是经过了重试才建立成功的,具体表现在:
客户端发送 SYN 包请求建立连接,但此时由于服务端全连接队列溢出或者半连接队列溢出,该 SYN 包就会被丢弃,当客户端迟迟无法收到服务端的 SYN+ACK 包后,客户端超时重发 SYN 包,如果再次超时,那么根据内核设置的 SYN 超时重试次数决定是否继续重发 SYN 包。
假设重试次数为 6 次:
- 第一次发送 SYN 后等待 1 s (2^0);
- 第二次发送 SYN 后等待 2 s (2^1);
- 第三次发送 SYN 后等待 4 s (2^1);
- …
所以当我们发现服务端出现了问题的时候,可以适当提高 SYN 重试的次数;当然过大的值也会影响问题的快速发现;
可以通过设置:
$ sysctl -w net.ipv4.tcp_syn_retries=2
The End
Ok, 到这里基本上把 tcp 3 次握手比较细节的地方讲到了。 tcp 真是一个巨复杂的协议,还有不少值得深挖的东西!
Blog:
rebootcat.com
email: linuxcode2niki@gmail.com
2020-11-14 于杭州
By 史矛革
相关文章:

[转] ASP.NET MVC3 路由和多数据集的返回
1.ASP.NET MVC3 中的路由 同前边一样本篇并不会过多的介绍理论知识,我们在Global.asax.cs文件中可以看到如下代码: routes.MapRoute("Default", // Route name"{controller}/{action}/{id}", // URL with parametersnew { controlle…
给Python代码加上酷炫进度条的几种姿势
作者 | 刘早起来源 | 早起Python(ID: zaoqi-python)大家好,在下载某些文件的时候你一定会不时盯着进度条,在写代码的时候使用进度条可以便捷的观察任务处理情况,除了使用print来打印之外,今天本文就介绍几种…

(转)mongodb分片
本文转载自:http://www.cnblogs.com/huangxincheng/archive/2012/03/07/2383284.html 在mongodb里面存在另一种集群,就是分片技术,跟sql server的表分区类似,我们知道当数据量达到T级别的时候,我们的磁盘,内…

深入浅出paxos
原文 https://rebootcat.com/2020/12/05/paxos/
Uber 前无人驾驶工程师告诉你,国内无人驾驶之路还要走多久?
受访者 | Graviti 创始人&CEO 崔运凯记者 | Aholiab,编辑 | Carol出品 | AI科技大本营(ID:rgznai100)经过数年的发展,现在的人们谈到“AI”已经不再像过去一般感到遥不可及。但 AI 在国内发挥的作用仍然只是冰山一角ÿ…

oracle服务
OracleOraDb11g_home1ClrAgent服务,在网上查找到了资料 http://download.oracle.com/docs/cd/B19306_01/win.102/b14306/install.htm Configuring Extproc Agent Using Windows Service As part of Oracle Database Extensions for .NET installation, a Windows se…

B00009 C语言分割字符串库函数strtok
切割字符串是常用的处理。 这里给出一个使用函数strtok切割字符串的例子。 使用C语言的库函数strtok来切割字符串的好处在于,可以指定任意字符作为分隔符来切割单词。使用该函数,切割字符串的分隔符可以同时指定多个,放在一个字符串数组中。 …

WEB打印大全
1、控制"纵打"、 横打”和“页面的边距。 (1)<script defer> function SetPrintSettings() { // -- advanced features factory.printing.SetMarginMeasure(2) // measure margins in inches factory.SetPageRange(false, 1, 3) // ne…
漫画 | 程序媛小姐姐带你一次了解什么是排序算法
来源 | 小齐本齐封图 | CSDN 付费下载自视觉中国插入排序借用《算法导论》里的例子,就是我们打牌的时候,每新拿一张牌都会把它按顺序插入,这,其实就是插入排序。齐姐声明:虽然我们用打牌的例子,但是可不能学…

POJ 1207 The 3n + 1 problem
题目链接:http://poj.org/problem?id1207 题目大意:给你一个数x,规定一个函数F(x),如果x为1则F(x)1,否则如果x是偶数,F(x)F(x/2),x为奇数F(x)F(3*x1)计算给定x到变换到1的步数。 注意点&#x…

PopupWindow响应返回键的问题
假设情景是这样的:在一个Activity中弹出一个PopupWindow,要求在按返回键时关闭该PopupWindow。 如果该PopupWindow是无焦点的(默认情况),那么可以在Activity中响应返回键(onBackPressed)&#x…

Unix / Linux世界里的4-2-1
Unix / Linux世界里的4-2-1 在Unix / Linux世界里,4代表可读( r ),2代表可写入 ( w ),1代表可执行 ( x ) 如果拥有7 421 的权限,即代表这个人可以对档案完全控制。 以0777为例: 去掉0,第一个7代表着拥有者…
深度学习概述:NLP vs CNN
作者 | Manish Kuwar译者 | 苏本如,责编 | 郭芮头图 | CSDN 下载自视觉中国出品 | CSDN(ID:CSDNnews)以下为译文:当今,人工智能已经不仅仅是一个技术术语了。这项技术在过去十年的时间内几乎将其影响扩展到…

oracle 求A中不存在于B的记录
oracle 求A中不存在于B的记录 select * from a minus select * from b 是求A中不存在于B的记录select * from a union select * from b 是求A和B的DISTINCT的并集select * from a union all select * from b 是求A和B的冗余并集那么A和B的交集是什么函数来的?交集是 INTERSE…

正则表达式grep、egrep--already
第一式 grep是什么 #man grepgrep(global search regular expression(RE)是一种强大的文本搜索工具,它能使用正则表达式搜索文本,并把匹配的行打印出来。UNIX的grep家族包括grep、egrep和fgrep。egrep和fgrep的命令…
万字长文综述目标检测领域,你要的都在这里
来源 | AI专栏(ID: pursue-Y-future)目标检测是计算机视觉中的一个重要问题,近年来传统检测方法已难以满足人们对目标检测效果的要求,随着深度学习在图像分类任务上取得巨大进展,基于深度学习的目标检测算法逐渐成为主…

ASP.net随机数应用实例
家可能都用过Chinaren的校友录,不久前它的留言簿上加了一个防止灌水的方法,就是系统每次产生一个由随机的数字和字母组成的图片,每次留言必须正确地输入这些随机产生的字符,否则不能添加留言。这是一个很好的防止恶意攻击的方法&a…

PreferenceActivity是什么?
我们看到Android系统本身就大量用到了PreferenceActivity来对系统进行信息配置和管理,那么它是怎么保存数据的呢,如何创建PrefenceActivity的呢?创建Android项目,并添加一个pref.xml文件(先建一个xml名的Folder)。注意,这次选择的…
坑系列 --- 时间和空间的平衡
这是坑系列的最后一弹了,这篇文章非常长,希望你能看完,要是看完有很酣畅的感觉就最好了。这一篇的坑主要来说说架构中时间和空间的平衡吧,这里的时间指代比较广,可能是开发时间,但大部分指的是执行时间&…

C#中调用Windows API的要点
在.Net Framework SDK文档中,关于调用Windows API的指示比较零散,并且其中稍全面一点的是针对Visual Basic .net讲述的。本文将C#中调用API的要点汇集如下,希望给未在C#中使用过API的朋友一点帮助。另外如果安装了Visual Studio .net的话&…
线上直播丨Hinton等6位图灵奖得主、百余位顶级学者邀你群聊AI
Geoffrey Hinton等6位图灵奖得主亲临,百余位顶级学者邀请你加入群聊「2020北京智源大会」,深入系统探讨「人工智能的下一个十年」。自2009年深度学习崛起以来,第三波人工智能浪潮席卷全球,推动了新一波技术革命。在这波澜壮阔的11…

ServerSocket
ServerScoket 这个类用于与 Socket 进行通信。 在实例化ServerSocket 的时候,服务器相当于已经开始了,但是还需要通过socket来accept (socket serverSocket.accept())以使服务器选择性与某一Client进行连接。如果有指定了允许连接…

NDK开发 - C/C++ 访问 Java 变量和方法
上一篇有提到 JNI 访问引用数组,涉及了 C/C 访问 Java 实例的方法和变量。虽然在之前的开发中,并没有用到 C/C 范围 Java 层数据,但是这部分内容还是很有用的。传送门:NDK开发 - C/C 访问 Java 变量和方法 C/C 访问 Java 层的方法…

在C#中应用哈希表(Hashtable)
一,哈希表(Hashtable)简述 在.NET Framework中,Hashtable是System.Collections命名空间提供的一个容器,用于处理和表现类似key/value的键值对,其中key通常可用来快速查找,同时key是区分大小写;value用于存储对应于key的…
俄罗斯自研Elbrus CPU参数曝光,CEO年近九旬仍未退休
导语:俄罗斯自研 CPU 参数最近曝光,虽然比起主流产品仍存在较大差距,但是这也是俄罗斯在自研道路上的一大进展。虽说自研 CPU 并非易事,但为了避免被美国牵制,很多国家都在这方面投入了巨大的人力与资金。战斗民族俄罗…

停止Password Manager Agent服务导致应用程序启动缓慢
在一个实施环境中,部署了Password Manager用来实现单点登录功能,但是由于Password Manager的提示基本都是以英文为主,而且配置也比较麻烦,普通用户看见会比较影响用户体验,所以用户决定暂时关闭Password Manager功能&a…

web 前端常用组件【06】Upload 控件
因为有万恶的IE存在,所以当Web项目初始化并进入开发阶段时。 如果是项目经理,需要知道客户将会用什么浏览器来访问系统。 明确知道限定浏览器的情况下,你才能从容的让手下的封装必要的前端组件。 本篇文章试图从常见的上传方式和组件进行分析…
性能超越最新序列推荐模型,华为诺亚方舟提出记忆增强的图神经网络
作者 | Chen Ma, Liheng Ma等译者 | Rachel出品 | AI科技大本营(ID:rgznai100)用户-商品交互的时间顺序可以揭示出推荐系统中用户行为随时间演进的序列性特征。用户与之交互的商品可能受到用户曾经接触的商品的影响。但是,用户和商…

ASP.net 中的页面继承实现和通用页面的工厂模式的实现
最近用.Net做web项目的时候遇到了一些问题,就是很多的页面的处理一样的,不一样的就是我们写的存储过程不同,为了考虑代码的重复利用和可维护性和可 扩展性,于是写了一个对于单据页面的工厂模式,采用界面的继承技术&…

5502的时钟组
5502有四个时钟组,分别为: C55x Subsystem Clock GroupFast Peripherals Clock GroupSlow Peripherals Clock GroupExternal Memory Interface Clock Group1、C55x Subsystem Clock Group该时钟组包括C55X CPU core、内存(DARAM和ROM…