1. 程式人生 > >socket+執行緒池,寫服務端和客戶端進行互動

socket+執行緒池,寫服務端和客戶端進行互動

以下內容轉自:
https://www.cnblogs.com/gnoc/p/4866788.html

前言  
socket(套接字),Socket和ServerSocket位於java.net包中,持續開啟服務端,接收來自客戶端的資訊,並響應。

最開始,咱們先來兩段最簡單的服務端和客戶端的程式碼

最簡單的服務端程式碼:

package com.socket;

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.ServerSocket;
import
java.net.Socket; public class TestSocketService { //自定義一個埠號 private static final int PORT = 8888; public static void main(String[] args) { ServerSocket server = null; Socket socket = null; DataInputStream dataInputStream = null; DataOutputStream dataOutputStream = null
; try { server = new ServerSocket(PORT); System.out.println("監聽埠:" + PORT); socket = server.accept(); // 接受客戶端請求 dataInputStream = new DataInputStream(socket.getInputStream()); String request = dataInputStream.readUTF(); System.out.println("from client..."
+ request); // 響應客戶端 dataOutputStream = new DataOutputStream(socket.getOutputStream()); String response = "收到"; dataOutputStream.writeUTF(response); } catch (IOException e) { e.printStackTrace(); } finally { try { if (dataInputStream != null) { dataInputStream.close(); } if (dataOutputStream != null) { dataOutputStream.close(); } } catch (IOException e) { e.printStackTrace(); } } } }

最簡單的客戶端程式碼:

package com.socket;

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.Socket;


public class TestSocketClient {
    private static final String HOST = "127.0.0.1";
    private static final int PORT = 8888;

    public static void main(String[] args) {

        Socket socket = null;
        DataInputStream dataInputStream = null;
        DataOutputStream dataOutputStream = null;
        try {
            socket = new Socket(HOST, PORT);

            //給服務端傳送請求
            dataOutputStream = new DataOutputStream(socket.getOutputStream());
            String request = "我是客戶1";
            dataOutputStream.writeUTF(request);

            dataInputStream = new DataInputStream(socket.getInputStream());
            String response = dataInputStream.readUTF();
            System.out.println(response);

        } catch (IOException e) {
            e.printStackTrace();
        }finally{
            try {
                if(dataInputStream != null){
                    dataInputStream.close();
                }
                if(dataOutputStream != null){
                    dataOutputStream.close();
                }
                if(socket != null){
                    socket.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

客戶端和服務端分別執行,能收發資料,但是服務端只服務了一次就停止了,這明顯不符合需求,服務端應該響應完客戶端之後,繼續監聽埠,等待下一個客戶端的連線。

讓服務端一直提供服務
將服務端的程式碼寫入迴圈中持續迴圈,一直監聽來自客戶端的請求。修改服務端的程式碼:

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

        ServerSocket server = null;
        Socket socket = null;
        DataInputStream dataInputStream = null;
        DataOutputStream dataOutputStream = null;
        server = new ServerSocket(PORT);
        System.out.println("監聽埠:" + PORT);
        while(true){
            try {
                socket = server.accept();

                // 接受客戶端請求
                dataInputStream = new DataInputStream(socket.getInputStream());
                String request = dataInputStream.readUTF();
                System.out.println("from client..." + request);

                // 響應客戶端
                dataOutputStream = new DataOutputStream(socket.getOutputStream());
                String response = "收到";
                dataOutputStream.writeUTF(response);

            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                try {
                    if (dataInputStream != null) {
                        dataInputStream.close();
                    }
                    if (dataOutputStream != null) {
                        dataOutputStream.close();
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

通過while(true)迴圈,服務端一直監聽埠,由於socket是阻塞的,只有服務端完成了當前客戶端的響應,才會繼續處理下一個客戶端的響應。這樣一直讓主線執行緒去處理socket請求不合適,因此需要為服務端加上多執行緒功能,同時處理多個socket請求。

給服務端加上多執行緒
修改程式碼,將服務端的socket處理抽取出來,並且封裝到Runnable介面的run方法中:

package com.socket;

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.Socket;


public class TestThread extends Thread {

    private Socket socket;

    public TestThread(Socket socket){
        this.socket = socket;
    }

    public void run() {

        DataInputStream dataInputStream = null;
        DataOutputStream dataOutputStream = null;
        try {
            // 接受客戶端請求
            dataInputStream = new DataInputStream(socket.getInputStream());
            String request = dataInputStream.readUTF();
            System.out.println("from client..." + request+" 當前執行緒:"+Thread.currentThread().getName());

            // 響應客戶端
            dataOutputStream = new DataOutputStream(socket.getOutputStream());
            String response = "收到";
            dataOutputStream.writeUTF(response);

        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                if (dataInputStream != null) {
                    dataInputStream.close();
                }
                if (dataOutputStream != null) {
                    dataOutputStream.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

修改服務端,新增多執行緒功能:

package com.socket;

import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;


public class TestSocketService {

    private static final int PORT = 8888;

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

        ServerSocket server = null;
        Socket socket = null;
        server = new ServerSocket(PORT);
        System.out.println("監聽埠:" + PORT);
        while(true){
                socket = server.accept();
                new TestThread(socket).start();
        }
    }
}

弊端分析

儘管服務端現在已經有了多執行緒處理能力,但是服務端每次接收到客戶端的請求後,都會建立一個新的執行緒去處理,而jvm的執行緒數量過多是,服務端處理速度會變慢。而且如果併發較高的話,瞬間產生的執行緒數量也會比較大,因此,我們需要再給服務端加上執行緒池的功能。

使用java.util.concurrent.Executor類就可以建立一個簡單的執行緒池,程式碼如下:

package com.socket;

import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;


public class TestSocketService {

    private static final int PORT = 8888;

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

        ServerSocket server = null;
        Socket socket = null;
        server = new ServerSocket(PORT);
        System.out.println("監聽埠:" + PORT);

        //FixedThreadPool最多開啟3(引數)個執行緒,多餘的執行緒會儲存在佇列中,等執行緒處理完了
        //再從佇列中獲取執行緒繼續處理
        Executor executor = Executors.newFixedThreadPool(3);
        while(true){
                socket = server.accept();
                executor.execute(new TestThread(socket));
        }
    }
}

Executor一共有4種執行緒池實現,這裡使用了FixedThreadPool最多開啟3(引數)個執行緒,多餘的執行緒會儲存在佇列中,等執行緒處理完了再從佇列中獲取,繼續處理。這樣的話無論併發量多大,服務端只會最多3個執行緒進行同時處理,使服務端的壓力不會那麼大。