【Java EE初阶十二】网络编程TCP/IP协议(二)
1. 关于TCP
1.1 TCP 的socket api
tcp的socket api和U大片的socket api差异很大,但是和前面所讲的文件操作很密切的联系
下面主要讲解两个关键的类:
1、ServerSocket:给服务器使用的类,使用这个类来绑定端口号
2、Socket:即会给服务器使用,又会给客户端使用;
TCP是字节流的,传输的基本单位是Byte;
所谓连接:通信双方是否会记录保存对端的信息;
对于UDP来说,每一次发送数据报都要手动在send方法中指定目标的地址(UDP自身没有存储这个信息)
对于TCP来说,则不需要,前提是需要先把连接建立起来(连接如何建立,不需要我们通过代码进行干预,是系统内核自动负责完成的)
对于应用程序来说,客户端这边主要是发起“建立连接”动作;
服务器这边,主要是把建立好的连接从内核中拿到应用程序;
如果有客户端和服务器建立连接买这个时候服务器的应用程序是不需要做出任何操作(也没有任何感知),内核直接完成了连接建立的流程(三次握手),完成流程之后,就会在内核的队列中排队(这个队列是每一个serverSocket都有这样一个队列),应用程序要想和这个客户端进行通信,就需要通过一个accept方法把内核队列中已经建立好的连接对象,拿到应用程序中;
2 基于TCP实现通信
2.1 代码实现
服务器代码:
package network;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.ExecutorService;
import java.util.concurrent.Executors;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("服务器启动!");ExecutorService service = Executors.newCachedThreadPool();while (true) {// 通过 accept 方法, 把内核中已经建立好的连接拿到应用程序中.// 建立连接的细节流程都是内核自动完成的. 应用程序只需要 "捡现成" 的.Socket clientSocket = serverSocket.accept();// 此处不应该直接调用 processConnection, 会导致服务器不能处理多个客户端.// 创建新的线程来调用更合理的做法.// 这种做法可行, 不够好
// Thread t = new Thread(() -> {
// processConnection(clientSocket);
// });
// t.start();// 更好一点的办法, 是使用线程池.service.submit(new Runnable() {@Overridepublic void run() {processConnection(clientSocket);}});}}// 通过这个方法, 来处理当前的连接.public void processConnection(Socket clientSocket) {// 进入方法, 先打印一个日志, 表示当前有客户端连上了.System.out.printf("[%s:%d] 客户端上线!\n", clientSocket.getInetAddress(),clientSocket.getPort());// 接下来进行数据的交互.try (InputStream inputStream = clientSocket.getInputStream();OutputStream outputStream = clientSocket.getOutputStream()) {// 使用 try ( ) 方式, 避免后续用完了流对象, 忘记关闭.// 由于客户端发来的数据, 可能是 "多条数据", 针对多条数据, 就循环的处理.while (true) {Scanner scanner = new Scanner(inputStream);if (!scanner.hasNext()) {// 连接断开了. 此时循环就应该结束System.out.printf("[%s:%d] 客户端下线!\n", clientSocket.getInetAddress(), clientSocket.getPort());break;}// 1. 读取请求并解析. 此处就以 next 来作为读取请求的方式.// next 的规则是, 读到 "空白符" 就返回.String request = scanner.next();// 2. 根据请求, 计算响应.String response = process(request);// 3. 把响应写回到客户端.// 可以把 String 转成字节数组, 写入到 OutputStream// 也可以使用 PrintWriter 把 OutputStream 包裹一下, 来写入字符串.PrintWriter printWriter = new PrintWriter(outputStream);// 此处的 println 不是打印到控制台了, 而是写入到 outputStream 对应的流对象中, 也就是写入到 clientSocket 里面.// 自然这个数据也就通过网络发送出去了. (发给当前这个连接的另外一端)// 此处使用 println 带有 \n 也是为了后续 客户端这边 可以使用 scanner.next 来读取数据.printWriter.println(response);// 此处还要记得有个操作, 刷新缓冲区. 如果没有刷新操作, 可能数据仍然是在内存中, 没有被写入网卡.printWriter.flush();// 4. 打印一下这次请求交互过程的内容System.out.printf("[%s:%d] req=%s, resp=%s\n", clientSocket.getInetAddress(), clientSocket.getPort(),request, response);}} catch (IOException e) {e.printStackTrace();} finally {try {// 在这个地方, 进行 clientSocket 的关闭.// processConnection 就是在处理一个连接. 这个方法执行完毕, 这个连接也就处理完了.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();}
}
客户端代码:
package network;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 的同时, 和服务器 "建立连接", 此时就得告诉 Socket 服务器在哪里~~// 具体建立连接的细节, 不需要咱们代码手动干预. 是内核自动负责的.// 当我们 new 这个对象的时候, 操作系统内核, 就开始进行 三次握手 具体细节, 完成建立连接的过程了.socket = new Socket(serverIp, serverPort);}public void start() {// tcp 的客户端行为和 udp 的客户端差不多.// 都是:// 3. 从服务器读取响应.// 4. 把响应显示到界面上.Scanner scanner = new Scanner(System.in);try (InputStream inputStream = socket.getInputStream();OutputStream outputStream = socket.getOutputStream()) {PrintWriter writer = new PrintWriter(outputStream);Scanner scannerNetwork = new Scanner(inputStream);while (true) {// 1. 从控制台读取用户输入的内容System.out.print("-> ");String request = scanner.next();// 2. 把字符串作为请求, 发送给服务器// 这里使用 println, 是为了让请求后面带上换行.// 也就是和服务器读取请求, scanner.next 呼应writer.println(request);writer.flush();// 3. 读取服务器返回的响应.String response = scannerNetwork.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();}
}
2.2 代码分析
1、基础内容
所谓的空白符,是一类特殊的字符(类似于换行,回车符,空格,制表符,翻页付,垂直制表符),后续客户端发起的请求,会议空白符作为结束标记(此处就约定使用\n)
TCP 是字节流通信方式,每次传输多少个字节,每次读取多少个字节,我们往往会手动约定出,从哪里到哪里是一个完整的数据报.上述这里就是约定了使用 \n 作为数据报的结束标记. 就正好可以搭配scanner.next 来完成请求的读取过程,
ClientSocket 则是在循环中,每次有一个新的客户端来建立连接,都会创建出新的 clientSocket
每一次执行到clientSocket语句时,,都会创建新的 clientsockete,并且这个 socket 最多使用到该客户端退出 (断开连接),此时,如果有很多客户端都来建立连接,此时,就意味着每个连接都会创建 clientSocket.当连接断开后clientsocket 就失去作用了,但是如果没有手动 close,此时这个 socket 对象就会占据着文件描述符表的位置
这里的关闭, 只是关闭了 clientsocket 上自带的流对象,并没有关闭 socket 本身.在这个代码中,需要在方法末尾通过 finally 加上 close,保证当前这里的 socket 能够被正确关闭掉;
2、关于多线程
当前启动两个客户端,同时连接服务器.其中一个客户端(先启动的客户端)一切正常,另一个客户端 (后启动的客户端)则没法和服务器进行任何交豆.(服务器不会提示"建立连接”,也不会针对 请求 做出任何响应,这就是关于多线程的一个很明显的问题;
第一个客户端过来之后,accept 就返回了,得到一个 clientSocket.进入processConnection
,又进入一个 while 循环,这个循环中, 就需要反复处理客户端发来的请求数据.如果客户端这会没发请求,服务器的代码就会阻塞在scanner.hasNext 这里;此时此刻,第二个客户端也过来建立连接了,此时连接是可以建立成功(内核负责的),建立成功之后,连接对象就会在内核的队列里等待代码通过 accept 把连接给取出来,在代码中处理
当前的代码,其实无法第一时间执行到第二次的 accept
为了让一个服务器可以同时接待多个客户端,上述问题解决的关键就是引入多线程,让每一个客户端都能进行入到accept方法,进入第二次循环;
3、关于引入线程池
此时这个服务器,每个客户端都要创建一个线程,如果有很多客户端.频繁的来进行建立连接/断开连接,这个时候就会导致服务器频繁的 创建/销毁 线程,(开销就很大了),所以可以使用线程池,来进一步的优化关于线程开销的问题;
2.3 代码运行分析:
tcp 程序, 客户端启动,就会和服务器建立连接,服务器这边就能感受到(accept 方法就会返回,进一步的进入到 processConnection 中,如果启动多个客户端,即多个客户端同时和服务器建立连接,默认情况下,IDEA 只允许一个代码只能创建一个进程.通过下图所示操作,勾选了 Allow multiple instances,此时就可以运行多个进程了.
最后通过使用多线程和线程池的相关内容,完成tcp通信,如下图所示:
ps:本篇文章主要讲解了关于tcp实现通信连接的相关的知识点,如果大家感兴趣的话就请一键三连哦!!!
相关文章:

【Java EE初阶十二】网络编程TCP/IP协议(二)
1. 关于TCP 1.1 TCP 的socket api tcp的socket api和U大片的socket api差异很大,但是和前面所讲的文件操作很密切的联系 下面主要讲解两个关键的类: 1、ServerSocket:给服务器使用的类,使用这个类来绑定端口号 2、Socket…...

Idea Git Review插件
idea git plugin 添加了一些常用的小插件 可以右键打开git bash窗口 可以右键选中文字点击baidu fanyi 可以通过搜索git用户名 指定开始时间查询某个版本自己提交的所有代码文件 可以通过点击蓝色行数,跳转到指定的改动代码块 资源地址: git-pl…...
python的turtle可以定义多个海龟对象
import turtle as t t.color(purple) t.right(10) t.fd(100) t1 t.Turtle() #t1.color(red) t1.left(10) t1.fd(100) t2 t.Turtle() #t2.color(green) t2.left(20) t2.fd(100) t3 t.Turtle() #t3.color(blue) t3.left(30) t3.fd(100) python的turtle可以定义多个海龟对象…...

LocalAI 部署(主要针对 mac m2 启动)
LocalAI 部署 介绍 LocalAI 是免费的开源 OpenAI 替代方案。 LocalAI 充当 REST API 的直接替代品,与本地推理的 OpenAI API 规范兼容。 它无需 GPU,还有多种用途集成,允许您使用消费级硬件在本地或本地运行 LLM、生成图像、音频等等&#…...

Swift Combine 管道 从入门到精通三
Combine 系列 Swift Combine 从入门到精通一Swift Combine 发布者订阅者操作者 从入门到精通二 1. 用弹珠图描述管道 函数响应式编程的管道可能难以理解。 发布者生成和发送数据,操作符对该数据做出响应并有可能更改它,订阅者请求并接收这些数据。 这…...
【RISC-V DSP设计】基于CEVA DSP架构的指令集分析(二)-函数列表
目录 表3-1:定点滤波器功能 表3-2:定点快速傅里叶变换(FFT)函数 表3-3:定点数学函数 表3-4:定点三角函数 表3-5:定点向量函数 表3-6:定点矩阵函数 表3-7:浮点滤波…...

蓝桥杯(Web大学组)2022国赛真题:水果消消乐
思路: 记录点击次数,点击次数为1时,记录点击下标(用于隐藏or消除)、点击种类,点击次数为2时,判断该下标所对应种类与第一次是否相同 相同:两个都visibility:hidden (占…...
LeetCode--代码详解 155.最小栈
155.最小栈 题目 设计一个支持 push ,pop ,top 操作,并能在常数时间内检索到最小元素的栈。 实现 MinStack 类: MinStack() 初始化堆栈对象。void push(int val) 将元素val推入堆栈。void pop() 删除堆栈顶部的元素。int top() 获取堆栈顶…...
第6讲后端鉴权拦截器实现
后端鉴权拦截器实现 package com.java1234.interceptor;import com.java1234.util.JwtUtils; import com.java1234.util.StringUtil; import io.jsonwebtoken.Claims; import org.springframework.web.method.HandlerMethod; import org.springframework.web.servlet.HandlerI…...
uniapp从入门到进阶
一、了解uniapp 跨平台开发:Uniapp可以同时开发多个平台的应用,只需编写一套代码即可。开发者可以通过编写Vue组件来构建界面,通过编写JavaScript代码来实现业务逻辑。 页面和组件:Uniapp的页面和组件都是基于Vue组件的ÿ…...
CDN缓存404、403状态码
可以参考一下:浏览器缓存和 CDN 在前端的落地 事故记录: 前端发版时存在新旧两个容器,在资源替换的间隙,用户请求到的肯定是新容器的html文件,但是根据新容器的html向新静态资源发起请求,此时旧容器还没有…...

【Python网络编程之DHCP服务器】
🚀 作者 :“码上有前” 🚀 文章简介 :Python开发技术 🚀 欢迎小伙伴们 点赞👍、收藏⭐、留言💬 Python网络编程之DHCP服务器 代码见资源,效果图如下一、实验要求二、协议原理2.1 D…...

【MySQL】:深入理解并掌握DML和DCL
🎥 屿小夏 : 个人主页 🔥个人专栏 : MySQL从入门到进阶 🌄 莫道桑榆晚,为霞尚满天! 文章目录 📑前言一. DML1.1 添加数据1.2 修改数据1.3 删除数据 二. DCL2.1 管理用户2.2 权限控制…...

CSP-动态规划-最长公共子序列(LCS)
一、动态规划 动态规划(Dynamic Programming,简称DP)主要用于求解可以被分解为相似子问题的复杂问题,特别是在优化问题上表现出色,如最短路径、最大子数组和、编辑距离等。动态规划的核心思想是将原问题分解为较小的子…...
安装nodejs2011并配置npm仓库
1. 安装nodejs 选择2011版本下载 在安装目录(个人情况)下 D:\Program Files\nodejs2011创建2个文件夹: node_global (依赖库) node_cache (缓存) 然后在当前目录下cmd进入dos窗口,执行: npm c…...
排序C++代码(已更:快速排序,归并排序)
一、快速排序 #include<iostream> using namespace std;//设定三个数组,判断排序算法代码的正确性 int a[100]{3,4,2,6,9,7,1,0,1,2,3,3,5,6,7,8,3,4,5}; int b[100]{1,5,3,4}; int c[100]{7,8,9,1,2,3};void quickSort(int* num,int l,int r){if(l>r) re…...

CentOS 7.9安装Tesla M4驱动、CUDA和cuDNN
正文共:1333 字 21 图,预估阅读时间:2 分钟 上次我们在Windows上尝试用Tesla M4配置深度学习环境(TensorFlow识别GPU难道就这么难吗?还是我的GPU有问题?),但是失败了。考虑到Windows…...
Java设计模式——策略
前言 策略模式是平时Java开发中常用的一种,虽然已有很多讲解设计模式的文章,但是这里还是写篇文章来从自己理解的角度讲解一下。 使用场景 我们不妨进行场景假设,要对我们的软件进行授权管理:在启动我们的软件之前先要校验是否…...
线性代数的本质 1 向量
向量是线性代数中最为基础的概念。 何为向量? 从物理上看, 向量就是既有大小又有方向的量,只要这两者一定,就可以在空间中随便移动。 从计算机应用的角度看,向量和列表很接近,可以用来描述某对象的几个不同…...

基于JAVA的贫困地区人口信息管理系统 开源项目
目录 一、摘要1.1 项目介绍1.2 项目录屏 二、功能模块2.1 人口信息管理模块2.2 精准扶贫管理模块2.3 特殊群体管理模块2.4 案件信息管理模块2.5 物资补助模块 三、系统设计3.1 用例设计3.2 数据库设计3.2.1 人口表3.2.2 扶贫表3.2.3 特殊群体表3.2.4 案件表3.2.5 物资补助表 四…...
进程地址空间(比特课总结)
一、进程地址空间 1. 环境变量 1 )⽤户级环境变量与系统级环境变量 全局属性:环境变量具有全局属性,会被⼦进程继承。例如当bash启动⼦进程时,环 境变量会⾃动传递给⼦进程。 本地变量限制:本地变量只在当前进程(ba…...
Oracle查询表空间大小
1 查询数据库中所有的表空间以及表空间所占空间的大小 SELECTtablespace_name,sum( bytes ) / 1024 / 1024 FROMdba_data_files GROUP BYtablespace_name; 2 Oracle查询表空间大小及每个表所占空间的大小 SELECTtablespace_name,file_id,file_name,round( bytes / ( 1024 …...

CocosCreator 之 JavaScript/TypeScript和Java的相互交互
引擎版本: 3.8.1 语言: JavaScript/TypeScript、C、Java 环境:Window 参考:Java原生反射机制 您好,我是鹤九日! 回顾 在上篇文章中:CocosCreator Android项目接入UnityAds 广告SDK。 我们简单讲…...

Springcloud:Eureka 高可用集群搭建实战(服务注册与发现的底层原理与避坑指南)
引言:为什么 Eureka 依然是存量系统的核心? 尽管 Nacos 等新注册中心崛起,但金融、电力等保守行业仍有大量系统运行在 Eureka 上。理解其高可用设计与自我保护机制,是保障分布式系统稳定的必修课。本文将手把手带你搭建生产级 Eur…...

【配置 YOLOX 用于按目录分类的图片数据集】
现在的图标点选越来越多,如何一步解决,采用 YOLOX 目标检测模式则可以轻松解决 要在 YOLOX 中使用按目录分类的图片数据集(每个目录代表一个类别,目录下是该类别的所有图片),你需要进行以下配置步骤&#x…...
【python异步多线程】异步多线程爬虫代码示例
claude生成的python多线程、异步代码示例,模拟20个网页的爬取,每个网页假设要0.5-2秒完成。 代码 Python多线程爬虫教程 核心概念 多线程:允许程序同时执行多个任务,提高IO密集型任务(如网络请求)的效率…...
【RockeMQ】第2节|RocketMQ快速实战以及核⼼概念详解(二)
升级Dledger高可用集群 一、主从架构的不足与Dledger的定位 主从架构缺陷 数据备份依赖Slave节点,但无自动故障转移能力,Master宕机后需人工切换,期间消息可能无法读取。Slave仅存储数据,无法主动升级为Master响应请求ÿ…...
大数据学习(132)-HIve数据分析
🍋🍋大数据学习🍋🍋 🔥系列专栏: 👑哲学语录: 用力所能及,改变世界。 💖如果觉得博主的文章还不错的话,请点赞👍收藏⭐️留言Ǵ…...
docker 部署发现spring.profiles.active 问题
报错: org.springframework.boot.context.config.InvalidConfigDataPropertyException: Property spring.profiles.active imported from location class path resource [application-test.yml] is invalid in a profile specific resource [origin: class path re…...

R语言速释制剂QBD解决方案之三
本文是《Quality by Design for ANDAs: An Example for Immediate-Release Dosage Forms》第一个处方的R语言解决方案。 第一个处方研究评估原料药粒径分布、MCC/Lactose比例、崩解剂用量对制剂CQAs的影响。 第二处方研究用于理解颗粒外加硬脂酸镁和滑石粉对片剂质量和可生产…...