在线小说阅读系统:
功能实现
1.一级菜单:登录 注册 退出系统 2.二级菜单:查看小说列表 上传小说 下载小说 在线阅读 返回上级菜单
技术要点
1.面向对象思想 oop思想 2.TCP通信 :Socket通信(这里用TCP,其实还有UDP) 客户端不管做登录/注册/还是其他操作,用到的信息都是保存到了服务器端,那小说的信息也是在服务器端,所以客户端都要通过TCP协议,Socket通信去访问服务器,服务器再反馈信息给用户(客户端) 进行TCP/Socket通信时是基于一种流的传输 -> 输入输出流 我们需要把数据通过IO流的方式传过去 3.IO流 对象流 -> 序列化 反序列化 ?? 文件上传于下载:->用到本地流的IO流,不是用对象了,用到字节流 4.多线程:一个服务器可以针对多个客户端(用户) 当一个客户端连接过来的时候我们就创建一个线程来处理 5.用户的信息怎么存?这里不用数据库暂时,这里需要用到xml来存储用户信息,我们需要了解并掌握xml的解析(用第三方jar 和 dom4j) 6.上传和下载信息 使用配置文件的方法来配置信息 -> properties文件 这个文件如何读取,改? -> 用java.utils下的Map实现类 7.集合 信息传输是作为一个整体传输过去的,需要存到一个集合
https协议是基于TCP协议的
面试点:三次握手
1.确定客户端可以发可以收->两件事情2.服务器也要确定他可以发也可以收->两件事情
一共4件事,那为什么说是3次握手呢?因为中间的一次可以表示服务器可以发客服端可以收是在中间第二次就可以确定了
三次握手过程:
首先是客户端先发一个信息到服务器,这是能确定一件事:->客户端能发,但是不能确定服务器能收,第二次握手:就是服务器要收到客户端发过来的信息,服务器有对客户端有一个回复,所以第二次握手客户端就能自己的两件事情:第一个就是它能发,并且它也能收,但是它现在也能确定服务器也能发,但是服务器不知道它自己能发,它知道自己能收,所以第三次握手就是为了确定服务器这边可以/是否能发!!
1.客户端访问/操作服务器:通过网络,这种访问时基于TCP通信 http协议底层是基于TCP通信
登录时一定要保证数据的可靠不能丢数据->TCP通信 UDP协议:不可靠的,无连接的,只负责发送,至于接受是否,是否丢包不管->玩家的移动数据,比如你在地图中移动,发送了什么技能...都是UDP协议,它有效率高的特点,
2.服务器可以处理多个客户端的请求:怎么处理?对于TCP而言,需要连接,必须连接,这个连接是基于三次握手来建立这个可靠连接,确保它是通的,TCP有一个三次握手
3.Socket通信(TCP里面的) 客户端有一个Socket 服务器有一个ServerSocket
4.处理多客户端的请求:那么服务器就会有多个Socket 基于Socket来进行,你这边一连接我服务器这边有一个Socket跟你通信,但是服务器这端的代码,注意客户端的Socket是自己new出来的,但是服务器端的Socket(需要与客户端的Socket保持同步)是当客户端发了一个Socket通信时,服务器监听到这个Socket之后实现一个沿袭的效果,ServerSocket:里面有一个Accpet,另外我们知道两个人通信:一定有IP(地址)和端口,所以ServerSocket实现了一个监听的作用,通过Accpet得到一个关于服务器端的Socket通信/对象,但是这个这个ServerSocket中的accpet是一个单线程的,比如说main方法就是一个单线程,一个主线程,就是说发一个信息过来,主线程代码从上往下执行,一次性的,线程就结束了,那么就会有服务器只能接受一次请求的问题,接受完了main线程就结束了,相当于服务的程序停下来了,不能再接受,所以一定要用到多线程
客户端程序
服务器程序
1.0版本开始
1.0版本结束
问题:只要有一个客服端连接,服务器就结束了,再运行一个client会不行的,因为服务器已经关闭了,服务器不能进行多个处理(面对客户端的发送请求) 只是做到了一个一对一 原因:服务器serverSocket.accpet()写在main线程里面,它是一个单线程程序 new Thread和new Runable方法创建线程以外,还有一个默认线程就是main方法,main方法一启动就启动这个单线程,我们叫他主线程,里面的代码相当于run方法里面的代码,我们知道线程的run方法执行完之后,意味着这个线程已经结束了,死亡状态!! 解决方案:把服务器段的代码由单线程代码变成多线程代码 serverSocket.accept();写出死循环数据接收发送变成一个线程来处理!当有一个客户端发起请求/访问时,我服务器这端就会启动一个线程来处理
(那么server代码的处理和Socket分开,蓝色的做蓝色的事情,绿色的做绿色的事情,并发处理,两者是同时进行的,不用等待)
面试点:线程实现的方式
Thread类 实现Runable接口 Callable :两者类似,只是说Runable没有返回值 线程池:线程的开始和关闭需要消耗资源 所以这里我们不需要它了 需要重复使用 线程执行完了让他等着等待下一个任务的处理,节省了
登录:先客户端发送数据过去,用户名和密码输入,在客户端
客户端:用户名,密码,程序 -> 服务器不能辨别你发过来的是用户名、密码,客户端需要告诉服务器的操作类型,是登录还是注册
xml文件 解析xml文件 对于用户信息:需要永久保存 保存到磁盘中 以文件的方式 一..txt文件 -》 可读性不高 1,zhangsan,123 2.lishi,234 二.xml文件 xml:配置文件,简单的数据存储文件 xml:标签形式,xml的标签自定义,不作为页面展示,作为数据展示 xml规范: 1.有一个根标签 2.根标签包含子标签,每个标签有自己的属性
这是一个文件:对于txt文件而言,通过,分隔去解析txt文件,但是对于xml文件,解析其中的数据话,需要通过 js:使用DOM去解析文档对象,操作文档对象 xml:jdk自带 dom解析。使用第三方jar dom4j jar:一个压缩文件,里面包含.class字节码文件 ideal添加第三方jar(手动) 以后就用maven <users> <user userId="1"> <username>zs</username> <password>123</password> </user> <user userId="2"> <username>ls</username> <password>123</password> </user> </users>
读取xml文件
把jar包放到lib目录下
设置,把对应的jar设置为library
xml开始读文件:
1.获取SAX解析器 SAXReader
2.读取xml文件 得到Document文档
3.用gerRootELement()得到根节点
4.通过根节点找子节点,再湖区节点属性 文本 标签名
<?xml version="1.0" encoding="UTF-8"?> <users> <user userId="1"> <username>admin</username> <password>123</password> </user> <user userId="2"> <username>lisi</username> <password>123</password> </user> <user userId="3"> <username>wangwu</username> <password>123456</password> </user> </users>
写入数据到xml文件
登录
客户端:
1.显示一级菜单 -》登录 注册 退出
2.用户选择登录,输入usernamepassword
3.通过网络Socket ,IO流数据发送给服务器
用户名,密码,操作 User 操作封装成一个Map 用户信息封装成User 用对象流ObjectIO 发送Map对象 对象的传输:简单流不能使用,用对象流ObjectIO写这个User,但是此时没有报操作进行封装,所以还要封装,Map->User,操作,而Map对象已经实现序列化了,最后发送给服务器, 然后接受服务器的返回的结果 -》 登录成功 -》登陆失败 如果成功:进入二级菜单 登陆失败:回到一级菜单重新登录
服务器端:
(此时服务器已经封装成一个线程来处理客户端的请求)
1.接受客户端的信息,那边发的是是一个ObjectOutputStream 接收:Map对象 2.获取map的操作,判断客户端是做什么操作的 3.如果是登陆操作,执行登录 4.获取客户端传递的User对象 与User.xml文件中的包含的user对象进行比较 5.如果有:发送登录成功给客户端 如果没有发送登录失败给客户端
开启网络:创建Socket 用Socket拿到InputStream。。。等流,再通过这些流拿到对象流
//开启网络:创建Socket 用Socket拿到InputStream。。。等流,再通过这些流拿到对象流 //模拟浏览器的段连接 登录一完 Socket就关掉 QQ就是长连接(一直在线,一直跟服务器连着)
ctrl + alt + t :包裹代码快捷键
对象通过IO传输
不能直接把对象写过去,ObjectoutputStream本质是一个字节流,但是字节流每次只能写/读一个字节,而User对象是一个整体>一个字节,不能被它读/写,所以我们需要把User拆解,拆成一个字节数组(序列化)。方便传输,网上买家具,家具也是拆过来然后封装给你,你需要自己再组装,java对象 -> 打散成字节数组的过程:叫做序列化
接收方:字节数据 -> 自己组装 -> User对象(反序列化) 序列化的目的:把对象方便传输 但是jdk默认不允许序列化和反序列化因为易导致数据不安全 需要设置对象的类实现序列化接口(只是一个标志) ->注意Map本身已经实现序列化了
客户端与服务器约定:
1.登录 2.注册 3.查看小说列表 4.下载 5.上传 6.在线阅读 map.put("op",1);//不建议写1这种魔法数字 用public static final 定义常量
一旦线程类加载,那么就会自动读取User.xml 并且解析 保存为一个User的集合
Thread.java
EOFException:读到文件末尾的异常
作业注册功能:
1.userId自增 找到现存用户userId 然后+1 需要写一个更新xml的方法
2.用户名唯一
小说列表查询功能
客户端
1.发送请求,只要携带一个OP数据
2.接收服务器的返回的结果,这里是一个list集合 里面包含很多book list<book>
3.在客户端控制台输出小说列表,把List<Book>保存到Client属性上,方便后期使用
服务器
1.解析xml文件,所以第一需要接收客户端请求
2.调用查询小说列表的方法,这个方法主要用于解析Book.xml,封装成list<Book>
3.把list<Book> 通过输入流发送给客户端
Book类设计到网络传输那么就需要实现Serilaziable接口,实现可序列化,从而实现对象的一个整体的数据的传输。
private String type;//这个类型应该应该是一个xml文件,所以用户在上传时,可以选择类型,这里没有设置了,后续需要优化
path是存在服务器端的!!
在线阅读
1.实现翻页效果
需要网络流和本地流两种流。
小说列表: 客户端 0.显示·小说列表,并且让用户选择小说 1.发送请求,携带一个OP数据和小说名数据 -> 当前页码和小说名 2.接收服务器的返回的结果,这里是一个String 在客户端控制台输出, 发 收 显示 服务器 1.第一需要接收客户端请求 2.调用在线阅读的方法,目前规定每一页显示1000字 3.使用本地字符输入流,读取本地磁盘的小说文件,输入IO:skip()方法 跳过指定个数的字符 4.把读取到的内容发给客户端(网络IO) Book类设计到网络传输那么就需要实现Serilaziable接口,实现可序列化,从而实现对象的一个整体的数据的传输。
需要把这些需要修改的,不在代码中写死,就是为了实现字节码文件不能修改!需要把这些类似于配置的代码专门使用一种文件来保存。
配置文件
.xml文件(需要解析,比较麻烦)
.proerties配置文件(key=value 每个key/value独占一行,解析是不麻烦,跟map集合类似,解析比较简单,不需要DOM解析
但是如果key
stu.name=zhangsan
stu.age=21
stu.sex=男
这些key都有统一的前缀,如果前缀重复写就会麻烦
)
.yml配置文件 key: value 格式缩进 相同的前缀只写一个
stu:
name:zhangsan
age:21
sex:男
解决只有一页的细节问题,因为只有一页的情况下,会出现不展示第一页内容的问题? 在Book.xml里面加一个 页数自定义标签 程序规定了每一页是1000字那么我可以大概求出我这本书大概有多少页,得到一个值
使用properties
读取properties文件中的内容
小说上传
1.客户端把本地的txt小说文件文档 上传到 服务器端, 然后把小说txt文本保存到服务器的本地,还要更新Book.xml文件 同一个局域网以内可以实现通信 思路:与注册相似 客户端 1.用户输入我们的需要上传的小说名,作者名,描述,类型, //通过本地IO读取客户端磁盘上的txt文件,存放到map中 String 2.发送请求到服务器,map 3.接收服务器的返回结果,打印结果 4.返回到二级菜单 5.如果是成功的,上传文件,另开一个线程 服务器 1.接受客户端请求 2.调用上传的方法(->这个方法) 3.读取小说信息,更新到Book.xml中,更新books集合(添加新数据到book集合) 4.读取小说txt文本,使用本地IO保存到服务器的磁盘 5.响应结果给客户端
获取最大Book.xml中的Id
Book book = (Book) map.get("data"); // int maxId = books.stream().mapToInt(book1 -> book.getBookId()).max().getAsInt(); // System.out.println(maxId); int maxId = getmaxId1(); System.out.println(maxId); book.setBookId(maxId+1); //得到最大UserId方法 private int getmaxId() { int maxId = 0; for (User user:users){ if(user.getUserId() > maxId){ maxId = user.getUserId(); } } return maxId; } private int getmaxId1() { int maxId = 0; for (Book book:books){ if(book.getBookId() > maxId){ maxId = book.getBookId(); } } return maxId; }
下载功能:
与在线阅读功能相似
把小说内容读过来,一次性发给客户端,客户端通过本地流保存在本地硬盘上就行了
下载功能
客户端 (Client1.java): 创建下载小说的请求,并将请求发送到服务器。 接收从服务器返回的小说内容。 将小说内容保存到本地文件。 服务器端 (Thread1.java 或其他服务器线程): 接收客户端的下载小说请求。 根据客户端请求的小说名称,读取相应的小说内容。 将小说内容发送回客户端。 客户端负责请求发起、内容保存,服务器端负责接收请求、提供小说内容。以下是大致的代码框架示例: