---随笔--Java实现TCP通信(双端通信接收与发送)

news2024/11/26 18:38:52

---随笔--Java实现TCP通信(双端通信接收与发送)

    • 引言
    • 1. 什么是TCP通信
    • 2. 服务器与客户端核心代码
      • 2.1 服务器ServerSocket端核心代码
      • 2.2 用户Socket端核心代码
      • 2.3 小贴士之关于try-with-resources自动关闭资源的使用
    • 3. 具体服务器端实现
    • 4. 具体客户端实现
    • 5. 运行效果图
    • 6. 总结

引言

设计一个基于最基本Socket的P2P“聊天软件”(文本互传),要求在同一个网关内可以通过任意常用端口建立会话并进行socket通讯、双向文本收发。

1. 什么是TCP通信

TCP(Transmission Control Protocol),也称传输控制协议,是一种在计算机网络中常用的协议,用于在两个设备之间建立可靠的连接并进行数据交换。它是一种面向连接的协议,意味着通信的两端在交换数据之前必须先建立连接。TCP提供了许多功能,使得数据可以在网络上可靠地传输,包括数据分段、重传机制、流量控制和拥塞控制等。

具体来说,TCP通信的过程通常包括以下几个步骤:

  1. 建立连接(三次握手):通信的两端(客户端和服务器端)首先需要通过三次握手来建立连接。在这个过程中,客户端向服务器发送一个连接请求报文(SYN),服务器收到后回复一个确认报文(SYN-ACK),并确认客户端的连接请求,最后客户端再回复一个确认报文(ACK),确认服务器的确认。这样,连接就建立起来了。

  2. 数据传输:连接建立之后,通信双方就可以开始传输数据。数据会被分成小的数据段,并通过网络传输到接收端。TCP会对数据进行编号,以保证数据的顺序不会被打乱,同时会对数据进行检验,以确保数据的完整性。

  3. 连接终止(四次挥手):当数据传输完成后,通信的两端需要进行连接的关闭。这个过程包括四次挥手:首先,客户端发送一个连接释放报文(FIN),服务器接收到后发送确认报文(ACK),但仍然允许数据传输;然后,服务器发送一个连接释放报文(FIN),客户端收到后发送确认报文(ACK),进入 TIME-WAIT 状态。最后,客户端发送最后一个确认报文(ACK),完成连接的关闭。

关于更详细的三次握手和四次挥手可以参考我的以下文章:–随笔–带你轻松理解TCP中的三次握手,–随笔–带你轻松理解TCP中的四次挥手

2. 服务器与客户端核心代码

自己做了个图,方便大家理解下,看了图再往下看就能很容易理解了:
Java中TCP通信简易流程

2.1 服务器ServerSocket端核心代码

			//绑定监听端口
            ServerSocket serverSocket = new ServerSocket(port);
            System.out.println("Server is running, listening on port " + port);

            while (true) {
                // 等待并监听客户端的连接请求
                Socket clientSocket = serverSocket.accept();
                // 为客户端创建一个线程处理请求
                new Thread(new ClientHandler(clientSocket)).start();
            }

这段代码片段建立了一个服务器,它在指定的端口上监听传入的客户端连接。当客户端连接时,它创建一个新的线程来处理该客户端的请求,从而允许并发的客户端-服务器交互。这段代码的主要功能是创建一个能够处理多个客户端连接的多线程服务器。

以上代码主要进行了以下操作:

  1. 初始化ServerSocket:
    ServerSocket serverSocket = new ServerSocket(port);
    在这里,创建了一个名为serverSocket的ServerSocket对象,并将其绑定到特定的端口(port在main方法中预先定义为1234)。这个ServerSocket负责在指定端口上监听传入的客户端连接请求。

  2. 服务器运行反馈:
    System.out.println("Server is running, listening on port " + port);
    这行代码简单地向控制台打印一条消息,指示服务器正在运行并在特定端口上进行监听。

  3. 客户端连接处理循环:
    while (true) {
    Socket clientSocket = serverSocket.accept();
    new Thread(new ClientHandler(clientSocket)).start();
    }

    在代码块中,while循环无限地运行,不断等待并处理客户端连接。

  • serverSocket.accept(): 这是一个阻塞方法,它等待客户端连接到服务器。当检测到客户端连接时,它会返回代表客户端的Socket对象。
  • new Thread(new ClientHandler(clientSocket)).start(): 这一行代码创建一个新的Thread,并将一个ClientHandler实例设置为其目标。ClientHandler负责管理与连接的客户端的交互。当线程启动时,ClientHandler的run方法将独立执行,允许通过多线程同时为多个客户端提供服务。

2.2 用户Socket端核心代码

			//创建指定主机和端口的Socket
            Socket socket = new Socket(host, port);
            //获取Socket的输出流和输入流
            PrintWriter writer = new PrintWriter(socket.getOutputStream(), true);
            BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
            //获取系统标准输入流
            BufferedReader sysReader = new BufferedReader(new InputStreamReader(System.in));

这段代码用于创建一个 socket 连接到指定的主机和端口,并为与服务器的通信建立输入和输出流。此外,它创建一个读取器,用于从标准输入流中捕获输入,允许用户与客户端程序进行交互。该代码的主要功能是为客户端设置通信基础设施,使其能够发送和接收与服务器的消息。

这段代码是用于创建一个客户端,通过 Socket 连接到服务器。具体步骤如下:

  1. Socket socket = new Socket(host, port)
    创建一个新的 Socket 对象,指定连接的主机和端口。这个 Socket 代表了客户端和服务器之间通信的端点。
  2. PrintWriter writer = new PrintWriter(socket.getOutputStream(), true)
    初始化一个 PrintWriter 对象,用于向 Socket 的输出流写入数据。PrintWriter 类提供了便捷的方法,用于向流中写入各种数据类型。
  3. BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream()))
    通过初始化 BufferedReader 对象,用于从 Socket 的输入流中读取数据。BufferedReader 类允许从字符输入流中高效地读取字符。
  4. BufferedReader sysReader = new BufferedReader(new InputStreamReader(System.in))
    创建一个 BufferedReader 对象,用于从标准输入流(键盘)中读取输入。这将用于捕获用户输入,并将其发送到服务器

2.3 小贴士之关于try-with-resources自动关闭资源的使用

代码中运用到许多try-with-resources的语法,try-with-resources 是 Java 7 中引入的一个语言特性,用于自动关闭资源。在 Java 中,程序常常需要操作文件、数据库连接、网络连接等资源,这些资源需要在不再需要时及时关闭,以释放系统资源并避免资源泄漏。传统的做法是在 finally 块中关闭资源,但这样的代码容易出错,而且代码量较多。

try-with-resources 语句简化了资源管理的代码,确保在代码块结束后自动关闭资源,无需手动编写 finally 块。其语法结构如下:

	try (ResourceType1 resource1 = new ResourceType1();
	     ResourceType2 resource2 = new ResourceType2();
	     // 可以有多个资源声明,用分号隔开
	     ) {
	    // 使用资源的代码块
	    // 资源会在代码块结束后自动关闭
	} catch (Exception e) {
	    // 异常处理代码块
	}

即在try后使用小括号,将资源写进小括号中再接大括号,即 try( Resources ){ your code }
需要注意的是,在 try 关键字后面的括号内声明需要管理的资源,这些资源必须实现 java.lang.AutoCloseable 接口或其子接口(例如 java.io.Closeable)。在代码块结束后,Java 会自动调用资源的 close() 方法关闭资源,即使代码块中出现异常也会关闭资源。如果资源的 close() 方法抛出异常,这些异常会被抑制,并且可以通过 Throwable.getSuppressed() 方法获取。

3. 具体服务器端实现

MyServer.class

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;

public class MyServer {
    public static void main(String[] args) {
        int port = 1234;

        try {
            //绑定监听端口
            ServerSocket serverSocket = new ServerSocket(port);
            System.out.println("Server is running, listening on port " + port);

            while (true) {
                // 等待并监听客户端的连接请求
                Socket clientSocket = serverSocket.accept();
                // 为客户端创建一个线程处理请求
                new Thread(new ClientHandler(clientSocket)).start();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

class ClientHandler implements Runnable {
    private Socket clientSocket;
    private BufferedReader reader;
    private PrintWriter writer;

    public ClientHandler(Socket clientSocket) {
        this.clientSocket = clientSocket;
        try {
            this.reader = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));
            this.writer = new PrintWriter(clientSocket.getOutputStream(), true);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    @Override
    public void run() {
        try {
            // 向客户端发送欢迎消息
            writer.println("Welcome to our Chat! Type anything pressing enter to send message.(Type 'exit' to end the chat)");
            BufferedReader sysReader = new BufferedReader(new InputStreamReader(System.in));
            String message;
            while (true) {
                if (reader.ready()) {  // 检查是否有消息从客户端发来
                    message = reader.readLine();
                    //监听客户端若输入exit则关闭客户端连接
                    if ("exit".equalsIgnoreCase(message)) {
                        System.out.println("Client exit, System shutdown...");
                        //立即终止JVM,自动释放资源
                        System.exit(0);
                    }
                    System.out.println("Client: " + message);
                }
                if (sysReader.ready()) {  // 检查是否有自定义消息要发送给客户端
                    String messageToClient = sysReader.readLine();
                    writer.println(messageToClient);
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                if (clientSocket != null) {
                    clientSocket.close(); // 关闭Socket
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

4. 具体客户端实现

MyClient.class

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.Socket;

public class MyClient {
    public static void main(String[] args) {
        String host = "127.0.0.1";
        int port = 1234;

        // try-with-resources 语法块自动关闭资源
        try(
            //创建指定主机和端口的Socket
            Socket socket = new Socket(host, port);
            //获取Socket的输出流和输入流
            PrintWriter writer = new PrintWriter(socket.getOutputStream(), true);
            BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
            //获取系统标准输入流
            BufferedReader sysReader = new BufferedReader(new InputStreamReader(System.in));)
        {
            new Thread(() -> {
                try {
                    String receivedMessage;
                    while (true) {
                        //立即返回,而不是通过readLine()等待,避免阻塞
                        if (reader.ready()) {
                            receivedMessage = reader.readLine();
                            System.out.println("Server: " + receivedMessage);
                        }
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }).start();

            String messageToServer;
            while (true) {
                if (sysReader.ready()) {
                    messageToServer = sysReader.readLine();
                    writer.println(messageToServer);
                    //exit命令退出系统
                    if ("exit".equalsIgnoreCase(messageToServer)){
                        System.out.println("You have exited the chat");
                        System.exit(0);
                    }
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

5. 运行效果图

服务器运行后接受到客户端发来的消息并回发内容
服务器运行后接受到客户端发来的消息并回发内容
客户端向服务器发送消息并获取服务器返回的消息
客户端向服务器发送消息并获取服务器返回的消息

6. 总结

一开始想着用Java简单实现下TCP并不难,但实际动手后发现需要注意的点还挺多的,要考虑到双方数据的发送和获取,资源的关闭,对话的终结,真正做起来还是花了点时间,但至少强化了记忆也学习到了一些新方法,内心还是挺快乐的,总之还是要多动手多实践,相信你也可以做到一次编写一次过的,加油!
加油

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1669138.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

WordPress 、Typecho 站点的 MySQL/MariaDB 数据库优化

今天明月给大家分享一下 WordPress 、Typecho 站点的 MySQL/MariaDB 数据库优化,无论你的站点采用是 WordPress 还是 Typecho,都要用到 MySQL/MariaDB 数据库,我们以 MySQL 为主(MariaDB 其实跟 MySQL 基本没啥大的区别&#xff0…

STC8增强型单片机开发【LED呼吸灯(PWM)⭐⭐】

目录 一、引言 二、硬件准备 三、PWM技术概述 四、电路设计 五、代码编写 EAXSFR: 六、编译与下载 七、测试与调试 八、总结 一、引言 在嵌入式系统开发中,LED呼吸灯是一种常见的示例项目,它不仅能够展示PWM(脉冲宽度调制…

解决 Content type ‘application/json;charset=UTF-8‘ not supported

文章目录 问题描述原因分析解决方案参考资料 问题描述 我项目前端采用vue-elementUi-admin框架进行开发,后端使用SpringBoot,但在前后端登录接口交互时,前端报了如下错误 完整报错信息如下 前端登录接口JS代码如下 export function login(…

Busybox 在 Docker 中的部署和启动

可以使用 docker pull 指令下载 busybox:latest 镜像: PS C:\Users\yhu> docker pull busybox:latest latest: Pulling from library/busybox ec562eabd705: Pull complete Digest: sha256:5eef5ed34e1e1ff0a4ae850395cbf665c4de6b4b83a32a0bc7bcb998e24e7bbb St…

网安面经之文件包含漏洞

一、文件包含漏洞 1、文件包含漏洞原理?危害?修复? 原理:开发⼈员⼀般希望代码更灵活,所以将被包含的⽂件设置为变量,⽤来进⾏动态调⽤,但是由于⽂件包含函数加载的参数没有经过过滤或者严格的…

Selenium 自动化 —— 一篇文章彻底搞懂XPath

更多关于Selenium的知识请访问“兰亭序咖啡”的专栏:专栏《Selenium 从入门到精通》 文章目录 前言 一、什么是xpath? 二、XPath 节点 三. 节点的关系 1. 父(Parent) 2. 子(Children) 3. 同胞(S…

LLM量化

Efficient Finetuning prefix tuning 针对每种任务,学习prefix vector 启发于prompting,调整上下文内容让模型去输出自己想要的内容 核心就是找到一个上下文去引导模型解决NLP生成任务 传统情况下,我们为了得到想要的结果,会…

1.9. 离散时间鞅-组合方法分析随机游动(不用鞅方法)

组合方法分析随机游动-不用鞅方法 组合方法分析随机游动(不用鞅方法)1. 反射原理 → \rightarrow →首击时分布2. 反正弦定律(末击时分布)组合方法分析随机游动(不用鞅方法) 本节将不使用鞅更深入地研究简单随机游动的属性,有益于过渡到马氏链的研究. 1. 反射原理

20240512,函数对象,常用算法:遍历,查找

函数对象 函数对象基本使用 重载 函数调用操作符 的类,其对象被称为函数对象;函数对象使用重载的()时,行为类似函数调用,也叫仿函数 函数对象(仿函数)本质是一个类,不是…

【GlobalMapper精品教程】080:WGS84转UTM投影

参考阅读:ArcGIS实验教程——实验十:矢量数据投影变换 文章目录 一、加载实验数据二、设置输出坐标系三、数据导出一、加载实验数据 打开配套案例数据包中的data080.rar中的矢量数据,如下所示: 查看源坐标系:双击图层的,图层投影选项卡,数据的已有坐标系为WGS84地理坐标…

opencompass实践

参考教程 https://github.com/InternLM/Tutorial/blob/camp2/opencompass/readme.md 下载opencompass,配置必要的环境之后,解压下载的数据集 cp /share/temp/datasets/OpenCompassData-core-20231110.zip /root/opencompass/ unzip OpenCompassData-co…

Smurf 攻击是不是真的那么难以防护

Smurf攻击是一种网络攻击方式,属于分布式拒绝服务(DDoS)攻击的变种。以 1990 年代流行的名为 Smurf 的漏洞利用工具命名。该工具创建的 ICMP 数据包很小,但可以击落大目标。 它利用ICMP协议中的回声请求(ping&#x…

第七次--大模型测评

在该章节中更多是体验与尝试 一、理论学习 类型:语言大模型和多模态大模型这两种是主要的类型。 挑战:要想办法建立一个全面的评价体系,还要能处理大规模的数据,并且要保证评测的准确性和可重复性,真不是一件容易的事…

Unity生命周期函数详解

Unity生命周期函数详解 Unity生命周期函数是Unity引擎中用于控制游戏对象行为的一系列方法。它们在游戏的不同阶段被自动调用,允许开发者在适当的时机执行特定的代码。了解和正确使用生命周期函数对于创建流畅和高效的游戏至关重要。 生命周期函数概述 Unity生命…

变量的解构赋值

变量的解构赋值 数组的解构赋值对象解构赋值 对象的解构与数组有一个重要的不同。嵌套结构的对象 字符串的解构赋值数值和布尔值的解构赋值函数参数的解构赋值 变量的解构赋值用途 交换变量的值从函数返回多个值函数参数的定义提取JSON数据设置默认值遍历Map和Set不完全解构输入…

TM1650 并联在I2C 信号线的处理方法

目的是可以并联多个TM1650 在标准I2C 总线上,并且不影响其他标准I2C 器件。思路就是拿个额外的开关控制每一片TM1650 的使能,就像SPI 的CS 信号那样。 协议 TM1650 的通信协议虽说不是标准I2C,但也算是比较兼容的,比方说&#x…

azkaban-tools 项目介绍

本文背景 应一个用户的好心和好奇心,在最近水深火热的百忙之中抽时间写完了一个简短的项目介绍,其实就是几个azkaban的批量操作脚本,但在大数据集群的“运维生涯”中,还是帮了自己不少忙,也算是为了它做一个简单的回顾…

SpringBoot集成jxls2实现复杂(多表格)excel导出

核心依赖 需求 导出多个表格,包含图片,类似商品标签 1.配置模板 创建一个xlsx的模板文件,配置如下 该模板进行遍历了两次,因为我想要导出的数据分为两列展示,左右布局,一个循环实现不了,所以采…

重学JavaScript高阶知识点(三)—— 详解Js中的内存管理

详解Js中的内存管理 1. 简介2. 内存生命周期3. JavaScript 的内存分配4. 垃圾回收 1. 简介 很多底层语言一般都有底层的内存管理接口,比如 C语言,可以调用对应的API去创建和释放内存空间。意思是需要手动去创建和释放内存空间,很明显&#x…

TCP服务器实现将客服端发送的信息广播发送(使用内核链表管理客户端信息)

目录 1.服务器端实现思路 2.服务器端代码 3.客户端代码 4.内核链表代码 5.运行格式 一、服务器端 二、客户端 6.效果 1.服务器端实现思路 Tcp广播服务初始化 等待客户端连接 广播发送 2.服务器端代码 #include "list.h" #include <signal.h> #def…