1 需求分析

经分析,本程序是一个C/S结构,使用TCP协议实现聊天功能,需要实现的功能有如下几点。

  1. 本程序需要有客户端以及服务器端。
  2. 客户端应有良好的交互界面,服务器端应有转发客户端发来的消息和临时保存客户端发来的文件
  3. 本程序应支持多用户,用户可在线进行即时交流
  4. 用户端可发送文字,图片。且支持群聊和私聊
  5. 本程序登陆需要提供账号和密码,账号密码由数据库保存。
  6. 客户端提供注册功能,可选择设置自己的账号密码和昵称。

1.1客户端功能

  1. 客户端可在好友列表看到已上线的好友。
  2. 客户端可在输入框中编辑文字并且可以发送文字消息。
  3. 客户端可选择图片发送给指定好友
  4. 客户端可以进行群聊和私聊。
  5. 客户端保存服务器发来的图片文件,并显示。
  6. 客户端可显示服务器发过来的消息。

1.2服务器功能

  1. 接受文字消息并转发给指定客户端
  2. 接受并且保存客户端发来的图片并转发
  3. 每当有一个客户端连接,就给所有客户端更新好友列表
  4. 每当有一个客户端连接,就给所有客户端发送好友上线通知
  5. 每当有一个客户端离线,就给所有客户端发送好友下线通知

 

程序流程图

JAVA写个聊天小程序 java聊天程序设计代码_gui

 

用例图

2.3.1用户用例图

用户用例图如下

JAVA写个聊天小程序 java聊天程序设计代码_JAVA写个聊天小程序_02

 

 

2.3.2客户端用例图

JAVA写个聊天小程序 java聊天程序设计代码_gui_03

 

 

2.3.3服务器端用例图

JAVA写个聊天小程序 java聊天程序设计代码_java_04

 

数据库设计

本程序使用mySQL数据库保存用户信息。

本程序所涉及到的表有用户表qq_user。

3.1用户表设计

如图为qq_user表的设计

JAVA写个聊天小程序 java聊天程序设计代码_gui_05

数据库连接关键代码

private static String userName;

private static String password;

private static String id;



private static String connectString = "";

static {

    try {

        connectString = "jdbc:mysql://localhost:3306/qqpro";  //端口+数据库名字

        password = "123456";   //mysql账号

        userName = "root"; //mysql密码

        Class.forName("com.mysql.jdbc.Driver");

    } catch (ClassNotFoundException e) {

        e.printStackTrace();

    }



}



public static Connection getConnect() {

    try {

        return DriverManager.getConnection(connectString, userName,password);

    } catch (SQLException e) {

        e.printStackTrace();

    }

    return null;

}

界面设计

4.1.1登录界面

JAVA写个聊天小程序 java聊天程序设计代码_JAVA写个聊天小程序_06

 

图4-1登陆界面

4.1.2 注册界面

JAVA写个聊天小程序 java聊天程序设计代码_聊天软件_07

 

图4-2注册界面

4.1.3聊天主界面

JAVA写个聊天小程序 java聊天程序设计代码_java_08

 

图4-3主界面

 

5 功能展示

启动SERVER,然后启动三次Login,打开三个客户端。

JAVA写个聊天小程序 java聊天程序设计代码_客户端_09

 

点击登录进入主界面

JAVA写个聊天小程序 java聊天程序设计代码_JAVA写个聊天小程序_10

 

接着PYTHON发送一条信息给所有人

JAVA写个聊天小程序 java聊天程序设计代码_聊天软件_11

 

再发一条图片给所有人

JAVA写个聊天小程序 java聊天程序设计代码_java_12

 

再由JAVA发送一条消息给C++。双击好友栏可私聊。

JAVA写个聊天小程序 java聊天程序设计代码_JAVA写个聊天小程序_13

 

再发送图片给PYTHON

JAVA写个聊天小程序 java聊天程序设计代码_JAVA写个聊天小程序_14

 

某一用户下线其他用户可收到系统提示消息,同时好友列表不再显示该用户,这里选择C++下线。

JAVA写个聊天小程序 java聊天程序设计代码_JAVA写个聊天小程序_15

 

同样上线其他用户也能收到提示消息,这里C++再上线

JAVA写个聊天小程序 java聊天程序设计代码_gui_16

 

再开一个客户端,进入注册界面,注册一个新的用户

JAVA写个聊天小程序 java聊天程序设计代码_客户端_17

 

点击注册按钮。

JAVA写个聊天小程序 java聊天程序设计代码_聊天软件_18

 

再使用该账号登录

JAVA写个聊天小程序 java聊天程序设计代码_java_19

 

功能展示完毕。

关键代码

Send类封装了客户端发送消息的功能

public class Send implements Runnable{

    private DataOutputStream dos;
    private FileInputStream fis=null;
    byte[] sendBytes = new byte[1024];

    private boolean isrunning = true;
    private User user;
    private OutputStream out;
    private Socket client;

    public User getUser() {
        return user;
    }

    public void setUser(User user) {
        this.user = user;
    }

    public Send(){

    }
    public Send(Socket socket, User user){
        this();
        try {
            this.user = user;
            this.client = socket;
            out = socket.getOutputStream();
            dos = new DataOutputStream(out);

            sendUserName();
        } catch (IOException e) {
            System.out.println("Send构造异常");

            CloseUtil.closeAll(dos);
            isrunning = false;
        }
    }

    private void sendUserName(){
        try {
            if (user.getUser_name() != null &&! user.getUser_name().equals("")){
                dos.writeUTF(user.getUser_name());

                dos.flush();
            }
        } catch (IOException e) {
            System.out.println("发送失败000");

            isrunning = false;
            CloseUtil.closeAll(dos);
        }
    }
    private String getMsgFromUser(){

        String info = null;
        if (user.getMsg()!=null&& !user.getMsg().equals("")){
            System.out.println("msg not null");
            info = user.getMsg();
        }
        if (info!=null){
            System.out.println(info);
        }

        return info;
    }
    public void send(String info){
        try {
            if (info != null &&!info.equals("")){
                dos.writeUTF(info);
                dos.flush();
                user.setMsg("");    //清空待发送消息
            }
        } catch (IOException e) {
            System.out.println("发送失败001");

            isrunning = false;
            CloseUtil.closeAll(dos);
        }

    }
    public void sendImage(File file){
        try {

            int length = 0;
            fis = new FileInputStream(file);

            while ((length = fis.read(sendBytes, 0, sendBytes.length)) > 0) {
                dos.write(sendBytes, 0, length);
                dos.flush();
            }

            }catch(IOException e){
                System.out.println(e.getMessage());
                System.out.println("发送失败002");
                isrunning = false;
                CloseUtil.closeAll(dos, fis);
            }
        }

    @Override
    public void run() {

    }
}

Receive类负责接受服务器端发来的消息

public class Receive implements Runnable {
    //输入流
    private DataInputStream dis;
    private FileOutputStream fos=null;
    private Socket socket;
    byte[] inputByte = new byte[1024];
    private boolean isrunning = true;
    private UserView userView;
    private InputStream ins;
    private User user;

    public Receive(){

    }
    public Receive(Socket socket, UserView userView){
        this();
        this.socket = socket;
        this.userView = userView;
        try {
            //接受欢迎信息
            ins = socket.getInputStream();
            dis = new DataInputStream(socket.getInputStream());
            String name = dis.readUTF();
            user = new User();
            user.setUser_name(name);
            String  str=dis.readUTF();
            if (!str.equals("")){

                UserView.FontAttrib fontAttrib = new UserView.FontAttrib(str);
                userView.insert(fontAttrib);
            }


        } catch (IOException e) {
            isrunning = false;
            CloseUtil.closeAll(dis);

        }
    }
    public void receive(){
        String data = null;
        try {
            String tem = dis.readUTF();
            System.out.println("接受命令"+tem);
            if (!tem.equals("") && tem!=null){      //消息不为空
                if (tem.equals(Server.CONSTANT_SERVER_MSG)){    //如果是系统消息
                    data = dis.readUTF();   //读数据
                    UserView.FontAttrib fontAttrib = new UserView.FontAttrib(data);
                    userView.insert(fontAttrib);
                }else if (tem.equals(Server.CONSTANT_UPDATE_FRIEND_LIST)){  //更新好友列表消息
                    String str = dis.readUTF(); //读字符串
                    if (str!=null && !str.equals("")){
                        //更新好友列表
                        String[] split = str.split(",");
                        for (String s :split){
                            if (userView.getListModel1().contains(s)){
                                continue;
                            }else {
                                userView.getListModel1().addElement(s);
                            }
                        }
                    }
                    //好友下线消息
                }else if (tem.equals(Server.CONSTANT_LOGIN_OUT)){
                    data = dis.readUTF();
                    UserView.FontAttrib fontAttrib = new UserView.FontAttrib(data);
                    userView.insert(fontAttrib);
                    data = dis.readUTF();
                    userView.getListModel1().removeElement(data);
                    //接受文字给所有人的消息
                }else if (tem.equals(Server.CONSTANT_MSG_ALL)){
                    data = dis.readUTF();
                    UserView.FontAttrib fontAttrib = new UserView.FontAttrib(data);
                    userView.insert(fontAttrib);
                    //私聊消息
                }else if (tem.equals(Server.CONSTANT_MSG)){
                    data = dis.readUTF();
                    UserView.FontAttrib fontAttrib = new UserView.FontAttrib(data);
                    userView.insert(fontAttrib);
                    //图片消息
                }else if (tem.equals(Server.CONSTANT_IMAGE_ONE)||tem.equals(Server.CONSTANT_IMAGE_ALL)){
                    String fileString = dis.readUTF();
                    System.out.println("此处有filename"+fileString);   //打印文件名+文件长度
                    String[] split = fileString.split("#");
                    ReceiveImage(tem, split[0],split[1]);
                } else {
                    System.out.println("未被捕获的"+tem);
                }
            }

        } catch (IOException e) {
            System.out.println("错误");
            isrunning = false;
            CloseUtil.closeAll(dis);
        }

    }
    private void ReceiveImage(String whoReceive, String fileName, String fileLength){
        try {
            System.out.println("创建用户本地文件夹");
            File dir = new File(System.getProperty("user.dir")+"\\src\\Image\\User\\"+user.getUser_name());
            if(!dir.exists()){
                dir.mkdir();    //文件夹不存在,创建文件夹
            }
            File file = new File(dir, fileName);
            fos = new FileOutputStream(file);
            System.out.println("开始接收数据...");
            int length = 0;
            int Size_len =0;
            int i = Integer.parseInt(fileLength);   //约定读取长度等于文件长度时,跳出循环,防止阻塞!!!
            while ((length = dis.read(inputByte, 0, inputByte.length)) > 0) {
                System.out.println(length);
                fos.write(inputByte, 0, length);
                fos.flush();
                Size_len = Size_len+length;
                System.out.println(Size_len);
                if (Size_len == i){
                    break;
                }
            }

            System.out.println("完成接收");

            //显示图片
            userView.insertIcon(file, whoReceive);
        } catch (IOException e) {
            e.printStackTrace();
        }

    }

    //线程体
    @Override
    public void run() {

        while (isrunning){
            receive();
        }
    }
}

服务器端代码

public class Server {
    private List<MyChannel> all = new ArrayList<MyChannel>();
    private List<String> allUser = new ArrayList<String>();
    public static String CONSTANT_SERVER_MSG = "10000";
    public static String CONSTANT_UPDATE_FRIEND_LIST = "10001";
    public static String CONSTANT_MSG_ALL = "10002";
    public static String CONSTANT_MSG = "10003";
    public static String CONSTANT_LOGIN_OUT = "10004";
    public static String CONSTANT_IMAGE_ONE = "10005";
    public static String CONSTANT_IMAGE_ALL = "10006";


    public static void main(String[] args) throws IOException {
        //创建服务器+端口
        new Server().start();
    }
    public void start() throws IOException {
        ServerSocket serverSocket = new ServerSocket(9999);
        //接受客户端连接 阻塞式
        while (true) {
            Socket socket = serverSocket.accept();
            System.out.println("一个客户端建立连接");
            MyChannel Channel = new MyChannel(socket);
            all.add(Channel);
            new Thread(Channel).start();
        }
    }

    private class MyChannel implements Runnable{
        private DataOutputStream dos;
        private DataInputStream dis;
        private FileOutputStream fos = null;
        private FileInputStream fis = null;

        private byte[] inputByte = new byte[1024];
        private byte[] sendBytes = new byte[1024];

        private boolean isrunning=true;
        private InputStream ins;
        private OutputStream out;
        private User user;
        private Socket server;

        public User getUser() {
            return user;
        }

        public void setUser(User user) {
            this.user = user;
        }

        public MyChannel(Socket socket) {
            try {
                server = socket;
                dos = new DataOutputStream(socket.getOutputStream());
                dis = new DataInputStream(socket.getInputStream());
                ins = socket.getInputStream();
                out = socket.getOutputStream();
                user = new User();
                user.setUser_name(dis.readUTF());
                allUser.add(user.getUser_name());
                this.send(user.getUser_name());
                this.send(String.format("欢迎您(%s)进入群聊!【系统消息】", user.getUser_name()));
                this.sendOthers(CONSTANT_SERVER_MSG);
                this.sendOthers("您的好友("+user.getUser_name()+")已上线【系统消息】");
                //发送已上线好友信息
                StringBuilder sb = new StringBuilder();
                if (allUser!=null && allUser.size()>0){
                    boolean flag = false;
                    for (String string : allUser) {
                        if (flag) {
                            sb.append(",");
                        } else {
                            flag = true;
                        }
                        sb.append(string);
                    }
                    System.out.println(sb.toString()); //01,02,03
                }
                this.send(CONSTANT_UPDATE_FRIEND_LIST);   //发送更新标识
                send(sb.toString());
                this.sendOthers(CONSTANT_UPDATE_FRIEND_LIST);   //发送更新标识
                sendOthers(sb.toString());

            } catch (IOException e) {
                isrunning = false;
                CloseUtil.closeAll(dos,dis);
            }
        }
        private String receive(){
            String msg = null;
            try {
                msg = dis.readUTF();
                if (msg!=null && !msg.equals("")){
                    System.out.println("服务器收到"+user.getUser_name()+msg);
                    //表示即将收到的是图片文件
                    if (msg.startsWith("#image_content#")){
                        //保存文件
                        System.out.println("保存文件");

//                        BufferedInputStream bin = new BufferedInputStream(ins);

                        File dir = new File(System.getProperty("user.dir")+"\\src\\Image\\Server\\"+user.getUser_name());

                        if(!dir.exists()){
                            dir.mkdir();    //文件夹不存在,创建文件夹
                        }
                        //解析字符串
                        String [] strArr = msg.split("#");
                        String  fileType = strArr[2];   //文件类型
                        String receiveUser = strArr[3];    //接收方
                        int fileLength = Integer.parseInt(strArr[4]);  //文件长度
                        String imageName = new String(DateStringUtil.getTimeStr2() +"." + fileType);    //保存文件名
                        File file = new File(dir, imageName);

//                        FileOutputStream fout = new FileOutputStream(file);

                        fos = new FileOutputStream(file);
                        System.out.println("开始接收数据...");
                        int length = 0;
                        int Size_len=0; //约定读取长度等于文件长度时,跳出循环,防止阻塞!!!
                        while ((length = dis.read(inputByte, 0, inputByte.length)) > 0) {
                            System.out.println(length);
                            fos.write(inputByte, 0, length);
                            fos.flush();
                            Size_len = Size_len+length;
                            System.out.println(Size_len);
                            if (Size_len == fileLength){
                                break;
                            }
                        }
//                        ins.close();
//                        ins.reset();
//                        ins = server.getInputStream();
//                        dis = new DataInputStream(ins);
//                        inputByte.notifyAll();
                        System.out.println("完成接收");



                        //发送文件

                        System.out.println("发送文件");
                        String s = String.valueOf(fileLength); //文件长度
                        sendImageToUser(file, receiveUser, imageName, s);


                    }else {
                        //私聊消息
                        if (msg.startsWith("@")){
                            String[] split = msg.split(":", 2);
                            String name = split[0].substring(1);
                            String message = split[1];
                            System.out.println(name);
                            sendOne(name, message);     //发送

                        }else {
                            //群聊消息
                            sendOthers(CONSTANT_MSG_ALL);
                            sendOthers(user.getUser_name()+":"+msg);
                            send(CONSTANT_MSG_ALL);
                            send(user.getUser_name()+":"+msg);
                        }
                    }
                }
            } catch (IOException e) {
                System.out.println("异常!" + user.getUser_name()+"已下线【系统消息】");

                sendOthers(CONSTANT_LOGIN_OUT);
                sendOthers("您的好友("+user.getUser_name()+")已下线【系统消息】");
                sendOthers(user.getUser_name());
                allUser.remove(user.getUser_name());
                all.remove(this);
                isrunning = false;
                CloseUtil.closeAll(dis);

            }
            return msg;
        }

        private void sendImage(File file){
            if (file!=null){
                try {
                    fis = new FileInputStream(file);
                    int length =0;
                    while ((length = fis.read(sendBytes, 0, sendBytes.length)) > 0) {
                        dos.write(sendBytes, 0, length);
                        dos.flush();
                    }
//                    server.shutdownOutput();
//                    Thread.currentThread().sleep(2000);
//                    out = server.getOutputStream();
//                    dos = new DataOutputStream(out);
                } catch (IOException e) {
                    System.out.println("发送image失败");

                    isrunning = false;

                    CloseUtil.closeAll(dos);
                    all.remove(this);

                }
            }
        }

        private void sendImageToUser(File file, String receiveUser, String imageName, String fileLength){
            if (receiveUser.startsWith("@")){
                String name = receiveUser.substring(1,receiveUser.length()-1);
                sendOne(name," ");
                for (MyChannel tem:all){
                    if (tem.getUser().getUser_name().equals(user.getUser_name())){

                        tem.send(CONSTANT_IMAGE_ONE);
                        tem.send(imageName+"#"+fileLength);   //发送文件名+文件长度
                        tem.sendImage(file);
//                            tem.server.shutdownOutput();    //通知客户端文件已发送完
                    }
                    if (all.size() == 1){   //如果只有一个用户则认为是自己给自己发送图片,需要等待一段时间,避免接受失败!
                        try {
                            Thread.currentThread().sleep(2000);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                    if (tem.getUser().getUser_name().equals(name)){
                        tem.send(CONSTANT_IMAGE_ONE);
                        tem.send(imageName+"#"+fileLength);   //发送文件名+文件长度
                        tem.sendImage(file);
//                            tem.server.shutdownOutput();


                    }
                }
            }else if (receiveUser.equals("all")){
                System.out.println("image send to all");
                for (MyChannel tem:all){
                    tem.send(CONSTANT_MSG_ALL);
                    tem.send(user.getUser_name()+":");
                    tem.send(CONSTANT_IMAGE_ALL);
                    tem.send(imageName+"#"+fileLength);   //发送文件名+文件长度
                    //发送文件
                    tem.sendImage(file);
//                        tem.server.shutdownOutput();


                }
            }else {
                System.out.println(receiveUser);
            }


        }

        private void sendOne(String name, String msg){
            for (MyChannel tem:all){
                if (tem.getUser().getUser_name().equals(user.getUser_name())){
                    tem.send(CONSTANT_MSG);
                    tem.send("@我("+user.getUser_name()+")对("+name+")说:"+msg);

                }
                if (tem.getUser().getUser_name().equals(name)){
                    tem.send(CONSTANT_MSG);
                    tem.send("@("+user.getUser_name()+")对我("+name+")说:"+msg);
                }
            }
        }
        private void send(String msg){
            if (msg!=null&&!msg.equals("")){
                try {
                    dos.writeUTF(msg);
                    dos.flush();
                } catch (IOException e) {
                    System.out.println("发送失败100");

                    isrunning = false;
                    CloseUtil.closeAll(dos);
                    all.remove(this);
                }
            }
        }
        private void sendOthers(String msg){

            for (MyChannel other:all){
                if (other == this){
                    continue;
                }
                other.send(msg);
            }
        }


        @Override
        public void run() {
            while (isrunning){
                receive();
            }
        }
    }
}

以上就是该程序最重要的代码啦