JAVA SERVER

前言
偶然在网络上看到一篇十年前的博客。来自一位真正的计算机领域的大牛,Paul Tyma。曾任职于Google, Linked In,现在于硅谷一家公司担任CEO。下边是原博客地址。

http://paultyma.blogspot.com/2008/03/writing-java-multithreaded-servers.html

Paul在文中主要提出了四个观点。

  1. Java asynchronous NIO has higher throughput than Java IO (false)
  2. Thread context switching is expensive (false)
  3. Synchronization is expensive (false, usually)
  4. Thread per connection servers cannot scale (false)

分别是说:
NIO效率真的比JAVA IO高?
线程切换之间消耗很大?
同步很昂贵?
一个请求一个线程服务不能进行扩展。
这是一篇08年的博客,至今11年,计算机在这个领域并没有发生本质上的改变,个人认为技术的更新迭代提升的只是开发人员的效率,并没有去提升利用计算机的能力,这可能是这个领域走进的一个误区,个人很愿意把这些观点分享给各位从事程序员工作者,在前人的思想上希望更进一步,个人也花费了时间去研究了JAVA 中的SOCKET,写下了此文。

第一章JAVA ServerSocket

ServerSocket serverSocket = new ServerSocket(8080);
通过该方式来进行一个ServerSocket的实例化,每个ServerSocket都需要一个端口的绑定。
在这儿需要提到C++中socket编程的三种方式,以作了解。
socket编程有三种,(TCP)流式套接字(SOCK_STREAM),(UDP)数据报套接字(SOCK_DGRAM),原始套接字(SOCK_RAW),前两者较常用。基于TCP的socket编程是流式套接字,而原始套接字(SOCK_RAW)可以处理ICMP、IGMP等网络报文;SOCK_RAW也可以处理特殊的IPv4报文;此外,利用原始套接字,可以通过IP_HDRINCL套接字选项由用户构造IP头。
在JAVA中ServerSocket是基于TCP的,DatagramSocket是基于UDP的,而JAVA并没有原始套接字的概念,需要使用原始套接字就必须得用C++。

第二章ServerSocket常见的三种设计方式
我的认知里有三种实现服务端的方式,可能也有其他方式。分别是:All in one Server类型,就是一个服务处理客户端连接并且处理客户端的请求。
第二种是one connection per thread,服务端只处理客户端接入。每个客户端请求都有一个单独的线程来完成。第三种是threadPool方式,由一个线程池来处理请求的连接。

第一种方式实现,附代码:

package socket;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketAddress;

/**
 * @author liubh
 * @date 2019/12/15 21:06
 */
public class Server implements Runnable{
    private int port = 8080;
    private ServerSocket server = null;
    public boolean isClose() {
        return isClose;
    }

    public void setClose(boolean close) {
        isClose = close;
    }
    private boolean isClose = false;
    public Server(int port){
        this.port = port;
    }
    public int getPort() {
        return port;
    }
    public void setPort(int port) {
        this.port = port;
    }

    @Override
    public void run() {
        synchronized (this){
        }
        //第一步  启动服务器
        openServer();
        //第二步 监听客户端
        while (!isClose){

            Socket socket = null;
            try {
                socket = this.server.accept();
            } catch (IOException e) {
                e.printStackTrace();
            }
            //第三步 处理监听到的客户端请求
            try {
                processClient(socket);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    private void processClient(Socket socket) throws IOException {
        OutputStream outputStream = socket.getOutputStream();
        InputStream inputStream = socket.getInputStream();
        byte[] content = ("<html><body>hello-World</body></html>").getBytes();
        byte[] header = ("HTTP/1.1 200 OK\r\n" +
                "Content-Type: text/html, charset=UTF-8\r\n" +
                "Content-Length:"+content.length+"\r\n\r\n").getBytes();
        outputStream.write(header);
        outputStream.write(content);
        outputStream.flush();
        outputStream.close();
        inputStream.close();
    }
    private void openServer() {
        try {
            this.server = new ServerSocket(this.port);
            System.out.println("服务器启动");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) throws InterruptedException {
//        Server server = new Server(8081);
//        new Thread(server).start();
//        Server server1 = new Server(9000);
//        new Thread(server1).start();
//        Thread.currentThread().sleep(20 * 1000l);
//        server.setClose(true);
    }
}
    private int port = 8080;
    private ServerSocket server = null;
    public boolean isClose() {
        return isClose;
    }

    public void setClose(boolean close) {
        isClose = close;
    }
    private boolean isClose = false;
    public Server(int port){
        this.port = port;
    }
    public int getPort() {
        return port;
    }
    public void setPort(int port) {
        this.port = port;
    }

    @Override
    public void run() {
        synchronized (this){
        }
        //第一步  启动服务器
        openServer();
        //第二步 监听客户端
        while (!isClose){

            Socket socket = null;
            try {
                socket = this.server.accept();
            } catch (IOException e) {
                e.printStackTrace();
            }
            //第三步 处理监听到的客户端请求
            try {
                processClient(socket);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    private void processClient(Socket socket) throws IOException {
        OutputStream outputStream = socket.getOutputStream();
        InputStream inputStream = socket.getInputStream();
        byte[] content = ("<html><body>hello-World</body></html>").getBytes();
        byte[] header = ("HTTP/1.1 200 OK\r\n" +
                "Content-Type: text/html, charset=UTF-8\r\n" +
                "Content-Length:"+content.length+"\r\n\r\n").getBytes();
        outputStream.write(header);
        outputStream.write(content);
        outputStream.flush();
        outputStream.close();
        inputStream.close();
    }
    private void openServer() {
        try {
            this.server = new ServerSocket(this.port);
            System.out.println("服务器启动");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) throws InterruptedException {
//        Server server = new Server(8081);
//        new Thread(server).start();
//        Server server1 = new Server(9000);
//        new Thread(server1).start();
//        Thread.currentThread().sleep(20 * 1000l);
//        server.setClose(true);
    }
}

首先个人写代码的思路是:
1.先启动服务。
2.监听接入。
3.处理请求
这三种方式的区别都只是在第3步上有所差异,上述代码完全按照该思路进行编写。

第三章IO扩展

在IO之外,还有NIO的方式,首先明确不论是什么方式的IO,都一定是基于TCP/IP UDP协议。只要底层协议不改变,计算机处理的方式就不会发生太大改变。我之前花了大量时间去学习NIO和netty相关的东西,以为相比于IO这是种更加高级的处理方式,其实这并没有,具体要按照实际情况而定。
在这儿,主要描述Paul提到的观点。
Linux中的NPTL对线程的支持是可以做得很好的,完全可以支持很多线程的处理。而JAVA虚拟机对线程做了限制,导致JAVA不能使用大量的线程。(个人并没有找到证据,上一句话只是个人观点。)
glibc\nptl\sysdeps\pthread\createthread.cglibc\nptl\pthread_create.c
Linux源码中有相关具体实现,我个人对C++不懂,就不赘述。

第四章TCP UDP HTTP协议

笔者认为协议是每个程序员都需要去了解的东西,可以帮助去理解真正的Computer Science。在这儿就做个简单的总结,另外个人读的书是一位日本程序员上野宣的图解HTTP,个人就简单做个描述。
TCP:很好的进行信息的传达。
UDP:快速进行传达,但对不对lz不管。
HTTP:文本展示,你通过TCP/UDP按照我的规定来,我就能进行相应的展示。
在上述的代码例子中就是遵守了HTTP协议的一个例子,只要按照HTTP协议的相关规定就能完成相应的展示。