Java 第三阶段增强分析需求,代码实现能力【多用户即时通信系统】
服务端代码链接:https://download.csdn.net/download/qq_52354698/86438353?spm=1001.2014.3001.5501
客户端代码链接:https://download.csdn.net/download/qq_52354698/86438352?spm=1001.2014.3001.5501
1. 需求分析
2. 界面设计
3. 功能实现-用户登录
1. 功能说明
我们认为规定用户名/id = 100,密码 = 12345 就可以登录,其它用户不能登录
2. 思路分析、程序框架图
3. 代码分析
表示客户端和服务器端通信时的消息对象
package com.qdu.qqcommon;
import java.io.Serializable;
/**
* @author dell
* @version 1.0
* 表示客户端和服务器端通信时的消息对象
*/
public class Message implements Serializable {
private static final long serialVersionUID = 1L;
private String sender;//发送者
private String getter;//接收者
private String content;//消息内容
private String sendTime;//发生时间
private String mesType;//消息类型[可以在接口中定义消息类型]
public String getMesType() {
return mesType;
}
public void setMesType(String mesType) {
this.mesType = mesType;
}
public String getSender() {
return sender;
}
public void setSender(String sender) {
this.sender = sender;
}
public String getGetter() {
return getter;
}
public void setGetter(String getter) {
this.getter = getter;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
public String getSendTime() {
return sendTime;
}
public void setSendTime(String sendTime) {
this.sendTime = sendTime;
}
}
在接口中定义一些常量
不同的常量值代表不同的消息类型
package com.qdu.qqcommon;
/**
* @author dell
* @version 1.0
* 在接口中定义一些常量
* 不同的常量值代表不同的消息类型
*/
public interface MessageType {
String MESSAGE_LOGIN_SUCCEED = "1";//表示登录成功
String MESSAGE_LOGIN_FAIL = "2";//表示登录失败
}
package com.qdu.qqcommon;
import java.io.Serializable;
/**
* @author dell
* @version 1.0
* 表示一个用户信息
*/
public class User implements Serializable {
private static final long serialVersionUID = 1L;
private String userId;//用户名
private String passwd;//密码
public User(){
}
public User(String userId, String passwd) {
this.userId = userId;
this.passwd = passwd;
}
public String getUserId() {
return userId;
}
public void setUserId(String userId) {
this.userId = userId;
}
public String getpasswd() {
return passwd;
}
public void setPasswd(String passwd) {
this.passwd = passwd;
}
}
客户端
客户端的菜单界面
package com.qdu.qqclient.view;
import com.qdu.qqclient.service.UserClientService;
import com.qdu.qqclient.utils.Utility;
import java.io.IOException;
/**
* @author dell
* @version 1.0
* 客户端的菜单界面
*/
public class QQView {
private boolean loop = @H_502_856@true;//控制是否显示菜单
private String key = "";
private UserClientService userClientService = new UserClientService();//用于用户登录和注册
public static void main(String[] args) throws IOException, ClassNotFoundException {
new QQView().mainMenu();
System.out.println("客户端系统退出......");
}
//显示主菜单
private void mainMenu() throws IOException, ClassNotFoundException {
System.out.println("==========欢迎登录网络通信系统==========");
System.out.println("\t\t 1.登录系统");
System.out.println("\t\t 9.退出系统");
System.out.print("请输入您的选择:");
key = Utility.readString(1);
//根据用户输入,处理不同的逻辑
switch (key) {
case "1":
System.out.print("请输入用户名:");
String userId = Utility.readString(50);
System.out.print("请输入密 码:");
String pwd = Utility.readString(50);
//单独一个类来验证用户登录
if (userClientService.checkUser(userId, pwd)) {
System.out.println("==========欢迎 " + userId + "==========");
//二级菜单
while (loop){
System.out.println("==========网络通信系统二级菜单(用户 " + userId + " 登录成功)==========");
System.out.println("\t\t 1 显示在线用户列表");
System.out.println("\t\t 2 群发消息");
System.out.println("\t\t 3 私聊消息");
System.out.println("\t\t 4 发送文件");
System.out.println("\t\t 9 退出系统");
key = Utility.readString(1);
switch (key){
case "1":
System.out.println("显示在线用户列表");
break;
case "2":
System.out.println("群发消息");
break;
case "3":
System.out.println("私聊消息");
break;
case "4":
System.out.println("发送文件");
break;
case "9":
loop = @H_502_856@false;
break;
}
}
} else {
System.out.println("==========登录失败==========");
}
break;
case "9":
loop = @H_502_856@false;
break;
}
}
}
package com.qdu.qqclient.service;
import com.qdu.qqcommon.Message;
import com.qdu.qqcommon.MessageType;
import com.qdu.qqcommon.User;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.InetAddress;
import java.net.Socket;
/**
* @author dell
* @version 1.0
* 该类完成用户登录验证和用户注册等功能.
*/
public class UserClientService {
//因为我们可能在其他地方用使用user信息, 因此作出成员属性
private User user = new User();
//因为Socket在其它地方也可能使用,因此作出属性
private Socket socket;
//根据userId 和 pwd 到服务器验证该用户是否合法
public boolean checkUser(String userId, String pwd) {
boolean b = @H_502_856@false;
//创建User对象
user.setUserId(userId);
user.setPasswd(pwd);
try {
//连接到服务端,发送u对象
socket = new Socket(InetAddress.getByName("127.0.0.1"), 9999);
//得到ObjectOutputStream对象
ObjectOutputStream objectOutputStream = new ObjectOutputStream(socket.getoutputStream());
objectOutputStream.writeObject(user);//发送User对象
//读取从服务器回复的Message对象
ObjectInputStream objectInputStream = new ObjectInputStream(socket.getInputStream());
Message message = (Message) objectInputStream.readobject();
if (message.getMesType().equals(MessageType.MESSAGE_LOGIN_SUCCEED)) {//登录OK
//创建一个和服务器端保持通信的线程-> 创建一个类 ClientConnectServerThread
ClientConnectServerThread clientConnectServerThread = new ClientConnectServerThread(socket);
//启动客户端的线程
clientConnectServerThread.start();
//这里为了后面客户端的扩展,我们将线程放入到集合管理
ManageClientConnectServerThread.addClientConnectServerThread(userId, clientConnectServerThread);
b = @H_502_856@true;
} else {
//如果登录失败, 我们就不能启动和服务器通信的线程, 关闭socket
socket.close();
}
} catch (Exception e) {
e.printstacktrace();
}
return b;
}
}
在线程中创建socket
package com.qdu.qqclient.service;
import com.qdu.qqcommon.Message;
import java.io.ObjectInputStream;
import java.net.Socket;
/**
* @author dell
* @version 1.0
* 在线程中创建socket
*/
public class ClientConnectServerThread extends Thread{
//该线程需要持有Socket
private Socket socket;
//构造器可以接受一个Socket对象
public ClientConnectServerThread(Socket socket){
this.socket = socket;
}
@Override
public void run() {
//因为Thread在后台和服务器通信,因此我们while循环
while (@H_502_856@true){
try {
System.out.println("客户端线程,等待读取从服务器端发送的消息");
ObjectInputStream objectInputStream = new ObjectInputStream(socket.getInputStream());
//如果服务器端没有发生Message对象,线程就会阻塞在这里
Message message = (Message) objectInputStream.readobject();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
public Socket getSocket() {
return socket;
}
}
该类管理客户端连接到服务器端的线程的类
package com.qdu.qqclient.service;
import java.util.HashMap;
/**
* @author dell
* @version 1.0
* 该类管理客户端连接到服务器端的线程的类
*/
public class ManageClientConnectServerThread {
//我们将多个线程放入到HashMap集合中,key就是用户id,value就是线程
private static HashMap<String, ClientConnectServerThread> hashMap = new HashMap<>();
//将某个线程放入到集合中
public static void addClientConnectServerThread(String userId, ClientConnectServerThread clientConnectServerThread){
hashMap.put(userId, clientConnectServerThread);
}
//通过userId可以得到对应的线程
public static ClientConnectServerThread getClientConnectServerThread(String userId) {
return hashMap.get(userId);
}
}
服务端
该类创建QQServer,启动后台服务
package com.qdu.qqframe;
import com.qdu.qqserver.service.QQServer;
/**
* @author dell
* @version 1.0
* 该类创建QQServer,启动后台服务
*/
public class Qqframe {
public static void main(String[] args) {
new QQServer();
}
}
这是服务器, 在监听9999,等待客户端的连接,并保持通信
package com.qdu.qqserver.service;
import com.qdu.qqcommon.Message;
import com.qdu.qqcommon.MessageType;
import com.qdu.qqcommon.User;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.concurrent.ConcurrentHashMap;
/**
* @author dell
* @version 1.0
* 这是服务器, 在监听9999,等待客户端的连接,并保持通信
*/
public class QQServer {
private ServerSocket serverSocket = null;
//创建一个集合,存放多个用户,如果是这些用户登录,就认为是合法
//这里我们也可以使用 ConcurrentHashMap, 可以处理并发的集合,没有线程安全
//HashMap 没有处理线程安全,因此在多线程情况下是不安全
//ConcurrentHashMap 处理的线程安全,即线程同步处理, 在多线程情况下是安全
private static ConcurrentHashMap<String, User> validUsers = new ConcurrentHashMap<>();
//private static ConcurrentHashMap<String, ArrayList<Message>> offLineDb = new ConcurrentHashMap<>();
static { //在静态代码块,初始化 validUsers
validUsers.put("100", new User("100", "123456"));
validUsers.put("200", new User("200", "123456"));
validUsers.put("300", new User("300", "123456"));
validUsers.put("至尊宝", new User("至尊宝", "123456"));
validUsers.put("紫霞仙子", new User("紫霞仙子", "123456"));
validUsers.put("菩提老祖", new User("菩提老祖", "123456"));
}
//验证用户是否有效的方法
private boolean checkUser(String userId, String passwd) {
User user = validUsers.get(userId);
//过关的验证方式
if (user == null) {//说明userId没有存在validUsers 的key中
return @H_502_856@false;
}
if (!user.getpasswd().equals(passwd)) {//userId正确,但是密码错误
return @H_502_856@false;
}
return @H_502_856@true;
}
public QQServer() {
//注意:端口可以写在配置文件.
try {
System.out.println("服务端在9999端口监听...");
//启动推送新闻的线程
// new Thread(new SendNewsToAllService()).start();
serverSocket = new ServerSocket(9999);
while (@H_502_856@true) { //当和某个客户端连接后,会继续监听, 因此while
Socket socket = serverSocket.accept();//如果没有客户端连接,就会阻塞在这里
//得到socket关联的对象输入流
ObjectInputStream objectInputStream = new ObjectInputStream(socket.getInputStream());
//得到socket关联的对象输出流
ObjectOutputStream objectOutputStream = new ObjectOutputStream(socket.getoutputStream());
User user = (User) objectInputStream.readobject();//读取客户端发送的User对象
//创建一个Message对象,准备回复客户端
Message message = new Message();
//验证用户 方法
if (checkUser(user.getUserId(), user.getpasswd())) {//登录通过
message.setMesType(MessageType.MESSAGE_LOGIN_SUCCEED);
//将message对象回复客户端
objectOutputStream.writeObject(message);
//创建一个线程,和客户端保持通信, 该线程需要持有socket对象
ServerConnectClientThread serverConnectClientThread = new ServerConnectClientThread(socket, user.getUserId());
//启动该线程
serverConnectClientThread.start();
//把该线程对象,放入到一个集合中,进行管理.
ManageClientThreads.addClientThread(user.getUserId(), serverConnectClientThread);
} else { // 登录失败
System.out.println("用户 id=" + user.getUserId() + " pwd=" + user.getpasswd() + " 验证失败");
message.setMesType(MessageType.MESSAGE_LOGIN_FAIL);
objectOutputStream.writeObject(message);
//关闭socket
socket.close();
}
}
} catch (Exception e) {
e.printstacktrace();
} finally {
//如果服务器退出了while,说明服务器端不在监听,因此关闭ServerSocket
try {
serverSocket.close();
} catch (IOException e) {
e.printstacktrace();
}
}
}
}
该类对应的对象和某个客户端保持通信
package com.qdu.qqserver.service;
import com.qdu.qqcommon.Message;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.net.Socket;
/**
* @author dell
* @version 1.0
* 该类对应的对象和某个客户端保持通信
*/
public class ServerConnectClientThread extends Thread{
private Socket socket;
private String userId;//连接到服务端的用户id
public ServerConnectClientThread(Socket socket, String userId) {
this.socket = socket;
this.userId = userId;
}
@Override
public void run() {//线程处于run状态,可以发送、接收消息
while (@H_502_856@true){
System.out.println("服务端和客户端 " + userId + " 保持通信,读取数据...");
try {
ObjectInputStream objectInputStream = new ObjectInputStream(socket.getInputStream());
Message message = (Message) objectInputStream.readobject();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
}
该类用于管理和客户端通信的线程
package com.qdu.qqserver.service;
import java.util.HashMap;
/**
* @author dell
* @version 1.0
* 该类用于管理和客户端通信的线程
*/
public class ManageClientThreads {
private static HashMap<String, ServerConnectClientThread> hashMap = new HashMap<>();
//添加线程对象到hashmap集合
public static void addClientThread(String userId, ServerConnectClientThread serverConnectClientThread){
hashMap.put(userId, serverConnectClientThread);
}
//根据userId返回serverConnectClientThread线程
public static ServerConnectClientThread getServerConnectClientThread(String userId){
return hashMap.get(userId);
}
}
4. 功能实现-拉取在线用户列表
1. 功能说明
2. 思路分析+程序框架图
客户端要获取所有在线的列表,只能向服务器发送请求获得在线列表(因为只有服务器知道全部的在线列表)
3. 代码实现
扩展 MessageType 种类
String MESSAGE_COMM_MES = "3";//普通信息包
String MESSAGE_GET_ONLINE_FRIEND = "4";//要求返回在线用户列表
String MESSAGE_RET_ONLINE_FRIEND = "5";//返回的在线用户列表
String MESSAGE_CLIENT_EXIT = "6";//客户端请求退出
//向服务器端请求在线用户列表
public void onlineFriendList() {
//发送一个Message,类型MESSAGE_GET_ONLINE_FRIEND
Message message = new Message();
message.setMesType(MessageType.MESSAGE_GET_ONLINE_FRIEND);
//发送给服务器
//应该得到当前线程的socket对应的ObjectOutputStream对象
try {
//从管理线程的集合中,通过userid,得到这个线程对象
ClientConnectServerThread clientConnectServerThread = ManageClientConnectServerThread.getClientConnectServerThread(user.getUserId());
//通过当前线程的socket对应的ObjectOutputStream对象
Socket socket1 = clientConnectServerThread.getSocket();
ObjectOutputStream objectOutputStream = new ObjectOutputStream(socket1.getoutputStream());
} catch (IOException e) {
throw new RuntimeException(e);
}
}
//编写方法,返回在线用户列表
public static String getonlineUser() {
//遍历hashmap
Iterator<String> iterator = hashMap.keySet().iterator();
String onlineUserList = "";
while (iterator.hasNext()) {
Object next = iterator.next();
onlineUserList += next.toString() + " ";
}
return onlineUserList;
}
5. 功能实现-无异常退出系统
1. 功能说明
直接退出时,是退出的主线程,而主线程中和服务端通信的线程还在一直循环中,无法正常退出
解决:在退出时,客户端给服务端发送一个message对象,告诉服务端退出
2. 思路分析+程序框架图
3. 代码实现
//编写方法,退出客户端,并给服务端发送一个退出的message对象
public void logout() {
Message message = new Message();
message.setMesType(MessageType.MESSAGE_CLIENT_EXIT);
message.setSender(user.getUserId());//一定要指定是哪一个客户端要退出
try {
ObjectOutputStream objectOutputStream = new ObjectOutputStream(ManageClientConnectServerThread.getClientConnectServerThread(user.getUserId()).getSocket().getoutputStream());
objectOutputStream.writeObject(message);
System.out.println(user.getUserId() + " 退出了系统");
System.exit(0);//结束客户端进行
} catch (IOException e) {
throw new RuntimeException(e);
}
}
服务端接收到客户端的告知,将该客户端对应的线程从服务端线程管理集合中删除,并关闭与该客户端的连接
else if (message.getMesType().equals(MessageType.MESSAGE_CLIENT_EXIT)) {
System.out.println(message.getSender() + " 退出");
//将这个客户端对应的线程,从集合中删除
ManageClientThreads.removeServerConnectClientThread(message.getSender());
socket.close();//关闭连接
break;//退出while循环->退出run方法
}
6. 功能实现-私聊
1. 功能说明
2. 思路分析+程序框架图
3. 代码实现
客户端发送消息给服务器端,发送者id,接收者id,发送内容,发送时间,消息类型
package com.qdu.qqclient.service;
import com.qdu.qqcommon.Message;
import com.qdu.qqcommon.MessageType;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.util.Date;
/**
* @author dell
* @version 1.0
* 该类/对象,提供和消息相关的服务方法
*/
public class MessageClientService {
/**
*
* @param content 内容
* @param senderId 发送用户id
* @param getterId 接收用户id
*/
public void sendMessagetoOne(String content, String senderId, String getterId) {
Message message = new Message();
message.setMesType(MessageType.MESSAGE_COMM_MES);
message.setSender(senderId);
message.setGetter(getterId);
message.setContent(content);
message.setSendTime(new Date().toString());//设置发送时间
System.out.println(senderId + " 对 " + getterId + " 说 " + content);
//发送给服务器
try {
ObjectOutputStream objectOutputStream = new ObjectOutputStream(ManageClientConnectServerThread.getClientConnectServerThread(senderId).getSocket().getoutputStream());
objectOutputStream.writeObject(message);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
服务端转发消息
else if (message.getMesType().equals(MessageType.MESSAGE_COMM_MES)) {
//根据message获取getterid,然后再得到对应的线程
ServerConnectClientThread serverConnectClientThread = ManageClientThreads.getServerConnectClientThread(message.getGetter());
//得到对应socket的对象输出流,将message转发给指定的客户端
ObjectOutputStream objectOutputStream = new ObjectOutputStream(serverConnectClientThread.getSocket().getoutputStream());
objectOutputStream.writeObject(message);//转发
}
7. 功能实现-群聊
1. 功能说明
2. 思路分析+程序框架图
群发消息和私聊消息基本类似,不同点在于,群发消息,要将消息转发给除自己外的所有在线用户ing
3. 代码实现
客户端发送消息给服务器端,发送者id,发送内容,发送时间,消息类型
public void sendMessagetoAll(String content, String senderId) {
Message message = new Message();
message.setMesType(MessageType.MESSAGE_TO_ALL_MES);
message.setSender(senderId);
message.setContent(content);
message.setSendTime(new Date().toString());//设置发送时间
System.out.println(senderId + " 对大家说 " + content);
//发送给服务器
try {
ObjectOutputStream objectOutputStream = new ObjectOutputStream(ManageClientConnectServerThread.getClientConnectServerThread(senderId).getSocket().getoutputStream());
objectOutputStream.writeObject(message);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
服务端将消息转发给除自己外的所有在线用户
else if (message.getMesType().equals(MessageType.MESSAGE_TO_ALL_MES)) {
//遍历管理线程的集合,把所有的线程socket得到,除去自己
HashMap<String, ServerConnectClientThread> hashMap = ManageClientThreads.getHashMap();
Iterator<String> iterator = hashMap.keySet().iterator();
while (iterator.hasNext()) {
Object next = iterator.next();
//取出在线用户的id,除去自己
String onLineUserId = iterator.next().toString();
if (!onLineUserId.equals(message.getSender())) {
ObjectOutputStream objectOutputStream = new ObjectOutputStream(hashMap.get(onLineUserId).getSocket().getoutputStream());
objectOutputStream.writeObject(message);
}
}
8. 功能实现-发文件
1. 功能说明
2. 思路分析+程序框架图
3. 代码实现
客户端从电脑读取文件并发送给服务器端
package com.qdu.qqclient.service;
import com.qdu.qqcommon.Message;
import com.qdu.qqcommon.MessageType;
import java.io.*;
/**
* @author dell
* @version 1.0
*/
public class FileClientService {
/**
* @param src 源文件
* @param dest 存放目录
* @param senderId 发送者id
* @param getterId 接收者id
*/
public void sendFiletoOne(String src, String dest, String senderId, String getterId) {
Message message = new Message();
message.setMesType(MessageType.MESSAGE_FILE_MES);
message.setSender(senderId);
message.setGetter(getterId);
message.setSrc(src);
message.setDest(dest);
//将文件读取
FileInputStream fileInputStream = null;
byte[] fileBytes = new byte[(int) new File(src).length()];
try {
fileInputStream = new FileInputStream(src);
fileInputStream.read(fileBytes);//将src文件读入到程序的字节数组
//将文件对应的字节数组设置message
message.setFileBytes(fileBytes);
} catch (IOException e) {
throw new RuntimeException(e);
} finally {
if (fileInputStream != null){
try {
fileInputStream.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
System.out.println("\n" + getterId + " 给 " + senderId + " 发送文件:" + src + " 到对方的电脑的目录 " + dest);
//发送
try {
ObjectOutputStream objectOutputStream = new ObjectOutputStream(ManageClientConnectServerThread.getClientConnectServerThread(senderId).getSocket().getoutputStream());
objectOutputStream.writeObject(message);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
服务器端转发文件给对应的客户端
else if (message.getMesType().equals(MessageType.MESSAGE_FILE_MES)) {
//将文件转发给指定的客户端
ServerConnectClientThread serverConnectClientThread = ManageClientThreads.getServerConnectClientThread(message.getGetter());
ObjectOutputStream objectOutputStream = new ObjectOutputStream(serverConnectClientThread.getSocket().getoutputStream());
//转发
objectOutputStream.writeObject(message);
}
客户端将服务端转发的文件保存到电脑
else if (message.getMesType().equals(MessageType.MESSAGE_FILE_MES)) {
System.out.println("\n" + message.getSender() + " 给 " + message.getGetter() + " 发送文件 " + message.getSrc() + " 到我的电脑目录 " + message.getDest());
//取出message的文件字节数组,通过文件输出流写出到磁盘
FileOutputStream fileOutputStream = new FileOutputStream(message.getDest());
fileOutputStream.write(message.getFileBytes());
fileOutputStream.close();
System.out.println("\n保存文件成功~");
}
9. 功能实现-服务器推送新闻
1. 功能说明
2. 思路分析+程序框架图
相当于服务器端的群发消息
3. 代码实现
package com.qdu.qqserver.service;
import com.qdu.qqcommon.Message;
import com.qdu.qqcommon.MessageType;
import com.qdu.utils.Utility;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Scanner;
/**
* @author dell
* @version 1.0
*/
public class SendNewsToAllService implements Runnable {
@Override
public void run() {
//方便可以多次推送新闻,使用while
while (@H_502_856@true) {
System.out.println("请输入服务器要推送的消息/新闻[输入exit表示退出推送]:");
String news = Utility.readString(1000);
if ("exit".equals(news)) {
break;
}
Message message = new Message();
message.setSender("服务器");
message.setMesType(MessageType.MESSAGE_TO_ALL_MES);
message.setContent(news);
message.setSendTime(new Date().toString());
System.out.println("服务器推送消息给所有人 说 " + news);
//遍历当前的所有通信线程,得到socket,并发送message
HashMap<String, ServerConnectClientThread> hashMap = ManageClientThreads.getHashMap();
Iterator<String> iterator = hashMap.keySet().iterator();
while (iterator.hasNext()) {
Object next = iterator.next();
String onLineUserId = next.toString();
try {
ObjectOutputStream objectOutputStream = new ObjectOutputStream(hashMap.get(onLineUserId).getSocket().getoutputStream());
objectOutputStream.writeObject(message);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
}
}