【JavaEE初阶】 TCP服务器与客户端的搭建

简介: 【JavaEE初阶】 TCP服务器与客户端的搭建


🌲前言

TCP服务器与客户端的搭建需要借助以下API

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, intport) 创建一个客户端流套接字Socket,并与对应IP的主机上,对应端口的进程建立连接

Socket 方法

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

🍀TCP中的长短连接

博主在前面的博文里面说到,TCP是面向连接的通信方式,TCP发送数据时,需要先建立连接,而这个连接又分为长短连接:

  • 短连接:每次接收到数据并返回响应后,都关闭连接,即是短连接。也就是说,短连接只能一次收发数据。
  • 长连接:不关闭连接,一直保持连接状态,双方不停的收发数据,即是长连接。也就是说,长连接可以多次收发数据

对比以上长短连接,两者区别如下:

  • 建立连接、关闭连接的耗时:短连接每次请求、响应都需要建立连接,关闭连接;而长连接只需要第一次建立连接,之后的请求、响应都可以直接传输。相对来说建立连接,关闭连接也是要耗时的,长连接效率更高。
  • 主动发送请求不同:短连接一般是客户端主动向服务端发送请求;而长连接可以是客户端主动发送请求,也可以是服务端主动发。
  • 两者的使用场景有不同:短连接适用于客户端请求频率不高的场景,如浏览网页等。长连接适用于客户端与服务端通信频繁的场景,如聊天室,实时游戏等

拓展

  • 基于BIO(同步阻塞IO)的长连接会一直占用系统资源。对于并发要求很高的服务端系统来说,这样的消耗是不能承受的。
  • 由于每个连接都需要不停的阻塞等待接收数据,所以每个连接都会在一个线程中运行。一次阻塞等待对应着一次请求、响应,不停处理也就是长连接的特性:一直不关闭连接,不停的处理请求
  • 实际应用时,服务端一般是基于NIO(即同步非阻塞IO)来实现长连接,性能可以极大的提升。

博主下面实现的TCP服务器与客户端属于长连接

🎍建立TCP回显客户端与服务器

什么叫回显客户端与与服务器呢?

其实就是:客户端向服务端发送请求,一般来说我们的服务端会对我们发送的请求进行处理,我们这里为了简单,就省略里面的处理过程,只实现将请求重新发回客户端,不做任何处理。

🚩TCP搭建服务器

我们分为以下几步来实现:

  1. 创建TcpEchoServer类来表示我们的服务器,并创建ServerSocket对象,初始值为null
  2. 在TcpEchoServer的构造方法里进行ServerSocket对象的实例化
  3. 用一个start()方法表示启动程序
  4. 在该方法内我们首先要使用accept()进行连接,并用Socket对象进行接收
  5. 我们再用一个processConnection(Socket clientSocket)方法处理我们的连接

由于我们的TCP传输是以流的形式传播的,所以我们这里用到了读写数据流的方法来进行书写,不会这一部分的小伙伴,可以去看看博主所写《【JavaEE初阶】 文件内容的读写 —— 数据流》进行查看学习

接下来我们书写这个processConnection(Socket clientSocket)方法

  1. 读取请求,构造输入流的Scanner,并判断后面如果没有数据就关闭连接
  2. 然后我们将读取的数据交给我们的 response()构造响应
  3. 响应后的数据写入该套接字的输出流中,最后flush(),进行刷新,确保写入

为了释放资源,我们每一次交互完毕都需要对我们的套接字进行关闭,这里我们使用fially来进行处理

代码实现如下:

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;
public class TcpEchoServer {
    private ServerSocket serverSocket = null;
    public TcpEchoServer(int port) throws IOException {
        serverSocket = new ServerSocket(port);
    }
    public void start() throws IOException {
        System.out.println("启动服务器");
        Socket socket = serverSocket.accept();
        processConnection(socket);
    }
    // 使用这个方法来处理一个连接.
    // 这一个连接对应到一个客户端. 但是这里可能会涉及到多次交互.
    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 = new Scanner(inputStream);
                if (!scanner.hasNext()) {
                    // 没有下个数据, 说明读完了. (客户端关闭了连接)
                    System.out.printf("[%s:%d] 客户端下线! \n", clientSocket.getInetAddress().toString(), clientSocket.getPort());
                    break;
                }
                // 注意!! 此处使用 next 是一直读取到换行符/空格/其他空白符结束, 但是最终返回结果里不包含上述 空白符 .
                String request = scanner.next();
                // 2. 根据请求构造响应
                String response = process(request);
                // 3. 返回响应结果.
                //    OutputStream 没有 write String 这样的功能. 可以把 String 里的字节数组拿出来, 进行写入;
                //    也可以用字符流来转换一下.
                PrintWriter printWriter = new PrintWriter(outputStream);
                // 此处使用 println 来写入. 让结果中带有一个 \n 换行. 方便对端来接收解析.
                printWriter.println(response);
                // flush 用来刷新缓冲区, 保证当前写入的数据, 确实是发送出去了.
                printWriter.flush();
                System.out.printf("[%s:%d] req: %s; resp: %s \n", clientSocket.getInetAddress().toString(), clientSocket.getPort(),
                        request, response);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            // 更合适的做法, 是把 close 放到 finally 里面, 保证一定能够执行到!!
            try {
              clientSocket.close();
                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搭建客户端

搭建客户端我们也可以分为以下几步:

  1. 创建TcpEchoClient类表示我们的客户端,创建Soket对象用于与客户端通信·
  2. 再TcpEchoClient构造方法里进行实例化Socket的对象
  3. 创建start()方法用于我们的操作
  4. 读取键盘所要输入的数据
  5. 将所读的数据通过输出流进行写入
  6. 读取响应的输入流,进行打印
  7. main函数中进行启动

代码实现如下:

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 {
        // Socket 构造方法, 能够识别 点分十进制格式的 IP 地址. 比 DatagramPacket 更方便.
        // new 这个对象的同时, 就会进行 TCP 连接操作.
        socket = new Socket(serverIp, serverPort);
    }
    public void start() {
        System.out.println("客户端启动!");
        Scanner scanner = new Scanner(System.in);
        try (InputStream inputStream = socket.getInputStream();
             OutputStream outputStream = socket.getOutputStream()) {
            while (true) {
                // 1. 先从键盘上读取用户输入的内容
                System.out.print("> ");
                String request = scanner.next();
                if (request.equals("exit")) {
                    System.out.println("goodbye");
                    break;
                }
                // 2. 把读到的内容构造成请求, 发送给服务器.
                PrintWriter printWriter = new PrintWriter(outputStream);
                printWriter.println(request);
                // 此处加上 flush 保证数据确实发送出去了.
                printWriter.flush();
                // 3. 读取服务器的响应
                Scanner respScanner = new Scanner(inputStream);
                String response = respScanner.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();
    }
}

客户端启动展示:

🚩通信过程展示:

🌳多个客户端对一个服务器

在博主写的【JavaEE初阶】 UDP服务器与客户端的搭建中多个客户端面对一个服务器的时候,我们只需要进行设置以下即可,但是了,在我们上述写的TCP代码中是不可行的,就算可以启动,但是后续的客户端会出现阻塞,并不会执行相应响应

这是因为这里是可连接的,当一个客户端与服务器进行建立后,后面的线程就只能等待。

就相当于你正在和你的女朋友打电话,这时候你兄弟打电话来了,你就只能让你兄弟先等着,等你和你女朋友打完电话后,才可以接你兄弟的电话

那我们的解决方法是什么呢?

这里用的解决方法是对服务器用多线程进行处理,使得服务器可以和多台客户端进行通信。由于我们在实际开发环境中客户端非常的多,而我们频繁创建销毁线程会增加开销,所以我们这里有用了一个线程池来实现

对上述服务器修改的代码如下:

public void start() throws IOException {
        System.out.println("启动服务器");
        // 此处使用 CachedThreadPool, 使用 FixedThreadPool 不太合适 (线程数不太应该是有固定的....)
        ExecutorService threadPool = Executors.newCachedThreadPool();
        while (true) {
            // 使用这个 clientSocket 和具体的客户端进行交流.
            Socket clientSocket = serverSocket.accept();
            // 此处使用多线程来处理.
            // 这里的多线程版本的程序, 最大的问题就是可能会涉及到频繁申请释放线程.
//            Thread t = new Thread(() -> {
//                processConnection(clientSocket);
//            });
//            t.start();
            // 使用线程池.
            threadPool.submit(() -> {
                processConnection(clientSocket);
            });
        }
    }

🚩拓展(IO多路复用/IO多路转接)

上述代码解决了多个客户端与一个服务器通信的问题,但是呢,在实际应用中,要是客户端太多,且客户端都不退出通信,这时候服务器被占满了,后续就无法处理个更多客户端

这时候呢,有两种解决方法:

  1. 增加服务器的数量

但是呢,这种方法虽然好,但是缺点是需要钱来购买服务器

  1. 所以我们一般采用另一种更省钱的做法,这时候操作系统就提供了一种API,也就是IO多路复用/IO多路转接

简单理解就是客户端在进行IO操作时可能有很多的空闲时间,这时候该线程处于空闲的状态,这时候就可以让该线程去做一些其他的操作,等空闲时间一过,又立即回来继续执行

这样即省钱,又可以容纳更多的客户端进行通信。这里博主只是扩展一下哎,所以就不做过多赘述了。

⭕总结

关于《【JavaEE初阶】 TCP服务器与客户端的搭建》就讲解到这儿,感谢大家的支持,欢迎各位留言交流以及批评指正,如果文章对您有帮助或者觉得作者写的还不错可以点一下关注,点赞,收藏支持一下!

相关文章
|
26天前
|
存储 开发工具 git
[Git] 深入理解 Git 的客户端与服务器角色
Git 的核心设计理念是分布式,每个仓库既可以是客户端也可以是服务器。通过 GitHub 远程仓库和本地仓库的协作,Git 实现了高效的版本管理和代码协作。GitHub 作为远程裸仓库,存储项目的完整版本历史并支持多客户端协作;本地仓库则通过 `.git` 文件夹独立管理版本历史,可在离线状态下进行提交、回滚等操作,并通过 `git pull` 和 `git push` 与远程仓库同步。这种分布式特性使得 Git 在代码协作中具备强大的灵活性和可靠性。
51 18
[Git] 深入理解 Git 的客户端与服务器角色
|
2月前
|
缓存 网络协议 Java
【JavaEE】——TCP回显服务器(万字长文超详细)
ServerSocket类,Socket类,PrintWriter缓冲区问题,Socket文件释放问题,多线程问题
|
2月前
|
网络协议 Java API
【JavaEE】——Udp翻译器的实现(回显服务器)
网络编程,DatagramSocket 和 DatagramPacket类,回显服务器,服务器实现,客户端实现,
|
2月前
|
存储 人工智能 自然语言处理
ChatMCP:基于 MCP 协议开发的 AI 聊天客户端,支持多语言和自动化安装 MCP 服务器
ChatMCP 是一款基于模型上下文协议(MCP)的 AI 聊天客户端,支持多语言和自动化安装。它能够与多种大型语言模型(LLM)如 OpenAI、Claude 和 OLLama 等进行交互,具备自动化安装 MCP 服务器、SSE 传输支持、自动选择服务器、聊天记录管理等功能。
283 15
ChatMCP:基于 MCP 协议开发的 AI 聊天客户端,支持多语言和自动化安装 MCP 服务器
|
3月前
|
开发框架 .NET C#
在 ASP.NET Core 中创建 gRPC 客户端和服务器
本文介绍了如何使用 gRPC 框架搭建一个简单的“Hello World”示例。首先创建了一个名为 GrpcDemo 的解决方案,其中包含一个 gRPC 服务端项目 GrpcServer 和一个客户端项目 GrpcClient。服务端通过定义 `greeter.proto` 文件中的服务和消息类型,实现了一个简单的问候服务 `GreeterService`。客户端则通过 gRPC 客户端库连接到服务端并调用其 `SayHello` 方法,展示了 gRPC 在 C# 中的基本使用方法。
64 5
在 ASP.NET Core 中创建 gRPC 客户端和服务器
|
3天前
|
机器学习/深度学习 人工智能 程序员
阿里云出手DeepSeek拒绝服务器繁忙,程序员直呼:真香!
阿里云PAI平台支持一键部署DeepSeek-V3和DeepSeek-R1大模型,用户无需编写代码即可完成从训练到部署的全过程。通过PAI Model Gallery,开发者可轻松选择并部署所需模型版本,享受高效、便捷的AI开发体验。教程详细介绍了开通PAI、选择模型及一键部署的具体步骤,帮助用户快速上手。
|
4天前
|
机器学习/深度学习 存储 弹性计算
阿里云gpu云服务器租用价格:最新收费标准及活动价格参考
阿里云gpu云服务器多少钱?A10卡GN7i GPU云服务器32核188G3213.99/1个月起,V100卡GN6v GPU云服务器8核32G3830.00/1个月起,阿里云GPU云服务器是基于GPU应用的计算服务,多适用于视频解码,图形渲染,深度学习,科学计算等应用场景,该产品具有超强计算能力、网络性能出色、购买方式灵活、高性能实例存储( GA1和GN5特有)等特点。下面小编来介绍下阿里云gpu云服务器最新的收费标准及活动价格。
|
4天前
|
存储 机器学习/深度学习 人工智能
2025年阿里云GPU服务器租用价格、选型策略与应用场景详解
随着AI与高性能计算需求的增长,阿里云提供了多种GPU实例,如NVIDIA V100、A10、T4等,适配不同场景。2025年重点实例中,V100实例GN6v单月3830元起,适合大规模训练;A10实例GN7i单月3213.99元起,适用于混合负载。计费模式有按量付费和包年包月,后者成本更低。针对AI训练、图形渲染及轻量级推理等场景,推荐不同配置以优化成本和性能。阿里云还提供抢占式实例、ESSD云盘等资源优化策略,支持eRDMA网络加速和倚天ARM架构,助力企业在2025年实现智能计算的效率与成本最优平衡。 (该简介为原文内容的高度概括,符合要求的字符限制。)
|
4天前
|
机器学习/深度学习 存储 人工智能
2025年阿里云GPU服务器的租赁价格与选型指南
随着AI、深度学习等领域的发展,GPU服务器成为企业及科研机构的核心算力选择。阿里云提供多种GPU实例类型(如NVIDIA V100、A100等),涵盖计算型、共享型和弹性裸金属等,满足不同场景需求。本文详解2025年阿里云GPU服务器的核心配置、价格策略及适用场景,帮助用户优化选型与成本控制,实现高效智能计算。
|
4天前
|
存储 人工智能 弹性计算
2025年阿里云企业高性能云服务器租用价格与选型详解
随着企业数字化转型,阿里云于2025年推出多款高性能云服务器实例,涵盖计算、通用和内存密集型场景。文章分析了企业选择云服务器的核心要点,包括明确业务需求(如计算密集型任务推荐计算型实例)、性能与架构升级(如第八代实例性能提升20%),以及第九代实例支持AI等高算力需求。同时提供了配置价格参考和成本优化策略,助力企业实现效率与成本的最优平衡。

热门文章

最新文章