JAVA SERVER
前言
偶然在网络上看到一篇十年前的博客。来自一位真正的计算机领域的大牛,Paul Tyma。曾任职于Google, Linked In,现在于硅谷一家公司担任CEO。下边是原博客地址。
http://paultyma.blogspot.com/2008/03/writing-java-multithreaded-servers.html
Paul在文中主要提出了四个观点。
- Java asynchronous NIO has higher throughput than Java IO (false)
- Thread context switching is expensive (false)
- Synchronization is expensive (false, usually)
- 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协议的相关规定就能完成相应的展示。