网络编程 04:TCP连接,客户端与服务器的区别,实现 TCP 聊天及文件上传,Tomcat 的简单使用

发布于:2025-08-30 ⋅ 阅读:(13) ⋅ 点赞:(0)

一、概述

记录时间 [2025-08-29]

前置文章

网络编程 01:计算机网络概述,网络的作用,网络通信的要素,以及网络通信协议与分层模型

网络编程 02:IP 地址,IP 地址的作用、分类,通过 Java 实现 IP 地址的信息获取

网络编程 03:端口的定义、分类,端口映射,通过 Java 实现了 IP 和端口的信息获取

本文讲述网络编程相关知识——TCP连接,包括客户端与服务器的区别,如何实现 TCP 聊天及文件上传等。

同时,文章简单介绍了 Tomcat(服务器)的相关使用。


关于创作纪念日

维持现状。512 纪念日快乐。

里程碑专区



二、TCP

1. TCP 聊天

思路整理

客户端和服务器之间如何进行通信——创建连接 + 收发消息。

客户端

  • 连接服务器 Socket
  • 发送消息

服务器

  • 建立服务的端口 ServerSocket
  • 等待用户的连接 accept
  • 接收用户消息

客户端和服务器之间收发消息通过 I/O 流来实现。

  • 发送消息,socket.getOutputStream()
  • 接收消息,socket.getInputStream()

为防止接收消息乱码,接收方需要使用管道流来处理接收到的消息。

  • new ByteArrayOutputStream()

所有资源在使用后都需要正确关闭,如,socket,serverSocket 等。

  • 关闭资源的顺序为:先开后关
  • 关闭资源前要先判断它是否为空,非空则关闭;
  • 关闭资源操作需要抛出异常。

服务器代码实现

服务器先启动,处在监听过程中,等待客户端的连接。

服务器有一个地址(IP + Port),通过这个地址,客户端才能和服务器连接。

通过 serverSocket.accept() 获取连接过来的客户端,就是客户端的 socket

然后读取客户端的信息。

经过管道处理后,输出信息。

结束后关闭资源。


import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.ServerSocket;
import java.net.Socket;

// 服务端
public class TcpServerDemo01 {
    public static void main(String[] args) {

        // 服务端地址
        ServerSocket serverSocket = null;
        // 连接过来的客户端
        Socket socket = null;
        // 输入流
        InputStream is = null;
        // 管道流
        ByteArrayOutputStream baos = null;

        try {
            // 1. 我得有一个地址
            serverSocket = new ServerSocket(9999);

            // 循环等待客户端连接过来
            while (true) {
                // 2. 等待客户端连接过来
                socket = serverSocket.accept();

                // 3. 读取客户端的消息
                // 消息从客户端流出 Out,流进服务器 Input
                is = socket.getInputStream();

                // 管道流
                // 给流进来的消息套一个管道,得到从管道中流出来的消息,所有用 Output
                baos = new ByteArrayOutputStream();
                byte[] buffer = new byte[1024];
                int len;
                while ((len = is.read(buffer)) != -1) {
                    baos.write(buffer, 0, len);
                }
                System.out.println(baos.toString());
            }

        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            // 4. 关闭资源, 判非空, 然后先开后关
            // null 了就没必要关了
            if (baos != null) {
                try {
                    baos.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }

            if (is != null) {
                try {
                    is.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }

            if (socket != null) {
                try {
                    socket.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }

            if (serverSocket != null) {
                try {
                    serverSocket.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }

    }
}

客户端代码实现

客户端通过服务器的地址(IP + Port)连接上服务器,连接的方式是 socket

客户端给服务器发消息。

结束后关闭资源。


import java.io.IOException;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.Socket;

// 客户端
public class TcpClientDemo01 {
    public static void main(String[] args) {

        // 客户端连接
        Socket socket = null;
        // 输出流
        OutputStream os =  null;

        try {
            // 1. 要知道服务器的地址, 端口号
            InetAddress serverIP = InetAddress.getByName("127.0.0.1");
            int port = 9999;

            // 2. 创建一个 socket 连接
            socket = new Socket(serverIP, port);

            // 3. 发送 IO 消息流
            os = socket.getOutputStream();
            os.write("你好,欢迎".getBytes());


        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            // 4. 关闭资源, 判非空, 然后先开后关
            if (os != null) {
                try {
                    os.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }

            if (socket != null) {
                try {
                    socket.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }

        }

    }
}

2. TCP 文件上传

文件流,流的概念

客户端给服务端传文件:

  • 文件通过文件管道流出:客户端中,先流入(In)文件管道,再流出(Out)去到服务端。
  • 文件流入(In)服务端,流入要读(read);服务端用文件管道流出(Out),流出要写(write), 就是保存。

就相当于客户端流出,到服务端流入,然后它们自己可以套管道,管道一头流入,另一头流出。


流入要读(read), 流出要写(write)

while ((len = fis.read(buffer)) != -1) {
	os.write(buffer, 0, len);
}

工作目录

当我们要读取一个文件时,得先知道该文件的位置,也就是文件路径。

new File("file"):获取项目文件的方法,需要传入的参数是文件路径,如果是相对路径的话,起始路径为当前 Java 项目的工作目录。

在项目中有一个图片资源,如何获取这个图片的相对路径呢?需要通过 Java 项目的工作目录。


获取当前 Java 项目的工作目录的方式:

public class TestPath {
    public static void main(String[] args) {
        String currentDir = System.getProperty("user.dir");
        System.out.println("当前工作目录: " + currentDir);
    }
}

例如:

当前工作目录为:C:\JavaSE

NetStudyJavaSE 项目中的一个模块,图片资源 tx.jpg 位于 JavaSE/NetStudy 目录下;

那么 tx.jpg 的获取方式是:new File("NetStudy/tx.jpg")


服务器代码实现

服务器监听、等待客户端的连接。

接收客户端发送过来的文件,通过文件管道流处理后,保存文件。(这里的文件是一张图片)

通知客户端,文件接收完成。

结束后关闭资源。


import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;

// 服务端
public class TcpServerDemo02 {
    public static void main(String[] args) throws Exception {
        // 1. 创建服务, 给出连接的端口
        ServerSocket serverSocket = new ServerSocket(9000);

        // 2. 监听客户端的连接
        // 一直等待, 会阻塞直到有客户端连接
        // 这个 socket 就是客户端的 socket
        Socket socket = serverSocket.accept();

        // 3. 获取输入流
        // 创建一个输入流,用来输入客户端的流
        InputStream is = socket.getInputStream();

        // 4. 文件输出, 接收客户端的文件
        // 用文件管道流写出文件, 给出文件保存到位置和文件名
        FileOutputStream fos = new FileOutputStream(new File("NetStudy/receive.jpg"));

        byte[] buffer = new byte[1024];
        int len;
        while ((len = is.read(buffer)) != -1) {
            fos.write(buffer, 0, len);
        }

        // 5. 通知客户端, 文件接收完成
        // 创建一个输出流, 用来输出给客户端
        OutputStream os = socket.getOutputStream();
        // 传信息给客户端
        os.write("文件已被服务端接收完成".getBytes());

        // 6. 关闭资源
        os.close();
        fos.close();
        is.close();
        socket.close();
        serverSocket.close();

    }
}

客户端代码实现

文件先通过文件管道流读入项目里,然后才能通过 socket 发送。

建立 socket 连接后,向服务器发送文件。

文件发送完毕后,结束输出流,并告知服务器。(因为服务器和客户端用的是同一个 socket,如果文件发送完不结束流的话,会影响后面的消息发送和接收)

接收服务器发送的 “完成信号”。

结束后关闭资源。


import java.io.*;
import java.net.InetAddress;
import java.net.Socket;

// 客户端
public class TcpClientDemo02 {
    public static void main(String[] args) throws Exception {
        // 1. 建立服务端连接,ip+port
        Socket socket = new Socket(InetAddress.getByName("127.0.0.1"), 9000);

        // 2. 创建一个输出流
        // 用来输出给服务端
        OutputStream os = socket.getOutputStream();

        // 3. 传文件给服务端
        // 读取文件: 文件输入流, 先把文件输入管道,管道才能读出来
        // System.getProperty("user.dir"); 获取项目的工作目录
        // 获取 tx.jpg 的相对位置
        FileInputStream fis = new FileInputStream(new File("NetStudy/tx.jpg"));

        // 读出文件管道流中的文件, 并向服务端写出文件
        byte[] buffer = new byte[1024];
        int len;
        while ((len = fis.read(buffer)) != -1) {
            os.write(buffer, 0, len);
        }

        // 4. 通知服务端文件传输完毕
        socket.shutdownOutput();
        System.out.println("通知服务端文件传输完毕");

        // 5. 确定服务端接收完毕, 才能断开连接
        // 收到服务端的完成信号后,断开连接
        // 创建一个输入流,用来接收服务端的流
        InputStream is = socket.getInputStream();
        // 用管道流写出文件
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        byte[] buffer2 = new byte[1024];
        int len2;
        while ((len2 = is.read(buffer2)) != -1) {
            baos.write(buffer2, 0, len2);
        }
        // 输出管道流里的内容
        System.out.println(baos.toString());

        // 6. 关闭资源
        baos.close();
        is.close();
        fis.close();
        os.close();
        socket.close();

    }
}


三、使用 Tomcat

在前面的 TCP 中,我们讲到了客户端与服务器(Client/Server),服务器就是用来接收客户端的请求的。

而 Tomcat,就是一个充当服务器的角色。


1. 启动 Tomcat

可以通过脚本启动,双击 bin 目录下的 startup.bat 即可。

默认在 8080 端口下启动,启动成功后可通过 localhost:8080 进行访问。

如果端口被占用,则无法成功启动。

在这里插入图片描述


2. Tomcat 乱码

Tomcat 启动过程中会打印日志信息,不难发现,日志信息中存在乱码现象

导致乱码的原因:字符编码在解码过程中选择了错误的解码方式。

解码规范 / 方式:GBK,UTF-8 等。

文件在计算机中是以字符编码的形式存储的,而我们平常看到文字是字符串形式的。这之间就有编码和解码两个操作。

而 GBK 这类规范就是告诉计算机应该用何种方式进行编码或解码。


那么,如果一个文件是用 GBK 进行编码的,却使用 UTF-8 进行解码,那么就会导致乱码。正所谓 “解铃还须系铃人”,GBK 的编码需要用 GBK 来解码,UTF-8 同理。


conf 目录下,有日志配置文件 logging.properties,在里面可以修改编码 / 解码方式。

  • CMD 选择 GBK
  • IDEA 选择 UTF-8

在这里插入图片描述


在这里插入图片描述


要解决 IDEA 控制台乱码,需要同时设置 JVM 加载 .class 文件时使用 UTF-8 字符集。

-Dfile.encoding=UTF-8

在这里插入图片描述


3. Tomcat 访问部署的资源

启动 Tomcat 后,可以访问其部署的资源,在 webapps 目录下。

访问根目录:localhost:8080

访问自定义资源:webapps 目录下的 test 中的 hello.txt 文件。


http://localhost:8080/test/hello.txt


参考资料

狂神说 - 网络编程:https://www.bilibili.com/video/BV1LJ411z7vY

Java 8 帮助文档:https://docs.oracle.com/javase/8/docs/api/