初始JavaEE篇 —— 网络编程(2):了解套接字,从0到1实现回显服务器

 找往期文章包括但不限于本期文章中不懂的知识点:

个人主页:我要学编程程(ಥ_ಥ)-CSDN博客

所属专栏:JavaEE

目录

 TCP 与 UDP

Socket套接字

UDP

TCP


 

网络基础知识   在一篇文章中,我们了解了基础的网络知识,网络的出现就是为了不同机器之间进行通信从而实现资源共享。现如今我们使用网络进行的一系列操作,打游戏、网上购物、网上聊天等都是客户端与服务器之间通信,准确的来说是多个客户端之间通过服务器这个平台来实现通信。而今天我们就是要来实现一个最简单的服务器与客户端。在此之前还得了解一些基本概念。

 TCP 与 UDP

上文了解了 TCP/IP 五层协议的基本分层,在以后的日常开发中,写的一些应用程序都是工作在应用层,而应用层是基于传输层的,我们也是需要了解传输层的传输协议的,主要是两个协议:TCP协议 与 UDP 协议。 

TCP 是 有连接、可靠传输、面向字节流、全双工。

UDP 是 无连接、不可靠传输、面向数据报、全双工。

连接:是指通信双方是否会保存对方的信息。有连接就说明,通信的双方会保存对方的信息。

可靠传输:由于数据在经过封装之后,是通过网卡将二进制的数据传输给另一方的,这里的二进制是通过电信号或者光信号传播的,而这种传播方式肯定是会收到外界的影响,例如,太阳爆发耀斑等情况就会影响数据的传输。因此数据传输的过程中可能会失败,如果传输失败之后,有提醒重新传输的话,这就是可靠传输,反之,传输之后不管不顾了,这就是不可靠传输。

面向字节流与面向数据报是指两者的数据传输的方式不一样,虽然最终通过网卡出去的数据都是二进制的,但是在通过传输层时,会根据协议的不同,而选择不同的方式。使用UDP传输时,就需要将数据封装成数据包的形式继续传给下一层。

全双工:是指数据既可以从一方传向另一方,也可以从另一方传向这一方,也就是和车流量一样,既有从左到右的车流,也有从右到左的车流。与之相反的一个名词是:半双工,这个就和管中的水流一样,只能从一方流向另一方,而不能从同时有两个方向的水流。

了解了TCP 与 UDP 的基本点之后,还需要了解 JVM对于操作系统提供的API封装后的结果,毕竟我们通过Java代码来编写网络编程时,是直接使用Java标准库中提供的类。

Socket套接字

Socket套接字,是由操作系统提供用于网络通信的技术,是基于TCP/IP协议的网络通信的基本操作单元。 基于Socket套接字的网络程序开发就是网络编程。而经过JVM封装之后,就主要是针对 TCP 和 UDP 的。

UDP

java中使用UDP协议通信,主要基于DatagramSocket 类来创建数据报套接字,并使用
DatagramPacket作为发送或接收的UDP数据报。

因为操作系统为了方面更好的管理系统资源(包括硬件资源),所以操作系统采用了文件管理的方式来管理这些资源,这也就意味着某个应用程序去使用这些资源时,就和使用文件资源没什么区别了,也就是打开文件、使用文件、关闭文件。因此网卡资源的使用也是如此。

1、打开网卡资源

2、进行读写操作

3、关闭网卡资源

下面就来学习相关方法:

DatagramSocket 是UDP Socket,用于发送和接收UDP数据报。

构造方法说明
DatagramSocket()创建一个UDP数据报套接字的Socket,绑定到本机任意一个随机端口 (一般用于客户端)
DatagramSocket(int port)创建一个UDP数据报套接字的Socket,绑定到本机指定的端口 (一般用于服务器)
普通方法说明
void receive(DatagramPacket p)从此套接字接收数据报(如果没有接收到数据报,该方法会阻塞等待)
void send(DatagramPacket p)从此套接字发送数据报包(不会阻塞等待,直接发送)
void close()关闭此数据报套接字

这里的数据报套接字我们可以简单的理解为网卡资源,receive方法就是通过网卡接收数据,send方法就是通过网卡发送数据。 构造方法是在打开网卡资源,close方法就是在关闭网卡资源。

DatagramPacket是UDP Socket发送和接收的数据报。

注意区分上述两个概念:DatagramSocket 是用来传送与接收数据报的,而DatagramPacket 是数据报本身的一层封装,简单理解就是数据报本身。生活中的例子,就是DatagramSocket 是属于快递站,而DatagramPacket 是属于包裹。包裹要通过快递站的分拣传递出去。

构造方法说明
DatagramPacket(byte[] buf, int length)构造一个DatagramPacket以用来接收数据报,接收的数据保存在字节数组(第一个参数buf)中,接收指定长度 (第二个参数length)
DatagramPacket(byte[] buf, int offset, int length,SocketAddress address) 构造一个DatagramPacket以用来发送数据报,发送的数据为字节数组(第一个参数buf)中,从offset到指定长
度(第二个参数length)。address指定目的主机的IP
和端口号
具体方法说明
InetAddress getAddress()从接收的数据报中,获取发送端主机IP地址;或从发送的数据报中,获取接收端主机IP地址
int getPort()从接收的数据报中,获取发送端主机的端口号;或从
发送的数据报中,获取接收端主机端口号
byte[] getData()获取数据报中的数据

 由于UDP是无连接的,因此构造UDP发送的数据报时,需要传入 SocketAddress ,该对象可以使用 InetSocketAddresS 来创建。即 InetSocketAddress 是 SocketAddress 的子类。

构造方法说明
InetSocketAddress(InetAddress addr, int port)创建一个Socket地址,包含IP地址和端口号

先来理解服务器与客户端这个两个名词的含义: 

举个例子:我们去学校食堂吃饭时,可能某个窗口的饭菜比较好吃,那么我们下一次或者以后都有可能会去这个窗口吃饭,而这个窗口肯定是一直在这个食堂的某个固定地点的,而这个窗口所服务的学生不是固定的,每个学生去吃饭时,肯定也是随机选择的座位坐下来吃饭。

针对上面的情况,食堂的窗口就是服务器,吃饭的学生就是客户端,客户端会给服务器提供请求(我们会把吃的菜告诉食堂阿姨),服务器会给客户端提供响应(食堂阿姨就会给我们打对应的菜)。因为服务器(食堂窗口)是需要给多个客户端提供响应,如果这个服务器的端口老是发生变化(窗口老是发生变化),那肯定是不方便客户端去访问的,因此服务器的IP与端口都是在一段时间内固定的,而客户端的端口(学生在吃饭找的座位)肯定是随机的,如果某个学生没在这里,但是他占了一个位置,那么肯定是不合理的,同样某个客户端没有启动进程访问服务器时,一直把端口号给踹在怀里肯定也是会对别的进程造成影响的(端口号是有限的)。

有了以上信息,我们就可以来写一个最简单服务器:回显服务器(接收到的请求就是响应,即接收的请求是什么,服务器返回的响应也就是什么,类似于鹦鹉学舌)。

服务器的处理逻辑:1、接收请求并解析;2、根据请求计算响应;3、将相应发送给响应的客户端;4、打印日志。

public class UdpEchoServer {// 创建网卡资源DatagramSocket socket = null;public UdpEchoServer(int port) throws SocketException {// 指定本机的一个固定端口号为服务器的端口号socket = new DatagramSocket(port);}// 启动服务器方法public void start() throws IOException {// 由于服务器是7*24小时的工作制,因此得死循环System.out.println("服务器启动成功~");while (true) {// 1、网卡接收请求并解析// 创建一个数据报来接收请求的具体内容// 数据报其实就是一个用来存储数据的包裹:字节数组+长度组成DatagramPacket requestPacket = new DatagramPacket(new byte[4096], 4096);socket.receive(requestPacket); // 将得到的数据存储在数据报的字节数组中// 将数据报中的内容转成字符串为后续处理做准备String request = new String(requestPacket.getData(), 0, requestPacket.getLength()); // 数据的有效长度// 2、根据请求计算响应String response = process(request);// 3、将响应返回给客户端// 也是通过数据报的形式// 由于UDP是无连接的,因此我们得手动去设置发送的IP与端口号DatagramPacket responsePacket = new DatagramPacket(response.getBytes(), 0, response.getBytes().length,requestPacket.getAddress(), requestPacket.getPort());socket.send(responsePacket);// 4、打印日志:客户端IP、客户端端口号、请求、响应System.out.printf("[%s %d]  request:%s  response:%s\n", requestPacket.getAddress(),requestPacket.getPort(), request, response);}}// 后续如果要修改服务器的功能,就只需要重载process方法即可private String process(String request) {return request; // 回显服务器的功能}public static void main(String[] args) throws IOException {// 创建一个服务器实例并启动服务器UdpEchoServer server = new UdpEchoServer(9090);server.start();}
}

有了服务器之后,就可以来创建客户端程序了。

public class UdpEchoClient {DatagramSocket socket = null;// UDP是不连接,因此客户端得保存对应服务器的IP与端口号private String serverIP = null;private int serverPort = 0;// 指定需要访问的服务器IP与端口号public UdpEchoClient(String serverIP, int serverPort) {this.serverIP = serverIP;this.serverPort = serverPort;}public void start() throws IOException {System.out.println("客户端启动成功(exit退出)~");// 创建网卡资源socket = new DatagramSocket();while (true) {// 1、开始接收用户的输入Scanner scanner = new Scanner(System.in);String request = scanner.nextLine();if (request.equals("exit")) {socket.close(); // 释放网卡资源System.out.println("客户端成功退出~")break;}// 2、将输入数据打包成数据报 (指定服务器IP与端口号)DatagramPacket requestPacket = new DatagramPacket(request.getBytes(), 0 ,request.getBytes().length, InetAddress.getByName(serverIP), serverPort);// 3、然后再给到服务器socket.send(requestPacket);// 4、接收服务器的响应DatagramPacket responsePacket = new DatagramPacket(new byte[4096], 4096);socket.receive(responsePacket);// 5、处理响应:打印响应的结果String response = new String(responsePacket.getData(), 0, responsePacket.getLength()); // 有效的长度System.out.println(response);}}public static void main(String[] args) throws IOException {// 指定对应服务器的IP与端口号UdpEchoClient client = new UdpEchoClient("127.0.0.1", 9090);client.start();}
}

注意:127.0.0.1 这就是代指当前机器的IP。 

运行结果:

客户端:

服务器:

由上图可知,客户端的运行与否和服务器没什么关系,服务器在正常运行的情况下会一直记录客户端的访问信息。 

下面就来使用另外一种协议来实现回显服务器:

TCP

ServerSocket 是创建TCP的服务器Socket的APl。

ServerSocket :

构造方法说明
ServerSocket(int port)创建一个服务器流套接字Socket,并绑定到指定端口
具体方法说明
Socket accept()开始监听指定端口(创建时绑定的端口),有客户端
连接后,返回一个服务端Socket对象,并基于该
Socket建立与客户端的连接,否则阻塞等待
void close()关闭此套接字

Socket:

Socket是客户端Socket,或服务器中接收到客户端建立连接(accept方法)的请求后,返回的服务器Socket。不管是客户端还是服务器Socket,都是双方建立连接以后,保存的对端信息,及用来与对方收发数据的。

构造方法说明
Socket(String host, int port)创建一个客户端流套接字Socket,并与对应IP的主机上,对应端口的进程建立连接

 这里的构造方法有很多,但是常用的就是通过 String类型的host 来建立连接的。

具体方法说明
InetAddress getlnetAddress()返回套接字所连接的地址(对端)
int getPort()返回套接字所连接的端口号(对端)
InputStream getlnputStream()返回此套接字的输入流
OutputStream getOutputStream()返回此套接字的输出流

对端:这个概念是相对的,站在服务器的角度,对端是指客户端;站在客户端的角度,对端指的是服务器。当然,也是可以获取本地程序的地址和端口号的, 使用的是 getLocalPort ,站在服务器的角度,获取的就是服务器自己所在端口。

这里的ServerSocket 可以理解为网卡资源,而Socket 就是保存TCP连接双方的连接。服务器的连接有很多个,因此我们需要为其申请网卡资源来随时获取新的连接。而客户端只需要和服务器连接即可,因此只需要去尝试申请对应的IP地址与端口号进行连接即可。

总体的实现思路还是和上面的UDP差不多,但是具体的实现方式有不同。

服务器:

public class TcpEchoServer {private ServerSocket serverSocket;public TcpEchoServer(int port) throws IOException {serverSocket = new ServerSocket(port);}public void start() throws IOException {System.out.println("服务器启动成功~");while (true) {System.out.println("等待客户端连接...");Socket socket = serverSocket.accept();System.out.println("客户端连接成功:" + socket.getInetAddress() + ":" + socket.getPort());// 处理客户端连接,进入通信过程handleClient(socket);}}private void handleClient(Socket socket) throws IOException {try (InputStream inputStream = socket.getInputStream();OutputStream outputStream = socket.getOutputStream()) {while (true) {byte[] buffer = new byte[4096];int len = 0;StringBuilder sb = new StringBuilder();// 循环读取客户端请求并响应while ((len = inputStream.read(buffer)) != -1) {sb.append(new String(buffer, 0, len));if (sb.toString().contains("\n")) {// 检测到换行符,认为请求结束break;}}String request = sb.toString();// 根据请求计算响应String response = process(request);// 先判断连接是否终止了if (socket.isClosed()) {return;}// 将响应返回给客户端outputStream.write(response.getBytes());outputStream.flush(); // 刷新缓冲区}} finally {socket.close();System.out.println("客户端已断开连接");}}private String process(String request) {return request+"\n";}public static void main(String[] args) throws IOException {TcpEchoServer server = new TcpEchoServer(9090);server.start();}
}

客户端:

public class TcpEchoClient {private String serverIp;private int serverPort;public TcpEchoClient(String serverIp, int serverPort) {this.serverIp = serverIp;this.serverPort = serverPort;}public void start() throws IOException {try (Socket socket = new Socket(serverIp, serverPort);OutputStream outputStream = socket.getOutputStream();InputStream inputStream = socket.getInputStream();Scanner scanner = new Scanner(System.in)) {System.out.println("客户端连接服务器成功~");// 循环发送请求并接收响应while (true) {System.out.print("输入请求数据(exit退出): ");// 加上换行符,让服务器在读取数据时,知道这个是结束的标志String request = scanner.nextLine()+"\n";if (request.equals("exit\n")) { // 因为手动加上了换行符,因此判断也要加上System.out.println("客户端请求断开连接");break;}// 发送请求数据outputStream.write(request.getBytes());outputStream.flush(); // 刷新缓冲区,更好地让数据发送// 接收服务器响应byte[] buffer = new byte[4096];StringBuilder responseBuilder = new StringBuilder();int len = 0;// 使用while循环读取直到服务器停止发送while ((len = inputStream.read(buffer)) != -1) {responseBuilder.append(new String(buffer, 0, len));if (responseBuilder.toString().contains("\n")) { // 检测到换行符,认为响应完整break;}}// 打印完整的响应String response = responseBuilder.toString();System.out.print("接收到服务器响应: " + response);}}}public static void main(String[] args) throws IOException {TcpEchoClient client = new TcpEchoClient("127.0.0.1", 9090);client.start();}
}

注意:

1、 因为TCP是字节流,因此我们使用的是前面文件IO操作的字节流来进行发送与读取数据。但方式略微不同,我们需使用连接获取字节输入流与字节输出流。

2、由于这里的输入输出流是建立在连接之上的,我们不知道什么时候输入与输出结束,因此我们得手动地去设置结束标志或者使用socket的shoudownOutput,后者不推荐使用,后者是直接关闭了输出流,从而导致连接中断,可能会影响后续程序逻辑的执行,而前者是我们手动地去使用标记符来判断,这样的处理更好。

3、对于资源的关闭,也应该即使去做,这里是Socket、InputStream、OutputStream等资源都需要我们手动地去关闭,防止造成资源泄露,特别是Socket资源,可能会有非常多个客户端要建立连接,但是资源有限,因此会阻塞等待后面的,如果不释放的话,就导致后续客户端无法申请到。

上述代码虽然能够达到基本的运行效果,但是还存在部分缺陷(TCP的代码):

1、同一时刻只能有一个客户端去执行服务器的逻辑,因为我们在处理请求时,也是使用的一个循环,因此这里就会导致服务器的逻辑卡在了处理请求的代码中,而不会去尝试建立新的连接。

解决方法:多线程。将处理请求的代码放到一个新的线程中,这样后续的客户端都只会占用别的线程,而不会占用main线程。

2、在引入多线程的基础上,又有一个新的问题来了:如果客户端的请求非常简单(回显这种),且同一时刻有非常多的客户端去申请服务器为其服务的话,这时候就会出现线程频繁地创建与删除,这就会导致服务器的性能比较低,因此我们可以创建一个线程池来解决上述问题。

以上就是使用TCP与UDP实现网络通信的基本过程,后面我们在学习TCP与UDP的通信保障与具体实现等。

好啦!本期 初始JavaEE篇 —— 网络编程(2):了解套接字,从0到1实现回显服务器 的学习之旅就到此结束啦!我们下一期再一起学习吧!

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

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

相关文章

【月之暗面kimi-注册/登录安全分析报告】

前言 由于网站注册入口容易被黑客攻击,存在如下安全问题: 暴力破解密码,造成用户信息泄露短信盗刷的安全问题,影响业务及导致用户投诉带来经济损失,尤其是后付费客户,风险巨大,造成亏损无底洞 …

架构师备考-概念背诵(软件工程)

软件工程 软件开发生命周期: 软件定义时期:包括可行性研究和详细需求分析过程,任务是确定软件开发工程必须完成的总目标,具体可分成问题定义、可行性研究、需求分析等。软件开发时期:就是软件的设计与实现,可分成概要设计、详细设计、编码、测试等。软件运行和维护:就是…

[FBCTF 2019]rceservice 详细题解

知识点: json字符串 PHP正则表达式元字符 PCRE回溯机制绕过正则表达式 %0a 换行符绕过正则表达式(详细讲解) 提示 Enter command as JSON 题目还有一个附件,打开是index.php文件源码 <?php putenv(PATH/home/rceservice/jail); if (isset($_REQUEST[cmd])) {$json $_…

【竞技宝】DOTA2-梦幻联赛S24:圣剑美杜莎强拆基地终结比赛

北京时间11月9日,DOTA2的梦幻联赛S24继续进行。本日迎来第二阶段的B组二、三名加赛PARI对阵spirit。本场比赛双方前两局战至1-1平,决胜局同样是难分胜负打到了六十分钟之后,关键时刻spirit主动出击,圣剑美杜莎强拆基地成功一波结束比赛,最终spirit让一追二击败PARI。以下是本场…

计算机的错误计算(一百四十九)

摘要 探讨 MATLAB 中 的计算精度问题。当 为含有小数的大数或整数附近数时&#xff0c;输出会有错误数字。 例1. 已知 计算 直接贴图吧&#xff1a; 另外&#xff0c;16位的正确值分别为 0.6374239897486897e0、-0.6613118653236519e0、0.3769911184298822e-5 与…

力扣 多数元素

用了排序跟抵消。 题目 由题可知&#xff0c;多数元素是指在数组中出现次数大于一半的元素&#xff0c;且总是存在多数元素。不难想到&#xff0c;把数组排序后&#xff0c;这个数组的中间数一定是这个要找的元素。 用了sort排序&#xff0c;时间复杂度O&#xff08;nlogn&am…

Oracle OCP认证考试考点详解082系列11

题记&#xff1a; 本系列主要讲解Oracle OCP认证考试考点&#xff08;题目&#xff09;&#xff0c;适用于19C/21C,跟着学OCP考试必过。 51. 第51题&#xff1a; 题目 51.View the Exhibit and examine the description of the tables You execute this SQL statement Whi…

前端小知识:如何理解这个新特性 ?= 运算符

在日常的JavaScript开发中&#xff0c;我们经常会处理一些异步任务&#xff0c;避免代码出错&#xff0c;这时候常见的工具就是 try-catch 块和 async-await 语法。这些工具虽好&#xff0c;但当我们代码量一多&#xff0c;整个代码结构可能会显得很臃肿&#xff0c;阅读起来也…

Redhat切换其他源

1. 效果图 2. 安装 RPM 包的命令 rpm -ivh --nodeps --force epel-release-latest-8.noarch.rpm rpm -ivh --nodeps --force yum-4.7.0-4.el8.noarch.rpm rpm -ivh --nodeps --force yum-utils-4.0.21-3.el8.noarch.rpm 3. 修改默认源 vi /etc/yum.repos.d/redhat.repo[BaseO…

如何使用OpenCV和Python进行相机校准

《------往期经典推荐------》 一、AI应用软件开发实战专栏【链接】 项目名称项目名称1.【人脸识别与管理系统开发】2.【车牌识别与自动收费管理系统开发】3.【手势识别系统开发】4.【人脸面部活体检测系统开发】5.【图片风格快速迁移软件开发】6.【人脸表表情识别系统】7.【…

「Mac畅玩鸿蒙与硬件32」UI互动应用篇9 - 番茄钟倒计时应用

本篇将带你实现一个番茄钟倒计时应用&#xff0c;用户可以设置专注时间和休息时间的时长&#xff0c;点击“开始专注”或“开始休息”按钮启动计时&#xff0c;应用会在倒计时结束时进行提醒。番茄钟应用对于管理时间、提升工作效率非常有帮助&#xff0c;并且还会加入猫咪图片…

Qt/C++ 海康SDK开发示例Demo

*** 工业相机在机器视觉中起到关键作用&#xff0c;本文基于海康 SDK 详细解读了设备连接与控制的各个步骤。内容涵盖设备枚举、句柄创建、图像采集回调以及设备异常处理&#xff0c;帮助开发者快速理解如何通过代码控制相机&#xff0c;实时采集并处理图像数据。*** 1. 搜索并…

探索 Python 的新边疆:sh 库的革命性功能

文章目录 **探索 Python 的新边疆&#xff1a;sh 库的革命性功能**第一部分&#xff1a;背景介绍第二部分&#xff1a;sh 库是什么&#xff1f;第三部分&#xff1a;如何安装 sh 库&#xff1f;第四部分&#xff1a;简单库函数使用方法1. 执行 ls 命令2. 使用 grep 搜索文件内容…

深度学习——前向传播与反向传播、神经网络(前馈神经网络与反馈神经网络)、常见算法概要汇总

文章目录 &#x1f33a;深度学习面试八股汇总&#x1f33a;前向传播与反向传播前向传播&#xff08;Forward Propagation&#xff09;反向传播&#xff08;Back Propagation&#xff09;总结 神经网络简介结构类型前馈神经网络&#xff08;Feedforward Neural Network, FFNN&am…

MySQL 中的索引下推功能

看到索引&#xff0c;应该大家都可以联想到这个是和查询效率有关系的&#xff0c;既然有这个功能&#xff0c;那么那句古话说的好啊&#xff1a;存在即合理。那么这个就是说有了这个功能&#xff0c;可以提升查询效率。 什么是索引下推 我们先有一个大概的理解&#xff1a;在…

#渗透测试#SRC漏洞挖掘# 操作系统-Linux系统之基本命令、资源耗尽脚本编写

免责声明 本教程仅为合法的教学目的而准备&#xff0c;严禁用于任何形式的违法犯罪活动及其他商业行为&#xff0c;在使用本教程前&#xff0c;您应确保该行为符合当地的法律法规&#xff0c;继续阅读即表示您需自行承担所有操作的后果&#xff0c;如有异议&#xff0c;请立即停…

软考中级 软件设计师 上午考试内容笔记(个人向)Part.1

软考上午考试内容 1. 计算机系统 计算机硬件通过高/低电平来模拟1/0信息&#xff1b;【p进制】&#xff1a; K n K n − 1 . . . K 2 K 1 K 0 K − 1 K − 2... K − m K n r n . . . K 1 r 1 K 0 r 0 K − 1 r − 1 . . . K − m r − m K_nK_{n-1}...K_2K_1K_0K…

IDA*算法 Power Calculus————poj 3134

目录 闲聊 前言 DFS算法的无效搜索 BFS算法的空间浪费 IDDFS A*算法 IDA* Power Calculus 问题描述 输入 输出 问题分析 代码 闲聊 前几周在忙着数学竞赛&#xff0c;所以就没时间更新&#xff0c;高等数学&#xff0c;一生之敌&#xff0c;真不知道报名的时候我是怎么想…

基于python深度学习技术矩阵分解的推荐系统,通过学习隐含特征,实现推荐

实现了一个基于矩阵分解的推荐系统&#xff0c;用于预测用户对电影的评分。具体来说&#xff0c;该程序通过TensorFlow构建和训练一个模型&#xff0c;来学习用户和电影之间的隐含特征&#xff0c;并根据这些特征预测评分。以下是代码的主要功能和步骤的详细描述&#xff1a; …

C++高级编程(8)

八、标准IO库 1.输入输出流类 1)非格式化输入输出 2)put #include <iostream> #include <string> ​ using namespace std; int main() {string str "123456789";for (int i str.length() - 1; i > 0; i--) {cout.put(str[i]); //从最后一个字符开…