【Java】TCP网络编程:从可靠传输到Socket实战

【Java】TCP网络编程:从可靠传输到Socket实战

活动发起人@小虚竹 想对你说:

这是一个以写作博客为目的的创作活动,旨在鼓励大学生博主们挖掘自己的创作潜能,展现自己的写作才华。如果你是一位热爱写作的、想要展现自己创作才华的小伙伴,那么,快来参加吧!我们一起发掘写作的魅力,书写出属于我们的故事。我们诚挚邀请你参加为期14天的创作挑战赛!

提醒:在发布作品前,请将不需要的内容删除。

 各位看官,大家早安午安晚安呀~~~

如果您觉得这篇文章对您有帮助的话

欢迎您一键三连,小编尽全力做到更好
欢迎您分享给更多人哦


今天我们来学习【Java】TCP网络编程:从可靠传输到Socket实战

目录

1.首先我们再说一下TCP和UDP的区别和相同点

2.连接:通信双方都会记录对方的信息

3.主要是两个api ServerSocket和Socket

4.TCP服务端实战代码演示

5.TCP客户端实战代码演示


TCP的socket的api的差异很大,但是和前面的IO有很大的关联

1.首先我们再说一下TCP和UDP的区别和相同点

1.TCP是有连接的,UDP无连接(这一点可以在代码中体现)

2.TCP是面向字节流流的,UDP是面向数据报的

3.TCP是可靠传输的,UDP是不可靠传输的(这一点在代码中体现不出来)

4.TCP和UDP都是全双工的

2.连接:通信双方都会记录对方的信息

UDP:每次发送数据报都要指定对方的地址(UDP没有存储这个信息)

一张图



TCP:不需要(不过需要内核自动和客户端建连接(TCP的三次握手,后面我会进行讲解)这个过程是系统内核自动完成的)

对于应用程序来说,客户端是发起“建立连接”



服务器这边:把内核中建立好的连接拿到应用程序里面()

这个ServerSocket只负责绑定端口号,然后通过accept方法 把建立好的连接拿过来(但是一瞬间有很多连接的话,就像生产者消费者模型里面只能进行阻塞等待)

3.主要是两个api ServerSocket和Socket

ServerSocket是给服务器用的类,使用这个类用来绑定端口号(这个类负责把系统内核里面已经建立好的连接从队列里面拿过来,)

Socket:既会给服务器使用的类,也会给客户端使用,通过socket这个对象和客户端进行交互

(这两个类都是用来表示socket文件的,抽象了网卡这样的硬件设备)

4.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.Executor; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public class TcpEchoServer { // 读取用Scanner ,发送用PrintWriter // 1.首先serverSocket调用系统API把连接拿过来,然后交给socket // 我们就有了socket这个对象,这个时候我们就可以宣布这个客户端成功和我们服务器建立了联系并且我们拿到了 // 2.然后我们通过字节流把数据从socket抽象的文件里面读取到,用try包起来,用scanner从流里面读取 // 3. 我们拿到字符串进行响应 // 4. 把返回的字符串通过字节流(我们的字符串通过字符流转换成字节流)写回去 // 5. 把信息打印出来 private ServerSocket serverSocket = null; public TcpEchoServer(int serverPort) throws IOException { // 利用这个系统API从内核中取到已经建立好的连接 // 这个和客户端构造的socket完全不一样,客户端的那个是我们已经拿到的连接 serverSocket = new ServerSocket(serverPort); // 服务器自己分配端口号, } public void start() throws IOException { System.out.println("服务器启动"); // 把队列里面建立好的连接拿过来 while(true){ // 要一直不断地从那个内核里面不断地拿我们已经建立好的连接!!! Socket Clientsocket = serverSocket.accept(); /* //创建一个新的线程把这个请求进行响应 Thread t = new Thread(() ->{ possessCollection(Clientsocket); }); t.start();*/ // 但是线程池是更好一点点的选择 ExecutorService service = Executors.newCachedThreadPool(); service.submit(() ->{ possessCollection(Clientsocket); }); } } public void possessCollection(Socket Clientsocket){ // System.out.printf("[%s,%d] 客户端上线\n" , Clientsocket.getInetAddress(),Clientsocket.getPort()); // 把端口号和IP拿到 try( InputStream inputStream = Clientsocket.getInputStream(); // 这里要用 " ; " OutputStream outputStream = Clientsocket.getOutputStream()){ // 客户可能等会还会继续发送请求(我们循环处理) Scanner scanner = new Scanner(inputStream);//每一次读一次缓冲区,缓冲区里面的东西就少一次,都被我读出来了嘛 while(true){ if(!scanner.hasNext()){// 用户不再输入的时候,就直接跳出循环!!!,一直等待用户输入 System.out.printf("[%s,%d] 客户端下线\n" , Clientsocket.getInetAddress(),Clientsocket.getPort()); break; } String request = scanner.next(); // 拿到字符串进行响应 String response = process(request);// 拿到响应 //4. 拿到字符串的响应,然后我们通过字符流转字节流传递出去 PrintWriter printWriter = new PrintWriter(outputStream); printWriter.println(response); // 此处的println不是写到控制台了,而是写到outputStream的流对象了,也就是写入到ClientSocket里面了 // 自然这个数据也是通过网卡发出去了 printWriter.flush();// 再刷新一下缓存,防止没有发出去, // 总结: 发送和接收数据都是通过socket文件的字节流输入输出来实现,用scanner读字节流,用printWriter写字符串 // 5.打印一下交互过程 System.out.printf("[%s,%d] req=%s resp=%s\n",Clientsocket.getInetAddress(),Clientsocket.getPort(), request,response); } }catch(IOException e){ e.printStackTrace(); }finally{ try { Clientsocket.close(); } catch (IOException e) { throw new RuntimeException(e); } } } private String process(String request) { // 回显服务器 return request; } public static void main(String[] args) throws IOException { TcpEchoServer tcpEchoServer = new TcpEchoServer(9090); tcpEchoServer.start(); } } 

但是这里面会出现两个问题

问题一:为什么我们的ServerSocket对象没有进行close操作,但是Socket对象却需要close操作呢?这样不会出现文件资源泄露吗?

首先我们需要知道什么时候回造成文件资源泄露?

直频繁申请但是一直不释放就会(什么文件的表项啥的)

但是ServerSocket对象从头到尾只创建过一次对象,而且一直在把内核中建立的连接拿到。所以说ServerSocket这个对象的生命周期是是伴随着进程消失的(因此不需要特地的进行回收,等到进程解释JVM会把这个进程里面的东西一起回收了)


但是?

问题二:等待我们写完客户端的代码之后进行讲述

如果启动多个客户端和服务器进行连接(就不行了,这个服务器一直在等待客户端进行输入,我们就需要多个线程并发拿到这个连接)

但是频繁地创建和销毁线程也会有很大开销,线程池是更好一点点选择

5.TCP客户端实战代码演示

import com.sun.xml.internal.ws.policy.privateutil.PolicyUtils; 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 { /** * 1.随机分配一个端口的发出信息(我们要把服务器的IP地址和端口号给搞进去) * 2.我们循环输入一个字符串,把这个字符串用字符流转换成字节流写到socket抽象的文件里面 * 3.然后我们接收响应(不像服务器,我们又不需要进行处理) * 4.我们直接通过Scanner把字节流里面的内容读出来就好了 */ private Socket socket = null; public TcpEchoClient(String serverIP,int serverPort) throws IOException { socket = new Socket(serverIP,serverPort); } public void start(){ System.out.println(" -> "); Scanner scanner = new Scanner(System.in); try(OutputStream outputStream = socket.getOutputStream(); InputStream inputStream = socket.getInputStream()){ while(true){ /* if(!scanner.hasNext()){ // 用户不想输入了,就直接退出了 break; 不需要这个 }*/ // * 2.我们循环输入一个字符串,把这个字符串用字符流转换成字节流写到socket抽象的文件里面 String request = scanner.next(); PrintWriter printWriter = new PrintWriter(outputStream); // 这些流尽量都放到try()里面 printWriter.println(request); printWriter.flush();// 一定不要忘记刷新缓存区' // * 4.我们直接通过Scanner把字节流里面的内容读出来就好了 Scanner scannerNetwork = new Scanner(inputStream); // 这个Scanner尽量也是放到try()里面 String response = scannerNetwork.next(); System.out.println(response); } }catch(IOException e){ e.printStackTrace(); } } public static void main(String[] args) throws IOException { TcpEchoClient tcpEchoClient = new TcpEchoClient("127.0.0.1",9090); tcpEchoClient.start(); } } 
上述就是【Java】TCp网络编程:TCP网络编程:从可靠传输到Socket实战的全部内容啦

能看到这里相信您一定对小编的文章有了一定的认可。

有什么问题欢迎各位大佬指出
欢迎各位大佬评论区留言修正~~


您的支持就是我最大的动力​​​!!!

Read more

Java 中间件:RabbitMQ 延迟队列(死信交换机实现)

Java 中间件:RabbitMQ 延迟队列(死信交换机实现)

👋 大家好,欢迎来到我的技术博客! 📚 在这里,我会分享学习笔记、实战经验与技术思考,力求用简单的方式讲清楚复杂的问题。 🎯 本文将围绕Java中间件这个话题展开,希望能为你带来一些启发或实用的参考。 🌱 无论你是刚入门的新手,还是正在进阶的开发者,希望你都能有所收获! 文章目录 * Java 中间件:RabbitMQ 延迟队列(死信交换机实现) ⏳ * 什么是延迟队列?🤔 * 典型应用场景 🌟 * RabbitMQ 原生不支持延迟队列?那怎么办?🚫➡️✅ * 核心思想 💡 * 关键概念详解 🔑 * 1. TTL(Time-To-Live)⏱️ * 2. 死信(Dead Letter)⚰️ * 3. 死信交换机(DLX) & 死信路由键(DLK)🔄 * 4. 延迟队列的构建逻辑 🧩 * 环境准备 🛠️ * 启动 RabbitMQ(Docker 方式) * Spring Boot 项目搭建 🏗️ * 定义交换机、队列与绑定关系 📦 *

By Ne0inhk
【已解决】VScode 配置 Java 开发环境(2024新)Visual Studio Code 手把手教你 超详细教程 小白 jdk class

【已解决】VScode 配置 Java 开发环境(2024新)Visual Studio Code 手把手教你 超详细教程 小白 jdk class

配置 Visual Studio Code (VSCode) 以进行 Java 开发涉及几个步骤。以下是一个详细的指南,帮助你在 VSCode 中设置 Java 开发环境: 1. 安装 Java Development Kit (JDK) 下载并安装JDK 确保你的系统上已经安装了 JDK。你可以从 Oracle 或 Adoptium 下载最新版本的 JDK。 配置环境变量 安装完成JDK后,设置环境变量 JAVA_HOME 指向你安装的 JDK 目录,并将 %JAVA_HOME%\bin 添加到系统的 PATH 环境变量中。 2. 安装 Visual Studio Code 如果你还没有安装

By Ne0inhk
【Java】数据类型,运算符和方法重点总结

【Java】数据类型,运算符和方法重点总结

一、数据类型 1.1 两种数据类型 在Java中,数据类型主要分为 基本数据类型 和 引用数据类型 。 1.1.1 基本数据类型 基本数据类型共有四类八种: 整型:byte, short, int, long,浮点型:float, double,字符型:char,布尔型:boolean 八种基本数据类型的位数、取值范围和默认值如下表: 数据类型占用大小(字节)位数取值范围默认值描述byte18-128(-2⁷)到 127(2⁷-1)0最小的整数类型,适合用于节省内存的场景short216-32768(-2¹⁵)到32767(2¹⁵-1)0较少使用,通常用于需要节省内存且数据范围在该区间的场景int432-2147483648(-2³¹)到2147483647(2³¹-1)0最常用的整数类型,

By Ne0inhk

tomcat9使用指定的JDK版本

如果你想把JDK放到Tomcat9的根目录(CATALINA_HOME)下,通过相对路径配置专属JDK环境(不依赖系统全局JDK,实现Tomcat-JDK一体化),核心是利用Tomcat官方推荐的setenv(setclasspath)脚本配置JAVA_HOME(基于CATALINA_HOME的相对路径,避免修改原生启动脚本)。 第一步:标准化目录结构(关键) 先整理Tomcat9的目录,将解压版JDK(绿色版,非安装版)放到Tomcat根目录下的jdk子文件夹,结构如下(示例JDK8,Tomcat9推荐JDK8/JDK11,兼容最佳): apache-tomcat-9.0.XX/ # Tomcat9根目录(即CATALINA_HOME) ├─ bin/ # Tomcat启动/停止脚本目录 ├─ conf/ # 配置文件 ├─ webapps/ # 项目部署目录 ├─ jdk/ # 新建的JDK存放目录 │ └─ jdk1.8.0_391/ # 解压后的JDK完整目录(含bin、jre、lib等子文件夹)

By Ne0inhk