摘要:在設定時間內接收到相應操作的請求則返回可以處理請求的數量,否則在超時后返回,程序繼續執行。使用接收請求并處理接收到請求后調用返回的集合。保存了處理當前請求的和,并提供了不同的操作類型。默認值為且其值必須小于的值。
Java中的Socket可以分為普通Socket和NioSocket兩種。 普通Socket的用法
Java中的網絡通信是通過Socket實現的,Socket分為ServerSocket和Socket兩大類,ServerSocket用于服務端,可以通過accept方法監聽請求,監聽到請求后返回Socket,Socket用于具體完成數據傳輸,客戶端直接使用Socket發起請求并傳輸數據。
一個簡單的交互介紹ServerSocket及Socket的使用: 1. 創建ServerSocket。2. 調用創建出來的ServerSocket的accept方法進行監聽</>復制代碼
ServerSocket的構造方法一共有5個。最方便的是傳入一個端口參數的方法。
</>復制代碼
accept方法是阻塞方法,也就是說調用accept方法后程序會停下來等待連接請求,在接收到請求之前程序將不會繼續執行,當接收到請求之后,accept方法會返回一個Socket。
3. 使用accept方法返回的Socket與客戶端進行通信。
服務端代碼示例:
</>復制代碼
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
import java.text.SimpleDateFormat;
import java.util.Date;
public class Server {
public static void main(String[] args) {
try {
//創建一個ServeSocket,設置端口為8080
ServerSocket serverSocket = new ServerSocket(8080);
//運行Socket監聽,等待請求 此方法會阻塞線程,當有請求時才會繼續執行
Socket socket = serverSocket.accept();
//接收到請求之后使用Socket進行通信,創建BufferedReader用于讀取請求的數據
BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
PrintWriter out = new PrintWriter(socket.getOutputStream());
String line = in.readLine();
System.out.println(line);
//創建PrintlnWriter,用于發送數據
out.println("已經接受到了數據");
out.flush();
System.out.println("Server關閉" + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.sss").format(new Date()));
//關閉資源
out.close();
in.close();
socket.close();
serverSocket.close();
} catch (IOException e) {
e.printStackTrace();
} finally {
}
}
}
客戶端代碼示例:
</>復制代碼
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.Socket;
import java.text.SimpleDateFormat;
import java.util.Date;
/**
* 客戶端
* @author sanchan
*/
public class Client {
public static void main(String[] args) {
//需要先啟動Server否則報錯java.net.ConnectException: Connection refused
try {
String msg="你好,ServerSocket!";
//創建一個Socket,與本機8080端口連接
Socket socket=new Socket("127.0.0.1",8080);
//使用Socket創建PrintWriter和BufferedReader進行數據的讀寫
PrintWriter out=new PrintWriter(socket.getOutputStream());
out.println(msg);
out.flush();
BufferedReader in=new BufferedReader(new InputStreamReader(socket.getInputStream()));
String line=in.readLine();
System.out.println(line);
//關閉資源
in.close();
out.close();
socket.close();
System.out.println("client關閉"+new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.sss").format(new Date()));
} catch (Exception e) {
e.printStackTrace();
} finally {
}
}
}
NioSocket的使用
nio(new IO)是JDK1.4新增加的IO模式,nio在底層采用了與新的處理方式大大的提高了Java IO的效率。Socket也屬于IO的一種,nio提供了相對應的類:ServerSocketChannel和SocketChannel,分別對應原來的ServerSocket和Socket。
理解nio三基礎: 1. Buffer 2. Channel 3. Selector 小故事里有大智慧:試想一下如果電商是只要有訂單就派人直接取貨去送貨【這種模式就相當于之前的Socket方式】,而不是現在的快遞模式:將貨物統一到中轉站,分揀員按照配送范圍分配快遞員,然后快遞員統一送貨【這種模式就相當于NioSocket模式】。那么你得多長時間收到你得快遞/(ㄒoㄒ)/~~
Buffer就是貨物,Channel就是快遞員,Selector就是中轉站的分揀員。
</>復制代碼
SerSocketChannel可以使用自身的靜態工廠方法open創建。
每個ServerSocketChannel對應一個ServerSocket,可以調用其socket方法來獲取【不過如果使用該ServerSocket監聽請求就又回到原來的 普通Socket 模式了,一般只用于使用bind方法綁定端口】。
ServerSocketChannel可以通過`SelectableChannel configureBlocking(boolean block)` 方法來設置是否采用阻塞模式。設置非阻塞模式之后可以調用register方法注冊Selector【阻塞模式不可以使用Selector】
2. 創建Selector并注冊Selector到ServerSocketChannel上
</>復制代碼
Selector可以使用自身的靜態工廠方法open創建。
創建后通過上面所說的Channel的register方法注冊到ServerSocketChannel或者SocketChannel上。
3.調用Selector的select方法等待請求
</>復制代碼
通過select方法等待請求,select方法可傳入代表最長等待時間的long型參數。在設定時間內接收到相應操作的請求則返回可以處理請求的數量,否則在超時后返回0,程序繼續執行。如果傳入0或者使用無參的重載方法,則會采用阻塞模式直到有相應操作的請求出現。
4. 使用Selector接收請求并處理
</>復制代碼
接收到請求后Selector調用selectedKeys返回SelectionKey的Set集合。
5. 使用SelectionKey獲取到Channel、Selector和操作類型并進行具體操作。
</>復制代碼
SelectionKey保存了處理當前請求的Channel和Selector,并提供了不同的操作類型。前面提到的Channel注冊Selector的register方法參數中第二個參數就是SelectionKey定義的。共有四種:
</>復制代碼
SelectionKey.OP_ACCEPT //請求操作
SelectionKey.OP_CONNECT //鏈接操作
SelectionKey.OP_READ //讀操作
SelectionKey.OP_WRITE //寫操作
只有在register方法中注冊了對應的操作Selector才會關心相應類型操作的請求。
Buffer是專門用于存儲數據,有四個極為重要的屬性:</>復制代碼
Selector和Channel是多對多關系。
Selector是按不同的操作類型進行分揀,將分揀結果保存在SelectionKey中,可分別通過SelectionKey的channel、selector方法來獲取對應的Channel和Selector。可以使用SelectionKey的isAcceptable、isConnectable、isReadable和isWritable方法來判斷是什么類型的操作。
capacity:容量。
Buffer最多可以保存元素的數量,創建時設置,使用過程中不可修改。
limit:可以使用的上限。
剛創建Buffer時limit等于capacity。如果給limit設置【不能超過capacity】之后,limit就成了最大可訪問的值。
例如,一個Buffer的capacity為100,表示最多可以保存100個數據,只寫入20個之后就要讀取,在讀取時limit就會設置為20。
position:當前所操作元素所在索引位置。
position從0開始,隨著get和put方法自動更新。
mark:用來暫時保存position的值。
position保存到mark之后就可以修改并進行相關的操作,操作完成后可以通過reset方法將mark的值恢復到position。
mark默認值為-1,且其值必須小于position的值。
例如,Buffer中一共保存了20個數據,position為10,現在想讀取15到20之間的數據,這時就可以調用Buffer的mark方法將目前的position保存到mark中,然后調用Buffer的position(15)將position指向第15個元素,這時就可以讀取。讀取完成之后使用Buffer的reset就可以將position恢復到10.
如果調用Buffer的position方法時傳入的值小于mark當前的值,則會將mark設為-1。
這四個屬性大小關系:mark<=position<=limit<=capacity
我們將前面的普通Socket示例的服務端改寫一下:
</>復制代碼
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.nio.charset.Charset;
import java.util.Iterator;
/**
* @author sanchan
* @since 1.0
*/
public class NIOServer {
public static void main(String[] args) {
/**
* 啟動監聽
* 當監聽到請求時根據SelectionKey的操作類型交給內部類Handler進行處理
*/
try {
//創建ServerSocketChannel
ServerSocketChannel ssc = ServerSocketChannel.open();
//設置監聽8080端口
ssc.socket().bind(new InetSocketAddress(8080));
//設置為非阻塞模式
ssc.configureBlocking(false);
//為ServerSocketChannel注冊Selector
Selector selector = Selector.open();
ssc.register(selector, SelectionKey.OP_ACCEPT);
//創建Handler
Handler handler = new Handler(1024);
while (true) {
//等待請求,每次等待阻塞3s,超過3秒后線程繼續運行,如果傳入0或使用無參重載方法,將一直阻塞
if (selector.select(3000) == 0) {
System.out.println("等待請求超時~~~~~");
continue;
}
System.out.println("處理請求~~~~~");
//獲取等待處理的SelectionKey
Iterator keyIterator = selector.selectedKeys().iterator();
while (keyIterator.hasNext()) {
SelectionKey key = keyIterator.next();
try {
//根據不同請求操作選擇對應的處理方法
if (key.isAcceptable()) {
handler.handleAccept(key);
}
if (key.isReadable()) {
handler.handleRead(key);
}
} catch (IOException e) {
e.printStackTrace();
//如果異常就說明連接結束,移除
keyIterator.remove();
continue;
}
}
}
} catch (IOException e) {
e.printStackTrace();
} finally {
}
}
/**
* 請求處理類
*/
private static class Handler {
private int bufferSize = 1024;
private String localCharset = "UTF-8";
public Handler() {
}
public Handler(int bufferSize) {
this(bufferSize, null);
}
public Handler(String localCharset) {
this(-1, localCharset);
}
public Handler(int bufferSize, String localCharset) {
if (bufferSize > 0)
this.bufferSize = bufferSize;
if (localCharset != null)
this.localCharset = localCharset;
}
/**
* 處理請求操作
*
* @param key
* @throws IOException
*/
public void handleAccept(SelectionKey key) throws IOException {
//獲取Channel
SocketChannel sc = ((ServerSocketChannel) key.channel()).accept();
//設置非阻塞
sc.configureBlocking(false);
//注冊讀操作的Selector
sc.register(key.selector(), SelectionKey.OP_READ, ByteBuffer.allocate(bufferSize));
}
/**
* 處理讀操作
*
* @param key
* @throws IOException
*/
public void handleRead(SelectionKey key) throws IOException {
//獲取Channel
SocketChannel sc = ((SocketChannel) key.channel());
//獲取ByteBuffer
/**
* Buffer專門用于存儲數據,有四個極為重要的屬性:
* 1. capacity:容量。
* Buffer最多可以保存元素的數量,創建時設置,使用過程中不可修改。
* 2. limit:可以使用的上限。
* 剛創建Buffer時limit等于capacity。如果給limit設置【不能超過capacity】之后,limit就成了最大可訪問的值。
* 例如,一個Buffer的capacity為100,表示最多可以保存100個數據,只寫入20個之后就要讀取,在讀取時limit就會設置為20。
* 3. position:當前所操作元素所在索引位置。
* position從0開始,隨著get和put方法自動更新。
* 4. mark:用來暫時保存position的值。
* position保存到mark之后就可以修改并進行相關的操作,操作完成后可以通過reset方法將mark的值恢復到position。
* mark默認值為-1,且其值必須小于position的值。
* 例如,Buffer中一共保存了20個數據,position為10,現在想讀取15到20之間的數據,這時就可以調用Buffer的mark方法將目前的position保存到mark中,然后調用Buffer的position(15)將position指向第15個元素,這時就可以讀取。讀取完成之后使用Buffer的reset就可以將position恢復到10.
* 如果調用Buffer的position方法時傳入的值小于mark當前的值,則會將mark設為-1。
*/
ByteBuffer buffer = (ByteBuffer) key.attachment();
//重置ByteBuffer。設置limit=capacity、position=0、mark=-1
buffer.clear();
//沒有獲取到內容則關閉
if (sc.read(buffer) == -1) {
sc.close();
} else {
/**
* flip()作用:
* 在保存數據時保存一個數據position加1,保存完成后要讀取數據
* 就得設置limit=position,position=0
**/
buffer.flip();
//返回數據到客戶端
String receivedString = Charset.forName(localCharset).newDecoder().decode(buffer).toString();
System.out.println("從客戶端獲取到了數據:" + receivedString);
String sendString = "服務端已經獲取到了數據:" + receivedString;
buffer = ByteBuffer.wrap(sendString.getBytes(localCharset));
sc.write(buffer);
//關閉SocketChannel
sc.close();
}
}
}
}
我是廣告
本人的直播課程在 7 月份就要開始了,希望小伙伴們支持一下,現在報名有優惠噢
https://segmentfault.com/l/15...
https://segmentfault.com/l/15...
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://m.specialneedsforspecialkids.com/yun/67234.html
摘要:作為一個程序員,不了解內存模型就不能寫出能夠充分利用內存的代碼。程序計數器是在電腦處理器中的一個寄存器,用來指示電腦下一步要運行的指令序列。在虛擬機中,本地方法棧和虛擬機棧是共用同一塊內存的,不做具體區分。 作為一個 Java 程序員,不了解 Java 內存模型就不能寫出能夠充分利用內存的代碼。本文通過對 Java 內存模型的介紹,讓讀者能夠了解 Java 的內存的分配情況,適合 Ja...
摘要:相等判斷符介紹相等判斷符用于比較基本數據類型和引用類型數據當比較基本數據類型的時候比較的是數值當比較引用類型數據時比較的是引用指針判斷基本類型是否相等首先基本數據類型指的是中的八大數據類型這八大基本數據類型有個共同的特點是它們在內存中是有具相等判斷符==介紹 ? ==相等判斷符用于比較基本數據類型和引用類型數據. 當比較基本數據類型的時候比較的是數值, 當比較引用類型數據時比較的是引用(指...
摘要:相等判斷符介紹相等判斷符用于比較基本數據類型和引用類型數據當比較基本數據類型的時候比較的是數值當比較引用類型數據時比較的是引用指針判斷基本類型是否相等首先基本數據類型指的是中的八大數據類型這八大基本數據類型有個共同的特點是它們在內存中是有具相等判斷符==介紹 ==相等判斷符用于比較基本數據類型和引用類型數據. 當比較基本數據類型的時候比較的是數值, 當比較引用類型數據時比較的是引用(指針...
摘要:相等判斷符介紹相等判斷符用于比較基本數據類型和引用類型數據當比較基本數據類型的時候比較的是數值當比較引用類型數據時比較的是引用指針判斷基本類型是否相等首先基本數據類型指的是中的八大數據類型這八大基本數據類型有個共同的特點是它們在內存中是有具相等判斷符==介紹 ==相等判斷符用于比較基本數據類型和引用類型數據. 當比較基本數據類型的時候比較的是數值, 當比較引用類型數據時比較的是引用(指針...
閱讀 2386·2021-11-15 11:37
閱讀 2638·2021-09-23 11:21
閱讀 2967·2021-09-07 10:11
閱讀 3175·2019-08-30 15:53
閱讀 2835·2019-08-29 15:13
閱讀 1618·2019-08-26 13:57
閱讀 1112·2019-08-26 12:23
閱讀 2451·2019-08-26 11:51