基于RMI和Socket的Java聊天软件

Java期末大作业,总算肝完了。这学期才开始接触Java,比起C语言java用起来不要太舒服。期末大作业就想着做一个能用的东西出来,于是就选了这个,经过小组成员的努力,最终还是把这玩意儿弄出来了。

服务器端:

采用RMI和Socket实现服务器端与客户端通信
关于RMI
提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档

文章目录

    • 服务器端:
  • 前言
  • 一、功能介绍及环境
  • 二、服务器端
    • 1.RMI
    • 2.Socket
    • 3.数据库
    • 4.基本逻辑
  • 三、客户端
  • 四、部署到服务器
  • 总结


前言

java大作业总算肝完了。这一路走过来遇到很多问题,也有很多收获,写下本篇博客来记录。一个学期从一个没有接触过java的小白,到学会java网络编程,多线程等方面知识,再到成功讲软件部署到服务器上,这一路真的很艰辛。


一、功能介绍及环境

效果展示:demo
代码链接:src
功能就以上视频里展示的这些。
如果有需要请在评论区留言
java做可视化真的拉跨
jdk8
mysql 8(这个不同所需要的mysql-connector的jar包也不同)

二、服务器端

服务器端采用RMI和Socket与客户端通信

1.RMI

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){
  }
  //这一步没问题后就可以调用了

2.Socket

与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;
            }

        }
    }


    }
}

3.数据库

采用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 ;
    }
}

4.基本逻辑

RMI定义方法给客户端调用,如登录、发消息、接收消息、加好友等
Socket 用于确认用户登录状态

  1. 初始化RMI、Mysql成功
  2. Socket开始监听
  3. 用户调用登录方法,并与服务器端建立Socket连接
  4. 用户调用RMI方法

三、客户端

客户端不是我写的具体不是很清楚
看代码就完事了

四、部署到服务器

待更新

总结

你可能感兴趣的:(java项目,mysql,网络)