剛開始學的時候我們看到的就是Bio模式的socket、serverSocket 這也是java最早提供的,其實早期的tomcat對請求的處理方式也是這種多線程模式的,下面看看代碼……
一、客戶端
package com.test;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.nio.charset.Charset;
public class Client {
private static Socket socket;
private static InputStream inputStream;
private static OutputStream outputStream;
public Client() {
}
public static void main(String[] args) throws IOException, InterruptedException {
long begin = System.currentTimeMillis();
for (int i = 0; i < 500; i++) {
socket = new Socket("192.168.0.103", 3608);
outputStream = socket.getOutputStream();
String str = "client -" + i;
Charset charset = Charset.forName("utf-8");
byte[] buffer = str.getBytes(charset);
outputStream.write(buffer);
outputStream.flush();
inputStream = socket.getInputStream();
byte[] fromServer = new byte[1024];
int ren = inputStream.read(fromServer);
int len = fromServer.length;
String stssr = new String(fromServer, "utf-8");
System.out.println(" ren:" + ren + " len:" + len + " stssr:" + stssr);
outputStream.close();
inputStream.close();
inputStream = null;
outputStream = null;
socket.close();
socket = null;
fromServer = null;
}
long end = System.currentTimeMillis();
long total = end - begin;
System.out.println("Client-total:" + total);
}
}
二、服務端
package server;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class Servers {
private static ServerSocket serverSocket;
private static Socket socket;
private static ExecutorService executeService = Executors.newCachedThreadPool();
public Servers() {
}
static {
System.out.println("server begin……");
try {
serverSocket = new ServerSocket(3608); // 等待客戶端連接
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
public static void main(String[] args) throws IOException, InterruptedException {
while (true) {
socket = serverSocket.accept(); // 這就是bio同步阻塞
Handler handler = new Handler(socket);// 創建一個任務
executeService.execute(handler);// 任務交給線程池
}
}
}
package server;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.nio.charset.Charset;
import java.util.concurrent.TimeUnit;
public class Handler implements Runnable {
private Socket socket;
private InputStream inputStream;
private OutputStream outputStream;
public Handler() {
}
public Handler(Socket socket) {
this.socket = socket;
}
@Override
public void run() {
try {
inputStream = socket.getInputStream();
outputStream = socket.getOutputStream();
byte[] buffer = new byte[1024];
int result = inputStream.read(buffer);
String str = new String(buffer, "utf-8");
System.err.println("from client info:" + str + "thread:" + Thread.currentThread().getId());
String server = new String("from server:" + str);
Charset cs = Charset.forName("utf-8");
TimeUnit.MILLISECONDS.sleep(100);
byte[] bytes = server.getBytes(cs);
outputStream.write(bytes);
outputStream.flush();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} finally {
try {
outputStream.close();
inputStream.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
ok 這就是javaBio的僞異步實現方式,在請求併發量一般的情況下其實這種方式是能夠應付的,其實早期的tomcat的請求處理也是這種思想方式所以在一般的併發量下這種方式還是能應付的。
雖然這種方式能應付一般請求而且編寫簡單,但是在併發量大的請求下就有點喫力了,下面來分析分析這種方式主要的瓶頸。
在分析瓶頸時我們就使用非僞異步的socket、serverSocket 來說事,這樣感覺更容易說明……
1、當我們創建一個socket的時候其實是會先進行服務器、端口的查找以及socket客戶端和serverSocket服務端之間tcp的鏈接,我們知道tcp的鏈接是要三次握手的,如果客戶端socket獲得了和服務端serverSocket的聯繫即三次握手成功。這樣客戶端和服務端就建立了tcp鏈接,當我們通過這個鏈接從客戶端發送請求到服務端時,服務端不一定能立即獲得客戶端的請求,服務端獲得客戶端的請求時serverSocket.accept()方法,但是我們知道,服務端每當獲得一個客戶端請求後,就會去處理這個客戶端的請求,所以在這時如果有其他客戶端的請求,則服務端沒法處理,因爲上一個客戶端的請求還在處理中,所以客戶端來的請求就會先緩存在服務端的隊列中,當上一個請求處理完之後再來處理服務端隊列中的請求,但問題是客戶端發送請求後一直在同步等待服務端的相應,如果服務端處理的比較嗎,則客戶端就會出現超時異常。
2、客戶端請求處理是在一個線程中,如果某個請求處理出現了異常情況則後面的請求基本上都是超時等異常。
總的來說,在java的bio中客戶端要一個個等待來執行,因爲請求的接收在accept()方法,他是阻塞的所以請求的接收很慢,效率很低……
3、請求處理效率低的問題解決就是處理過程使用多線程,但他的消息返回還是同步的,還是對accept()的接收速度有影響。
4、其實當客戶端請求量大的時候,如果tcp鏈接成功而服務端的任務等待隊列滿了則有可能會爆出隊列溢棧威脅
5、在請求的時候tcp服務端可以設置最多的請求參數,如果請求數大於這個數則直接返回給客戶端,從而減少服務端壓力,
6、如果很多客戶端tcp鏈接處理要很長世間,在所有客戶端請求處理完之前tcp是不會關閉的,這樣很快就能達到tcp請求上限,之後的請求會直接返回,也就是限流。
7、客戶端請求被限流返回的前提條件是客戶端和服務端還沒建立tcp鏈接即還沒三次握手成功,如果三次握手成功則客戶端的請求還是會給執行的,只是有可能會慢一些……
總結:總的來說java Bio主要的缺陷就是accept的阻塞同步,多個客戶端請求線程的管理,以及釋放線程資源的處理。