TCP Socket API 讲解,以及回显服务器客户端的实现

news/2024/5/17 15:01:02 标签: 服务器, 网络, tcp

文章目录

    • TCP
      • ServerSocket API
      • Socket API
    • TCP 客户端服务器的实现

TCP

ServerSocket API

ServerSocket 是创建TCP服务端 Socket 的 API。

serverSocket构造方法:

方法签名方法说明
ServerSocket(int port)创建一个服务端流套接字Socket,并绑定到指定端口

serverSocket方法:

方法签名方法说明
Socket accept()开始监听指定端口(创建时绑定的端口),有客户端连接后,返回一个服务端Socket对象,并基于该Socket建立与客户端的连接,否则阻塞等待
void close()关闭此套接字

Socket API

Socket 是客户端Socket,或服务端中接收到客户端建立连接(accept方法)的请求后,返回的服务端 Socket。

不管是客户端还是服务端Socket,都是双方建立连接以后,保存的对端信息,及用来与对方收发数据的。

Socket 构造方法:

方法签名方法说明
Socket (String host, int port)创建一个客户端流套接字Socket,并与对应IP的主机上,对应端口的进程建立连接

Socket 方法:

方法签名方法说明
InetAddress getInetAddress()返回套接字所连接的地址
InputStream getInputStream()返回此套接字的输入流
OutputStream getOutputStream()返回此套接字的输出流

TCP 客户端服务器的实现

因为 TCP 是面向连接的,所以TCP 服务器是需要多线程的,每个客户端连接都需要一个独立的线程来处理数据的收发和状态的维护,否则一个客户端的阻塞会影响其他客户端的正常通信。因此,TCP 服务器需要多线程来支持多个客户端的连接和通信

核心思路

  1. 服务器

    • 首先给给这个服务器绑定一个端口号。(port:9090)

    • 启动服务器,利用 accept 方法建立与客户端的连接。

    • 建立线程池,后续操作在线程池中执行。

    • 接收客户端发送过来的请求。

    • 根据请求计算响应并发回,注意要添加一条“刷新缓冲区 (flush)”的操作。(回显服务器,响应值就是请求值)

    • 最后打印日志。

  2. 客户端:

    • 完成连接 TCP 服务器的操作。(ip: 127.0.0.1 , port: 9090)
    • 启动客户端,从控制台输入请求。
    • 把请求发送给服务器,同时还要加上刷新缓冲区 (flush) 的操作。
    • 最后读取服务器响应,并打印响应结果。
  3. 注意:

    因为 TCP 是字节流,所以利用 InputStream【Scanner】 和 OutputStream【PrintWriter】来读取或发送数据,由于InputStream 和 OutputStream 生命周期短,因此要及时 close(),这里我们用到 try-with-resources。

Java 打印流 PrintWriter 的介绍:PrintWriter 是 Java 中的一个字符类型的打印输出流,PrintWriter 可以将数据写入文件或其他输出流中,下面代码中用于 服务器向客户端发送数据/客户端向服务器发送数据

TCP 服务器的实现:

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


public class TcpEchoServer {
    private ServerSocket serverSocket = null;
    // 此处不应该创建固定线程数目的线程池.
    private ExecutorService service = Executors.newCachedThreadPool();

    // 这个操作就会绑定端口号
    public TcpEchoServer(int port) throws IOException {
        serverSocket = new ServerSocket(port);
    }

    // 启动服务器
    public void start() throws IOException {
        System.out.println("服务器启动!");
        while (true) {
            // 这个写法, 是能自动关闭, 也行. 实现 Closeable 接口就可以这么写.
            // 这么写会有其他问题. (结合后面讲第二个问题, 再说这个事)
            Socket clientSocket = serverSocket.accept();

            // 使用线程池, 来解决上述问题
            service.submit(new Runnable() {
                @Override
                public void run() {
                    processConnection(clientSocket);
                }
            });
        }
    }

    // 通过这个方法来处理一个连接的逻辑.
    private void processConnection(Socket clientSocket) {
        System.out.printf("[%s:%d] 客户端上线!\n", clientSocket.getInetAddress().toString(), clientSocket.getPort());
        // 接下来就可以读取请求, 根据请求计算响应, 返回响应三步走了.
        // Socket 对象内部包含了两个字节流对象, 可以把这俩字节流对象获取到, 完成后续的读写工作
        try (InputStream inputStream = clientSocket.getInputStream();
             OutputStream outputStream = clientSocket.getOutputStream()) {
            // 一次连接中, 可能会涉及到多次请求/响应
            while (true) {
                // 1. 读取请求并解析. 为了读取方便, 直接使用 Scanner.
                Scanner scanner = new Scanner(inputStream);
                if (!scanner.hasNext()) {
                    // 读取完毕, 客户端下线.
                    System.out.printf("[%s:%d] 客户端下线!\n", clientSocket.getInetAddress().toString(), clientSocket.getPort());
                    break;
                }
                // 这个代码暗含一个约定, 客户端发过来的请求, 得是文本数据, 同时, 还得带有空白符作为分割. (比如换行这种)
                String request = scanner.next();
                // 2. 根据请求计算响应
                String response = process(request);
                // 3. 把响应写回给客户端. 把 OutputStream 使用 PrinterWriter 包裹一下, 方便进行发数据.
                PrintWriter writer = new PrintWriter(outputStream);
                //    使用 PrintWriter 的 println 方法, 把响应返回给客户端.
                //    此处用 println, 而不是 print 就是为了在结尾加上 \n . 方便客户端读取响应, 使用 scanner.next 读取.
                writer.println(response);
                //    这里还需要加一个 "刷新缓冲区" 操作.
                writer.flush();

                // 日志, 打印当前的请求详情.
                System.out.printf("[%s:%d] req: %s, resp: %s\n", clientSocket.getInetAddress().toString(), clientSocket.getPort(), request, response);
                
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            // 在 finally 中加上 close 操作, 确保当前 socket 被及时关闭!!
            try {
                clientSocket.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    public String process(String request) {
        return request;
    }

    public static void main(String[] args) throws IOException {
        TcpEchoServer server = new TcpEchoServer(9090);
        server.start();
    }
}

TCP 客户端代码:

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.Socket;
import java.util.Scanner;

public class TcpEchoClient {
    private Socket socket = null;

    // 要和服务器通信, 就需要先知道, 服务器所在的位置.
    public TcpEchoClient(String serverIp, int serverPort) throws IOException {
        // 这个 new 操作完成之后, 就完成了 tcp 连接的建立.
        socket = new Socket(serverIp, serverPort);
    }

    public void start() {
        System.out.println("客户端启动");

        Scanner scannerConsole = new Scanner(System.in);

        try (InputStream inputStream = socket.getInputStream();
             OutputStream outputStream = socket.getOutputStream()) {
            while (true) {
                // 1. 从控制台输入字符串.
                System.out.print("-> ");
                String request = scannerConsole.next();
                // 2. 把请求发送给服务器
                PrintWriter printWriter = new PrintWriter(outputStream);
                //    使用 println 带上换行. 后续服务器读取请求, 就可以使用 scanner.next 来获取了
                printWriter.println(request);
                //    不要忘记 flush, 确保数据是真的发送出去了!!
                printWriter.flush();
                // 3. 从服务器读取响应.
                Scanner scannerNetwork = new Scanner(inputStream);
                String response = scannerNetwork.next();
                // 4. 把响应打印出来
                System.out.println(response);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) throws IOException {
        TcpEchoClient client = new TcpEchoClient("127.0.0.1", 9090);
        client.start();
    }
}

http://www.niftyadmin.cn/n/5229095.html

相关文章

Clickhouse Join

ClickHouse中的Hash Join, Parallel Hash Join, Grace Hash Join https://www.cnblogs.com/abclife/p/17579883.html https://clickhouse.com/blog/clickhouse-fully-supports-joins-full-sort-partial-merge-part3 总结 本文描述并比较了ClickHouse中基于内存哈希表的3种连接…

使用Python脚本实现图片合成PDF功能

目录 一、所需库 二、图片合成PDF的实现过程 三、完整的代码示例 四、注意事项 总结 在Python中,我们可以使用一些强大的库来实现图片合成PDF的功能。这个过程主要包括读取图片文件、将图片按照指定的顺序合并以及生成PDF文件。下面,让我们一起探索…

纯C读取文件实现解析H264裸流每一帧数据

最近研究硬解解码,需要解码H264流文件。 imx的例程是设置一个大的缓冲区,一次性读入不超过10M字节数据,然后全部送到解码器进行解码,对于小于10M字节的视频数据确实很方便的解码了,但是文件大了这种方式就不行了。 如果…

Reactor网络线程模型

目录 传统下网络服务模型 事件监听模型 NIO核心概念 单线程Reactor模式 多线程Reactor模式 Kafka 的网络设计 主要概念 类比思维理解 参考文章 传统下网络服务模型 线程太多无法处理大规模请求 事件监听模型 NIO核心概念 nio是实现reactor模式的底层API代码 单…

Wireshark之Intro, HTTP, DNS

源码地址👇 moranzcw/Computer-Networking-A-Top-Down-Approach-NOTES: 《计算机网络-自顶向下方法(原书第6版)》编程作业,Wireshark实验文档的翻译和解答。 (github.com) 目录 🌼Introduce 🎧前置 🎧过…

面试题:什么是负载均衡?常见的负载均衡策略有哪些?

文章目录 一、负载均衡二、负载均衡模型分类三、CDN负载均衡四、LVS负载均衡4.1 LVS 支持的三种模式4.1.1 DR 模式4.1.2 TUN 模式4.1.3 NAT 模式 4.2 LVS 基于 Netfilter 的框架实现 五、负载均衡策略是什么六、常用负载均衡策略图解6.1 轮询6.2 加权轮询6.3 最少连接数6.4 最快…

ThermalLabel SDK for .NET 13.0.23.1113 Crack

ThermalLabel SDK for .NET 是一个 .NET 典型类库,它允许用户和开发人员创建非常创新的条码标签并将其发布在 zebra ZPL、EPL、EPSON ESC、POS 以及 Honeywell intermec 指纹中通过在 VB.NET 或 C# 上编写 .NET 纯代码来实现热敏打印机,以实现项目框架的…

借助文档控件Aspose.Words,在 C# 中比较两个 PDF 文件

在当今的数字世界中,管理和比较文档是一项至关重要的任务,尤其是在商业和法律领域。在 C# 中处理 PDF 文档时,Aspose.Words for .NET 提供了用于比较 PDF 文档的强大解决方案。在这篇博文中,我们将探讨如何在 C# 应用程序中比较 P…