JavaEE-网络编程(2)

目录

1. TCP的socket api

1.1 ServerSocket

1.2 Socket

1.3 关于连接

2. 写一个TCP回显服务器

代码的基本结构

2.1.建立连接

2.2 使用 try catch 语法

2.3 对操作流进行封装

2.4 使用 flush() 冲刷缓冲区

2.5 用 close() 关闭对客户端的连接

2.6 println 和 hasnext

3. TCP 客户端

4.一个服务器同时给多个客户端服务

4.1 现象以及原因分析

4.2 优化方案:创建多线程

4.3 利用线程池进一步优化


1. TCP的socket api

1.1 ServerSocket

ServerSocket 是专门给服务器用的

构造方法如下:

与UDP类似,服务器启动,需要先绑定端口号

建立连接的方法如下:

与 UDP 不同,UDP 的特性是“无连接”,TCP 是“有连接”

这里的 accept 是联通连接的关键操作,返回值是一个 Socket 类型,需要用 Socket 类来接收

1.2 Socket

Socket 是服务器和客户端都会用的

构造方法:


用于接收请求和发送响应的方法:

TCP并没有 send / receive 这样的操作,使用的是字节流来进行接收和响应

TCP 的一个核心特点就是,面向字节流

读写数据的基本单位就是字节 byte 

(UDP的单位是一个数据报)


关闭对客户端的连接:


1.3 关于连接

连接可以理解为打电话,客户端给服务器打电话,当电话打通了,就是建立了连接。

一旦打通了电话,就可以说话,说话可以说一句就挂断,也可以说很多句再挂断。

对于 TCP 服务器也是如此,一旦进行连接,就可以发送一个或者多个请求,直到想结束了,再断开连接。

2. 写一个TCP回显服务器

代码的基本结构

直接看代码:

public class TcpEchoServer {private ServerSocket serverSocket=null;public TcpEchoServer(int port) throws IOException {this.serverSocket = new ServerSocket(port);}//服务器连接客户端public void start() throws IOException {System.out.println("服务器启动!");//不断的接收客户端的连接请求while(true){//与客户端进行连接//如果没有客户端发送请求,accept会进入阻塞状态Socket clientSocket = serverSocket.accept();processConnection(clientSocket);}}//连接成功之后为客户端进行服务private void processConnection(Socket clientSocket) throws IOException {System.out.printf("[%s:%d] 客户端已上线!\n",clientSocket.getInetAddress(),clientSocket.getPort());try(InputStream inputStream=clientSocket.getInputStream();//结束之后会自动关闭输入流OutputStream outputStream=clientSocket.getOutputStream()//结束之后会自动关闭输出流){//针对 InputerStream 套了一层Scanner scanner=new Scanner(inputStream);//针对 OutputStream 套了一层PrintWriter writer=new PrintWriter(outputStream);//三个步骤while(true){if(!scanner.hasNext()){System.out.printf("[%s:%d] 客户端已下线!\n",clientSocket.getInetAddress(),clientSocket.getPort());break;}//1.接收请求//byte[] request=new byte[1024];//inputStream.read(request);String request=scanner.next();//2.根据请求计算响应String response=process(request);//3.发送响应writer.println(response);writer.flush();//4.打印日志System.out.printf("[%s:%d] req:%s  resp:%s \n",clientSocket.getInetAddress(),clientSocket.getPort(),request,response);}}catch (IOException e){throw new RuntimeException();}finally {clientSocket.close();}}private String process(String request) {return request;}public static void main(String[] args) throws IOException {TcpEchoServer tcpEchoServer=new TcpEchoServer(9090);tcpEchoServer.start();}
}

【注意】

2.1.建立连接

建立连接的时候要使用serverSocket.accept(),该方法的返回值是一个Socket类型,当成功建立连接后,再用 Socket 类的方法来操作字节流,进行接收请求和发送响应

2.2 使用 try catch 语法

操作打开后需要进行close操作,此处可以使用 try catch 语法将其包裹,在try()内打开,当try内的代码块执行完毕后就会自动关闭

2.3 对操作流进行封装

可以直接使用 inputStream.read 来读取请求的数据

也可以使用Scanner对inputStream进行封装

实际上,Scanner 的构造方法,填入的其实是一个 InputStream 对象:

我们平时使用的 System.in 其实就是一个 InputStream 类型:

也就是说,当 Scanner 的构造方法中,传入的是 System.in 的输入流时,就进行系统输入

当传入的是 clientSocket.getInputStream 的输入流时,就进行网络输入。

同理,outputStream 也可以使用 PrintWriter 进行封装:

2.4 使用 flush() 冲刷缓冲区

writer.println(request) 这个操作只是把数据放到“发送缓冲区”(内存空间)中,还没有真正写入到网卡里

要调用 flush()方法来“冲刷缓冲区”,才能真正发送数据

如果没有冲刷,是发送不出去的:

2.5 用 close() 关闭对客户端的连接

我在上篇文章说过:是否要对一个程序进行 close 操作,这取决于这个程序的生命周期

而此处 clientSocket 的生命周期就是从连接成功断开连接,即使客户端与服务器断开了连接,服务器也依旧在运转,等待连接下一个客户端,也就是说,clientSocket 并没有伴随整个进程的始终

因此, clientSocket 是需要进行 close() 操作的

此处的 close() 操作应当放入 finally{} 代码块中,这样一来,无论程序如何执行,close() 操作都一定会被执行到。

2.6 println 和 hasnext

println 后面这个ln,行为是给字符串末尾加上\n

当把 ln 去掉,把 println 改成 print ,会发现服务器无法返回响应:

事实上是,客户端确实成功向服务器发送了数据,服务器也收到了数据,但是服务器却没有处理。

原因:

hasNext() 的行为是判定收到的数据中是否包含“空白符”(换行,回车,空格,制表符,翻页符...)

当遇到空白符,才会认为是一个“完整的next”,在遇到之前,都会阻塞

UDP 是以 DatagramPacket 作为单位的。

TCP 则是以 字节 为单位

但是实际上,一个请求往往是由多个字节构成的

此时就需要一个标记来区分,到底多少字节为一个“完整的请求”

于是就暗暗约定,一个请求/响应 使用 \n 作为结束标记

对端读的时候,也是读到 \n 就结束(认为读到了一个“完整的请求”)

3. TCP 客户端

public class TcpEchoClient {Socket socket=null;public TcpEchoClient(String serverIp,int port) throws IOException {this.socket = new Socket(serverIp,port);}public void start(){Scanner scanner=new Scanner(System.in);try(InputStream inputStream=socket.getInputStream();OutputStream outputStream=socket.getOutputStream()) {//针对 InputerStream 套了一层Scanner scannerNet=new Scanner(inputStream);//针对 OutputStream 套了一层PrintWriter writer=new PrintWriter(outputStream);while(true){//1.向服务器发送请求System.out.println("请输入要发送的内容:");String request=scanner.next();writer.println(request);writer.flush();//2.接收服务器的响应String response=scannerNet.next();//3.打印响应System.out.println(response);}}catch (IOException e){throw new RuntimeException(e);}}public static void main(String[] args) throws IOException {TcpEchoClient tcpEchoClient=new TcpEchoClient("127.0.0.1",9090);tcpEchoClient.start();}
}

4.一个服务器同时给多个客户端服务

4.1 现象以及原因分析

运行服务器,再运行客户端,客户端可以正常与服务器进行通信:


Tips:如何运行多个main?


如果在上述的基础上,再运行一个客户端,会发现第二个客户端无法正常通信:

原因:

 

服务器必须与客户端连接上了,才能够给客户端提供服务

然而此处服务器代码运行到 processConnection(clientSocket) 这一行时就没再往下执行了

因为当前线程还在为第一个客户端提供服务,所以后续客户端想要与服务器创建连接,必须阻塞等待

4.2 优化方案:创建多线程

当一个客户端与服务器成功连接时,创建一个新的线程,让这个新的线程单独服务这个客户端

此时的main线程就不需要进入 processConnection 方法内部,而是直接进入下一轮while循环

当代码运行到 serverSocket.accept() 时,如果有新的客户端请求连接,就创建连接

如果没有新的客户端请求连接,就阻塞等待,直到有新的客户端请求与服务器连接

    public void start() throws IOException {System.out.println("服务器启动!");//不断的接收客户端的连接请求while(true){//与客户端进行连接//如果没有客户端发送请求,accept会进入阻塞状态Socket clientSocket = serverSocket.accept();//创建新的线程,让新的线程来为客户端提供服务,使得当前main线程能够空闲下来,为客户端和服务器创建连接Thread thread=new Thread(()->{try {processConnection(clientSocket);} catch (IOException e) {throw new RuntimeException(e);}});thread.start();}}

再次启动服务器和两个客户端:

服务器可以同时为两个客户端提供服务

4.3 利用线程池进一步优化

当前这个程序,有客户端连接上,就创建一个新的线程。客户端断开,就销毁一个线程。

而创建和销毁线程实际上是很消耗资源的操作。

因此可以引用线程池来解决这个问题:

    public void start() throws IOException {System.out.println("服务器启动!");ExecutorService executorService= Executors.newCachedThreadPool();//不断的接收客户端的连接请求while(true){//与客户端进行连接//如果没有客户端发送请求,accept会进入阻塞状态Socket clientSocket = serverSocket.accept();//使用线程池executorService.submit(()->{processConnection(clientSocket);});}}

如果哪里有疑问的话欢迎来评论区指出和讨论,如果觉得文章有价值的话就请给我点个关注还有免费的收藏和赞吧,谢谢大家!

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

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

相关文章

2.5D视觉——Aruco码定位检测

目录 1.什么是Aruco标记2.Aruco码解码说明2.1 Original ArUco2.2 预设的二维码字典2.3 大小Aruco二维码叠加 3.函数说明3.1 cv::aruco::detectMarkers3.2 cv::solvePnP 4.代码注解4.1 Landmark图说明4.2 算法源码注解 1.什么是Aruco标记 ArUco标记最初由S.Garrido-Jurado等人在…

云厂商双十一,无新可拉

失去意义的促销秀。 作者|文昌龙 编辑|杨舟 与电商平台双十一的“低价诱惑”和套路满满不同,云市场的双十一更像是一个买方市场,客户牢牢掌握主导权,厂商不得不低头争抢每一位潜在客户。 电商平台「双11」的本质,初始来看&…

Spring Boot出现java: 错误: 无效的源发行版:16的解决方式

第一步: 修改为SDK的目标字节码版本 第二步:CtrlShiftAltS进入项目结构 第三步:pom.xml文件中 在网上搜索和自己SDK适配的Springboot版本,1.8对应的是2.7.1(可以用) 修改Java版本为1.8 最后的最后&a…

删除k8s 或者docker运行失败的脚本

vi delete_exited_containers.sh#!/bin/bash# 列出所有停止的容器并存储到数组 list_exited_containers() {echo -e "\nStopped containers:"containers()# 获取停止的容器信息并存入数组while IFS read -r line; docontainers("$line")done < <(do…

Java之Spring MVC篇三

​​​​​​​ 目录 响应 返回静态页面 RestController 和 Controller的区别和联系 返回数据ResponseBody 关于ResponseBody 返回HTML代码片段 返回JSON 设置状态码 设置Header 设置Content-Type 没设置Content-Type之前 设置Content-Type之后 响应 返回静态页面…

Revisiting Prompt Engineering via Declarative Crowdsourcing

文章目录 题目摘要简介LLMS 和众包声明式提示工程讨论结论 题目 通过声明式众包重新审视快速工程 论文地址&#xff1a;https://arxiv.org/abs/2308.03854 摘要 大型语言模型 (LLM) 在理解和生成文本形式的数据方面非常强大&#xff0c;但很脆弱且容易出错。出现了以所谓的提…

数据库概述

1.为什么要使用数据库 使用数据库有以下几个重要原因&#xff1a; 数据的集中管理&#xff1a;数据库可以集中管理和存储大量的数据&#xff0c;而不需要将数据分散保存在不同的文件中。这样可以方便地对数据进行访问、修改和更新。 数据的持久化存储&#xff1a;数据库通过将…

嵌入式驱动开发详解1(系统调用)

文章目录 符设备驱动架构read函数详解用户层read函数内核层read函数 具体实现用户层代码 内核层代码细节分析 符设备驱动架构 如上图所示&#xff0c;应用层程序直接用系统提供的API函数即可调用驱动层相应的函数&#xff0c;中间的具体过程都是由linux内核实现的&#xff0c;…

开源 - Ideal库 - 枚举扩展设计思路及实现难点(三)

今天想和大家分享关于枚举扩展设计思路和在实现过程中遇到的难点。 01、设计思路 设计思路说起来其实也很简单&#xff0c;就是通过枚举相关信息&#xff1a;枚举值、枚举名、枚举描述、枚举项、枚举类型&#xff0c;进行各种转换&#xff0c;通过一个信息获取其他信息。比如通…

学习笔记021——Ubuntu 安装 MySQL 5.7版本

本文通过是Ubuntu自带的apt安装的。 目录 1、查看可安装 MySQL 版本 2、安装 3、设置密码、开启远程访问 4、修改 sql_mode 和 设置 不区分大小写。&#xff08;根据自己需求来定&#xff09; 5、改端口等设置&#xff1a; 6、启动命令 7、验证 1、查看可安装 MySQL 版…

IDE配置tomcat

1.导航到 Tomcat 安装目录 E:\apache-tomcat-9.0.95-windows-x64\apache-tomcat-9.0.95 2.启动 Tomcat 服务&#xff1a;bin\startup.bat

STL关联式容器之平衡二叉搜索树

平衡二叉搜索树 在STL关联式容器介绍-CSDN博客中对二叉搜索树做了简要的描述&#xff1b;但是因为没有对二叉搜索树对数的深度及插入后树的结构进行调整&#xff0c;二叉搜索树可能失去平衡&#xff0c;造成搜寻效率低落的情况。如下所示&#xff1a; 所谓树形平衡与否&#xf…

集群聊天服务器(13)redis环境安装和发布订阅命令

目录 环境安装订阅redis发布-订阅的客户端编程环境配置客户端编程 环境安装 sudo apt-get install redis-server 先启动redis服务 /etc/init.d/redis-server start默认在6379端口上 redis是存键值对的&#xff0c;还可以存链表、数组等等复杂数据结构 而且数据是在内存上存…

微信小程序 最新获取用户头像以及用户名

一.在小程序改版为了安全起见 使用用户填写来获取头像以及用户名 二.代码实现 <view class"login_box"><!-- 头像 --><view class"avator_box"><button wx:if"{{ !userInfo.avatarUrl }}" class"avatorbtn" op…

视频智能分析软件LiteAIServer视频智能分析平台玩手机打电话检测算法

在当今这个数字化时代&#xff0c;智能手机已成为我们日常生活中不可或缺的一部分&#xff0c;它极大地便利了我们的沟通与学习。然而&#xff0c;当这份便利被不恰当地带入到如工厂生产线、仓库以及学校课堂等特定的工作和学习环境中时&#xff0c;其潜在的危害便逐渐显露出来…

【pytest】pytest注解使用指南

前言&#xff1a;在 pytest 测试框架中&#xff0c;注解&#xff08;通常称为装饰器&#xff09;用于为测试函数、类或方法提供额外的信息或元数据。这些装饰器可以影响测试的执行方式、报告方式以及测试的组织结构。pytest 提供了多种内置的装饰器&#xff0c;以及通过插件扩展…

gvim添加至右键、永久修改配置、放大缩小快捷键、ctrl + c ctrl +v 直接复制粘贴、右键和还原以前版本(V)冲突

一、将 vim 添加至右键 进入安装目录找到 vim91\install.exe 管理员权限执行 Install will do for you:1 Install .bat files to use Vim at the command line:2 Overwrite C:\Windows\vim.bat3 Overwrite C:\Windows\gvim.bat4 Overwrite C:\Windows\evim.bat…

一文快速掌握 AMD FPGA IO约束 常用电平标准

FPGA开发中IO约束是不可缺少的部分&#xff0c;正确的电平约束是确保电路稳定运行与兼容性的关键所在。 今天分享下IO约束中常用的电平标准&#xff0c;帮助大家快速理解和掌握。 一、 LVTTL系列 LVTTL全称为Low - Voltage Transistor - Transistor Logic&#xff0c;是一种…

没钱买KEGG怎么办?REACTOME开源通路更强大

之前搜集免费生物AI插图时简单提到了通路数据库Reactome&#xff08;https://reactome.org/&#xff09;&#xff0c; 那些精美的生物插图只能算是该数据库附赠的小礼品&#xff0c;他的主要功能还是作为一个开源的通路数据库&#xff0c;为相关领域的研究者提供直观的可视化生…

ubuntu显示管理器_显示导航栏

ubuntu文件管理器_显示导航栏 一、原始状态&#xff1a; 二、显示导航栏状态&#xff1a; 三、原始状态--->导航栏状态: 1、打开dconf编辑器&#xff0c;直接在搜索栏搜索 dconf-editor ------如果没有安装&#xff0c;直接按流程安装即可。 2、进入目录&#xff1a;org …