搜尋
首頁Javajava教程Java Socket探究
Java Socket探究Nov 23, 2016 pm 03:00 PM

Java中的Socket可以分為普通Socket和NioSocket兩種。

普通Socket的用法

Java中的網路通訊是透過Socket實現的,Socket分為ServerSocket和Socket兩大類,ServerSocket用於服務端,可以透過accept方法監聽請求,監聽到請求後返回Socket,Socket用於具體完成資料傳輸,客戶端直接使用Socket發起請求並傳輸資料。

一個簡單的互動介紹ServerSocket及Socket的使用:

1. 建立ServerSocket。

ServerSocket的構造方法總共有5個。最方便的是傳入一個連接埠參數的方法。

2. 呼叫創建出來的ServerSocket的accept方法進行監聽

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就是中转站的分拣员。

NioSocket使用步骤:

1. 创建ServerSocketChannel并设置相应参数

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才会关心相应类型操作的请求。
Selector和Channel是多对多关系。
Selector是按不同的操作类型进行分拣,将分拣结果保存在SelectionKey中,可分别通过SelectionKey的channel、selector方法来获取对应的Channel和Selector。可以使用SelectionKey的isAcceptable、isConnectable、isReadable和isWritable方法来判断是什么类型的操作。

Buffer是专门用于存储数据,有四个极为重要的属性:

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

我们将前面的普通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<SelectionKey> 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();
            }

        }
    }
}


陳述
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn
如何使用咖啡因或Guava Cache等庫在Java應用程序中實現多層緩存?如何使用咖啡因或Guava Cache等庫在Java應用程序中實現多層緩存?Mar 17, 2025 pm 05:44 PM

本文討論了使用咖啡因和Guava緩存在Java中實施多層緩存以提高應用程序性能。它涵蓋設置,集成和績效優勢,以及配置和驅逐政策管理最佳PRA

如何在Java中實施功能編程技術?如何在Java中實施功能編程技術?Mar 11, 2025 pm 05:51 PM

本文使用lambda表達式,流API,方法參考和可選探索將功能編程集成到Java中。 它突出顯示了通過簡潔性和不變性改善代碼可讀性和可維護性等好處

Java的類負載機制如何起作用,包括不同的類載荷及其委託模型?Java的類負載機制如何起作用,包括不同的類載荷及其委託模型?Mar 17, 2025 pm 05:35 PM

Java的類上載涉及使用帶有引導,擴展程序和應用程序類負載器的分層系統加載,鏈接和初始化類。父代授權模型確保首先加載核心類別,從而影響自定義類LOA

如何將JPA(Java持久性API)用於具有高級功能(例如緩存和懶惰加載)的對象相關映射?如何將JPA(Java持久性API)用於具有高級功能(例如緩存和懶惰加載)的對象相關映射?Mar 17, 2025 pm 05:43 PM

本文討論了使用JPA進行對象相關映射,並具有高級功能,例如緩存和懶惰加載。它涵蓋了設置,實體映射和優化性能的最佳實踐,同時突出潛在的陷阱。[159個字符]

如何將Maven或Gradle用於高級Java項目管理,構建自動化和依賴性解決方案?如何將Maven或Gradle用於高級Java項目管理,構建自動化和依賴性解決方案?Mar 17, 2025 pm 05:46 PM

本文討論了使用Maven和Gradle進行Java項目管理,構建自動化和依賴性解決方案,以比較其方法和優化策略。

如何將Java的Nio(新輸入/輸出)API用於非阻滯I/O?如何將Java的Nio(新輸入/輸出)API用於非阻滯I/O?Mar 11, 2025 pm 05:51 PM

本文使用選擇器和頻道使用單個線程有效地處理多個連接的Java的NIO API,用於非阻滯I/O。 它詳細介紹了過程,好處(可伸縮性,性能)和潛在的陷阱(複雜性,

如何使用適當的版本控制和依賴項管理創建和使用自定義Java庫(JAR文件)?如何使用適當的版本控制和依賴項管理創建和使用自定義Java庫(JAR文件)?Mar 17, 2025 pm 05:45 PM

本文使用Maven和Gradle之類的工具討論了具有適當的版本控制和依賴關係管理的自定義Java庫(JAR文件)的創建和使用。

如何使用Java的插座API進行網絡通信?如何使用Java的插座API進行網絡通信?Mar 11, 2025 pm 05:53 PM

本文詳細介紹了用於網絡通信的Java的套接字API,涵蓋了客戶服務器設置,數據處理和關鍵考慮因素,例如資源管理,錯誤處理和安全性。 它還探索了性能優化技術,我

See all articles

熱AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover

AI Clothes Remover

用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool

Undress AI Tool

免費脫衣圖片

Clothoff.io

Clothoff.io

AI脫衣器

AI Hentai Generator

AI Hentai Generator

免費產生 AI 無盡。

熱門文章

R.E.P.O.能量晶體解釋及其做什麼(黃色晶體)
3 週前By尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.最佳圖形設置
3 週前By尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.如果您聽不到任何人,如何修復音頻
3 週前By尊渡假赌尊渡假赌尊渡假赌

熱工具

VSCode Windows 64位元 下載

VSCode Windows 64位元 下載

微軟推出的免費、功能強大的一款IDE編輯器

WebStorm Mac版

WebStorm Mac版

好用的JavaScript開發工具

DVWA

DVWA

Damn Vulnerable Web App (DVWA) 是一個PHP/MySQL的Web應用程序,非常容易受到攻擊。它的主要目標是成為安全專業人員在合法環境中測試自己的技能和工具的輔助工具,幫助Web開發人員更好地理解保護網路應用程式的過程,並幫助教師/學生在課堂環境中教授/學習Web應用程式安全性。 DVWA的目標是透過簡單直接的介面練習一些最常見的Web漏洞,難度各不相同。請注意,該軟體中

SecLists

SecLists

SecLists是最終安全測試人員的伙伴。它是一個包含各種類型清單的集合,這些清單在安全評估過程中經常使用,而且都在一個地方。 SecLists透過方便地提供安全測試人員可能需要的所有列表,幫助提高安全測試的效率和生產力。清單類型包括使用者名稱、密碼、URL、模糊測試有效載荷、敏感資料模式、Web shell等等。測試人員只需將此儲存庫拉到新的測試機上,他就可以存取所需的每種類型的清單。

Atom編輯器mac版下載

Atom編輯器mac版下載

最受歡迎的的開源編輯器