栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

Socket学习--网络通信系统

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

Socket学习--网络通信系统

Socket–韩顺平简易网络通信系统 涉及技术
  1. 面向对象编程
  2. 网络编程
  3. 多线程
  4. IO流
  5. 集合
  6. 框架设计
实现功能
  1. 用户登录
  2. 拉取在线用户列表
  3. 无异常退出(客户端, 服务器端)
  4. 私聊
  5. 群发
  6. 发文件
  7. 服务器端推送消息
实现效果 1.主界面及用户登录

2.拉取在线用户列表

登陆多个用户

3.私聊

发送方

接收方

服务端显示

4.群发消息

发送方

接收者

服务器端显示

5.发送文件

发送方

接收方文件本地地址

服务器端

6.服务器推送消息

服务器端显示

7.离线聊天发送文件

发送方

接收方上线

客户端代码

客户端结构总览

QQView主界面
package com.wu.qqclient.view;

import com.wu.qqclient.service.FileClientService;
import com.wu.qqclient.service.MessageClientService;
import com.wu.qqclient.service.UserClientService;
import com.wu.qqclient.utils.Utility;


public class QQView {
    private boolean loop = true;//控制主菜单中循环
    private String key = "";
    private UserClientService userClientService = new UserClientService();//负责登陆,拉取用户列表,退出功能
    private MessageClientService messageClientService = new MessageClientService();//消息传输相关功能实现
    private FileClientService fileClientService = new FileClientService();

    //客户端主函数
    public static void main(String[] args) {
        new QQView().mainMenu();
        System.out.println("退出系统");
    }

    
    private void mainMenu() {

        while (loop) {

            System.out.println("=============欢迎登录网络通信系统=============");
            System.out.println("ttt 1 登录系统");
            System.out.println("ttt 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(20);
                    //检测用户名和密码
                    if (this.userClientService.checkUser(userId, pwd)) {
                        System.out.println("欢迎" + userId + "用户登录成功");
                        //二级菜单
                        while (loop) {
                            System.out.println("n=============网络通信系统二级菜单(用户:" + userId + ")============="");
                            System.out.println("ttt 1 显示在线用户列表");
                            System.out.println("ttt 2 群发消息");
                            System.out.println("ttt 3 私聊消息");
                            System.out.println("ttt 4 发送文件");
                            System.out.println("ttt 9 退出系统");
                            System.out.print("请输入你的选择: ");
                            key = Utility.readString(1);
                            switch (key) {
                                //拉取在线用户列表
                                case "1":
                                    userClientService.onlineFriendList();
                                    break;
                                //群发消息
                                case "2":
                                    System.out.print("输入群聊消息:");
                                    String info = Utility.readString(50);
                                    messageClientService.groupChat(info, userId);
                                    break;
                                //发送私聊消息
                                case "3":
                                    System.out.print("输入想要私聊的用户名(在线): ");
                                    String getterId = Utility.readString(50);
                                    System.out.print("请输入内容");
                                    String content = Utility.readString(100);
                                    messageClientService.privateChat(content, userId, getterId);
                                    break;
                                //发送文件
                                case "4":
                                    System.out.print("输入想要发送文件的用户名:");
                                    String fileGetterId = Utility.readString(50);
                                    System.out.print("输入想要传输的文件地址");
                                    String src = Utility.readString(50);
                                    fileClientService.sendFile(src, userId, fileGetterId);
                                    break;
                                //退出系统
                                case "9":
                                    userClientService.exitQQClient();
                                    break;
                            }
                        }
                    } else {
                        System.out.println("=============登  录  失  败=============");
                    }
                    break;
                case "9":
                    loop = false;
                    break;
            }
        }
    }


}



Service服务类 1.UserClientService用户请求处理类
package com.wu.qqclient.service;

import com.wu.qqcommon.Message;
import com.wu.qqcommon.MessageType;
import com.wu.qqcommon.User;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.InetAddress;
import java.net.Socket;


public class UserClientService {
    private User user = new User();
    private boolean flag = false;
    private Socket socket;

    
    public boolean checkUser(String userId, String pwd) {
        user.setUserId(userId);
        user.setPasswd(pwd);

        try {
            //以本机为服务器.端口为9999的socket对象
            socket = new Socket(InetAddress.getByName("127.0.0.1"), 9999);
            //得到输出流
            ObjectOutputStream oos = new ObjectOutputStream(socket.getOutputStream());
            //将user传输给服务器
            oos.writeObject(user);
            //socket输入流
            ObjectInputStream ois = new ObjectInputStream(socket.getInputStream());
            //读取数据报
            Message ms = (Message)ois.readObject();
            //登录成功
            if (ms.getMesType().equals(MessageType.MESSAGE_LOGIN_SUCCED)) {
                //创建一个线程对象
                ClientConnectService clientConnectService = new ClientConnectService(socket);
                //开启线程
                clientConnectService.start();
                //将线程加入线程池中,以userId(用户名)为键值,线程对象为Value
                ManageClientConnectServiceThread.addClientConnectServiceThread(userId, clientConnectService);
                //成功登陆,返回值设为true
                flag = true;
            } else {
                //登录失败,关闭socket
                socket.close();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        //返回值
        return flag;
    }

    
    public void onlineFriendList() {
        Message message = new Message();//创建一个数据报对象message
        message.setMesType(MessageType.MESSAGE_GET_ONLINE_FRIEND);//设置消息雷响
        message.setSender(user.getUserId());//设置发送用户信息

        try {
            //通过用户名得到线程池中对应线程,再通过该线程得到的socket对象get输出流
            ObjectOutputStream oos = new ObjectOutputStream(ManageClientConnectServiceThread.
                    getClientConnectServiceThread(this.user.getUserId()).getSocket().getOutputStream());
            //写入数据
            oos.writeObject(message);
        } catch (IOException var3) {
            var3.printStackTrace();
        }

    }

    
    public void exitQQClient() {
        Message message = new Message();
        message.setMesType(MessageType.MESSAGE_CLIENT_EXIT);//消息类型
        message.setSender(user.getUserId());//发送者信息

        try {
            ObjectOutputStream oos = new ObjectOutputStream(socket.getOutputStream());
            oos.writeObject(message);
            System.out.println("用户:"+user.getUserId()+"退出系统");
            //ManageClientConnectServiceThread.removeClientServiceThread(user.getUserId());
            System.exit(0);//退出
        } catch (IOException e) {
            e.printStackTrace();
        }

    }
}

2.ClientConnectService客户端连接线程类
package com.wu.qqclient.service;

import com.wu.qqcommon.Message;
import com.wu.qqcommon.MessageType;

import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.PrintStream;
import java.net.Socket;



public class ClientConnectService extends Thread {
    private Socket socket;
    //private ObjectInputStream ois = null;

    public ClientConnectService(Socket socket) {
        this.socket = socket;
    }

    //线程
    @Override
    public void run() {
        while (true) {
            try {
                //创建输入流
                ObjectInputStream ois = new ObjectInputStream(socket.getInputStream());
                //读取对象
                Message message = (Message) ois.readObject();

                //拉取用户列表
                if (message.getMesType().equals(MessageType.MESSAGE_RET_ONLINE_FRIEND)) {
                    System.out.println("n当前在线用户列表:");
                    String[] onlineUsers = message.getContent().split(" ");
                    for (String s : onlineUsers) {
                        System.out.println("用户: " + s);
                    }
                    //私聊消息
                } else if (message.getMesType().equals(MessageType.MESSAGE_COMM_MES)) {
                    System.out.println("n" + message.getSender() + "发来消息:" + message.getContent());
                    System.out.println("============================================");
                    //群聊消息
                } else if (message.getMesType().equals(MessageType.MESSAGE_GROUP_CHAT_MES)) {
                    System.out.println("n" + message.getSender() + "发来消息:" + message.getContent());
                    System.out.println("============================================");
                    //用户不存在
                } else if (message.getMesType().equals(MessageType.MESSAGE_NOT_ONLINE_USER)) {
                    System.out.println("n该用户未在线,用户上线时将通知");

                } else if (message.getMesType().equals(MessageType.MESSAGE_FILE_MES)) {
                    System.out.println(message.getSender() + "发来文件");
                    FileClientService.readFile(message.getGetter(), message.getFileMessage());

                } else if (message.getMesType().equals(MessageType.MESSAGE_COMM_MES_TOALL)) {
                    System.out.println("n" + message.getSender() + "发来消息:" + message.getContent());

                } else if (message.getMesType().equals(MessageType.MESSAGE_NOT_FOUND_USER)) {
                    System.out.println("n该用户不存在");

                } else if (message.getMesType().equals(MessageType.MESSAGE_OFFLINE_MES)) {
                    if (message.getFileMessage() != null) {
                        System.out.println("n" + message.getSender() + "发来文件,地址为:" + FileClientService.getFilePath
                                (message.getGetter(), message.getFileMessage()));
                        FileClientService.readFile(message.getGetter(), message.getFileMessage());
                    } else {
                        System.out.println("n" + message.getSender() + "发来消息:" + message.getContent());
                    }
                }
//                else if (message.getMesType().equals(MessageType.MESSAGE_CLIENT_EXIT)) {
//                    System.out.println("用户" + message.getGetter() + "退出");
//                    ManageClientConnectServiceThread.removeClientServiceThread(message.getGetter());
//                    ois.close();
//                    socket.close();
//                    return;
//                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    public Socket getSocket() {
        return this.socket;
    }
}

3.ManageClientConnectServiceThread管理客户端连接到服务端的线程的类
package com.wu.qqclient.service;

import java.util.HashMap;



public class ManageClientConnectServiceThread {
    //创建一个HashMap的集合来存储管理线程
    private static HashMap hm = new HashMap();

    
    public static void addClientConnectServiceThread(String userId, ClientConnectService clientConnectService) {
        hm.put(userId, clientConnectService);
    }

    
    public static ClientConnectService getClientConnectServiceThread(String userId) {
        return hm.get(userId);
    }

    
    public static void removeClientServiceThread(String userId) {
        hm.remove(userId);
    }
}

4.FileClientService文件请求处理类
package com.wu.qqclient.service;

import com.wu.qqclient.utils.StreamUtils;
import com.wu.qqcommon.FileMessage;
import com.wu.qqcommon.Message;
import com.wu.qqcommon.MessageType;


import java.io.*;
import java.util.Date;




public class FileClientService {

    private static final String ABSPATH = "E:\java\java_qqproject\qqclientfile";

    
    public void sendFile(String src, String dest, String senderId, String getterId) {
        Message message = new Message();
        message.setSender(senderId);
        message.setGetter(getterId);
        message.setSendTime(new Date().toString());
        message.setMesType(MessageType.MESSAGE_FILE_MES);
        FileInputStream fileInputStream = null;
        File file = new File(src);
        if (file.exists()) {
            try {
                fileInputStream = new FileInputStream(file);
                byte[] bytes = StreamUtils.streamToByteArray(fileInputStream);
                int fileLen = bytes.length;
                message.setFileMessage(new FileMessage(bytes, fileLen, src, dest));
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                if (fileInputStream != null) {
                    try {
                        fileInputStream.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
            try {
                //通过用户名得到线程池中对应线程,再通过该线程得到的socket对象get输出流
                ObjectOutputStream oos = new ObjectOutputStream(ManageClientConnectServiceThread.
                        getClientConnectServiceThread(senderId).getSocket().getOutputStream());
                oos.writeObject(message);
            } catch (IOException e) {
                e.printStackTrace();
            }
        } else {
            System.out.println("该文件不存在");
        }
    }

    public void sendFile(String src, String senderId, String getterId) {
        Message message = new Message();
        message.setSender(senderId);
        message.setGetter(getterId);
        message.setSendTime(new Date().toString());
        message.setMesType(MessageType.MESSAGE_FILE_MES);
        FileInputStream fileInputStream = null;
        File file = new File(src);
        if (file.exists()) {
            try {
                fileInputStream = new FileInputStream(file);
                byte[] bytes = StreamUtils.streamToByteArray(fileInputStream);
                int fileLen = bytes.length;
                message.setFileMessage(new FileMessage(bytes, fileLen, src));
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                if (fileInputStream != null) {
                    try {
                        fileInputStream.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
            try {
                //通过用户名得到线程池中对应线程,再通过该线程得到的socket对象get输出流
                ObjectOutputStream oos = new ObjectOutputStream(ManageClientConnectServiceThread.
                        getClientConnectServiceThread(senderId).getSocket().getOutputStream());
                oos.writeObject(message);
            } catch (IOException e) {
                e.printStackTrace();
            }
        } else {
            System.out.println("该文件不存在");
        }
    }

    
    static void readFile(String getterId, FileMessage fileMessage) {
        String dest = getFilePath(getterId, fileMessage);

        fileMessage.setDest(dest);

        FileOutputStream fileOutputStream = null;
        try {
            fileOutputStream = new FileOutputStream(fileMessage.getDest());
            fileOutputStream.write(fileMessage.getFileBytes());
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                if (fileOutputStream != null) {
                    fileOutputStream.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    
    static String getFilePath(String getterId, FileMessage fileMessage) {
        File file = new File(ABSPATH + "\" + getterId);
        if (!file.exists()) {
            file.mkdir();
        }
        File file1 = new File(fileMessage.getSrc().trim());
        String fileName = file1.getName();
        return ABSPATH + "\" + getterId + "\" + fileName;
    }
}

5.MessageClientService消息请求处理类
package com.wu.qqclient.service;

import com.wu.qqcommon.Message;
import com.wu.qqcommon.MessageType;

import java.io.IOException;
import java.io.ObjectOutputStream;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;



public class MessageClientService {

    
    public void privateChat(String content, String senderId, String getterId) {
        Message message = new Message();//可以用构造器
        message.setSender(senderId);//发送用户
        message.setGetter(getterId);//接收用户
        message.setContent(content);//内容
        message.setSendTime(getTime());//当前时间
        System.out.println(senderId + "对" + getterId + "说:" + content);
        message.setMesType(MessageType.MESSAGE_COMM_MES);//消息类型为私聊
        try {
            //通过用户名得到线程池中对应线程,再通过该线程得到的socket对象get输出流
            ObjectOutputStream oos = new ObjectOutputStream(ManageClientConnectServiceThread.
                    getClientConnectServiceThread(senderId).getSocket().getOutputStream());
            oos.writeObject(message);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    
    public void groupChat(String content, String senderId) {
        Message message = new Message();
        message.setSender(senderId);//发送用户
        message.setContent(content);//接收用户
        message.setSendTime(getTime());//当前时间
        System.out.println(senderId + "对所有人说:" + content);
        message.setMesType(MessageType.MESSAGE_GROUP_CHAT_MES);
        try {
            //通过用户名得到线程池中对应线程,再通过该线程得到的socket对象get输出流
            ObjectOutputStream oos = new ObjectOutputStream(ManageClientConnectServiceThread.
                    getClientConnectServiceThread(senderId).getSocket().getOutputStream());
            oos.writeObject(message);
        } catch (IOException e) {
            e.printStackTrace();
        }

    }

    
    private String getTime() {
        LocalDateTime ldt = LocalDateTime.now();
        DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
        return dateTimeFormatter.format(ldt);
    }
}

服务器端代码

服务器端结构总览

服务器启动主程序
package com.wu;

import com.wu.qqserver.service.QQServer;


public class main {
    public static void main(String[] args) {
        new QQServer();
    }
}
QQServer服务器接口端
package com.wu.qqserver.service;

import com.wu.qqcommon.Message;
import com.wu.qqcommon.MessageType;
import com.wu.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.concurrent.ConcurrentHashMap;


public class QQServer {
    //用户信息集合
    private static ConcurrentHashMap hashMap = new ConcurrentHashMap<>();
    private ServerSocket serverSocket = null;

    //添加用户信息
    static {
        hashMap.put("110", new User("110", "123456"));
        hashMap.put("120", new User("120", "123456"));
        hashMap.put("130", new User("130", "123456"));
        hashMap.put("刹那", new User("刹那", "123456"));
        hashMap.put("菲尔特", new User("菲尔特", "123456"));
        hashMap.put("高达", new User("高达", "123456"));
    }

    
    public boolean checkUser(String userId, String pwd) {
        User user = hashMap.get(userId);
        if (user == null) {
            return false;
        }
        return user.getPasswd().equals(pwd);
    }

    public static ConcurrentHashMap getHashMap() {
        return hashMap;
    }

    
    public QQServer() {
        //端口信息可以在配置文件中记录
        try {
            System.out.println("在9999端口监听");

            new Thread(new SendNewsToAllService()).start();
            serverSocket = new ServerSocket(9999);

            while (true) {//在与某个客户端连接后,依然可以持续监听

                Socket socket = serverSocket.accept();
                //得到socket关联的对象输入流
                ObjectInputStream ois = new ObjectInputStream(socket.getInputStream());
                //得到socket关联的对象输出流
                ObjectOutputStream oos = new ObjectOutputStream(socket.getOutputStream());
                //读取对象
                User user = (User) ois.readObject();
                //创建message对象,准备进行回复
                Message message = new Message();
                //判断用户信息是否正确
                if (checkUser(user.getUserId(), user.getPasswd())) {//正确则回复对应报文,并开启一个与之联系的线程服务
                    message.setMesType(MessageType.MESSAGE_LOGIN_SUCCED);
                    //回复报文
                    oos.writeObject(message);
                    //建立一个以userId为区分的线程服务对象
                    ServerConnectClientThread serverConnectClientThread = new ServerConnectClientThread(socket, user.getUserId());
                    //开启线程
                    serverConnectClientThread.start();
                    //将线程键入一个线程集合中
                    ManageServerThread.addServerThread(user.getUserId(), serverConnectClientThread);
                } else {//错误则回复对应报文,并关闭socket
                    System.out.println("用户:" + user.getUserId() + "密码:" + user.getPasswd() + "服务器登录失败");
                    message.setMesType(MessageType.MESSAGE_LOGIN_FAIL);
                    oos.writeObject(message);
                    socket.close();
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                serverSocket.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

    }
}

Socket线程相关 1.ServerConnectClientThread处理用户请求类
package com.wu.qqserver.service;

import com.wu.qqcommon.Message;
import com.wu.qqcommon.MessageType;

import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.Socket;



public class ServerConnectClientThread extends Thread {

    private Socket socket;
    private String userId;

    public ServerConnectClientThread(Socket socket, String userId) {
        this.socket = socket;
        this.userId = userId;
    }

    public Socket getSocket() {
        return socket;
    }

    //线程
    @Override
    public void run() {
        while (true) {
            try {
                System.out.println("服务器端和客户端" + userId + "保持通信,读取信息");

                if(ManageOfflineMessage.getMessagehm().containsKey(userId)){
                    ManageOfflineMessage.getOfflineMes(userId);
                    ManageOfflineMessage.removeOffMes(userId);
                }

                //获取输入流
                ObjectInputStream ois = new ObjectInputStream(socket.getInputStream());
                Message message = (Message) ois.readObject();


                if (message.getMesType().equals(MessageType.MESSAGE_GET_ONLINE_FRIEND)) {//拉取在线用户列表
                    ObjectOutputStream oos = new ObjectOutputStream(socket.getOutputStream());//输出流
                    System.out.println(message.getSender() + "请求的在线用户列表");
                    Message messageUserList = new Message();//创建一个数据报对象
                    String onlineUser = ManageServerThread.getOnlineUser();//在线用户的userId拼接的字符串
//                    messageUserList.setContent(onlineUser);
//                    messageUserList.setMesType(MessageType.MESSAGE_RET_ONLINE_FRIEND);
                    messageUserList.setGetter(message.getSender());//设置接收者
                    oos.writeObject(MessageService.setReturnOnlineLstMes(messageUserList, onlineUser));//设置数据报并写入数据

                } else if (message.getMesType().equals(MessageType.MESSAGE_COMM_MES)) {//私聊消息
                    //获取对应用户的socket线程
                    ServerConnectClientThread serverThread = ManageServerThread.getServerThread(message.getGetter());

                    if (serverThread != null) {//该用户在线
                        System.out.println(message.getSender() + "向" + message.getGetter() + "发送消息");
                        ObjectOutputStream oosRetrains = new ObjectOutputStream(serverThread.socket.getOutputStream());
                        oosRetrains.writeObject(message);
                    } else if (QQServer.getHashMap().containsKey(message.getGetter())) {//用户不在线
                        ManageOfflineMessage.addOfflineMes(message.getGetter(), message);
                        ObjectOutputStream oos = new ObjectOutputStream(socket.getOutputStream());
                        message.setMesType(MessageType.MESSAGE_NOT_ONLINE_USER);
                        oos.writeObject(message);
                    } else {
                        ObjectOutputStream oos = new ObjectOutputStream(socket.getOutputStream());
                        message.setMesType(MessageType.MESSAGE_NOT_FOUND_USER);
                        oos.writeObject(message);
                    }

                } else if (message.getMesType().equals(MessageType.MESSAGE_GROUP_CHAT_MES)) {//群聊消息
                    System.out.println(message.getSender() + "向全体在线用户发消息");
                    String onlineUser = ManageServerThread.getOnlineUser();//在线用户的userId拼接的字符串
                    String[] users = onlineUser.split(" ");//得到在线用户userId的字符串数组
                    //message.setMesType(MessageType.MESSAGE_COMM_MES);
                    for (String s : users) {
                        if (!s.equals(message.getSender())) {//向除了自己以外的所有在线用户发送消息
                            ObjectOutputStream oos = new ObjectOutputStream(ManageServerThread.getServerThread(s).socket.getOutputStream());
                            oos.writeObject(message);
                        }
                    }

                } else if (message.getMesType().equals(MessageType.MESSAGE_FILE_MES)) {//发送文件
                    ServerConnectClientThread serverThread = ManageServerThread.getServerThread(message.getGetter());
                    if (serverThread != null) {//该用户在线
                        System.out.println(message.getSender() + "向" + message.getGetter() + "发送文件");

                        ObjectOutputStream oosRetrains = new ObjectOutputStream(serverThread.socket.getOutputStream());
                        oosRetrains.writeObject(message);

                    } else if (QQServer.getHashMap().containsKey(message.getGetter())) {//用户不在线
                        ManageOfflineMessage.addOfflineMes(message.getGetter(), message);
                        ObjectOutputStream oos = new ObjectOutputStream(socket.getOutputStream());
                        message.setMesType(MessageType.MESSAGE_NOT_ONLINE_USER);
                        oos.writeObject(message);
                    } else {
                        ObjectOutputStream oos = new ObjectOutputStream(socket.getOutputStream());
                        message.setMesType(MessageType.MESSAGE_NOT_FOUND_USER);
                        oos.writeObject(message);
                    }

                } else if (message.getMesType().equals(MessageType.MESSAGE_CLIENT_EXIT)) {//退出系统
                    System.out.println(message.getSender() + "用户退出系统");
                    Message messageExit = new Message();
//                    messageExit.setMesType(MessageType.MESSAGE_CLIENT_EXIT);
                    messageExit.setGetter(message.getSender());
                    //oos.writeObject(MessageService.setClientExitMes(messageExit));
                    ManageServerThread.removeServerThread(message.getSender());
                    ois.close();
                    socket.close();
                    break;
                }
            } catch (Exception e) {
                e.printStackTrace();
            } finally {

            }

        }
    }
}

2.ManageServerThread管理socket线程类
package com.wu.qqserver.service;

import com.wu.qqcommon.User;

import java.net.Socket;
import java.util.HashMap;
import java.util.Set;


public class ManageServerThread {
    //userId为键值,socket线程对象为Value的HashMap集合
    private static HashMap hm = new HashMap<>();

    
    public static void addServerThread(String userId, ServerConnectClientThread serverConnectClientThread) {
        hm.put(userId, serverConnectClientThread);
    }

    

    public static ServerConnectClientThread getServerThread(String userId) {
        return hm.get(userId);
    }

    
    public static void removeServerThread(String userId) {
        hm.remove(userId);
    }

    
    public static String getOnlineUser() {
        StringBuilder userList = new StringBuilder();
        Set set = hm.keySet();//keySet获得键值用户名
        for (String s : set) {
            userList.append(s).append(" ");
        }
        return userList.toString();
    }

}

服务器端服务处理类 1.MessageService数据报加工类
package com.wu.qqserver.service;

import com.wu.qqcommon.Message;
import com.wu.qqcommon.MessageType;


public class MessageService {
    //在线用户列表数据报(服务器端)
    public static Message setReturnOnlineLstMes(Message message, String onlineUser) {
        message.setContent(onlineUser);
        message.setMesType(MessageType.MESSAGE_RET_ONLINE_FRIEND);
        message.setSender("服务器端口9999");
        return message;
    }

    //退出系统数据报(服务器端)
    public static Message setClientExitMes(Message message) {
        message.setMesType(MessageType.MESSAGE_CLIENT_EXIT);
        message.setSender("服务器端口9999");
        return message;
    }

}
2.SendNewsToAllService服务器推送消息类
package com.wu.qqserver.service;

import com.wu.qqcommon.Message;
import com.wu.qqcommon.MessageType;
import com.wu.utils.Utility;

import java.io.IOException;
import java.io.ObjectOutputStream;
import java.util.Date;

public class SendNewsToAllService implements Runnable {


    @Override
    public void run() {
        while (true) {

            System.out.println("请输入想要推送的消息(输入exit退出推送系统):");
            String news = Utility.readString(100);

            if (news.equals("exit")) {
                break;
            }

            Message message = new Message();
            message.setSender("服务器");
            message.setContent(news);
            message.setSendTime(new Date().toString());
            message.setMesType(MessageType.MESSAGE_COMM_MES_TOALL);

            String onlineUser = ManageServerThread.getOnlineUser();
            String[] users = onlineUser.split(" ");

            for (String s : users) {
                try {
                    ObjectOutputStream oos = new ObjectOutputStream(
                            ManageServerThread.getServerThread(s).getSocket().getOutputStream());
                    oos.writeObject(message);
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }


    }
}
3.ManageOfflineMessage处理离线消息类
package com.wu.qqserver.service;

import com.wu.qqcommon.Message;
import com.wu.qqcommon.MessageType;

import java.io.IOException;
import java.io.ObjectOutputStream;
import java.util.ArrayList;
import java.util.concurrent.ConcurrentHashMap;

public class ManageOfflineMessage {
    private static ConcurrentHashMap> messagehm = new ConcurrentHashMap<>();

    
    public static void addOfflineMes(String getterId, Message message) {
        ArrayList tempMessages = messagehm.get(getterId);
        if (tempMessages != null) {
            tempMessages.add(message);
        } else {
            ArrayList messages = new ArrayList<>();
            messages.add(message);
            messagehm.put(getterId, messages);
        }

    }


    
    public static void getOfflineMes(String getterId) {
        ArrayList messages = messagehm.get(getterId);
        for (Message mes : messages) {

            try {
                mes.setMesType(MessageType.MESSAGE_OFFLINE_MES);
                ObjectOutputStream oos = new ObjectOutputStream(
                        ManageServerThread.getServerThread(getterId).getSocket().getOutputStream());
                oos.writeObject(mes);
                Thread.sleep(50);
            } catch (IOException | InterruptedException e) {
                e.printStackTrace();
            }

        }

    }

    
    public static void removeOffMes(String getterId) {
        ArrayList messages = messagehm.get(getterId);
        messagehm.remove(getterId, messages);
    }

    public static ConcurrentHashMap> getMessagehm() {
        return messagehm;
    }
}
公共代码

客户端和服务器端公有文件,此处仅贴一份.

实体类 1.User用户信息类
package com.wu.qqcommon;

import java.io.Serializable;
import java.util.Objects;

public class User implements Serializable {
    private String userId;//用户名
    private String passwd;//密码
    private static final long serialVersionUID = 1L;

    public User(String userId, String passwd) {
        this.userId = userId;
        this.passwd = passwd;
    }

    public User() {
    }


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

}
2.Message数据报类
package com.wu.qqcommon;

import java.io.Serializable;

public class Message implements Serializable {
    private String sender;//发送者
    private String getter;//接收者
    private String content;//内容
    private String sendTime;//发送时间
    private String mesType;//信息类型
    private FileMessage fileMessage;//文件信息

    private static final long serialVersionUID = 1L;

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

    public String getMesType() {
        return mesType;
    }

    public void setMesType(String mesType) {
        this.mesType = mesType;
    }

    public FileMessage getFileMessage() {
        return fileMessage;
    }

    public void setFileMessage(FileMessage fileMessage) {
        this.fileMessage = fileMessage;
    }
}
3.FileMessage文件信息类
package com.wu.qqcommon;

import java.io.Serializable;

public class FileMessage implements Serializable {
    private byte[] fileBytes;
    private int fileLen = 0;
    private String src;
    private String dest;

    public FileMessage(byte[] fileBytes, int fileLen, String src, String dest) {
        this.fileBytes = fileBytes;
        this.fileLen = fileLen;
        this.src = src;
        this.dest = dest;
    }

    public FileMessage(byte[] fileBytes, int fileLen, String src) {
        this.fileBytes = fileBytes;
        this.fileLen = fileLen;
        this.src = src;
    }

    public FileMessage(String src, String dest) {
        this.src = src;
        this.dest = dest;
    }

    public FileMessage() {

    }

    public byte[] getFileBytes() {
        return fileBytes;
    }

    public void setFileBytes(byte[] fileBytes) {
        this.fileBytes = fileBytes;
    }

    public int getFileLen() {
        return fileLen;
    }

    public void setFileLen(int fileLen) {
        this.fileLen = fileLen;
    }

    public String getDest() {
        return dest;
    }

    public void setDest(String dest) {
        this.dest = dest;
    }

    public String getSrc() {
        return src;
    }

    public void setSrc(String src) {
        this.src = src;
    }

}
4.消息类型接口
package com.wu.qqcommon;

public interface MessageType {
    //不同的值代表不同的消息类型
    String MESSAGE_LOGIN_SUCCED = "1";//登录成功
    String MESSAGE_LOGIN_FAIL = "2";//登录失败
    String MESSAGE_COMM_MES = "3";//常规消息(私聊)
    String MESSAGE_GET_ONLINE_FRIEND = "4";//拉取在线用户列表(客户端)
    String MESSAGE_RET_ONLINE_FRIEND = "5";//拉取在线用户列表(服务器端)
    String MESSAGE_CLIENT_EXIT = "6";//退出系统
    String MESSAGE_NOT_FOUND_USER = "7";//未找到用户
    String MESSAGE_NOT_ONLINE_USER = "8";//未在线用户
    String MESSAGE_GROUP_CHAT_MES = "9";//发送群聊消息
    String MESSAGE_FILE_MES = "10";//发送文件
    String MESSAGE_COMM_MES_TOALL = "11";
    String MESSAGE_OFFLINE_MES = "12";//离线消息
}
工具类 1.Utility控制台输入类
package com.wu.qqclient.utils;




import java.util.*;

public class Utility {
   //静态属性。。。
    private static Scanner scanner = new Scanner(System.in);

    
    
   public static char readMenuSelection() {
        char c;
        for (; ; ) {
            String str = readKeyBoard(1, false);//包含一个字符的字符串
            c = str.charAt(0);//将字符串转换成字符char类型
            if (c != '1' && c != '2' && 
                c != '3' && c != '4' && c != '5') {
                System.out.print("选择错误,请重新输入:");
            } else break;
        }
        return c;
    }

   
    public static char readChar() {
        String str = readKeyBoard(1, false);//就是一个字符
        return str.charAt(0);
    }
    
    
    public static char readChar(char defaultValue) {
        String str = readKeyBoard(1, true);//要么是空字符串,要么是一个字符
        return (str.length() == 0) ? defaultValue : str.charAt(0);
    }
   
    
    public static int readInt() {
        int n;
        for (; ; ) {
            String str = readKeyBoard(2, false);//一个整数,长度<=2位
            try {
                n = Integer.parseInt(str);//将字符串转换成整数
                break;
            } catch (NumberFormatException e) {
                System.out.print("数字输入错误,请重新输入:");
            }
        }
        return n;
    }
    
    public static int readInt(int defaultValue) {
        int n;
        for (; ; ) {
            String str = readKeyBoard(10, true);
            if (str.equals("")) {
                return defaultValue;
            }
         
         //异常处理...
            try {
                n = Integer.parseInt(str);
                break;
            } catch (NumberFormatException e) {
                System.out.print("数字输入错误,请重新输入:");
            }
        }
        return n;
    }

    

    public static String readString(int limit) {
        return readKeyBoard(limit, false);
    }

    
   
    public static String readString(int limit, String defaultValue) {
        String str = readKeyBoard(limit, true);
        return str.equals("")? defaultValue : str;
    }


   
    public static char readConfirmSelection() {
        System.out.println("请输入你的选择(Y/N)");
        char c;
        for (; ; ) {//无限循环
           //在这里,将接受到字符,转成了大写字母
           //y => Y n=>N
            String str = readKeyBoard(1, false).toUpperCase();
            c = str.charAt(0);
            if (c == 'Y' || c == 'N') {
                break;
            } else {
                System.out.print("选择错误,请重新输入:");
            }
        }
        return c;
    }

    
    private static String readKeyBoard(int limit, boolean blankReturn) {
        
      //定义了字符串
      String line = "";

      //scanner.hasNextLine() 判断有没有下一行
        while (scanner.hasNextLine()) {
            line = scanner.nextLine();//读取这一行
           
         //如果line.length=0, 即用户没有输入任何内容,直接回车
         if (line.length() == 0) {
                if (blankReturn) return line;//如果blankReturn=true,可以返回空串
                else continue; //如果blankReturn=false,不接受空串,必须输入内容
            }

         //如果用户输入的内容大于了 limit,就提示重写输入  
         //如果用户如的内容 >0 <= limit ,我就接受
            if (line.length() < 1 || line.length() > limit) {
                System.out.print("输入长度(不能大于" + limit + ")错误,请重新输入:");
                continue;
            }
            break;
        }

        return line;
    }

}
2.StreamUtils流的读写类
package com.wu.qqclient.utils;

import java.io.BufferedReader;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;


public class StreamUtils {
   
   public static byte[] streamToByteArray(InputStream is) throws Exception{
      ByteArrayOutputStream bos = new ByteArrayOutputStream();//创建输出流对象
      byte[] b = new byte[1024];//字节数组
      int len;
      while((len=is.read(b))!=-1){//循环读取
         bos.write(b, 0, len);//把读取到的数据,写入bos   
      }
      byte[] array = bos.toByteArray();//然后将bos 转成字节数组
      bos.close();
      return array;
   }
   
   
   public static String streamToString(InputStream is) throws Exception{
      BufferedReader reader = new BufferedReader(new InputStreamReader(is));
      StringBuilder builder= new StringBuilder();
      String line;
      while((line=reader.readLine())!=null){
         builder.append(line+"rn");
      }
      return builder.toString();
      
   }

}
总结评价 开发过程中出现的异常

1.Socket java.net.SocketException: Connection reset

查阅博客:发送数据不符合服务器接收格式,服务器会自动断开连接,socket就会被关闭,就不能使用IO流读取数据,这样就会报这个错误

异常原因:添加FileMessage类后,在服务器端项目代码中,忘记让该类实现Serializable,导致服务器端在接收消息时socket关闭,出现异常.

总结:对于自定义类,想要让其可以在网络上传输,需使其可以串行化.

2.java.io.StreamCorruptedException: invalid stream header: 7371007E

查阅博客:java 序列化追加文件及反序列化读取多个对象

异常原因: 序列化时 new ObjectOutputStream() 会调用 writeStreamHeader() 写入四个字节的表头, 反序列化时 new ObjectInputStream() 只会调用一次 readStreamHeader() 来读表头,导致将表头认为数据进行读取,发生该异常.

我在服务器端的 ServerConnectClientThread 类的线程中定义一个 ObjectOutputStream() ,其写入多次数据,结构为一个表头多份数据.在客户端的 ClientConnectService 的接收线程中,每次接收数据都 new 了一个新的 ObjectInputStream ,其结果就是将数据误认为表头进行处理,发生该异常.

总结:

  • 保证序列化和反序列化时,写入和读取的表头数量一致.
  • 在写入多个数据时,只保留第一个表头,删除添加其他数据时加入的表头,保证在读取时只检测一次表头不会出现异常.
评价
  • 该通信系统简易地实现了社交软件中的常见功能,运行稳定.但是无界面,功能简单,未实现数据库连接,使用相关功能不便捷,仍有许多可以改进的地方.
  • 在代码方面,整体结构较为简单明朗,但是存在许多代码冗余的状况,可以进行精简,部分代码需要进行封装.
  • 开发过程中,对于IO流,Socket的相关API调用不够熟练,项目的整体架构不清晰带来的无从下手也值得注意.
  • 经过该项目加深了对Socket.IO流.面向对象.String字符串等相关知识的理解
  • 使用git版本控制工具,将项目推送到GItee和Github上并进行更新,熟悉了git的操作流程
参考文献
  1. java.io.StreamCorruptedException: invalid stream header
  2. java 序列化追加文件及反序列化读取多个对象
  3. 3种Java从文件路径中获取文件名的方法
  4. 韩顺平java学习笔记
  5. 【零基础 快速学Java】韩顺平 零基础30天学会Java
码云访问地址

服务器端

客户端

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/1020054.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号