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

开发 后端
JDK 提供了对 TCP(Transmission Control Protocol,传输控制协议)和 UDP(User Datagram Protocol,用户数据报协议)这两个数据传输协议的支持。本文开始探讨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:

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

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

一个简单的 TCP 客户端

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

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

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

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

【编辑推荐】

  1. 三个类似Sinatra的Java框架介绍
  2. Java调用C/C++编写的第三方dll动态链接库
  3. 深入理解JavaScript之强大的原型和原型链
  4. 制作完整的Java可执行文件
  5. Apache Camel 2.9.0发布 Java规则引擎
责任编辑:林师授 来源: 蜀山兆孨龘的博客
相关推荐

2014-12-11 09:20:30

TCP

2015-05-28 10:47:38

Unix网络编程TCP

2015-10-16 09:33:26

TCPIP网络协议

2014-12-17 09:22:10

网络·安全技术周刊

2015-03-31 11:24:02

2021-02-05 15:20:06

网络安全套接字命令

2014-12-15 09:28:54

UDP

2009-03-10 13:59:41

C#套接字编程

2021-03-14 18:22:23

套接字网络通信

2010-07-06 15:33:10

UDP套接字

2013-12-27 13:39:23

Java套接字

2020-10-15 19:10:05

LinuxAPI函数

2019-04-08 08:44:10

TCPIP网络协议

2021-12-12 18:15:06

Python并发编程

2015-04-24 09:48:59

TCPsocketsocket编程

2012-09-24 15:13:50

C#网络协议TCP

2019-06-04 09:00:00

Linux进程进程间通信

2019-09-18 20:07:06

AndroidTCP协议

2019-08-26 09:50:15

TCP连接Socket

2011-12-15 09:40:06

Javanio
点赞
收藏

51CTO技术栈公众号