網絡編程的基本模型是Client/Server模型,也就是兩個進程之間進行相互通信,其中服務端提供位置信息(綁定的IP地址和監聽端口),客戶端通過連接操作向服務端監聽的地址發起連接請求, 通過三 次握手建立連接, 如果連接建立成功, 雙方就可以通過網絡套接字 ( Socket) 進行通信。
在基於傳統同步阻塞模型開發中, ServerSocket負責綁定 IP 地址, 啓動監聽端口;Socket負責發起連接操作。連接成功之後, 雙方通過輸入和輸出流進行同步阻塞式通信。
Bl〇通信模型
採用BIO通信模型的服務端, 通常由一個獨立的Acceptor線程負責監聽客戶端的連接 ,當它接收到客戶端連接請求之後,會爲每個客戶端創建一個新的線程進行鏈路處理, 處理完成之後, 通過輸出流返回應答給客戶端, 最後銷燬線程,這就是典型的一請求一應答通信模型。
BIO最大的問題就是缺乏彈性伸縮能力,每當有一個新的客戶端請求接入時, 服務端必須創建一個新的線程處理新接入的客戶端鏈路, 一個線程只能處理一個客戶端連接。 當客戶端併發訪問量增加後, 服務端的線程個數和客戶端併發訪問數呈1:1的正比關係,由於線程是Java虛擬機珍貴的系統資源, 當線程數劇增之後, 系統的性能將急劇下降, 隨着併發訪問量的繼續增大, 系統會發生線程堆棧溢出、創建新線程失敗等問題 , 並最終導致進程宕機或僵死,不能對外提供服務 。而在高性能服務器應用領域,往往需要面向成幹上萬個客戶端的併發連接,這種模型顯然正法滿足高性能、高併發接入的場景 。
下面我們通過一個案例來說明同步阻塞IO的弊端
public class BioServer {
public static void main(String[] args) throws IOException{
int port = 8080;
if(args != null && args.length >0 ){
try{
port = Integer.valueOf(args[0]);
}catch (NumberFormatException e){
}
}
ServerSocket server = null;
try{
server = new ServerSocket(port);
System.out.println("the bio server is start in port :"+port);
Socket socket = null;
while (true){
socket = server.accept();
new Thread(new BioserverHandler(socket)).start();
}
}finally {
if(server!=null){
System.out.println("the time server close");
server.close();
server = null;
}
}
}
}
BioServer根據傳入的參數設置監聽端口,如果沒有,使用默認值8080。然後new一個ServerSocket, 如果端口合法且沒有被佔用, 服務端監聽成功 。通過一個無限循環來監聽客戶端的連接, 如果沒有客戶端接入,則主線程阻塞在ServerSocket的accept操作上。當有新的客戶端接入的時候,以 Socket爲參數構造 BioServerHandler對象, BioServerHandler 是 一個 Runnable, 使用它爲構造函數的參數創建一個新的客戶端線程處理這條 socket鏈路,下面是BioServerHandler代碼:
public class BioServerHandler implements Runnable {
private Socket socket;
public BioServerHandler(Socket socket) {
this.socket = socket;
}
public void run(){
BufferedReader in = null;
PrintWriter out = null;
try{
in = new BufferedReader(new InputStreamReader(this.socket.getInputStream()));
out = new PrintWriter(this.socket.getOutputStream(),true);
String body = null;
while(true){
body = in.readLine();
if(body == null){
break;
}
System.out.print("The bio server receive :"+body);
out.println(new Date());
}
}catch (Exception e){
e.printStackTrace();
}finally {
try {
if(in!=null)
in.close();
} catch (IOException e) {
e.printStackTrace();
}
if(out != null){
out.close();
out=null;
}
if(this.socket != null){
try {
this.socket.close();
} catch (IOException e) {
e.printStackTrace();
}
this.socket = null;
}
}
}
}
BioClient類
客戶端創建Socket連接,向服務器發送消息,然後讀取服務器發送的消息, 隨後關閉連接,釋放資源,退出程序。
public class BioClient {
public static void main(String[] args){
Socket socket = null;
BufferedReader in = null;
PrintWriter out = null;
try{
// socket.connect(new InetSocketAddress("localhost", 8000));
socket = new Socket("127.0.0.1",8080);
out = new PrintWriter(socket.getOutputStream(),true);
out.println("hello server !!");
out.flush();
System.out.println(" send msg to server succeed");
in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
String resp = in.readLine();
System.out.println("receiver from server : " +resp);
}catch (Exception e){
e.printStackTrace();
}finally {
if(socket != null){
try {
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
socket = null;
}
if(out != null){
out.close();
out = null;
}
if(in!=null){
try {
in.close();
} catch (IOException e) {
e.printStackTrace();
}
in = null;
}
}
}
}
代碼在這了,大家可以自己跑下效果,我想大家在學習socket編程的時候,估計這樣的demo都寫爛了,好了,不說什麼了