Socket编程
服务器通信原理
Demo01单线程
package java0.nio01;import java.io.IOException;import java.io.PrintWriter;import java.net.ServerSocket;import java.net.Socket;public class HttpServer01 {public static void main(String[] args) throws IOException{ServerSocket serverSocket = new ServerSocket(8801);while (true) {try {Socket socket = serverSocket.accept();service(socket);} catch (IOException e) {e.printStackTrace();}}}private static void service(Socket socket) {try {Thread.sleep(20);PrintWriter printWriter = new PrintWriter(socket.getOutputStream(), true);printWriter.println("HTTP/1.1 200 OK");printWriter.println("Content-Type:text/html;charset=utf-8");printWriter.println();printWriter.write("hello,nio");printWriter.close();socket.close();} catch (IOException | InterruptedException e) {e.printStackTrace();}}}
➜ ~ wrk -c60 -d30s http://localhost:8801/Running 1m test @ http://localhost:8801/8 threads and 30 connectionsThread Stats Avg Stdev Max +/- StdevLatency 547.17ms 25.70ms 589.86ms 98.08%Req/Sec 1.57 2.61 19.00 91.36%521 requests in 1.00m, 161.13KB readSocket errors: connect 0, read 2619, write 0, timeout 0Requests/sec: 8.67Transfer/sec: 2.68KB
import java.io.IOException; import java.io.PrintWriter; import java.net.ServerSocket; import java.net.Socket;
public class HttpServer02 { public static void main(String[] args) throws IOException{ ServerSocket serverSocket = new ServerSocket(8802); while (true) { try { final Socket socket = serverSocket.accept(); new Thread(() -> { service(socket); }).start(); } catch (IOException e) { e.printStackTrace(); } } }
private static void service(Socket socket) {
try {
Thread.sleep(20);
PrintWriter printWriter = new PrintWriter(socket.getOutputStream(), true);
printWriter.println("HTTP/1.1 200 OK");
printWriter.println("Content-Type:text/html;charset=utf-8");
printWriter.println();
printWriter.write("hello,nio");
printWriter.close();
socket.close();
} catch (IOException | InterruptedException e) {
e.printStackTrace();
}
}
}
```shell
➜ ~ wrk -c60 -d30s http://localhost:8802/
Running 30s test @ http://localhost:8802/
2 threads and 60 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 23.83ms 5.62ms 87.51ms 98.78%
Req/Sec 36.30 35.25 200.00 85.19%
980 requests in 30.10s, 807.37KB read
Socket errors: connect 0, read 13138, write 0, timeout 0
Requests/sec: 32.56
Transfer/sec: 26.82KB
import java.io.IOException; import java.io.PrintWriter; import java.net.ServerSocket; import java.net.Socket; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors;
public class HttpServer03 { public static void main(String[] args) throws IOException{ ExecutorService executorService = Executors.newFixedThreadPool(40); final ServerSocket serverSocket = new ServerSocket(8803); while (true) { try { final Socket socket = serverSocket.accept(); executorService.execute(() -> service(socket)); } catch (IOException e) { e.printStackTrace(); } } }
private static void service(Socket socket) {
try {
Thread.sleep(20);
PrintWriter printWriter = new PrintWriter(socket.getOutputStream(), true);
printWriter.println("HTTP/1.1 200 OK");
printWriter.println("Content-Type:text/html;charset=utf-8");
printWriter.println();
printWriter.write("hello,nio");
printWriter.close();
socket.close();
} catch (IOException | InterruptedException e) {
e.printStackTrace();
}
}
}
```shell
➜ ~ wrk -c60 -d30s http://localhost:8803/
Running 30s test @ http://localhost:8803/
2 threads and 60 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 27.04ms 6.27ms 64.07ms 77.84%
Req/Sec 28.90 26.77 260.00 81.23%
889 requests in 30.01s, 0.89MB read
Socket errors: connect 0, read 14768, write 1, timeout 0
Requests/sec: 29.62
Transfer/sec: 30.22KB
IO
计算机任务两大类
CPU密集型
- 消耗CPU进行计算
如果是CPU密集型的任务,CPU核心上的每个线程都满负荷运转,只需要运行等于CPU核心数的线程即可
IO密集型
分类
- 磁盘IO
- 网络IO
如果是IO密集型任务,CPU大部分都在等待,所以可以创建多于CPU内核数的线程来处理业务
Demo问题
单线程不能并发处理
多线程如果很多也会造成线程间资源竞争cs,不便管理,所以要综合考虑设置线程池的数量
IO模型与相关概念
同步/异步/阻塞/非阻塞
同步/异步是通信模式
-
五种IO模型
阻塞式IO
一般通过在while(true)循环中服务端会调用accept()方法等待接收客户端的连接的方式监听请求
- 一旦接收到一个请求就可以建立通信套接字,在这个套接字上进行读写操作
- 此时不能再接收其他客户端的连接请求,只能等待当前连接处理完毕
- 类似Demo01
非阻塞IO
- 和阻塞IO类比,内核会立即返回,返回后获得足够的CPU时间继续做其他的事情
- 用户进程第一个阶段不是阻塞的,需要不断的主动询问kernel数据好了没有,第二个阶段依然是阻塞的
多路复用IO
- IO multiplexing,也称事件驱动EventDrivenIO
- 在单个线程同时监听多个套接字,通过select/poll轮询所负责的所有socket,当某个socket有数据到了就通知用户进程
- IO复用同非阻塞IO本质上一样,不过又利用了新的select系统调用,由内核来负责本来是进程该做的轮询操作
- 进程先是阻塞在select/poll上,再是阻塞在读操作的第二阶段上
select/poll的不足
- 每次调用select,都需要把fd集合从用户态拷贝到内核态,这个开销在fd很多时会很大
- 每次调用select,都需要在内核遍历传递进来的所有fd,这个开销在fd很多时也会很大
-
epoll优势
在linux2.6内核正式引入,可完全代替select/poll,常用模型Reactor
内核与用户空间共享一块内存
- 通过回调解决遍历问题
-
信号驱动IO
与BIO和NIO最大的区别就是在IO执行的数据准备阶段,不会阻塞用户进程
- 当用户进程需要等待数据的时候会向内核发送一个信号,告诉内核我要什么数据,然后继续做别的事情,当内核把数据准备好后,给用户进程一个信号,用户进程收到信号后立马调用recvfrom接收数据
底层模型发展
- 线程池-> EDA -> SEDA(分阶段的事件驱动架构)
异步IO
- 真正实现了IO全流程的非阻塞
- 用户进程发出系统调用后立即返回,内核等待数据准备完成,然后将数据拷贝到用户进程缓冲区,然后发送信号告诉用户进程IO操作完毕(与SIGIO相比,一个是发送信号告诉用户进程数据准备完毕,一个是IO执行完毕)
- 常用模型Proactor




