一個(gè)簡(jiǎn)單的 TCP 服務(wù)器
JDK 提供了 ServerSocket 類(lèi)來(lái)代表 TCP 服務(wù)器的被動(dòng)套接字。下面的代碼演示了一個(gè)簡(jiǎn)單的 TCP 服務(wù)器(多線程阻塞模式),它不斷偵聽(tīng)并接受客戶(hù)端的連接,然后將客戶(hù)端發(fā)送過(guò)來(lái)的文本按行讀取,全文轉(zhuǎn)換為大寫(xiě)后返回給客戶(hù)端,直到客戶(hù)端發(fā)送文本行 bye:
- public class TcpServer implements Runnable {
- private ServerSocket serverSocket;
- public TcpServer(int port) throws IOException {
- // 創(chuàng)建綁定到某個(gè)端口的 TCP 服務(wù)器被動(dòng)套接字。
- serverSocket = new ServerSocket(port);
- }
- @Override
- public void run() {
- while (true) {
- try {
- // 以阻塞的方式接受一個(gè)客戶(hù)端連接,返回代表該連接的主動(dòng)套接字。
- Socket socket = serverSocket.accept();
- // 在新線程中處理客戶(hù)端連接。
- new Thread(new ClientHandler(socket)).start();
- } catch (IOException ex) {
- ex.printStackTrace();
- }
- }
- }
- }
- public class ClientHandler implements Runnable {
- private Socket socket;
- public ClientHandler(Socket socket) {
- this.socket = Objects.requireNonNull(socket);
- }
- @Override
- public void run() {
- try (Socket s = socket) { // 減少代碼量的花招……
- // 包裝套接字的輸入流以讀取客戶(hù)端發(fā)送的文本行。
- BufferedReader in = new BufferedReader(new InputStreamReader(
- s.getInputStream(), StandardCharsets.UTF_8));
- // 包裝套接字的輸出流以向客戶(hù)端發(fā)送轉(zhuǎn)換結(jié)果。
- PrintWriter out = new PrintWriter(new OutputStreamWriter(
- s.getOutputStream(), StandardCharsets.UTF_8), true);
- String line = null;
- while ((line = in.readLine()) != null) {
- if (line.equals("bye")) {
- break;
- }
- // 將轉(zhuǎn)換結(jié)果輸出給客戶(hù)端。
- out.println(line.toUpperCase(Locale.ENGLISH));
- }
- } catch (IOException ex) {
- ex.printStackTrace();
- }
- }
- }
阻塞模式的編程方式簡(jiǎn)單,但存在性能問(wèn)題,因?yàn)榉?wù)器線程會(huì)卡死在接受客戶(hù)端的 accept() 方法上,不能有效利用資源。套接字支持非阻塞模式,現(xiàn)在暫時(shí)略過(guò)。
一個(gè)簡(jiǎn)單的 TCP 客戶(hù)端
JDK 提供了 Socket 類(lèi)來(lái)代表 TCP 客戶(hù)端的主動(dòng)套接字。下面的代碼演示了上述服務(wù)器的客戶(hù)端:
- public class TcpClient implements Runnable {
- private Socket socket;
- public TcpClient(String host, int port) throws IOException {
- // 創(chuàng)建連接到服務(wù)器的套接字。
- socket = new Socket(host, port);
- }
- @Override
- public void run() {
- try (Socket s = socket) { // 再次減少代碼量……
- // 包裝套接字的輸出流以向服務(wù)器發(fā)送文本行。
- PrintWriter out = new PrintWriter(new OutputStreamWriter(
- s.getOutputStream(), StandardCharsets.UTF_8), true);
- // 包裝套接字的輸入流以讀取服務(wù)器返回的文本行。
- BufferedReader in = new BufferedReader(new InputStreamReader(
- s.getInputStream(), StandardCharsets.UTF_8));
- Console console = System.console();
- String line = null;
- while ((line = console.readLine()) != null) {
- if (line.equals("bye")) {
- break;
- }
- // 將文本行發(fā)送給服務(wù)器。
- out.println(line);
- // 打印服務(wù)器返回的文本行。
- console.writer().println(in.readLine());
- }
- // 通知服務(wù)器關(guān)閉連接。
- out.println("bye");
- } catch (IOException ex) {
- ex.printStackTrace();
- }
- }
- }
從 JDK 文檔可以看到,ServerSocket 和 Socket 在初始化的時(shí)候,可以設(shè)定一些參數(shù),還支持延遲綁定。這些東西對(duì)性能和行為都有所影響。后續(xù)兩篇文章將分別詳解這兩個(gè)類(lèi)的初始化。