Java期末大作业,总算肝完了。这学期才开始接触Java,比起C语言java用起来不要太舒服。期末大作业就想着做一个能用的东西出来,于是就选了这个,经过小组成员的努力,最终还是把这玩意儿弄出来了。
采用RMI和Socket实现服务器端与客户端通信
关于RMI
提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
java大作业总算肝完了。这一路走过来遇到很多问题,也有很多收获,写下本篇博客来记录。一个学期从一个没有接触过java的小白,到学会java网络编程,多线程等方面知识,再到成功讲软件部署到服务器上,这一路真的很艰辛。
效果展示:demo
代码链接:src
功能就以上视频里展示的这些。
如果有需要请在评论区留言
(java做可视化真的拉跨
jdk8
mysql 8(这个不同所需要的mysql-connector的jar包也不同)
服务器端采用RMI和Socket与客户端通信
RMI定义远程方法提供给客户端调用
定义的接口:(需要实现
import java.rmi.Remote;
import java.rmi.RemoteException;
import java.sql.SQLException;
import java.util.ArrayList;
public interface ServerMethod extends Remote {
/**
*RMI方法
* 登录 登录成功返回true 否则返回false
* @param UID 用户UID
* @param Password 用户密码
* @see boolean
*/
public boolean Login(String UID,String Password) throws RemoteException, SQLException;//登录
/**
*RMI方法
* 注册 注册成功返回true 否则返回false
* @param UID 用户UID
* @param Password 用户密码
* @param Nickname 昵称
* @see boolean
*/
public boolean Register(String UID ,String Password,String Nickname)throws SQLException,RemoteException;//注册
/**
*RMI方法
* 获取存在数据库的未读消息
* @param UID 用户UID
* @see ArrayList
*/
public ArrayList<String> getDBMessage(String UID) throws SQLException,RemoteException;//获取存储在数据库的消息
/**
*RMI方法
* 获取好友列表
* @param UID 用户UID
* @see ArrayList
*/
public ArrayList<String> getAllFriends(String UID) throws SQLException,RemoteException;//获取好友列表 格式: UID:Nickname
/**
*RMI方法
* 发送消息
* @param UID 发送者UID
* @param Nickname 昵称
* @param ReceiveID 接收者ID
* @param content 内容
* @see ArrayList
*/
public boolean SendMessage(String UID,String Nickname,String ReceiveID,String content) throws SQLException,RemoteException;//对方还不是好友,则发送失败
/**
*RMI方法
* 获取实时消息
* @param UID 用户UID
* @see ArrayList
*/
public ArrayList<String> GetMessage(String UID) throws Exception;//实时消息获取
/**
*RMI方法
* 发送群聊消息 发送成功返回true
* @param UID 发送者UID
* @param Nickname 昵称
* @param GroupUID 群组UID
* @param content 内容
* @see boolean
*/
public boolean SendMessageToGroup(String UID,String Nickname,String GroupUID,String content)throws SQLException,RemoteException;//向群聊发送消息
/**
*RMI方法
* 发送群聊消息 发送成功返回true
* @param SendUID 发送者UID
* @param Nickname 昵称
* @param ReceiveUID 接收者UID
* @param content 内容
* @see boolean
*/
public boolean AddFriendsRequest(String SendUID,String ReceiveUID,String Nickname,String content)throws SQLException,RemoteException;//发送添加好友请求
/**
*RMI方法
* 删除关系
* @param ID1 ID2
* @param ID2 ID1
*/
public void DeleteFriend(String ID1,String ID2)throws SQLException,RemoteException;//删除关系
/**
*RMI方法
* 新建群聊
* @param SendUID ID2
* @param GID ID1
*/
public boolean NewGroup(String SendUID,String GID)throws SQLException,RemoteException;//新建群聊
/**
*RMI方法
* 添加群聊
* @param UID 用户ID
* @param GID 群聊ID
*/
public void AddGroup(String UID,String GID)throws SQLException,RemoteException;//添加群聊 无需验证
/**
*RMI方法
* 修改昵称
* @param UID 用户ID
* @param newNickname 新用户昵称
* @see boolean
*/
public boolean ChangeNickname(String UID,String newNickname)throws SQLException,RemoteException;//修改昵称
/**
*RMI方法
* 修改密码
* @param UID 用户ID
* @param newPassword 新密码
* @see boolean
*/
public boolean ChangePassword(String UID,String newPassword)throws SQLException,RemoteException;//修改密码
/**
*RMI方法
* 获取用户消息
* @param UID 用户ID
*/
public String getUserInfo(String UID)throws SQLException,RemoteException;//获取用户信息 若为群组返回 G:GID 若为用户返回 U:UID:Nickname
/**
*RMI方法
* 同意添加好友
* @param SendUID 发送者ID
* @param ReceiveUID 接收者UID
*/
public void AgreeAddFriendRequest(String SendUID,String ReceiveUID)throws SQLException,RemoteException;
/**
*RMI方法
* 拒绝添加好友
* @param SendUID 发送者ID
* @param ReceiveUID 接收者UID
*/
public void RefuseAddFriendRequest(String SendUID,String ReceiveUID)throws SQLException,RemoteException;
}
RMI这部分真的不好配置,由于需要外网ip,一开始没有服务器,用的是内网穿透(花生壳和网穿云各可以白嫖一个端口),结果出现了一堆问题
这里是配置相关的代码:
System.setProperty("java.rmi.server.hostname","3956w07c04.wicp.vip");//外网域名
try {
RMISocketFactory.setSocketFactory(new SMRMISocket());
} catch (Exception ex) {
}
ServerMethod method = new ServerMethodImpl(DB);//RMI方法
LocateRegistry.createRegistry(53877);//注册
Naming.bind("//localhost:53877/ServerMethod",method );//绑定 端口与SMRMISocket中一致
System.out.println("RMI启动成功");
还需要定义一个 SMRMISocket 类
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.rmi.server.RMISocketFactory;
public class SMRMISocket extends RMISocketFactory {
@Override
public Socket createSocket(String host, int port) throws IOException {
return new Socket(host,port);
}
@Override
public ServerSocket createServerSocket(int port) throws IOException {
if(port == 0 ){
port = 53877;//端口号与RMI初始化时的端口一致
}
return new ServerSocket(port);
}
}
ps:使用内网穿透的同学需要注意一下,RMI内网的端口必须设置成与映射的外网端口一致。
客户端调用RMI接口:
ps:需要讲将服务器端的RMI接口源文件复制一份到客户端
try{
Registry registry = LocateRegistry.getRegistry("3956w07c04.wicp.vip",53877 );//域名端口与服务器端一致
ServerMethod method = (ServerMethod) registry.lookup("ServerMethod");}//初始化RMI方法
catch(Exception e){
}
//这一步没问题后就可以调用了
与RMI不同,Socket主要用于检测用户登录状态,每当用户登录成功后,服务器端会与客户端建立一个Socket连接,并开一个线程,并按照一定的时间间隔相互发送消息确认状态。这是为了在服务器端保留一个在线用户列表,以及防止登录同一用户两次。
这里是代码
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.net.Socket;
public class UserThread extends Thread{
private DataInputStream in;
private DataOutputStream out;
private DataBaseOpera DB;
private String UID;
private Socket socket;
private User user;
public UserThread(Socket client,DataBaseOpera DB){
try{this.socket = client;
this.DB = DB;
in =new DataInputStream (client.getInputStream());
out = new DataOutputStream(client.getOutputStream());
}catch(Exception e){
System.out.println(e);
}
}
public boolean initial(){
try{
UID = in.readUTF();
user = new User(UID,DB,socket);
synchronized (OnlineUserList.getInstance()){
OnlineUserList.getInstance().addUser(UID,user);
}
return true;
}catch(Exception e){
e.printStackTrace();
return false;
}
}
public void run(){
if(!initial()){
System.out.println("初始化失败:"+socket.getInetAddress());
return;
}
System.out.println("初始化成功:"+socket.getInetAddress()+" "+UID);
while(true){
try{
in.readUTF();
socket.setSoTimeout(5000);
sleep(100);
out.writeUTF("S");
}catch(Exception e){
synchronized (OnlineUserList.getInstance()){
if(OnlineUserList.getInstance().getUser(UID)==user){
try{
OnlineUserList.getInstance().removeUser(UID);
}catch (Exception f){
f.printStackTrace();
}
}
System.out.println(UID+" " +"连接失败" + "移除");
return;
}
}
}
}
}
采用mysql8
数据库操作代码如下
import java.sql.*;
public class DataBaseOpera {
/**
* 数据库操作
* @param uri 数据库地址
* @param con 与数据库的连接
* @param createTable 创建用户表sql
* @param queryMess 查询用户待接收信息
* @param queryFriend 查询用户好友列表
* @param queryPass 查询用户密码
* @param updateUserPass 更新用户密码
* @param updateUserNick 更新用户昵称
* @param insertMess 插入未接收消息
*
*/
private String uri;
private Connection con;
private PreparedStatement createTable;
private PreparedStatement queryGroup;
private PreparedStatement queryPass;
private PreparedStatement queryMess;
private PreparedStatement queryUID;
private PreparedStatement queryFriend;
private PreparedStatement updateUserPass;
private PreparedStatement updateUserNick;
private PreparedStatement insertMess;
private PreparedStatement insertUser;
private PreparedStatement insertFriend;
private PreparedStatement checkFriendship;
private PreparedStatement deleteMess;
private PreparedStatement deleteFriendShip;
private PreparedStatement newGroup;
private static DataBaseOpera instance;
private DataBaseOpera() {
try{
Class.forName("com.mysql.cj.jdbc.Driver");//加载数据库驱动
uri = "jdbc:mysql://127.0.0.1:3306/trade?user=root&password=root&useSSL=FALSE&characterEncoding=utf-8&serverTimezone=GMT&allowPublicKeyRetrieval=true";
con = DriverManager.getConnection(uri);
createTable = con.prepareStatement("drop table Friend");
createTable.execute();
createTable = con.prepareStatement("drop table Message");
createTable.execute();
createTable = con.prepareStatement("drop table User");
createTable.execute();
createTable = con.prepareStatement("drop table Groupps ");
createTable.execute();
queryGroup = con.prepareStatement("select * from Groupps where ID = ?");
queryUID = con.prepareStatement("select * from user where ID = ?");
queryPass = con.prepareStatement("select Password from user where ID = ? and Password = ?");
queryMess = con.prepareStatement("select content from Message where ReceiveID = ? order by Timeer");
queryFriend = con.prepareStatement("select ID2 from Friend where ID1 = ?");
updateUserPass = con.prepareStatement("update User set Password = ? where ID = ?");
updateUserNick = con.prepareStatement("update User set NickName = ? where ID = ?");
insertMess = con.prepareStatement("insert into Message (ReceiveID,content,Timeer) values(?,?,current_timestamp() )");
insertUser = con.prepareStatement("insert into User (User.ID,User.NickName,User.Password) values (?,?,?)");
insertFriend = con.prepareStatement("insert into Friend(ID1,ID2) values(?,?)");
checkFriendship = con.prepareStatement("select * from Friend where ID1 = ? and ID2=?");
deleteMess = con.prepareStatement("delete from Message where ReceiveID = ?");
deleteFriendShip = con.prepareStatement("delete from Friend where ID1=? and ID2 = ? ");
newGroup = con.prepareStatement("insert into Groupps(ID,OwnerUID) values(?,?)");
createTable = con.prepareStatement("create table User( ID varchar(20) primary key not null,NickName varchar(20) not null," +
"Password varchar(16) not null )");//创建User表
createTable.execute();
createTable = con.prepareStatement("create table Message( ID integer primary key,ReceiveID varchar(20) not null, " +
"content text not null,Timeer TIMESTAMP not null )");
createTable.execute();
createTable = con.prepareStatement("alter table Message modify ID integer auto_increment");
createTable.execute();
createTable = con.prepareStatement("create Table Friend (ID1 varchar(20),ID2 varchar(20))");
createTable.execute();
createTable = con.prepareStatement("ALTER TABLE Friend ADD PRIMARY KEY(ID1,ID2)");
createTable.execute();
createTable = con.prepareStatement("create Table Groupps(ID varchar(20) primary key,OwnerUID varchar(20))");
createTable.execute();
}catch(Exception e){
e.printStackTrace();
}
}
public static DataBaseOpera getInstance(){
if(instance==null){
instance = new DataBaseOpera();
}
return instance;
}
Boolean checkUIDRe(String UID)throws SQLException{
queryUID.setString(1,UID);
return queryUID.executeQuery().next();
}
Boolean checkPassword(String userid,String password) throws SQLException{
queryPass.setString(1,userid);
queryPass.setString(2,password);
return queryPass.executeQuery().next();
}
void updateUserPassword(String id,String newPassword) throws SQLException {
updateUserPass.setString(1,newPassword);
updateUserPass.setString(2,id);
updateUserPass.execute();
return;
}
void updateUserNickname(String id,String newNickname) throws SQLException{
updateUserNick.setString(1,newNickname);
updateUserNick.setString(2,id);
updateUserNick.execute();
return;
}
ResultSet queryGroup(String GID) throws SQLException{
queryGroup.setString(1,GID);
ResultSet re = queryGroup.executeQuery();
return re;
}
ResultSet queryMessages(String userid) throws SQLException{
queryMess.setString(1,userid);
ResultSet re = queryMess.executeQuery();
deleteMess.setString(1,userid);
deleteMess.execute();
return re;
}
ResultSet queryAllFriendship(String userid) throws SQLException {
queryFriend.setString(1,userid);
return queryFriend.executeQuery();
}
ResultSet queryUser(String UID) throws SQLException{
queryUID.setString(1,UID);
return queryUID.executeQuery();
}
void DeleteFriendship(String id1,String id2) throws SQLException{
deleteFriendShip.setString(1,id1);
deleteFriendShip.setString(2,id2);
deleteFriendShip.execute();
}
Boolean checkUserExist(String id) throws SQLException{
queryUID.setString(1,id);
ResultSet re = queryUID.executeQuery();
if(re.next()){
return true;
}
else {
return false;
}
}
Boolean checkFriendShip(String ID1,String ID2) throws SQLException{
checkFriendship.setString(1,ID1);
checkFriendship.setString(2,ID2);
return checkFriendship.executeQuery().next();
}
Boolean checkGroupExist(String id) throws SQLException{
queryGroup.setString(1,id);
return queryGroup.executeQuery().next();
}
void insertMessage(String receiveid,String content) throws SQLException{
insertMess.setString(1,receiveid);
insertMess.setString(2,content);
insertMess.execute();
return;
}
void insertFriendship(String ID1,String ID2) throws SQLException{
insertFriend.setString(1,ID1);
insertFriend.setString(2,ID2);
insertFriend.execute();
return;
}
void insertGroup(String GroupUID,String OwnerUID) throws SQLException{
newGroup.setString(1,GroupUID);
newGroup.setString(2,OwnerUID);
newGroup.execute();
insertFriendship(GroupUID,OwnerUID);
insertFriendship(OwnerUID,GroupUID);
return;
}
void insertUsertoTable(String id,String password,String Nickname) throws SQLException{
insertUser.setString(1,id);
insertUser.setString(2,Nickname);
insertUser.setString(3,password);
insertUser.execute();
return ;
}
}
RMI定义方法给客户端调用,如登录、发消息、接收消息、加好友等
Socket 用于确认用户登录状态
客户端不是我写的具体不是很清楚
看代码就完事了
待更新