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

Java網路程式設計之Socket

原文博主禁止轉載,不過我還是希望把一些關鍵的地方筆記下來,閱讀請移步 原文

以下是學習之後的個人筆記

一、Socket通訊基本例項

  通過伺服器-客戶端模式引入Socket通訊

在這裡插入圖片描述

伺服器端

package cn.itcast.net;

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

public class ChatServer {
    public static void main(String[] args) throws Exception {
        // 監聽指定的埠
        int port = 55533;
        ServerSocket server = new ServerSocket(port);

        // server將一直等待連線的到來
        System.out.println("server將一直等待連線的到來");
//        while (true) { // 如果伺服器是不斷的等待連線,那麼客戶端就能傳送多次
            Socket socket = server.accept();
            // 建立好連線後,從socket中獲取輸入流
            InputStream inputStream = socket.getInputStream();
            byte[] bytes = new byte[1024];
            int len;
            StringBuilder sb = new StringBuilder();
            while ((len = inputStream.read(bytes)) != -1) {
                //注意指定編碼格式,傳送方和接收方一定要統一,建議使用UTF-8
                sb.append(new String(bytes, 0, len,"UTF-8"));
            }
            System.out.println("get message from client: " + sb);
            inputStream.close();
            socket.close();
//        }
        server.close();
    }
}

客戶端

127.0.0.1不理解的看我部落格吧~這裡

package cn.itcast.net;

import java.io.OutputStream;
import java.net.Socket;

public class Client {
    public static void main(String args[]) throws Exception {
        // 要連線的服務端IP地址和埠
        String host = "127.0.1.1";
        int port = 55533;
        // 與服務端建立連線
        Socket socket = new Socket(host, port);
        // 建立連線後獲得輸出流
       // OutputStream outputStream = socket.getOutputStream();
       //不理解這句存在的必要,希望路過的大神指出
        String message="你好  yiwangzhibujian?";
        socket.getOutputStream().write(message.getBytes("UTF-8"));
        //outputStream.close();
        socket.close();
    }
}

輸出

server將一直等待連線的到來
get message from client: 你好  yiwangzhibujian

二、訊息通訊優化

2.1 雙向通訊,傳送訊息並接收訊息

伺服器端

  和上一個的區別是,在接收完資料的後,也能傳送資料給客戶端。也就是說可收可發

package cn.itcast.net;

import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;

public class ChatServer {
    public static void main(String[] args) throws Exception {
        // 監聽指定的埠
        int port = 55533;
        ServerSocket server = new ServerSocket(port);

        // server將一直等待連線的到來
        System.out.println("server將一直等待連線的到來");
        Socket socket = server.accept();
        // 建立好連線後,從socket中獲取輸入流,並建立緩衝區進行讀取
        InputStream inputStream = socket.getInputStream();
        byte[] bytes = new byte[1024];
        int len;
        StringBuilder sb = new StringBuilder();
        //只有當客戶端關閉它的輸出流的時候,服務端才能取得結尾的-1
        while ((len = inputStream.read(bytes)) != -1) {
            // 注意指定編碼格式,傳送方和接收方一定要統一,建議使用UTF-8
            sb.append(new String(bytes, 0, len, "UTF-8"));
        }
        System.out.println("get message from client: " + sb);

        OutputStream outputStream = socket.getOutputStream();
        outputStream.write("Hello Client,I get the message.".getBytes("UTF-8"));

        inputStream.close();
        outputStream.close();
        socket.close();
        server.close();
    }
}

客戶端

在客戶端也添加了接收來自伺服器端訊息的功能

package cn.itcast.net;

import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;

public class Client {
    public static void main(String args[]) throws Exception {
        // 要連線的服務端IP地址和埠
        String host = "127.0.0.1";
        int port = 55533;
        // 與服務端建立連線
        Socket socket = new Socket(host, port);
        // 建立連線後獲得輸出流
        OutputStream outputStream = socket.getOutputStream();
        String message = "你好  yiwangzhibujian";
        socket.getOutputStream().write(message.getBytes("UTF-8"));
        //通過shutdownOutput高速伺服器已經發送完資料,後續只能接受資料
        socket.shutdownOutput();

        InputStream inputStream = socket.getInputStream();
        byte[] bytes = new byte[1024];
        int len;
        StringBuilder sb = new StringBuilder();
        while ((len = inputStream.read(bytes)) != -1) {
            //注意指定編碼格式,傳送方和接收方一定要統一,建議使用UTF-8
            sb.append(new String(bytes, 0, len,"UTF-8"));
        }
        System.out.println("get message from server: " + sb);

        inputStream.close();
        outputStream.close();
        socket.close();
    }
}

2.2 雙向通訊的必要性

  客戶端開啟一個輸出流,如果不做約定,也不關閉它,那麼服務端永遠不知道客戶端是否傳送完訊息,那麼服務端會一直等待下去,直到讀取超時。所以怎麼告知服務端已經發送完訊息就顯得特別重要。

2.2.1 指定傳送規則

  文章中推薦一種方法規定傳送方式,這種方法是:先指明你接下去要傳送的訊息的長度,然後傳送這麼長的訊息。具體的看原文部落格,現在使用簡易版本。用前兩個位元組表示長度。

伺服器端:
package cn.itcast.net;

import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;

public class ChatServer {
    public static void main(String[] args) throws Exception {
        // 監聽指定的埠
        int port = 55533;
        ServerSocket server = new ServerSocket(port);

        // server將一直等待連線的到來
        System.out.println("server將一直等待連線的到來");
        Socket socket = server.accept();
        // 建立好連線後,從socket中獲取輸入流,並建立緩衝區進行讀取
        InputStream inputStream = socket.getInputStream();
        byte[] bytes;

        while (true) {
            int first = inputStream.read();

            if (first == -1) {
                break;
            }
            int second = inputStream.read();
            int len = (first << 8) + second;

            bytes = new byte[len];
            inputStream.read(bytes);
            System.out.println("get message from client: " + new String(bytes, "UTF-8"));
        }

        //反饋訊息給客戶端,也適用本方法,這裡就先不演示了
        OutputStream outputStream = socket.getOutputStream();
        outputStream.write("Hello Client,I get the message.".getBytes("UTF-8"));

        inputStream.close();
        outputStream.close();
        socket.close();
        server.close();
    }
}

客戶端:
package cn.itcast.net;

import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;

public class Client {
    public static void main(String args[]) throws Exception {
        // 要連線的服務端IP地址和埠
        String host = "127.0.0.1";
        int port = 55533;
        // 與服務端建立連線
        Socket socket = new Socket(host, port);
        // 建立連線後獲得輸出流
        OutputStream outputStream = socket.getOutputStream();
        String message = "你好  yiwangzhibujian";
        //計算訊息的長度,轉換成位元組陣列
        byte[] sendBytes = message.getBytes("UTF-8");
        //實現傳送訊息的長度,兩個位元組
        // 1、先發送長度的高8位
        outputStream.write(sendBytes.length >> 8);
        // 2、傳送長度的低8位
        outputStream.write(sendBytes.length);
        // 這才傳送訊息
        outputStream.write(sendBytes);
        outputStream.flush();


        //==========此處重複傳送一次,實際專案中為多個命名,此處只為展示用法
        message = "第二條訊息";
        sendBytes = message.getBytes("UTF-8");
        outputStream.write(sendBytes.length >>8);
        outputStream.write(sendBytes.length);
        outputStream.write(sendBytes);
        outputStream.flush();
        //==========此處重複傳送一次,實際專案中為多個命名,此處只為展示用法
        message = "the third message!";
        sendBytes = message.getBytes("UTF-8");
        outputStream.write(sendBytes.length >>8);
        outputStream.write(sendBytes.length);
        outputStream.write(sendBytes);

        //通過shutdownOutput高速伺服器已經發送完資料,後續只能接受資料,也可使用這種方法,這裡先不演示了
        socket.shutdownOutput();

        InputStream inputStream = socket.getInputStream();
        byte[] bytes = new byte[1024];
        int len;
        StringBuilder sb = new StringBuilder();
        while ((len = inputStream.read(bytes)) != -1) {
            //注意指定編碼格式,傳送方和接收方一定要統一,建議使用UTF-8
            sb.append(new String(bytes, 0, len,"UTF-8"));
        }
        System.out.println("get message from server: " + sb);

        inputStream.close();
        outputStream.close();
        socket.close();
    }
}

三、伺服器併發處理

  通常伺服器會處理多個Scoket請求,而不是像上面的接收一個Scoket連線後,就關閉伺服器,因此模板大概是這樣。

package cn.itcast.net;

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

public class ChatServer {
    public static void main(String args[]) throws IOException {
        // 監聽指定的埠
        int port = 55533;
        ServerSocket server = new ServerSocket(port);
        // server將一直等待連線的到來
        System.out.println("server將一直等待連線的到來");

        while(true){
            Socket socket = server.accept();
            // 建立好連線後,從socket中獲取輸入流,並建立緩衝區進行讀取
            InputStream inputStream = socket.getInputStream();
            byte[] bytes = new byte[1024];
            int len;
            StringBuilder sb = new StringBuilder();
            while ((len = inputStream.read(bytes)) != -1) {
                // 注意指定編碼格式,傳送方和接收方一定要統一,建議使用UTF-8
                sb.append(new String(bytes, 0, len, "UTF-8"));
            }
            System.out.println("get message from client: " + sb);
            inputStream.close();
            socket.close();
        }

    }
}

  這種一般也是新手寫法,但是能夠迴圈處理多個Socket請求,不過當一個請求的處理比較耗時的時候,後面的請求將被阻塞,所以一般都是用多執行緒的方式來處理Socket,即每有一個Socket請求的時候,就建立一個執行緒來處理它。

  不過在實際生產中,建立的執行緒會交給執行緒池來處理,為了:

  • 執行緒複用,建立執行緒耗時,回收執行緒慢
  • 防止短時間內高併發,指定執行緒池大小,超過數量將等待,方式短時間建立大量執行緒導致資源耗盡,服務掛掉
執行緒池開啟執行緒處理
package cn.itcast.net;

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

public class ChatServer {
    public static void main(String args[]) throws IOException {
        // 監聽指定的埠
        int port = 55533;
        ServerSocket server = new ServerSocket(port);
        // server將一直等待連線的到來
        System.out.println("server將一直等待連線的到來");

        //如果使用多執行緒,那就需要執行緒池,防止併發過高時建立過多執行緒耗盡資源
        ExecutorService threadPool = Executors.newFixedThreadPool(100);
        while (true) {
            Socket socket = server.accept();

            Runnable runnable = () -> {
                // 建立好連線後,從socket中獲取輸入流,並建立緩衝區進行讀取
                InputStream inputStream = null;
                try {
                    System.out.println("執行緒開啟");
                    inputStream = socket.getInputStream();
                    byte[] bytes = new byte[1024];
                    int len;
                    StringBuilder sb = new StringBuilder();
                    while ((len = inputStream.read(bytes)) != -1) {
                        // 注意指定編碼格式,傳送方和接收方一定要統一,建議使用UTF-8
                        sb.append(new String(bytes, 0, len, "UTF-8"));
                    }
                    System.out.println("get message from client: " + sb);
                    inputStream.close();
                    socket.close();

                } catch (IOException e) {
                    e.printStackTrace();
                }
            };
            threadPool.submit(runnable);
        }
    }
}
lambda被我替換成下面這段,應該也是可以的 lambda學習連結
 threadPool.submit(new Runnable() {
                @Override
                public void run() {
                    InputStream inputStream = null;
                    try {
                        System.out.println("執行緒開啟");
                        inputStream = socket.getInputStream();
                        byte[] bytes = new byte[1024];
                        int len;
                        StringBuilder sb = new StringBuilder();
                        while ((len = inputStream.read(bytes)) != -1) {
                            // 注意指定編碼格式,傳送方和接收方一定要統一,建議使用UTF-8
                            sb.append(new String(bytes, 0, len, "UTF-8"));
                        }
                        System.out.println("get message from client: " + sb);
                        inputStream.close();
                        socket.close();

                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            });

學到這,就能解決安卓課程設計的網路程式設計啦!!