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

---随笔--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.mfbz.cn/a/619313.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

LLM量化

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

jdk8的新特征

1: jdk8中新增的方法 在jdk8中对接口进行了增强,在jdk8之前 interface 接口名{ 静态常量: 抽象方法: } 在jdk8之后 interface 接口名{ 静态常量: 抽象方法: 默认方法: 静态方法: } 2…

Ubuntu20.4部署Cuda12.4

准备Ubuntu20.4 VM 安装Cuda12.4 1.进入如下界面安装安装Cuda12.4版本: CUDA Toolkit 12.4 Update 1 Downloads | NVIDIA Developerhttps://developer.nvidia.com/cuda-downloads?target_osLinux&target_archx86_64&DistributionUbuntu&target_vers…

97. 交错字符串-----回溯、动态规划

题目链接 97. 交错字符串 - 力扣(LeetCode) 解答 递归回溯 题目所述为两个字符串交替组成第三个字符串,之前好像做过相似的题目,直接联想到可以考虑使用递归回溯的做法,让字符串s1和字符串s2分别作为起始字符串&…

vm16安装最新版本的ubuntu虚拟机,并安装g++的步骤记录

背景 低版本的ubuntu安装G一直不成功,干脆安装最新版的 官网下载 bing搜索ubuntu 下载完成 vm16新建虚拟机 一直下一步,安装完成 终端输入命令 sudo apt-get update ᅟᅠ       sudo apt install gcc ᅟᅠ      sudo apt install g

工程师工具箱系列(1)MapStruct

文章目录 工程师工具箱系列(1)MapStruct芸芸众生初窥门径引入POM依赖创建转换器与方法进行使用IDEA好基友 游刃有余示例说明避免编写重复转换器实现复杂灵活转换 温故知新 工程师工具箱系列(1)MapStruct 芸芸众生 在Java项目开发…

SpringAI 技术解析

1. 发展历史 SpringAI 的发展历史可以追溯到对 Spring 框架的扩展和改进,以支持人工智能相关的功能。随着人工智能技术的快速发展,SpringAI 逐渐成为 Spring 生态系统中的一个重要组成部分,为开发者提供了便捷、灵活的解决方案。 项目的灵感来…

算法提高之单词接龙

算法提高之单词接龙 核心思想&#xff1a;dfs 预处理每两个字符串之间最短的公共部分长度 求最短公共 最终字符串是最长 dfs所有开头字符串 #include <iostream>#include <cstring>#include <algorithm>using namespace std;const int N 25;int g[N][N…

雷军-2022.8小米创业思考-6-互联网七字诀之快:天下武功,唯快不破;快不是目的,快是手段;不要用战术上的勤奋掩盖战略上的懒惰。

第六章 互联网七字诀 专注、极致、口碑、快&#xff0c;这就是我总结的互联网七字诀&#xff0c;也是我对互联网思维的高度概括。 快 我们期待非常多的快&#xff0c;比如研发进展快、库存周转快、资金回笼快等等。但在这里&#xff0c;我们集中讨论的是公司在业务发展和面对…

LeetCode题练习与总结:二叉树的中序遍历--94

一、题目描述 给定一个二叉树的根节点 root &#xff0c;返回 它的 中序 遍历 。 示例 1&#xff1a; 输入&#xff1a;root [1,null,2,3] 输出&#xff1a;[1,3,2]示例 2&#xff1a; 输入&#xff1a;root [] 输出&#xff1a;[]示例 3&#xff1a; 输入&#xff1a;roo…

C语言(指针)5

Hi~&#xff01;这里是奋斗的小羊&#xff0c;很荣幸各位能阅读我的文章&#xff0c;诚请评论指点&#xff0c;关注收藏&#xff0c;欢迎欢迎~~ &#x1f4a5;个人主页&#xff1a;小羊在奋斗 &#x1f4a5;所属专栏&#xff1a;C语言 本系列文章为个人学习笔记&#x…

Busybox 在 Docker 中的部署和启动

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

COX回归特征筛选

任务&#xff1a;利用cox筛选出P值小于0.05的特征 数据的格式第一列为标签&#xff0c;第二列为时间&#xff0c;第三列及后为特征 先想一想&#xff0c;想好了再更新 这里我们先举一个例子&#xff1a; import pandas as pd from lifelines import CoxPHFitter# 创建示例数…

项目管理-计算题公式【复习】

1.【进度】相关公式 1.1三点估算 PERT 三点估算法是基于 任务成本的三种估算值&#xff08;最可能成本CM&#xff0c;最乐观成本CO&#xff0c;最悲观成本CP&#xff09;来计算预期成本的方法。 三角 分布&#xff1a;预期成本&#xff08;最乐观成本最可能成本最悲观成本&am…

RabbitMq出现Not management user问题解决

在RabbitMq登录的时候突然弹出如下图&#xff1a; 提示“当前用户不是管理员用户”进入mq控制命令台下&#xff1a; windows版本在mq安装路径下的sbin下进入cmd弹出框&#xff1b; Linux版本没有测试&#xff1b; 输入以下命令&#xff1a; rabbitmqctl list_users 查询当…

【计算机网络篇】数据链路层(8)共享式以太网的退避算法和信道利用率

文章目录 &#x1f6f8;共享式以太网的退避算法&#x1f95a;截断二进制指数算法 &#x1f354;共享式以太网的信道利用率 &#x1f6f8;共享式以太网的退避算法 在使用CSMA/CD协议的共享总线以太网中&#xff0c;正在发送帧的站点一边发送帧一边检测碰撞&#xff0c;当检测到…

Springboot整合 Spring Cloud Alibaba Sentinel

1.Sentinel介绍 官方文档地址&#xff1a; https://sentinelguard.io/zh-cn/docs/introduction.html https://github.com/alibaba/Sentinel/wiki/%E5%A6%82%E4%BD%95%E4%BD%BF%E7%94%A8Sentinel 是面向分布式、多语言异构化服务架构的流量治理组件&#xff0c;主要以流量为切入…

探索共享内存:解锁并发编程的潜力

文章目录 序言shm 原理对shm的理解通过代码认识shm调用shmget方法实现 序言 system V版本 指定的一种规则(俗话说一流公司定规则,二流公司重服务,三流公司重技术).这个规则虽然有很多种(消息队列,共享内存等只是比较出名的几个).但是在内核的相关技术解决上是类似的,因为都是基…

吴恩达 深度学习 神经网络 softmax adam 交叉验证

神经网络中的层&#xff1a;输入层&#xff08;layer 0&#xff09;、隐藏层、卷积层&#xff08;看情况用这个&#xff09;、输出层。&#xff08;参考文章&#xff09; 激活函数&#xff1a; 隐藏层一般用relu函数&#xff1b; 输出层根据需要&#xff0c;二分类用sigmoid&…

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

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