1. 程式人生 > >Java 網路IO程式設計

Java 網路IO程式設計

什麼是Socket

Socket是應用層與TCP/IP協議族通訊的中間軟體抽象層,它是一組介面。在設計模式中,Socket其實就是一個門面模式,它把複雜的TCP/IP協議族隱藏在Socket介面後面,對使用者來說,一組簡單的介面就是全部,讓Socket去組織資料,以符合指定的協議。

如下圖所示

我們可以選擇基於TCP或UDP協議進行網路通訊

1. 基於TCP協議進行網路通訊

通過TCP傳送資料時,首先要建立連線。建立TCP連線後,TCP保證資料到達另一端,或者它會告訴我們發生了錯誤。

1.1 客戶端

為了實現通過TCP協議連線到伺服器,需要建立一個java.net.Socket並將其連線到伺服器。

1.1.1 建立Socket

如下為連線到ip為127.0.0.1,埠號為8000的伺服器。ip地址也可以為域名。

 Socket socket = new Socket("127.0.0.1", 8000);

1.1.2 將資料寫到Socket

socket.getOutputStream().write("hello world".getBytes());

1.1.3 從Socket讀取資料

InputStream inputStream = socket.getInputStream();
    while ((len = inputStream.read(data)) != -1) {
        System.out.println(new String(data, 0, len));
    }
  • 注意:在讀取檔案時,不能通過返回-1來判斷是否讀完這個檔案。因為只有在伺服器關閉的情況下連線才返回-1。

1.1.4 關閉Socket

socket.close();

完整程式碼如下:

@Slf4j
public class TCPClient {
    public static void main(String[] args) throws InterruptedException {
        CountDownLatch countDownLatch = new CountDownLatch(1);
        new Thread(() -> {
            try {
                Socket socket = new Socket("127.0.0.1", 8000);
                for (int i= 0;i<100;i++) {
                    try {
                        socket.getOutputStream().write((new Date() + ": hello world").getBytes());
                        log.info("client傳送訊息");
                    } catch (Exception e) {
                    }
                }
            } catch (IOException e) {
            }
        }).start();

        countDownLatch.await();
    }
}

1.2 服務端

為了實現通過TCP協議來監聽客戶端傳入Java伺服器的連線,需要使用到java.net.ServerSocket,當客戶端通過客戶端套接字連線到伺服器的ServerSocket時,伺服器上會為該連線分配一個Socket。客戶端和伺服器通過Socket-to-Socket方式通訊。

1.2.1 建立ServerSocket

如下,建立一個ServerSocket監聽埠8000。

ServerSocket serverSocket = new ServerSocket(8000);

1.2.2 監聽傳入的連線

while(true){
Socket socket = serverSocket.accept();
//todo:連線後的操作
}
  • 注意:只有當前服務執行緒呼叫accept()方法時,客戶端的連線才能被接收,除此之外的任何時間都沒有客戶端可以連線到伺服器。所以在接收連線後,一般都將Socket放到工作執行緒執行後續邏輯。

1.2.3 關閉客戶端Socket

當客戶端的請求完成,並且不會從該客戶端收到進一步的請求,則需要將Socket關閉。

socket.close();

1.2.4 關閉伺服器端 Socket

如果有需要關閉服務:

serverSocket.close();

完整程式碼如下:

@Slf4j
public class TCPServer {

    public static void main(String[] args) throws Exception {

        CountDownLatch countDownLatch = new CountDownLatch(1);
        ServerSocket serverSocket = new ServerSocket(8000);

        // (1) 接收新連線執行緒
        new Thread(() -> {
            while (true) {
                try {
                    // (1) 阻塞方法獲取新的連線
                    Socket socket = serverSocket.accept();
                    log.info("service接收訊息");
                    // (2) 每一個新的連線都建立一個執行緒,負責讀取資料
                    new Thread(() -> {
                        log.info("建立執行緒");
                        try {
                            int len;
                            byte[] data = new byte[1024];
                            InputStream inputStream = socket.getInputStream();
                            // (3) 按位元組流方式讀取資料
                            while ((len = inputStream.read(data)) != -1) {
                                log.info(new String(data, 0, len));
                            }
                        } catch (IOException e) {
                        }
                    }).start();

                } catch (IOException e) {
                }

            }
        }).start();

        countDownLatch.await();
    }
}

2. 基於UDP協議進行網路通訊

使用UDP協議,客戶端和伺服器之間沒有連線。客戶端可以向伺服器傳送資料,並且伺服器可以(或可以不)接收該資料。客戶端永遠不會知道資料是否在另一端收到。伺服器傳送資料到客戶端也是如此。

UDP和TCP埠不一樣。計算機可以具有不同的程序,例如在UDP和TCP中同時偵聽埠8000。

2.1 客戶端

2.1.1 建立DatagramSocket

DatagramSocket datagramSocket = new DatagramSocket();

2.1.2 建立DatagramPacket

引數data為此次通訊需要傳送的資料,inetAddress為此次通訊的節點地址的ip資訊,最後一個引數為埠號。

InetAddress inetAddress = InetAddress.getLocalHost();
byte[] data = "123123123".getBytes();
DatagramPacket datagramPacket = new DatagramPacket(data,data.length,inetAddress,8000);

2.1.3 傳送資料

datagramSocket.send(datagramPacket);

完整程式碼如下:

public class UDPClient {

    private static DatagramPacket datagramPacket;
    private static InetAddress inetAddress;
    private static DatagramSocket datagramSocket;

    public static void main(String[] args) throws IOException, InterruptedException {
        inetAddress = InetAddress.getLocalHost();
        datagramSocket = new DatagramSocket();
        while (true){
            byte[] data = "123123123".getBytes();
            datagramPacket = new DatagramPacket(data,data.length,inetAddress,8000);
            datagramSocket.send(datagramPacket);
            Thread.sleep(2000);
        }

    }
}

2.2 服務端

2.2.1 建立DatagramSocket

監聽8000埠

DatagramSocket datagramSocket = new DatagramSocket(8000);

2.2.2 建立DatagramPacket

建立一個緩衝區,接收客戶端傳送的資料

byte[] data = new byte[9];
datagramPacket = new DatagramPacket(data,data.length);

2.2.3 接收資料

接收的資料為byte陣列

byte[] receive = datagramPacket.getData();
System.out.println(new String(receive));

完整程式碼如下:

public class UDPServer {

    private static DatagramSocket datagramSocket;
    private static DatagramPacket datagramPacket;

    public static void main(String[] args) throws IOException, InterruptedException {
        //UDP和TCP埠不一樣。計算機可以具有不同的程序,例如在UDP和TCP中同時偵聽埠80。
        datagramSocket = new DatagramSocket(8000);
        while (true){
            byte[] data = new byte[9];
            datagramPacket = new DatagramPacket(data,data.length);
            datagramSocket.receive(datagramPacket);
            byte[] receive = datagramPacket.getData();
            System.out.println(new String(receive));
        }

    }
}

後記

Java IO程式設計,每建立一個連線,就需要提供一個單獨的執行緒從流中讀取資料。儘管可以把事件放到執行緒池中處理,但當併發量很大的時候,系統開銷仍然是支撐不住的。

參考連結:
Jenkov.com
圖片來源:

一碗清水CSDN