Socket编程

服务器通信原理

Socket通信模型.png

Demo01单线程

  1. package java0.nio01;
  2. import java.io.IOException;
  3. import java.io.PrintWriter;
  4. import java.net.ServerSocket;
  5. import java.net.Socket;
  6. public class HttpServer01 {
  7. public static void main(String[] args) throws IOException{
  8. ServerSocket serverSocket = new ServerSocket(8801);
  9. while (true) {
  10. try {
  11. Socket socket = serverSocket.accept();
  12. service(socket);
  13. } catch (IOException e) {
  14. e.printStackTrace();
  15. }
  16. }
  17. }
  18. private static void service(Socket socket) {
  19. try {
  20. Thread.sleep(20);
  21. PrintWriter printWriter = new PrintWriter(socket.getOutputStream(), true);
  22. printWriter.println("HTTP/1.1 200 OK");
  23. printWriter.println("Content-Type:text/html;charset=utf-8");
  24. printWriter.println();
  25. printWriter.write("hello,nio");
  26. printWriter.close();
  27. socket.close();
  28. } catch (IOException | InterruptedException e) {
  29. e.printStackTrace();
  30. }
  31. }
  32. }
  1. ~ wrk -c60 -d30s http://localhost:8801/
  2. Running 1m test @ http://localhost:8801/
  3. 8 threads and 30 connections
  4. Thread Stats Avg Stdev Max +/- Stdev
  5. Latency 547.17ms 25.70ms 589.86ms 98.08%
  6. Req/Sec 1.57 2.61 19.00 91.36%
  7. 521 requests in 1.00m, 161.13KB read
  8. Socket errors: connect 0, read 2619, write 0, timeout 0
  9. Requests/sec: 8.67
  10. Transfer/sec: 2.68KB
  • 不足
    • 线程sleep20ms
    • BIO,会阻塞线程
    • 单线程模型,只有main线程

      Demo02多线程

      ```java package java0.nio01;

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
  • 不足
    • Java里的线程是裸线程,需要资源时调用OS的createThread创建真正的线程,消耗资源大

      Demo03线程池

      ```java package java0.nio01;

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模型.png

    阻塞式IO

  • 一般通过在while(true)循环中服务端会调用accept()方法等待接收客户端的连接的方式监听请求

  • 一旦接收到一个请求就可以建立通信套接字,在这个套接字上进行读写操作
  • 此时不能再接收其他客户端的连接请求,只能等待当前连接处理完毕
  • 类似Demo01

阻塞式IO.png
线程阻塞.png

非阻塞IO

  • 和阻塞IO类比,内核会立即返回,返回后获得足够的CPU时间继续做其他的事情
  • 用户进程第一个阶段不是阻塞的,需要不断的主动询问kernel数据好了没有,第二个阶段依然是阻塞的

非阻塞式IO.png
非阻塞IO.png

多路复用IO

  • IO multiplexing,也称事件驱动EventDrivenIO
  • 在单个线程同时监听多个套接字,通过select/poll轮询所负责的所有socket,当某个socket有数据到了就通知用户进程
  • IO复用同非阻塞IO本质上一样,不过又利用了新的select系统调用,由内核来负责本来是进程该做的轮询操作
  • 进程先是阻塞在select/poll上,再是阻塞在读操作的第二阶段上

IO复用.png
IO复用.png

select/poll的不足

  • 每次调用select,都需要把fd集合从用户态拷贝到内核态,这个开销在fd很多时会很大
  • 每次调用select,都需要在内核遍历传递进来的所有fd,这个开销在fd很多时也会很大
  • select支持的fd数量太小,默认是1024

    epoll优势

    在linux2.6内核正式引入,可完全代替select/poll,常用模型Reactor

  • 内核与用户空间共享一块内存

  • 通过回调解决遍历问题
  • fd没有限制,可以支撑10万连接(c100k)

    信号驱动IO

  • 与BIO和NIO最大的区别就是在IO执行的数据准备阶段,不会阻塞用户进程

  • 当用户进程需要等待数据的时候会向内核发送一个信号,告诉内核我要什么数据,然后继续做别的事情,当内核把数据准备好后,给用户进程一个信号,用户进程收到信号后立马调用recvfrom接收数据

信号驱动IO.png

底层模型发展

  • 线程池-> EDA -> SEDA(分阶段的事件驱动架构)

线程池-EDA-SEDA.png
SEDA.png

异步IO

  • 真正实现了IO全流程的非阻塞
  • 用户进程发出系统调用后立即返回,内核等待数据准备完成,然后将数据拷贝到用户进程缓冲区,然后发送信号告诉用户进程IO操作完毕(与SIGIO相比,一个是发送信号告诉用户进程数据准备完毕,一个是IO执行完毕)
  • 常用模型Proactor

异步IO.png

IO场景

  • 去打印店打印文件

    同步阻塞

  • 直接排队,别的啥也不干

  • 轮到你了,你自己打印

    Reactor

  • 拿个号,回去该干啥干啥

  • 轮到你了,店主通知你来,你自己打印

    Proactor

  • 拿个号,回去该干啥干啥

  • 轮到你了,店主直接给你打印好,然后通知你直接取