Javaソケットの探索

Nov 23, 2016 pm 03:00 PM

Javaのソケットは通常のソケットとNioSocketの2種類に分けられます。

通常の Socket の使用法

Socket は、ServerSocket と Socket の 2 つのカテゴリに分けられます。ServerSocket は、accept メソッドを介してリクエストをリッスンします。 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(新IO)は、JDK1.4で新たに追加されたIOモードであり、nioは最下層で新しい処理方式を採用しています。 . Java IO の効率が向上しました。 Socket も IO の一種であり、nio は対応するクラス ServerSocketChannel と SocketChannel を提供します。これらは、それぞれ元の ServerSocket と Socket に対応します。

nio の 3 つの基本を理解します:

1. バッファ

2.

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<=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<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 までご連絡ください。

ホットAIツール

Undresser.AI Undress

Undresser.AI Undress

リアルなヌード写真を作成する AI 搭載アプリ

AI Clothes Remover

AI Clothes Remover

写真から衣服を削除するオンライン AI ツール。

Undress AI Tool

Undress AI Tool

脱衣画像を無料で

Clothoff.io

Clothoff.io

AI衣類リムーバー

Video Face Swap

Video Face Swap

完全無料の AI 顔交換ツールを使用して、あらゆるビデオの顔を簡単に交換できます。

ホットツール

メモ帳++7.3.1

メモ帳++7.3.1

使いやすく無料のコードエディター

SublimeText3 中国語版

SublimeText3 中国語版

中国語版、とても使いやすい

ゼンドスタジオ 13.0.1

ゼンドスタジオ 13.0.1

強力な PHP 統合開発環境

ドリームウィーバー CS6

ドリームウィーバー CS6

ビジュアル Web 開発ツール

SublimeText3 Mac版

SublimeText3 Mac版

神レベルのコード編集ソフト(SublimeText3)

会社のセキュリティソフトウェアはアプリケーションの実行に失敗していますか?それをトラブルシューティングと解決する方法は? 会社のセキュリティソフトウェアはアプリケーションの実行に失敗していますか?それをトラブルシューティングと解決する方法は? Apr 19, 2025 pm 04:51 PM

一部のアプリケーションが適切に機能しないようにする会社のセキュリティソフトウェアのトラブルシューティングとソリューション。多くの企業は、内部ネットワークセキュリティを確保するためにセキュリティソフトウェアを展開します。 ...

エンティティクラス変数名をエレガントに取得して、データベースクエリ条件を構築する方法は? エンティティクラス変数名をエレガントに取得して、データベースクエリ条件を構築する方法は? Apr 19, 2025 pm 11:42 PM

データベース操作にMyBatis-Plusまたはその他のORMフレームワークを使用する場合、エンティティクラスの属性名に基づいてクエリ条件を構築する必要があることがよくあります。あなたが毎回手動で...

MapsTructを使用したシステムドッキングのフィールドマッピングの問題を簡素化する方法は? MapsTructを使用したシステムドッキングのフィールドマッピングの問題を簡素化する方法は? Apr 19, 2025 pm 06:21 PM

システムドッキングでのフィールドマッピング処理は、システムドッキングを実行する際に難しい問題に遭遇することがよくあります。システムのインターフェイスフィールドを効果的にマッピングする方法A ...

名前を数値に変換してソートを実装し、グループの一貫性を維持するにはどうすればよいですか? 名前を数値に変換してソートを実装し、グループの一貫性を維持するにはどうすればよいですか? Apr 19, 2025 pm 11:30 PM

多くのアプリケーションシナリオでソートを実装するために名前を数値に変換するソリューションでは、ユーザーはグループ、特に1つでソートする必要がある場合があります...

Intellijのアイデアは、ログを出力せずにSpring Bootプロジェクトのポート番号をどのように識別しますか? Intellijのアイデアは、ログを出力せずにSpring Bootプロジェクトのポート番号をどのように識別しますか? Apr 19, 2025 pm 11:45 PM

intellijideaultimatiateバージョンを使用してスプリングを開始します...

データベースクエリにTKMYBATISを使用するときに、エンティティクラスの変数名の構築クエリ条件をエレガントに取得する方法は? データベースクエリにTKMYBATISを使用するときに、エンティティクラスの変数名の構築クエリ条件をエレガントに取得する方法は? Apr 19, 2025 pm 09:51 PM

データベースクエリにTKMYBATISを使用する場合、クエリ条件を構築するためにエンティティクラスの変数名を優雅に取得する方法は一般的な問題です。この記事はピン留めします...

Javaオブジェクトを配列に安全に変換する方法は? Javaオブジェクトを配列に安全に変換する方法は? Apr 19, 2025 pm 11:33 PM

Javaオブジェクトと配列の変換:リスクの詳細な議論と鋳造タイプ変換の正しい方法多くのJava初心者は、オブジェクトのアレイへの変換に遭遇します...

Redisキャッシュソリューションを使用して、製品ランキングリストの要件を効率的に実現する方法は? Redisキャッシュソリューションを使用して、製品ランキングリストの要件を効率的に実現する方法は? Apr 19, 2025 pm 11:36 PM

Redisキャッシュソリューションは、製品ランキングリストの要件をどのように実現しますか?開発プロセス中に、多くの場合、ランキングの要件に対処する必要があります。

See all articles