Java Socketプログラミングのこと(1)

5718 ワード

前言
最近面接や筆記試験の準備をしていて、JavaのIOに関する基礎を振り返ってみると、まだしっかり覚えていない基礎がたくさんあることに気づきました.今振り返って勉強し直して、socket通信から始めましょう.今は企業が直接socketを書くことは少ないと言っても、いくつかの封印されたフレームワーク、netty、minaなどを使っています.しかし、これらの基礎的な知識については、しっかり身につける必要があります.これからもっと深い枠組みと筆記試験の面接を学ぶのに役立ちます.
BIO、NIO、AIOの違い
Javaの中のIOの方式は3種類のJava BIOがあります:同期してブロックして、サーバーの実現モードは1つの接続の1つのスレッドで、つまりクライアントが接続の要求がある時サーバーの端は1つのスレッドを起動して処理する必要があって、もしこの接続が何もしないならば不要なスレッドのオーバーヘッドをもたらして、もちろんスレッドのプールのメカニズムを通じて改善することができます.JDK 1.4以前はこのような方式のJava NIOを採用していた:同期非ブロック、サーバー実装モードは1つの要求の1つのスレッドであり、つまりクライアントが送信した接続要求はすべてマルチプレクサに登録され、マルチプレクサはI/O要求が接続されている時にやっと1つのスレッドを起動して処理を行う.Java AIO(NIO.2):非同期非ブロック、サーバ実装モードは有効な要求スレッドであり、クライアントのI/O要求はOSが先に完了してからサーバアプリケーションにスレッドを起動するように通知して処理する.
ある文章でこんないい比喩を見たことがある.
宮保鶏丁丼が食べたいなら:
同时に渋滞します:あなたはレストランに行って食事を注文して、それからそこで待っていて、また叫びながら:いいですか!
同期非渋滞:レストランで食事を注文したら、犬の散歩に行きました.でもしばらくすると、レストランに帰って「よし、いいか」と叫んだ.
非同期渋滞:犬の散歩中、レストランから電話があり、ご飯ができたと言って、自分で取りに行かせます.
非同期非ブロック:レストランから電話がありました.私たちはあなたの位置を知っています.後で送ってきて、安心して犬を散歩すればいいと言っています.
ここでは一番簡単なBIOから勉強しましょう
シングルスレッド通信
まず、12345というポートを傍受し、クライアントの接続を待つサーバクラスを新規作成します.クライアントとサーバが接続されると、クライアントはサーバのように情報を送信することができ、byeを送信すると、通信を終了します.
package edu.gduf.bio.socket.demo1;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;

/**
 *   bio socket    
 * 
 * @author ZGJ
 * @date 2017 5 4 
 */
public class Server {
    public static void main(String[] args) {
        //    serversocket 10000     
        try (ServerSocket server = new ServerSocket(12345)) {
            //       ,             
            Socket socket = server.accept();
            BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
            PrintWriter out = new PrintWriter(socket.getOutputStream());

            while (true) {
                String msg = in.readLine();
                System.out.println(msg);
                out.println("Server received " + msg);
                out.flush();
                if (msg.equals("bye")) {
                    break;
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}


ここでtry (ServerSocket server = new ServerSocket(12345))はjava 7のtry-with-resources文法を採用しており、tryで宣言されたリソースは最後に自動的に閉じられ、手動で閉じる必要がなく、文法を簡略化するメリットがあります.
Clientクラス
package edu.gduf.bio.socket.demo1;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.Socket;

/**
 *   bio socket   
 * 
 * @author ZGJ
 * @date 2017 5 4 
 */
public class Client {
    public static void main(String[] args) {
        try (Socket socket = new Socket("127.0.0.1", 12345)) {
            //  socket   
            BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
            //socket   
            PrintWriter out = new PrintWriter(socket.getOutputStream());
            //     
            BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
            while (true) {
                String msg = reader.readLine();
                out.println(msg);
                out.flush();
                if (msg.equals("bye")) {
                    break;
                }
                System.out.println(in.readLine());
            }
        } catch (IOException e) {
            e.printStackTrace();
        }

    }
}

実行結果:Client:
hello Server received hello haha Server received haha bye
Server: hello haha bye
マルチスレッド通信
私達は発見して、サーバーは1つのクライアントの要求を処理することしかできなくて、サーバーは第1のクライアントの要求を処理した後に、後続のClientは更に接続することができなくて、この時私達はいくつかの変更をしなければならなくて、サーバーがクライアントの接続の要求を受け取った後に、クライアントの要求は1つの新しいスレッドの中で処理しますプライマリ・スレッドは、他のクライアントの接続を待機し続けます.これにより、サーバが他のクライアントの要求を処理するのをブロックすることはありません.サーバのコードを変更するだけで、クライアント・コードは元のものと同じです.
package edu.gduf.bio.socket.demo2;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;

/**
 *     client server
 * @author ZGJ
 * @date 2017 5 4 
 */
public class MulitClientServer {
    public static void main(String[] args) {
        try(ServerSocket server = new ServerSocket(12345)) {
            //         
            while(true) {
                Socket socket = server.accept();
                handle(socket);
            }
        } catch(IOException e) {
            e.printStackTrace();
        }
    }
    /**
     *       
     * @param socket
     */
    private static void handle(Socket socket) {
        //      ,lambda   
        new Thread(() -> {
            try(BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
                    PrintWriter writer = new PrintWriter(socket.getOutputStream(), true)) {
                while(true) {
                    String message =  in.readLine();
                    System.out.println(message);
                    writer.println("Server received: " + message);
                    if("bye".equals(message)) {
                        break;
                    }
                }
            } catch(IOException e) {
                e.printStackTrace();
            }
        }).start();
    }
}


サーバがクライアントのリクエストを受信した後、handle()メソッドを使用してクライアントのリクエストを処理することができます.ここでは、Java 8のlambda式を使用して匿名の内部クラスの構文に代わって、プログラミングを簡素化し、構文をより簡潔にします.各クライアントの要求に対して新しいスレッドを開いて処理します.もちろん、より最適化したい場合は、Executorスレッドプールを使用して処理し、スレッドの作成と閉じるコストを節約できます.