java于网络:P2P聊天系统 - Go语言中文社区

java于网络:P2P聊天系统


之前学习完了网络和java跟网络的相关知识,想试着写点东西,可又无从下手....于是就跟着书上完成了这个聊天系统


该系统能够提供聊天和多人聊天,只要输入注册名和IP地址注册和选择聊天对象即可

 

  

 

信息服务器 

信息服务器需要不断的检测新的客户端发来的请求,并且为已经连接的客户端提供服务,所以需要不断的执行1.接收请求 2.解析请求 3.发送响应这三个操作,解析请求又会根据不同类型的请求发送不同的响应

收到的请求,服务器完成的操作,发出的响应
   收到的请求                 服务器完成的操作           发出的响应
1.客服端注册 1.从服务器保存的客户端端信息中查阅是否有此人 2.已存在 1."该名字已被注册"
2.注册,并保存注册名,IP地址  1.注册成功
2.获取在线客户端 从服务器中生成在线客户端列表 2.返回列表
3..获得聊天对象的IP地址 通过注册名从服务器查找对应的IP地址 3.返回聊天对象的IP地址
4.客户端退出服务器 从服务器中删除该客户端的信息 4."已经退出"

服务器不断检测新的客户端发送的请求,每当有新的客服端注册,服务器就会生成一个子线程,只为该客户端服务。

 

客户端与服务器通信时,传递注册名和地址要保证传送的准确性和可靠,故选择TCP连接客户端和服务器,使用Socket对象,客户端与客户端之间进行通信时,要求实时性,不需要无比的准确,故选择UDP连接客户端与客户端,使用DatagramSocket对象进行通信,DatagramPacket为数据包

UDP为无连接传输,在DatagramSocket传送DatagramPacket时,只需要知道IP地址和端口号即可进行通信

服务器实现如下 

服务器有两个类,MessageServer类和MessageHandler类。MessageServer类实现主程序,MessageHandler类是子线程的线程体类,定义了子线程所需完成的各种方法

本例的ServerSocket构造函数,只定义了端口号,没有定义IP地址,IP地址采用默认的地址,即0.0.0.0,表示所有的IP地址,就表示ServerSocket监听在本机的所有IP地址上,通过任何一个IP地址都可以访问到.如果只想访问特定的IP地址,可以进行设置

 

 

 MessageServer 类

public class MessageServer {
    public static final int PORT=8000;//固定端口号
    public static final int MAX_QUEUE_LENGTH=100;

    public void start(){
        try{
            ServerSocket s=new ServerSocket(PORT, MAX_QUEUE_LENGTH);
            System.out.println("****服务器已经启动...****");
            while(true){
                Socket socket=s.accept();//监听是否有客户端的连接,如果有,返回socket对象
                System.out.println("已接收到客户来自: "+socket.getInetAddress());
                MessageHandler handler=new MessageHandler(socket);//为每个新连接的客户端创建一个子线程
                handler.start();//启动线程
            }
        }catch (Exception e){
            e.printStackTrace();
        }
    }
    public static void main(String[] args) {
        MessageServer ms=new MessageServer();
        ms.start();
    }
}
MessageHandler 类

public class MessageHandler implements  Runnable {
    private Socket socket;
    //与聊天端的通信时的输入,输出端
    private ObjectInputStream datainput;
    private ObjectOutputStream dataoutput;
    private Thread listener;
    private  static Hashtable<String,InetSocketAddress>clientMessage= new Hashtable<>();//保存p2p注册名和地址
    private Request request;//请求变量
    private Response response;//响应变量
    private boolean keepListening=true;
    //创建客户端子线程的线程体
    public MessageHandler(Socket socket){
        this.socket=socket;
    }
    public synchronized  void start(){
        if(listener==null){
            try{
                //初始化
                datainput=new ObjectInputStream(socket.getInputStream());
                dataoutput=new ObjectOutputStream(socket.getOutputStream());
                listener=new Thread(this);
                listener.start();
            }catch (IOException e){
                e.printStackTrace();
            }
        }
    }
    public synchronized  void stop(){
        if(listener!=null){
            try{
                listener.interrupt();;
                listener=null;
                datainput.close();
                dataoutput.close();
                socket.close();
            }catch (IOException e){
                e.printStackTrace();
            }
        }
    }
    public void run() {
        try {
            while(keepListening){
                receiveRequest();//接收请求
                parseRequest();//解析请求
                sendResponse();//发送响应
                request=null;
            }
            stop();
        }catch (ClassNotFoundException e){
            e.printStackTrace();
        }catch (IOException e){
            stop();
            System.err.println("与客户端通信出现错误...");
        }
    }
    private void receiveRequest()throws IOException,ClassNotFoundException{
        request=(Request)datainput.readObject();//从客户端接收请求
    }
    private void parseRequest(){
        if(request==null)
            return;
        response=null;
        int requestType=request.getRequestTyper();
        String registerName=request.getRegisterName();
        if(requestType!=1&&!registerNameHasBeenUsed(registerName)){
            response=new Response(1,registerName+"你还未注册!" );
            return;
        }
        switch (requestType){//测试请求类型
            case 1:
                if(registerNameHasBeenUsed(registerName)){
                    response=new Response(1,"|"+registerName+"|"+"已被其他人使用,请使用其他名字注册" );
                    break;
                }
                clientMessage.put(registerName, new InetSocketAddress(socket.getInetAddress(), request.getUDPPort()));
                response=new Response(1,registerName+",你已经注册成功!" );
                System.out.println("|"+registerName+"| 注册成功...");
                break;
            case 2:
                Vector<String> allNameOfRegister= new Vector<>();
                for(Enumeration<String>e=clientMessage.keys();e.hasMoreElements(); ){
                    //生成已注册的P2P端注册名列表
                    allNameOfRegister.addElement(e.nextElement());
                }
                response=new Response(2,allNameOfRegister );
                break;
            case 3:
                String chatRegisterName=request.getChatRegisterName();
                InetSocketAddress chatP2PEndAddress=clientMessage.get(chatRegisterName);
                response=new Response(3, chatP2PEndAddress);
                break;
            case 4:
                clientMessage.remove(registerName);
                response=new Response(1,registerName+",你已经从服务器退出!" );
                keepListening=false;
                System.out.println("|"+registerName+"| 从服务器退出...");
        }
    }
    private boolean registerNameHasBeenUsed(String registerName){
        if(registerName!=null&&clientMessage.get(registerName)!=null)
            return true;
        return false;
    }
    private void sendResponse()throws IOException{
        if(response!=null){
            dataoutput.writeObject(response);//将响应写回聊天端
        }
    }
}

 请求类和响应类

创建Request类和Respone类来封装请求信息和响应信息,Request类和Respone类的对象需要在网络中传输,需要进行序列化,实现Serializable接口

Request类

public class Request implements Serializable {
    private int requestTyper;//请求类型
    private String registerName;//注册名
    private int UDPPort;//端口号
    private String chatRegisterName;//聊天对象的注册名

    public Request(int requestTyper,String registerName){
        this.requestTyper=requestTyper;
        this.registerName=registerName;
    }
    public Request(int requestTyper,String registerName, int UDPPort){
        this(requestTyper,registerName);
        this.UDPPort=UDPPort;
    }
    public Request(int requestTyper,String registerName, String chatRegisterName){
        this(requestTyper,registerName);
        this.chatRegisterName=chatRegisterName;
    }

    public int getRequestTyper() {
        return requestTyper;
    }

    public String getRegisterName() {
        return registerName;
    }

    public int getUDPPort() {
        return UDPPort;
    }

    public String getChatRegisterName() {
        return chatRegisterName;
    }
}
Response 类

public class Response implements Serializable {
    private int responseType;
    private String message;//响应信息
    private Vector<String> allNameOfRegister;//存放所有客户端注册名的集合
    private InetSocketAddress chatP2PEndAddress;//聊天对象的地址
    public  Response(int responseType){
        this.responseType=responseType;
    }
    public Response(int responseType, String message) {
        this.responseType = responseType;
        this.message = message;
    }

    public Response(int responseType, Vector<String> allNameOfRegister) {
        this.responseType = responseType;
        this.allNameOfRegister = allNameOfRegister;
    }

    public Response(int responseType, InetSocketAddress chatP2PEndAddress) {
        this.responseType = responseType;
        this.chatP2PEndAddress = chatP2PEndAddress;
    }

    public int getResponseType() {
        return responseType;
    }

    public String getMessage() {
        return message;
    }

    public Vector<String> getAllNameOfRegister() {
        return allNameOfRegister;
    }

    public InetSocketAddress getChatP2PEndAddress() {
        return chatP2PEndAddress;
    }
}

聊天端的实现

1.P2PChatEnd类实现了主界面

 

public class P2PChatEnd extends JFrame {
    private Register register;
    private GetOnlineP2PEnds getOnlineP2PEnds;
    private Chat chat;
    private JLabel label;
    private JTabbedPane tabbedPane;
    private Exit exit;
    private CommWithServer commWithServer;//与服务器通信的线程
    public P2PChatEnd(){
        setTitle("P2P聊天端");
        label=new JLabel();
        label.setText("P2P聊天端");
        label.setForeground(Color.blue);
        label.setFont(new Font("隶书", Font.BOLD, 22));
        label.setHorizontalTextPosition(SwingConstants.RIGHT);
        label.setBackground(Color.green);

        commWithServer=new CommWithServer();
        register=new Register(commWithServer);
        getOnlineP2PEnds=new GetOnlineP2PEnds(commWithServer);
        chat=new Chat(this);
        register.setChat(chat);
        exit=new Exit(commWithServer,this);
        tabbedPane=new JTabbedPane(JTabbedPane.LEFT);
        tabbedPane.add("系统封面",label);
        tabbedPane.add("注册信息服务器",register);
        tabbedPane.add("选择聊天对象",getOnlineP2PEnds);
        tabbedPane.add("聊天",chat);
        tabbedPane.add("退出信息服务器",exit);
        add(tabbedPane,BorderLayout.CENTER);
        setBounds(120,60,400,147 );
        setVisible(true);
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    }

    public static void main(String[] args) {
        new P2PChatEnd();
    }
}

2.Register类实现了客户端的注册

public class Register extends JPanel implements ActionListener {
    private JLabel hintLabel;
    private JTextField registerNameField,serverIPField;//注册名和IP地址文本框
    private JButton submint;//提交按钮
    private CommWithServer commWithServer;
    private Chat chat;
    private Request request;
    private Response response;
    //使用对象流接收和发送响应和请求
    private ObjectOutputStream pipedOut;
    private ObjectInputStream pipedIn;
    private int clickNum=0;
    private boolean isRegister=false;//判读是否注册

    public Register(CommWithServer commWithServer){
        this.commWithServer=commWithServer;
        setLayout(new BorderLayout());
        hintLabel=new JLabel("注册",JLabel.CENTER);
        hintLabel.setFont(new Font("隶书", Font.BOLD, 18));
        registerNameField=new JTextField(10);
        serverIPField=new JTextField(10);
        submint=new JButton("提交");
        submint.addActionListener(this);
        Box box1=Box.createHorizontalBox();
        box1.add(new JLabel("注 册 名: ",JLabel.CENTER));
        box1.add(registerNameField);
        Box box2= Box.createHorizontalBox();
        box2.add(new JLabel("服 务 器IP: ",JLabel.CENTER));
        box2.add(serverIPField);
        Box boxH=Box.createVerticalBox();

        boxH.add(box1);
        boxH.add(box2);
        boxH.add(submint);

        JPanel panelC=new JPanel();
        panelC.setBackground(new Color(210,210,110 ));
        panelC.add(boxH);
        add(panelC,BorderLayout.CENTER);
        JPanel panelN=new JPanel();
        panelN.setBackground(Color.green);
        panelN.add(hintLabel);
        add(panelN,BorderLayout.NORTH);
    }
    public void setChat(Chat chat){
        this.chat=chat;
    }
    public void actionPerformed(ActionEvent e) {
        if(isRegister){
            String hint="不能重复注册";
            JOptionPane.showMessageDialog(this, hint,"警告",JOptionPane.WARNING_MESSAGE);
            clear();
            return;
        }
        clickNum++;
        String registerName=registerNameField.getText().trim();
        String serverIP=serverIPField.getText().trim();
        if (registerName.length()==0||serverIP.length()==0){
            String hint="必须输入注册名和服务器IP";
            JOptionPane.showMessageDialog(this, hint,"警告",JOptionPane.WARNING_MESSAGE);
            clear();
            return;
        }
        try {
            if(clickNum==1){
                //使用管道通信,让线程commWithServer可以和该类进行通信
                PipedInputStream pipedI=new PipedInputStream();
                PipedOutputStream pipedO=new PipedOutputStream(pipedI);
                //序列化和反序列化
                pipedOut=new ObjectOutputStream(pipedO);
                pipedIn=new ObjectInputStream(pipedI);
            }
            DatagramSocket socket=new DatagramSocket();
            Chat.setSocket(socket);
            int UDPPort=socket.getLocalPort();//获得一个UDP端口号
            request=new Request(1, registerName,UDPPort);//封装请求
            if(commWithServer!=null){
                if(commWithServer.isAlive()){//线程已经启动,已与信息服务器连接
                    commWithServer.close();//断开与信息服务器的连接
                    //连接信息服务器,pipedOut传递给commWithServer,commWithServer再将响应写到缓冲器
                    commWithServer.connect(serverIP,request,pipedOut);
                    commWithServer.notifyCommWithServer();//将线程唤醒
                }else{
                    commWithServer.connect(serverIP,request,pipedOut);//连接信息服务器
                    commWithServer.start();//启动线程,与信息服务器通信
                }
            }
            //pipedIn读取缓存区的响应
            response=(Response)pipedIn.readObject();
        }catch (Exception ex){
            JOptionPane.showMessageDialog(this, "无法连接或与服务器通信出错","警告",JOptionPane.WARNING_MESSAGE);
            clear();
            return;
        }
        String message=response.getMessage();
        boolean flag=true;
        if(message!=null&&message.equals(request.getRegisterName()+",你已经注册成功!")){
            message+="请单击左侧的"获取在线P2P端"";
            flag=false;
        }
        JOptionPane.showMessageDialog(null, message,"信息提示",JOptionPane.PLAIN_MESSAGE);
        if(flag){//注册没有成功,清除单行文本域,返回重新注册
            clear();
            return;
        }
        /*注册成功,将注册名传递给GetOnlineP2PEnds类对象,Chat类对象和Exit对象*/
        GetOnlineP2PEnds.setRegisterName(registerName);
        Chat.setRegisterName(registerName);
        Exit.setRegisterName(registerName);
        isRegister=true;//设置注册成功标志,控制不能重复注册
        //建立并启动"从其他P2P端接收信息"的子线程,等待接收信息
        new Thread(chat).start();
        clear();
    }
    private void clear(){
        registerNameField.setText(" ");
        serverIPField.setText(" ");
    }
}

3.GetOnlineP2PEnds类

public class GetOnlineP2PEnds extends JPanel implements ActionListener {
    private JButton getOnlineP2PEnds,submit;
    private JList list;
    private CommWithServer commWithServer;
    private Request request;
    private Response response;
    private ObjectOutputStream pipedOut;
    private ObjectInputStream pipedIn;
    private static String registerName;
    private int clickNum=0;
    public GetOnlineP2PEnds(CommWithServer commWithServer){
        this.commWithServer=commWithServer;
        setLayout(new BorderLayout());
        getOnlineP2PEnds=new JButton("获取在线P2P端");
        getOnlineP2PEnds.setBackground(Color.green);
        submit=new JButton("提 交");
        submit.setBackground(Color.green);
        getOnlineP2PEnds.addActionListener(this);
        submit.addActionListener(this);
        list=new JList();
        list.setFont(new Font("楷体", Font.BOLD, 15));
        JScrollPane scroll=new JScrollPane();
        scroll.getViewport().setView(list);
        Box box=Box.createHorizontalBox();
        box.add(new JLabel("单击 '获取' :",JLabel.CENTER));
        box.add(getOnlineP2PEnds);
        JPanel panelR=new JPanel(new BorderLayout());
        panelR.setBackground(new Color(201,210,110 ));
        panelR.add(submit,BorderLayout.SOUTH);
        JPanel panel=new JPanel(new BorderLayout());
        panel.setBackground(new Color(210,210,110 ));
        panel.add(box,BorderLayout.NORTH);
        panel.add(new JLabel("选择聊天P2P端:"),BorderLayout.WEST);
        panel.add(scroll,BorderLayout.CENTER);
        panel.add(panelR,BorderLayout.EAST);
        add(panel,BorderLayout.CENTER);
        submit.setEnabled(false);
        validate();
    }
    public static void setRegisterName(String name){
        registerName=name;
    }
    public void actionPerformed(ActionEvent e) {
        if(registerName==null||commWithServer==null||!commWithServer.isAlive()){
            JOptionPane.showMessageDialog(null, "你还没有注册!","信息提示",JOptionPane.PLAIN_MESSAGE);
            return;
        }
        try{
            if(e.getSource()==getOnlineP2PEnds){
                clickNum++;
                if(clickNum==1){
                    PipedInputStream pipedI=new PipedInputStream();
                    PipedOutputStream pipedO=new PipedOutputStream(pipedI);
                    pipedOut=new ObjectOutputStream(pipedO);
                    pipedIn=new ObjectInputStream(pipedI);
                }
                request=new Request(2, registerName);
                commWithServer.setRequest(request);
                commWithServer.setPipedOut(pipedOut);
                commWithServer.notifyCommWithServer();;
                response=(Response)pipedIn.readObject();
                //从响应中得到在线的P2P端注册名列表
                Vector<String> onLineP2PEnds=response.getAllNameOfRegister();
                //尝试将null值传递给此方法会导致未定义的行为,并且最有可能发生异常。 创建的模型直接引用给定的
                // Vector 。调用此方法后尝试修改Vector会导致未定义的行为。
                list.setListData(onLineP2PEnds);
                submit.setEnabled(true);
            }
            if(e.getSource()==submit){
                List<Object> list2=list.getSelectedValuesList();
                int len=list2.size();
                if(len==0){
                    JOptionPane.showMessageDialog(this, "你还未选择聊天P2P端!","信息提示",JOptionPane.PLAIN_MESSAGE);
                    return;
                }
                String register[]=new String[list2.size()];
                for(int i=0;i<list2.size();i++)
                    register[i]=(String)list2.get(i);
                Vector<InetSocketAddress> P2PEndAddress=new Vector<>();
                int chatP2PEnds=0;
                for(int i=0;i<len;i++){
                    if(register[i].equals(registerName))//如果聊天对象名与当前相同,则跳过
                        continue;
                    request=new Request(3, registerName, register[i]);
                    commWithServer.setRequest(request);
                    commWithServer.setPipedOut(pipedOut);
                    commWithServer.notifyCommWithServer();
                    response=(Response)pipedIn.readObject();
                    //以下代码将从响应中得到的聊天对象地址加入到列表中
                    P2PEndAddress.add(response.getChatP2PEndAddress());
                    chatP2PEnds++;
                }
                String message=null;
                if(chatP2PEnds==0){
                    message="你只选择了与自己聊天,请重新选择聊天端!";
                }else{
                    Chat.setChatP2PEndAddress(P2PEndAddress);
                    message="已获取到你选择P2P端的地址,请单击左侧的|聊天|按钮";
                }
                JOptionPane.showMessageDialog(this, message,"信息提示",JOptionPane.PLAIN_MESSAGE);
                P2PEndAddress.clear();//清空地址列表
                list.setListData(P2PEndAddress);
            }
        }catch (Exception e1){
            JOptionPane.showMessageDialog(this, "与服务器通信出错","警告",JOptionPane.WARNING_MESSAGE);
        }
    }
}

写到一半发现把代码都放在博客上不现实,太长了,还是放在GitHub上吧

版权声明:本文来源CSDN,感谢博主原创文章,遵循 CC 4.0 by-sa 版权协议,转载请附上原文出处链接和本声明。
原文链接:https://blog.csdn.net/qq_40866897/article/details/82958395
站方申明:本站部分内容来自社区用户分享,若涉及侵权,请联系站方删除。
  • 发表于 2020-04-19 13:33:22
  • 阅读 ( 880 )
  • 分类:

0 条评论

请先 登录 后评论

官方社群

GO教程

猜你喜欢