网络通信IO的演变过程(一)(一个门外汉的理解)
以前从来不懂IO的底层,只知道一个大概,就是输入输出的管道怼到一起,然后就可以传输数据了。
最近看了周志垒老师的公开课后,醍醐灌顶。
所以做一个简单的记录。
0 计算机组成原理相关
0.1. 计算机的基本组成大家都了解一点,如下图,当操作系统启动的时候,首先进入内存的除了BIOS,然后就是Linux内核程序。
- 内核暂时先理解成系统程序,比如我们想通过键盘获取到用户的输入,想打开网卡录取视频。这些硬件是受系统保护的,只能交给内核控制。不可能把控制权交给用户程序。
- 用户程序如果想访问硬件,只能用户调用内核暴露的一些调用,我们称这个为系统调用。
- 操作系统启动的时候,会把内核程序所在的地址空间设为绝对安全的空间,这个空间称为内核空间,这种机制称为保护模式。其他的空间即提供给用户程序使用,称为用户空间。比如JVM,QQ,微信对内核来说都是用户App。
- 操作系统启动后,OS会在一个叫做GDT(全局描述符表)的表里标识出内核空间的位置。
0.2. 中断
假设在单核CPU的电脑上,安装一个操作系统,系统中运行了N多的程序,包括内核程序和用户程序。
此时在一个瞬间CPU只会执行一个程序。
CPU是怎么进行各个进程的调度的呢?
Step1:
- 晶振1秒钟震动1千次或者1万次,晶振每震荡一次,都会传递一个时钟中断给CPU,假设当前运行的是用户程序1,晶振震动之后,CPU会把用户程序1的数据(现场)从CPU的高速寄存器中缓存到这个用户程序的空间中。
Step2:
- 操作系统启动的时候,会有一个中断向量表(中断表述符表IDT(Interrupt Descriptor Table)),OS启动时,内核程序注册的。
- 中断产生了之后,存在一个中断回调程序。这个回调程序是由内核注册的。比如这里的:时钟中断产生之后,CPU把现场保存了,CPU本不知道下一步要干什么。但是在OS启动的时候,内核已经注册了,说“CPU,当你收到时钟中断之后,先保护现场,然后来调用我内核程序中的一个调用,这个调用叫'进程调度'”。这个注册的东西就是注册中断向量表。代表“某一个中断发生了,CPU应该去这个表上查它下一步应该干啥”
- 所以当时钟中断产生之后,用户程序现场被保存下来,然后CPU调用内核的“进程调度”这个方法。
Step3:
- CPU调用内核的“进程调度”后,内核告诉CPU,现在可以执行用户程序2了,CPU就去执行用户程序2了,因为之前可能用户程序2被调度过,所以中断后的进程调度第一步就是恢复现场。
所以这里的中断会导致1:CPU的保护现场和恢复现场,会使CPU高速寄存器和内存之间的数据传递,会有消耗。
所以这里的中断会导致2:CPU进行系统调用"进程调度"的次数会有消耗。
【证明01:程序运行的越多,单位时间内,CPU浪费在内核调度上的时间会变多,浪费在寄存器与内存数据传递的时间会变多,真正运行程序的时间会变少。】
0.3. 软中断(陷阱)
假设用户程序想访问电脑的摄像头或者硬盘,此时用户程序会调用内核的系统调用。这种调用我们称为软中断或者陷阱(INT x80)
从CPU的角度考虑一下这个调用,CPU首先在执行用户的程序,但是用户程序写了一行“读取网卡输入”的代码,这个时候用户的编程语言的编译器会在指令里加入一个软中断int x80,表示需要进行系统调用。当CPU读取到int x80的指令的时候,内核之前已经注册了中断向量表(中断表述符表IDT(Interrupt Descriptor Table)),系统调用处理程序 system_call() 的入口地址放在系统的中断表述符表IDT(Interrupt Descriptor Table)中,Linux系统初始化时,由 trap_init() 将其填写完整,其设置系统调用处理程序的语句为:
set_system_gate(0x80, &system_call)
经过初始化以后,每当执行 int 0x80 指令时,产生一个异常使系统陷入内核空间并执行128号异常处理程序,即系统调用处理程序 system_call() 。
这一列骚操作会让CPU在用户态和内核态之间转换。所以依然会让CPU在单位时间产生更大的消耗。
【证明02:当程序调用了系统调用访问外设这种操作,会让CPU消耗更多的时间在用户态和内核态之间】
1. BIO (BlockingIO)
先上代码:
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.ServerSocket;
import java.net.Socket;
public class TestBio {
public static void main(String[] args) throws Exception {
ServerSocket serverSocket = new ServerSocket(8090); //监听端口8090,本机作为服务端
System.out.println("step1 new ServerSocket(8090)");
while(true) {
Socket client = serverSocket.accept(); //一旦客户端连接上来,新开辟一个线程处理客户端输入
System.out.println("client port :" + client.getPort());
new Thread(new Runnable() {
Socket ss;
public Runnable setSS(Socket s) {
ss = s;
return this;
}
@Override
public void run() {
try {
InputStream is = ss.getInputStream();
BufferedReader reader = new BufferedReader(new InputStreamReader(is));
while(true) {
System.out.println(reader.readLine());
}
} catch(IOException e) {
e.printStackTrace();
}
}
}.setSS(client)).start();
}
}
}
这段代码非常之简单,不多做解释。
利用自己的虚拟机进入Linux系统,把这份程序拷贝到Linux环境下运行。再利用strace命令跟踪java bio在linux系统中的调用过程。
什么是strace?
按照strace官网的描述, strace是一个可用于诊断、调试和教学的Linux用户空间跟踪器。我们用它来监控用户空间进程和内核的交互,比如系统调用、信号传递、进程状态变更等。
strace底层使用内核的ptrace特性来实现其功能。
[root@hadoop-senior code]# strace -ff -o out java TestBio
执行结果:
[root@hadoop-senior code]# strace -ff -o out java TestBio
step1 new ServerSocket(8090)
可以理解,此时服务器端(即自己的虚拟机)已经在8090监听来自客户端的连接。但是目前还没有任何连接建立,所以打印完输出文字之后就阻塞在这里。
通过ll
查看strace命令自动生成的线程跟踪文件:
[root@hadoop-senior code]# ll
total 252
-rw-r--r-- 1 root root 9424 Jun 26 15:23 out.6228
-rw-r--r-- 1 root root 177741 Jun 26 15:23 out.6229
-rw-r--r-- 1 root root 2123 Jun 26 15:23 out.6230
-rw-r--r-- 1 root root 931 Jun 26 15:23 out.6231
-rw-r--r-- 1 root root 1066 Jun 26 15:23 out.6232
-rw-r--r-- 1 root root 975 Jun 26 15:23 out.6233
-rw-r--r-- 1 root root 5003 Jun 26 15:23 out.6234
-rw-r--r-- 1 root root 3705 Jun 26 15:23 out.6235
-rw-r--r-- 1 root root 931 Jun 26 15:23 out.6236
-rw-r--r-- 1 root root 17321 Jun 26 15:23 out.6237
-rw-r--r-- 1 root root 1092 Jun 26 14:29 TestBio$1.class
-rw-r--r-- 1 root root 1128 Jun 26 14:29 TestBio.class
-rw-r--r-- 1 root root 1326 Jun 26 14:28 TestBio.java
[root@hadoop-senior code]#
可以发现生成了很多out开头的线程跟踪文件,通过less或者tail -f查看这些文件。
通过jps
,netstat -natp
查看服务器各个端口使用情况。
因为java是一个多线程的程序,查看最大size的文件:out.6229,这个文件跟踪的即是main方法的输出。前面4位数字是这个文件的行号,不用在意。
2421 socket(PF_INET6, SOCK_STREAM, IPPROTO_IP) = 5
2422 setsockopt(5, SOL_IPV6, IPV6_V6ONLY, [0], 4) = 0
2423 fcntl(5, F_GETFL) = 0x2 (flags O_RDWR)
2424 fcntl(5, F_SETFL, O_RDWR|O_NONBLOCK) = 0
2425 setsockopt(5, SOL_SOCKET, SO_REUSEADDR, [1], 4) = 0
2426 lseek(3, 58578982, SEEK_SET) = 58578982
2427 read(3, "PK\3\4\n\0\0\10\0\0!\260\365J\324\317\366B\341\2\0\0\341\2\0\0\26\0\0\0", 30) = 30
2428 lseek(3, 58579034, SEEK_SET) = 58579034
2429 read(3, "\312\376\272\276\0\0\0004\0%\n\0\10\0\32\t\0\7\0\33\n\0\t\0\34\n\0\t\0\35\7\0"..., 737) = 737
2430 lseek(3, 58572442, SEEK_SET) = 58572442
2431 read(3, "PK\3\4\n\0\0\10\0\0!\260\365J\355\177E>Q\31\0\0Q\31\0\0\35\0\0\0", 30) = 30
2432 lseek(3, 58572501, SEEK_SET) = 58572501
2433 read(3, "\312\376\272\276\0\0\0004\0015\n\0Y\0\217\7\0\220\10\0\221\n\0\2\0\222\n\0\223\0\224\7"..., 6481) = 6481
2434 lseek(3, 58571940, SEEK_SET) = 58571940
2435 read(3, "PK\3\4\n\0\0\10\0\0!\260\365J0\216\322;\271\1\0\0\271\1\0\0\37\0\0\0", 30) = 30
2436 lseek(3, 58572001, SEEK_SET) = 58572001
2437 read(3, "\312\376\272\276\0\0\0004\0\27\n\0\3\0\17\7\0\21\7\0\24\1\0\6<init>\1\0"..., 441) = 441
2438 lseek(3, 59057764, SEEK_SET) = 59057764
2439 read(3, "PK\3\4\n\0\0\10\0\0\33\260\365J_M\312\337\\\"\0\0\\\"\0\0\33\0\0\0", 30) = 30
2440 lseek(3, 59057821, SEEK_SET) = 59057821
2441 read(3, "\312\376\272\276\0\0\0004\1\222\n\0\20\0\314\n\0\4\0\315\t\0\20\0\316\7\0\317\n\0\320\0"..., 8796) = 8796
2442 lseek(3, 59053916, SEEK_SET) = 59053916
2443 read(3, "PK\3\4\n\0\0\10\0\0\33\260\365J\245h\220\331\274\16\0\0\274\16\0\0.\0\0\0", 30) = 30
2444 lseek(3, 59053992, SEEK_SET) = 59053992
2445 read(3, "\312\376\272\276\0\0\0004\0x\7\0J\n\0\26\0K\n\0\26\0L\t\0\26\0M\n\0\30\0"..., 3772) = 3772
2446 bind(5, {sa_family=AF_INET6, sin6_port=htons(8090), inet_pton(AF_INET6, "::", &sin6_addr), sin6_flowinfo=0, sin6_scope_id=0}, 28) = 0
2447 listen(5, 50) = 0
2448 write(1, "step1 new ServerSocket(8090)", 28) = 28
2449 write(1, "\n", 1) = 1
2450 lseek(3, 58689145, SEEK_SET) = 58689145
2451 read(3, "PK\3\4\n\0\0\10\0\0\20\260\365Jy\271LV\2416\0\0\2416\0\0\25\0\0\0", 30) = 30
2452 lseek(3, 58689196, SEEK_SET) = 58689196
2453 read(3, "\312\376\272\276\0\0\0004\1\345\n\0\6\1\27\t\0\233\1\30\t\0\233\1\31\t\0\233\1\32\t\0"..., 13985) = 13985
2454 accept(5,
关键来了
new ServerSocket(8090) 代表的第2421,2422,2446,2447 这几行。
意思就是
2421 socket(PF_INET6, SOCK_STREAM, IPPROTO_IP) = 5 //创建socket,定义为文件描述符5
2422 setsockopt(5, SOL_IPV6, IPV6_V6ONLY, [0], 4) = 0 //设置socket属性,如Ipv4或者Ipv6
2446 bind(5, {sa_family=AF_INET6, sin6_port=htons(8090), inet_pton(AF_INET6, "::", &sin6_addr), sin6_flowinfo=0, sin6_scope_id=0}, 28) = 0 //把文件描述符5绑定到8090端口
2447 listen(5, 50) //监听5文件描述符
serverSocket.accept()
代表的是2454 accept(5,
这个accept系统调用是阻塞的,表示如果没有客户端连接上来,那么这个方法会一直阻塞着等待。
新开一个xshell客户端,输入:
[root@hadoop-senior ~]# nc localhost 8090
第一个客户端连接上来,可以看到客户端的连接端口号是40368
[root@hadoop-senior code]# strace -ff -o out java TestBio
step1 new ServerSocket(8090)
client port :40368
再次查看less -N out.6229
文件,可以发现下面的新增内容:
2455 accept(5, {sa_family=AF_INET6, sin6_port=htons(40368), inet_pton(AF_INET6, "::1", &sin6_addr), sin6_flowinfo=0, sin6_scope_id=0}, [28]) = 6
2456 fcntl(6, F_GETFL) = 0x2 (flags O_RDWR)
2457 fcntl(6, F_SETFL, O_RDWR) = 0
2458 write(1, "client port :40368", 18) = 18
2459 write(1, "\n", 1) = 1
2460 stat("/opt/code/TestBio$1.class", {st_mode=S_IFREG|0644, st_size=1092, ...}) = 0
2461 open("/opt/code/TestBio$1.class", O_RDONLY) = 7
2462 fstat(7, {st_mode=S_IFREG|0644, st_size=1092, ...}) = 0
2463 stat("/opt/code/TestBio$1.class", {st_mode=S_IFREG|0644, st_size=1092, ...}) = 0
2464 read(7, "\312\376\272\276\0\0\0004\0H\n\0\16\0#\t\0\r\0$\n\0%\0&\7\0'\7\0(\n"..., 1024) = 1024
2465 read(7, "\0%\0\31\0\0\0\30\0\2\375\0\30\7\0\32\7\0\33\377\0\f\0\1\7\0\34\0\1\7\0\35"..., 68) = 68
2466 close(7) = 0
2467 lseek(3, 62390550, SEEK_SET) = 62390550
2468 read(3, "PK\3\4\n\0\0\10\0\0\16\260\365J9\267\215\270R\6\0\0R\6\0\0;\0\0\0", 30) = 30
2469 lseek(3, 62390639, SEEK_SET) = 62390639
2470 read(3, "\312\376\272\276\0\0\0004\0:\7\0!\n\0\v\0\"\t\0\10\0#\n\0\1\0$\t\0\v\0"..., 1618) = 1618
2471 mmap(NULL, 1052672, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS|MAP_STACK, -1, 0) = 0x7f584016d000
2472 clone(child_stack=0x7f584026cff0, flags=CLONE_VM|CLONE_FS|CLONE_FILES|CLONE_SIGHAND|CLONE_THREAD|CLONE_SYSVSEM|CL
2472 ONE_SETTLS|CLONE_PARENT_SETTID|CLONE_CHILD_CLEARTID, parent_tidptr=0x7f584026d9d0, tls=0x7f584026d700, child_tidp
2472 tr=0x7f584026d9d0) = 6333
2473 futex(0x7f583c009454, FUTEX_WAIT_PRIVATE, 21, NULL) = 0
2474 futex(0x7f583c009428, FUTEX_WAIT_PRIVATE, 2, NULL) = 0
2475 futex(0x7f583c009428, FUTEX_WAKE_PRIVATE, 1) = 0
2476 accept(5,
我们来看下面这关键的三行,
2455 accept(5, {sa_family=AF_INET6, sin6_port=htons(40368), inet_pton(AF_INET6, "::1", &sin6_addr), sin6_flowinfo=0, sin6_scope_id=0}, [28]) = 6
...
2472 clone(child_stack=0x7f584026cff0, flags=CLONE_VM|CLONE_FS|CLONE_FILES|CLONE_SIGHAND|CLONE_THREAD|CLONE_SYSVSEM|CL
2472 ONE_SETTLS|CLONE_PARENT_SETTID|CLONE_CHILD_CLEARTID, parent_tidptr=0x7f584026d9d0, tls=0x7f584026d700, child_tidp
2472 tr=0x7f584026d9d0) = 6333
...
2476 accept(5,
第一行之前被阻塞在accept(5,
这一时刻,当客户端连接进来的时候,系统调用被唤醒,然后因为我们的java程序写的是new了一个线程去处理这个客户端,所以看到一个clone命令
第二行就是clone命令,代表的是创建一个线程,去处理这个客户端的输入。
第三行就是继续阻塞,去接受新客户端的连接。
下面简单说一下新线程中的读取客户端输入的过程:
当使用nc命令连接服务端之后:strace新创建了一个out.6333
文件,这个文件的输出就是新线程接受客户端连接的那个动作
[root@hadoop-senior code]# ll
total 5776
-rw-r--r-- 1 root root 9424 Jun 26 15:23 out.6228
-rw-r--r-- 1 root root 179455 Jun 26 15:38 out.6229
-rw-r--r-- 1 root root 268997 Jun 26 15:50 out.6230
-rw-r--r-- 1 root root 931 Jun 26 15:23 out.6231
-rw-r--r-- 1 root root 1066 Jun 26 15:23 out.6232
-rw-r--r-- 1 root root 975 Jun 26 15:23 out.6233
-rw-r--r-- 1 root root 58561 Jun 26 15:50 out.6234
-rw-r--r-- 1 root root 57379 Jun 26 15:50 out.6235
-rw-r--r-- 1 root root 931 Jun 26 15:23 out.6236
-rw-r--r-- 1 root root 5277429 Jun 26 15:50 out.6237
-rw-r--r-- 1 root root 1782 Jun 26 15:49 out.6333
-rw-r--r-- 1 root root 1092 Jun 26 14:29 TestBio$1.class
-rw-r--r-- 1 root root 1128 Jun 26 14:29 TestBio.class
-rw-r--r-- 1 root root 1326 Jun 26 14:28 TestBio.java
查看out,6333,可以看到如果客户端没有输入的时候,服务器阻塞在recvfrom(6,
, 当我从nc命令行分别输入2次:"123"、"456"之后,
recvfrom(6, "123\n", 8192, 0, NULL, NULL) = 4 会从阻塞状态变为执行。从而读取到客户端的输入。
1 set_robust_list(0x7f584026d9e0, 0x18) = 0
2 gettid() = 6333
3 rt_sigprocmask(SIG_BLOCK, NULL, [QUIT], 8) = 0
4 rt_sigprocmask(SIG_UNBLOCK, [HUP INT ILL BUS FPE SEGV USR2 TERM], NULL, 8) = 0
5 rt_sigprocmask(SIG_BLOCK, [QUIT], NULL, 8) = 0
6 futex(0x7f583c009454, FUTEX_WAKE_OP_PRIVATE, 1, 1, 0x7f583c009450, {FUTEX_OP_SET, 0, FUTEX_OP_CMP_GT, 1}) = 1
7 futex(0x7f583c009428, FUTEX_WAKE_PRIVATE, 1) = 1
8 sched_getaffinity(6333, 32, { 1, 0, 0, 0 }) = 32
9 sched_getaffinity(6333, 32, { 1, 0, 0, 0 }) = 32
10 mmap(0x7f584016d000, 12288, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x7f584016d000
11 mprotect(0x7f584016d000, 12288, PROT_NONE) = 0
12 lseek(3, 30051273, SEEK_SET) = 30051273
13 read(3, "PK\3\4\n\0\0\10\0\0\20\260\365J\24w\0067E\3\0\0E\3\0\0\27\0\0\0", 30) = 30
14 lseek(3, 30051326, SEEK_SET) = 30051326
15 read(3, "\312\376\272\276\0\0\0004\0-\t\0\6\0\34\n\0\7\0\35\t\0\32\0\36\n\0\37\0\33\n\0"..., 837) = 837
16 lseek(3, 30053056, SEEK_SET) = 30053056
17 read(3, "PK\3\4\n\0\0\10\0\0\33\260\365J+\346oD\255\r\0\0\255\r\0\0 \0\0\0", 30) = 30
18 lseek(3, 30053118, SEEK_SET) = 30053118
19 read(3, "\312\376\272\276\0\0\0004\0\242\n\0Y\0Z\n\0-\0[\t\0,\0\\\t\0,\0]\t\0"..., 3501) = 3501
20 futex(0x7f583c0b1954, FUTEX_WAKE_OP_PRIVATE, 1, 1, 0x7f583c0b1950, {FUTEX_OP_SET, 0, FUTEX_OP_CMP_GT, 1}) = 1
21 futex(0x7f583c0b1928, FUTEX_WAKE_PRIVATE, 1) = 1
22 recvfrom(6, "123\n", 8192, 0, NULL, NULL) = 4
23 ioctl(6, FIONREAD, [0]) = 0
24 write(1, "123", 3) = 3
25 write(1, "\n", 1) = 1
26 recvfrom(6, "456\n", 8192, 0, NULL, NULL) = 4
27 ioctl(6, FIONREAD, [0]) = 0
28 write(1, "456", 3) = 3
29 write(1, "\n", 1) = 1
30 recvfrom(6,
总结:
无论BIO、NIO、多路复用,在linux系统下的网络通信都离不开: socket、bind、listen这三个系统调用。
传统BIO的底层调用流程就是这样:最大的特点:一个线程处理一个连接
accept是阻塞的
recvfrom是阻塞的
因为这俩哥们儿阻塞,所以BIO称为Blocking IO。
网络通信IO的演变过程(一)(一个门外汉的理解)的更多相关文章
- 网络通信IO的演变过程(二)(一个门外汉的理解)
2.NIO 当与别人谈论NIO时,一定要弄清楚别人说的NIO是指哪个含义? NIO有2种含义: 1.NonBlocking IO,基于操作系统谈 2.Java New IO,基于Java谈 我们这里主 ...
- 一个门外汉的理解 ~ Faster R-CNN
首先放R-CNN的原理图 显然R-CNN的整过过程大致上划分为四步: 1.输入图片 2.生成候选窗口 3.对局部窗口进行特征提取(CNN) 4.分类(Classify regions) 而R-CNN的 ...
- 配置Docker中国区官方镜像http://get.daocloud.io/ 很好的一个源http://get.daocloud.io/#install-docker
https://www.daocloud.io/mirror#accelerator-doc 配置Docker中国区官方镜像http://get.daocloud.io/ 很好的一个源http://g ...
- 用socket.io实现websocket的一个简单例子
socket.io 是基于 webSocket 构建的跨浏览器的实时应用. 逛博客发现几个比较好的 一.用socket.io实现websocket的一个简单例子 http://biyeah.iteye ...
- 通过实现一个TableView来理解iOS UI编程
推荐一篇神作: 通过实现一个TableView来理解iOS UI编程 http://blog.jobbole.com/61101/
- java 20 - 4 IO流概述和一个简单例子解析
IO流的分类: 流向: 输入流 读取数据 输出流 写出数据 数据类型: 字节流 字节输入流 读取数据 InputStream 字节输出流 写出数据 OutputStream 字符流 字符 ...
- 网络通信 --> IO多路复用之select、poll、epoll详解
IO多路复用之select.poll.epoll详解 目前支持I/O多路复用的系统调用有 select,pselect,poll,epoll,I/O多路复用就是通过一种机制,一个进程可以监视 ...
- IO到NIO的一个转变
本内容来源:Jack视频讲解和自己的一个理解. 1.故事还得从网络模型或者IO开始聊起 2.你有想过传统IO真正存在的问题吗? 3.如果你是设计者,IO可以怎样改进? 4.NIO原理分析以及代码实现 ...
- Socket网络通信——IO、NIO、AIO介绍以及区别
一 基本概念 Socket又称"套接字",应用程序通常通过"套接字"向网路发出请求或者应答网络请求. Socket和ServerSocket类位于java.ne ...
随机推荐
- Docker容器管理——运行容器命令
1.容器的生命周期(***重要,需要理解) 容器启动后,执行的第一条命令的PID为1 ========================>>>>>>>& ...
- GDAL的基本操作
上一节简单介绍了GDAL,这一节将介绍一些GDAL的基本操作,如影像读写.波段提取.波段合成等.代码均用python编写. 1.遥感影像的读写 众所周知,遥感影像是以栅格形式存储的,GDAL中使用da ...
- 20210823 数数,数树,鼠树,ckw的树
考场 乍一看都不好做 仔细想想发现 T1 的绝对值特别好,轮流选剩余的最大/最小值就行了 T2 又要计数,直接想部分分,发现一个 sb 容斥就有 35ps(但数据锅了,只有 25pts) T3 什么玩 ...
- java 线程状态 详解
线程被创建后,有一个生命周期,下图是线程的生命周期详解. java api java.lang.Thread.State 这个枚举中给出了六种线程状态,分别是: 线程状态 导致状态发生条件 NEW(新 ...
- Ubuntu / CoreOS修改DNS配置
不要直接手动修改文件 /etc/resolv.conf 安装好Ubuntu之后设置了静态IP地址,再重启后就无法解析域名.想重新设置一下DNS,打开/etc/resolv.conf cat /etc/ ...
- JS021. 拦截事件的显式处理与默认动作(Web API: event.preventDefault)
Web API - event.preventDefault( ) Event 接口的 preventDefault( ) 方法,告诉 user agent :如果此事件没有被显式处理,它默认的动作 ...
- 支持Cron表达式、间隔时间的工具(TaskScheduler)
后台任务如何支持间隔时间.Cron表达式两种方式? 分享一个项目TaskScheduler,这是我从Furion项目中拷出来的 源码:https://gitee.com/dot-net-core/ta ...
- Java Web实现登录验证码(Servlet+jsp)
1.生成验证码图片(Servlet) import java.awt.Color; import java.awt.Font; import java.awt.Graphics2D; import j ...
- DS博客作业04--图
这个作业属于哪个班级 数据结构--网络2011/2012 这个作业的地址 DS博客作业04--图 这个作业的目标 学习图结构设计及相关算法 姓名 黄静 目录 0.PTA得分截图 1.本周学习总结 1. ...
- javascript 求最大前5个数; 对象 深拷贝 deep copy
* 用数组 function getTopN(a, n) { function _cloneArray(aa) { var n = aa.length, a = new Array(n); for ( ...