Socket
Socket,又稱“套接字”募逞,應(yīng)用程序通常通過“套接字”向網(wǎng)絡(luò)發(fā)出請求或應(yīng)答網(wǎng)絡(luò)請求蛋铆。
Socket 和 ServerSocket類位于 java.net 包中。對于一個網(wǎng)絡(luò)連接來說放接,套接字是平等的刺啦,不因?yàn)樵诜?wù)器端或在客戶端而產(chǎn)生不同級別,它們的工作都是通過 SocketImpl 類及其子類完成的纠脾。
套接字之間的連接過程可以分為四個步驟:
1)服務(wù)器監(jiān)聽
2)客戶端請求服務(wù)器
3)服務(wù)器確認(rèn)
4)客戶端確認(rèn)
示例代碼:
Server.java
public class Server {
final static int PROT = 8765;
public static void main(String[] args) {
ServerSocket server = null;
try {
server = new ServerSocket(PROT);
System.out.println(" server start .. ");
//進(jìn)行阻塞
Socket socket = server.accept();
//新建一個線程執(zhí)行客戶端的任務(wù)
new Thread(new ServerHandler(socket)).start();
} catch (Exception e) {
e.printStackTrace();
} finally {
if(server != null){
try {
server.close();
} catch (IOException e) {
e.printStackTrace();
}
}
server = null;
}
}
Client.java
public class Client {
final static String ADDRESS = "127.0.0.1";
final static int PORT = 8765;
public static void main(String[] args) {
Socket socket = null;
BufferedReader in = null;
PrintWriter out = null;
try {
socket = new Socket(ADDRESS, PORT);
in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
out = new PrintWriter(socket.getOutputStream(), true);
//向服務(wù)器端發(fā)送數(shù)據(jù)
out.println("接收到客戶端的請求數(shù)據(jù)...");
out.println("接收到客戶端的請求數(shù)據(jù)1111...");
String response = in.readLine();
System.out.println("Client: " + response);
} catch (Exception e) {
e.printStackTrace();
} finally {
if(in != null){
try {
in.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if(out != null){
try {
out.close();
} catch (Exception e) {
e.printStackTrace();
}
}
if(socket != null){
try {
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
socket = null;
}
}
}
ServerHandler.java
public class ServerHandler implements Runnable{
private Socket socket ;
public ServerHandler(Socket socket){
this.socket = socket;
}
@Override
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.println("Server :" + body);
out.println("服務(wù)器端回送響的應(yīng)數(shù)據(jù).");
}
} catch (Exception e) {
e.printStackTrace();
} finally {
if(in != null){
try {
in.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if(out != null){
try {
out.close();
} catch (Exception e) {
e.printStackTrace();
}
}
if(socket != null){
try {
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
socket = null;
}
}
}
運(yùn)行結(jié)果:
服務(wù)端
Server :接收到客戶端的請求數(shù)據(jù)...
Server :接收到客戶端的請求數(shù)據(jù)1111...
客戶端
Client: 服務(wù)器端回送響的應(yīng)數(shù)據(jù).
采用線程池和任務(wù)隊列實(shí)現(xiàn)偽異步 IO玛瘸,將客戶端的 socket 封裝成 task 任務(wù)(實(shí)現(xiàn) Runnable 接口的類),然后投遞到線程池中去苟蹈。
示例代碼:
Server.java
public class Server {
final static int PORT = 8765;
public static void main(String[] args) {
ServerSocket server = null;
BufferedReader in = null;
PrintWriter out = null;
try {
server = new ServerSocket(PORT);
System.out.println("Server start");
Socket socket = null;
HandlerExecutorPool executorPool = new HandlerExecutorPool(50, 1000);
while(true){
socket = server.accept();
executorPool.execute(new ServerHandler(socket));
}
} catch (Exception e) {
e.printStackTrace();
} finally {
if(in != null){
try {
in.close();
} catch (Exception e1) {
e1.printStackTrace();
}
}
if(out != null){
try {
out.close();
} catch (Exception e2) {
e2.printStackTrace();
}
}
if(server != null){
try {
server.close();
} catch (Exception e3) {
e3.printStackTrace();
}
}
server = null;
}
}
}
HandlerExecutorPool.java
public class HandlerExecutorPool {
private ExecutorService executor;
public HandlerExecutorPool(int maxPoolSize, int queueSize){
this.executor = new ThreadPoolExecutor(
Runtime.getRuntime().availableProcessors(),
maxPoolSize,
120L,
TimeUnit.SECONDS,
new ArrayBlockingQueue<Runnable>(queueSize));
}
public void execute(Runnable task){
this.executor.execute(task);
}
}
ServerHandler.java
public class ServerHandler implements Runnable {
private Socket socket;
public ServerHandler (Socket socket){
this.socket = socket;
}
@Override
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.println("Server:" + body);
out.println("Server response");
}
} catch (Exception e) {
e.printStackTrace();
} finally {
if(in != null){
try {
in.close();
} catch (Exception e1) {
e1.printStackTrace();
}
}
if(out != null){
try {
out.close();
} catch (Exception e2) {
e2.printStackTrace();
}
}
if(socket != null){
try {
socket.close();
} catch (Exception e3) {
e3.printStackTrace();
}
}
socket = null;
}
}
}
Client.java
public class Client {
final static String ADDRESS = "127.0.0.1";
final static int PORT =8765;
public static void main(String[] args) {
Socket socket = null;
BufferedReader in = null;
PrintWriter out = null;
try {
socket = new Socket(ADDRESS, PORT);
in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
out = new PrintWriter(socket.getOutputStream(), true);
out.println("Client request");
String response = in.readLine();
System.out.println("Client:" + response);
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
} finally {
if(in != null){
try {
in.close();
} catch (Exception e1) {
e1.printStackTrace();
}
}
if(out != null){
try {
out.close();
} catch (Exception e2) {
e2.printStackTrace();
}
}
if(socket != null){
try {
socket.close();
} catch (Exception e3) {
e3.printStackTrace();
}
}
socket = null;
}
}
}
運(yùn)行結(jié)果:
服務(wù)端
Server start
Server:Client request
客戶端
Client:Server response
IO (BIO) 和 NIO 的區(qū)別:其本質(zhì)就是阻塞和非阻塞的區(qū)別糊渊。
阻塞:應(yīng)用程序在獲取網(wǎng)絡(luò)數(shù)據(jù)時,如果網(wǎng)絡(luò)傳輸數(shù)據(jù)很慢慧脱,那么程序就一直等著渺绒,直到傳輸完畢為止。
非阻塞:應(yīng)用程序直接可以獲取已經(jīng)準(zhǔn)備好的數(shù)據(jù)菱鸥,無需等待宗兼。
IO 為同步阻塞,NIO 為同步非阻塞氮采。NIO 并沒有實(shí)現(xiàn)異步殷绍,在 JDK1.7之后,升級了 NIO 庫包鹊漠,支持異步非阻塞通信模型主到,即 NIO2.0(AIO)殖侵。
同步和異步:同步和異步一般是面向操作系統(tǒng)與應(yīng)用程序?qū)?IO 操作的層面上來區(qū)別的。
同步時镰烧,應(yīng)用程序會直接參與 IO 讀寫操作拢军,并且應(yīng)用程序會直接阻塞到某一個方法上,直到數(shù)據(jù)準(zhǔn)備就緒怔鳖,或者采用輪詢的策略實(shí)時檢查數(shù)據(jù)的就緒狀態(tài)茉唉,如果就緒則獲取數(shù)據(jù)。
異步時结执,所有的 IO 讀寫操作交給操作系統(tǒng)處理度陆,與應(yīng)用程序沒有直接關(guān)系,當(dāng)操作系統(tǒng)完成 IO 讀寫操作時献幔,會給應(yīng)用程序發(fā)送通知懂傀,應(yīng)用程序直接拿走數(shù)據(jù)即可。
NIO 編程介紹
NIO 中三個重要概念:Buffer蜡感,Channel蹬蚁,Selector。
Buffer:Buffer 是一個對象郑兴,它包含一些要寫入或者要讀取的數(shù)據(jù)犀斋。在面向流的 IO 中,可以將數(shù)據(jù)直接寫入或讀取到 Stream 對象中情连。在 NIO 庫中叽粹,所有數(shù)據(jù)都是用緩沖區(qū)處理的。緩沖區(qū)實(shí)際上是一個數(shù)組却舀,通常是一個字節(jié)數(shù)組(ByteBuffer)虫几,也可以使用其他類型的數(shù)組。每一種 Java 基本數(shù)據(jù)類型都對應(yīng)了一種緩沖區(qū)(除了 boolean 類型)挽拔。
Channel:通道與流的不同之處在于辆脸,通道是雙向的,而流是單向的(一個流必須是 InputStream 或 OutputStream 的子類)篱昔。通道可以用于讀每强、寫或者二者同時進(jìn)行始腾,最重要的是可以和多路復(fù)用器結(jié)合起來州刽,有多種的狀態(tài)位,方便多路復(fù)用器去識別浪箭。
Selector:Selector 會不斷地輪詢注冊在其上的通道穗椅,如果某個通道發(fā)生了讀寫操作,這個通道就處于就緒狀態(tài)奶栖,會被 Selector 輪詢出來匹表,然后通過 SelectionKey 可以取得就緒的 Channel 集合门坷,從而進(jìn)行后續(xù)的 IO 操作。一個 Selector 可以負(fù)責(zé)成千上萬個 Channel 通道袍镀,沒有上限默蚌,這也就意味著,只要一個線程負(fù)責(zé) Selector 的輪詢苇羡,就可以接入成千上萬個客戶端绸吸。
Selector 線程就類似一個管理者,管理了成千上萬個管道设江,然后輪詢哪個管道的數(shù)據(jù)已經(jīng)準(zhǔn)備好锦茁,通知 CPU 執(zhí)行 IO 的讀取或?qū)懭氩僮鳌?/p>
Selector 模式:當(dāng) IO 事件(管道)注冊到 Selector 以后,Selector 會分配給每個管道一個 key 值叉存。Selector 以輪詢的方式進(jìn)行查找注冊的所有 IO 事件(管道)码俩,當(dāng) IO 事件(管道)準(zhǔn)備就緒后,Selector 就會識別歼捏,通過 key 值找到相應(yīng)的管道稿存,進(jìn)行相關(guān)的數(shù)據(jù)處理操作。
示例代碼:
Server.java
public class Server implements Runnable{
//1 多路復(fù)用器(管理所有的通道)
private Selector seletor;
//2 建立緩沖區(qū)
private ByteBuffer readBuf = ByteBuffer.allocate(1024);
//3
private ByteBuffer writeBuf = ByteBuffer.allocate(1024);
public Server(int port){
try {
//1 打開路復(fù)用器
this.seletor = Selector.open();
//2 打開服務(wù)器通道
ServerSocketChannel ssc = ServerSocketChannel.open();
//3 設(shè)置服務(wù)器通道為非阻塞模式
ssc.configureBlocking(false);
//4 綁定地址
ssc.bind(new InetSocketAddress(port));
//5 把服務(wù)器通道注冊到多路復(fù)用器上瞳秽,并且監(jiān)聽阻塞事件
ssc.register(this.seletor, SelectionKey.OP_ACCEPT);
System.out.println("Server start, port :" + port);
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
public void run() {
while(true){
try {
//1 必須要讓多路復(fù)用器開始監(jiān)聽
this.seletor.select();
//2 返回多路復(fù)用器已經(jīng)選擇的結(jié)果集
Iterator<SelectionKey> keys = this.seletor.selectedKeys().iterator();
//3 進(jìn)行遍歷
while(keys.hasNext()){
//4 獲取一個選擇的元素
SelectionKey key = keys.next();
//5 直接從容器中移除就可以了
keys.remove();
//6 如果是有效的
if(key.isValid()){
//7 如果為阻塞狀態(tài)
if(key.isAcceptable()){
this.accept(key);
}
//8 如果為可讀狀態(tài)
if(key.isReadable()){
this.read(key);
}
//9 寫數(shù)據(jù)
if(key.isWritable()){
//this.write(key); //ssc
}
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
private void write(SelectionKey key){
//ServerSocketChannel ssc = (ServerSocketChannel) key.channel();
//ssc.register(this.seletor, SelectionKey.OP_WRITE);
}
private void read(SelectionKey key) {
try {
//1 清空緩沖區(qū)舊的數(shù)據(jù)
this.readBuf.clear();
//2 獲取之前注冊的socket通道對象
SocketChannel sc = (SocketChannel) key.channel();
//3 讀取數(shù)據(jù)
int count = sc.read(this.readBuf);
//4 如果沒有數(shù)據(jù)
if(count == -1){
key.channel().close();
key.cancel();
return;
}
//5 有數(shù)據(jù)則進(jìn)行讀取 讀取之前需要進(jìn)行復(fù)位方法(把position 和limit進(jìn)行復(fù)位)
this.readBuf.flip();
//6 根據(jù)緩沖區(qū)的數(shù)據(jù)長度創(chuàng)建相應(yīng)大小的byte數(shù)組挠铲,接收緩沖區(qū)的數(shù)據(jù)
byte[] bytes = new byte[this.readBuf.remaining()];
//7 接收緩沖區(qū)數(shù)據(jù)
this.readBuf.get(bytes);
//8 打印結(jié)果
String body = new String(bytes).trim();
System.out.println("Server : " + body);
// 9..可以寫回給客戶端數(shù)據(jù)
} catch (IOException e) {
e.printStackTrace();
}
}
private void accept(SelectionKey key) {
try {
//1 獲取服務(wù)通道
ServerSocketChannel ssc = (ServerSocketChannel) key.channel();
//2 執(zhí)行阻塞方法
SocketChannel sc = ssc.accept();
//3 設(shè)置阻塞模式
sc.configureBlocking(false);
//4 注冊到多路復(fù)用器上,并設(shè)置讀取標(biāo)識
sc.register(this.seletor, SelectionKey.OP_READ);
} catch (IOException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
new Thread(new Server(8765)).start();;
}
}
Client.java
public class Client {
//需要一個Selector
public static void main(String[] args) {
//創(chuàng)建連接的地址
InetSocketAddress address = new InetSocketAddress("127.0.0.1", 8765);
//聲明連接通道
SocketChannel sc = null;
//建立緩沖區(qū)
ByteBuffer buf = ByteBuffer.allocate(1024);
try {
//打開通道
sc = SocketChannel.open();
//進(jìn)行連接
sc.connect(address);
while(true){
//定義一個字節(jié)數(shù)組寂诱,然后使用系統(tǒng)錄入功能:
byte[] bytes = new byte[1024];
System.in.read(bytes);
//把數(shù)據(jù)放到緩沖區(qū)中
buf.put(bytes);
//對緩沖區(qū)進(jìn)行復(fù)位
buf.flip();
//寫出數(shù)據(jù)
sc.write(buf);
//清空緩沖區(qū)數(shù)據(jù)
buf.clear();
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if(sc != null){
try {
sc.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
運(yùn)行結(jié)果:
客戶端(控制臺輸入)
hello nio
服務(wù)端
Server start, port :8765
Server : hello nio