• 【Java】Java网络编程菜鸟进阶:TCP和套接字入门


    Java网络编程菜鸟进阶:TCP和套接字入门

    JDK 提供了对 TCP(Transmission Control Protocol,传输控制协议)和 UDP(User Datagram Protocol,用户数据报协议)这两个数据传输协议的支持。本文开始探讨 TCP。

    TCP 基础知识

    在“服务器-客户端”这种架构中,服务器和客户端各自维护一个端点,两个端点需要通过网络进行数据交换。TCP 为这种需求提供了一种可靠的流式连接,流式的意思是传出和收到的数据都是连续的字节,没有对数据量进行大小限制。一个端点由 IP 地址和端口构成(专业术语为“元组 {IP 地址, 端口}”)。这样,一个连接就可以由元组 {本地地址, 本地端口, 远程地址, 远程端口} 来表示。

    连接过程

    在 TCP 编程接口中,端点体现为 TCP 套接字。共有两种 TCP 套接字:主动和被动,“被动”状态也常被称为“侦听”状态。服务器和客户端利用套接字进行连接的过程如下:

    1、服务器创建一个被动套接字,开始循环侦听客户端的连接。

    2、客户端创建一个主动套接字,连接服务器。

    3、服务器接受客户端的连接,并创建一个代表该连接的主动套接字。

    4、服务器和客户端通过步骤 2 和 3 中创建的两个主动套接字进行数据传输。

    下面是连接过程的图解:

    TCP 连接

    一个简单的 TCP 服务器

    JDK 提供了 ServerSocket 类来代表 TCP 服务器的被动套接字。下面的代码演示了一个简单的 TCP 服务器(多线程阻塞模式),它不断侦听并接受客户端的连接,然后将客户端发送过来的文本按行读取,全文转换为大写后返回给客户端,直到客户端发送文本行 bye:

    1. public class TcpServer implements Runnable {   
    2.     private ServerSocket serverSocket;   
    3.     
    4.     public TcpServer(int port) throws IOException {   
    5.         // 创建绑定到某个端口的 TCP 服务器被动套接字。   
    6.         serverSocket = new ServerSocket(port);   
    7.     }   
    8.     
    9.     @Override 
    10.     public void run() {   
    11.         while (true) {   
    12.             try {   
    13.                 // 以阻塞的方式接受一个客户端连接,返回代表该连接的主动套接字。   
    14.                 Socket socket = serverSocket.accept();   
    15.                 // 在新线程中处理客户端连接。   
    16.                 new Thread(new ClientHandler(socket)).start();   
    17.             } catch (IOException ex) {   
    18.                 ex.printStackTrace();   
    19.             }   
    20.         }   
    21.     }   
    22. }   
    23.     
    24. public class ClientHandler implements Runnable {   
    25.     private Socket socket;   
    26.     
    27.     public ClientHandler(Socket socket) {   
    28.         this.socket = Objects.requireNonNull(socket);   
    29.     }   
    30.     
    31.     @Override 
    32.     public void run() {   
    33.         try (Socket s = socket) {  // 减少代码量的花招……   
    34.             // 包装套接字的输入流以读取客户端发送的文本行。   
    35.             BufferedReader in = new BufferedReader(new InputStreamReader(   
    36.                     s.getInputStream(), StandardCharsets.UTF_8));   
    37.             // 包装套接字的输出流以向客户端发送转换结果。   
    38.             PrintWriter out = new PrintWriter(new OutputStreamWriter(   
    39.                     s.getOutputStream(), StandardCharsets.UTF_8), true);   
    40.     
    41.             String line = null;   
    42.             while ((line = in.readLine()) != null) {   
    43.                 if (line.equals("bye")) {   
    44.                     break;   
    45.                 }   
    46.     
    47.                 // 将转换结果输出给客户端。   
    48.                 out.println(line.toUpperCase(Locale.ENGLISH));   
    49.             }   
    50.         } catch (IOException ex) {   
    51.             ex.printStackTrace();   
    52.         }   
    53.     }   
    54. }  

    阻塞模式的编程方式简单,但存在性能问题,因为服务器线程会卡死在接受客户端的 accept() 方法上,不能有效利用资源。套接字支持非阻塞模式,现在暂时略过。

    一个简单的 TCP 客户端

    JDK 提供了 Socket 类来代表 TCP 客户端的主动套接字。下面的代码演示了上述服务器的客户端:

    1. public class TcpClient implements Runnable {   
    2.     private Socket socket;   
    3.     
    4.     public TcpClient(String host, int port) throws IOException {   
    5.         // 创建连接到服务器的套接字。   
    6.         socket = new Socket(host, port);   
    7.     }   
    8.     
    9.     @Override 
    10.     public void run() {   
    11.         try (Socket s = socket) {  // 再次减少代码量……   
    12.             // 包装套接字的输出流以向服务器发送文本行。   
    13.             PrintWriter out = new PrintWriter(new OutputStreamWriter(   
    14.                     s.getOutputStream(), StandardCharsets.UTF_8), true);   
    15.             // 包装套接字的输入流以读取服务器返回的文本行。   
    16.             BufferedReader in = new BufferedReader(new InputStreamReader(   
    17.                     s.getInputStream(), StandardCharsets.UTF_8));   
    18.     
    19.             Console console = System.console();   
    20.             String line = null;   
    21.             while ((line = console.readLine()) != null) {   
    22.                 if (line.equals("bye")) {   
    23.                     break;   
    24.                 }   
    25.     
    26.                 // 将文本行发送给服务器。   
    27.                 out.println(line);   
    28.                 // 打印服务器返回的文本行。   
    29.                 console.writer().println(in.readLine());   
    30.             }   
    31.     
    32.             // 通知服务器关闭连接。   
    33.             out.println("bye");   
    34.         } catch (IOException ex) {   
    35.             ex.printStackTrace();   
    36.         }   
    37.     }   
    38. }  

    从 JDK 文档可以看到,ServerSocket 和 Socket 在初始化的时候,可以设定一些参数,还支持延迟绑定。这些东西对性能和行为都有所影响。后续两篇文章将分别详解这两个类的初始化。

    原文链接:http://www.blogjava.net/shinzey/archive/2012/01/04/367846.html

  • 相关阅读:
    PostgreSQL远端访问
    PostgreSQL在线安装
    /usr/lib64改名字风波
    Provisional headers are shown(一)
    解析URL中的携带的参数到Map
    Mysql5.7的初始密码更改
    REST开放接口生成文档工具之apidoc
    自己来实现一套IOC注解框架
    RecyclerView打造通用的万能Adapter
    RecyclerView分隔线定制
  • 原文地址:https://www.cnblogs.com/daishuguang/p/3923611.html
Copyright © 2020-2023  润新知