12.Java实现P2P聊天软件(服务器端实现)

经过前面一段时间的学习,我们学习到了如何利用Socket实现各种功能。
下面,激动人心的时刻到了。
我们要对前面学习提纯的Demo进行综合利用,做出一款真正的聊天软件,这个聊天软件不仅可以发送消息,也可以发送图片,音频和视频等文件。
而且我们要做出一款P2P的聊天软件,即客户端与客户端的点对点通信,无需通过服务器中转消息文件!!

为了便于高效开发我们把开发分为三个阶段:
1.架构设计
2.编码实现
3.测试运行

下面正式开始吧:

1.架构设计

首先我们知道,如果想和一台机器通信,必须要约定好IP地址和端口号,那么如何做才能让客户端与客户端获取对方的ip和端口呢?
这时,服务器的作用就体现了,服务器用来存储这些信息,并实现ip和端口号的转发,这样做大大减小了服务器的压力。
架构图如下:
在这里插入图片描述

通过上面的架构,客户端与客户端通信方式如下:
首先,每个客户端运行时都会将自己的如下信息送至服务器进行更新:客户端唯一主键 ,当前ip地址,端口号
然后,客户端之间通过各自的唯一主键去服务器进行查询,之后建立联系进行通信:
比如client1想和client2进行通信,那么client1就去服务器查询client2当前的ip和端口号,然后通过ip和端口号连接client2,连接成功后,二者就可以实现通信了。

2.编码实现

好了,经过面的设计,我们得到了一个初步的方案,那么下面就是编码实现环节了!!

首先进行服务器的实现:
我们知道,服务器的主要功能是客户端信息登记和转发处理,所以它必须实现一对多的通信。
代码如下:

package InternetCode.Socket.JJTalk;

import InternetCode.Socket.JJTalk.ServerUtil.ServerThread;
import InternetCode.Socket.JJTalk.pojo.ClientInfo;

import java.net.ServerSocket;
import java.net.Socket;
import java.util.HashMap;
import java.util.Map;

public class JJTalkServer {
    
    
    // 声明ServerSocket对象
    private ServerSocket server;
    // 声明Socket对象socket
    private Socket socket;
    //客户端注册表
    private Map<String, ClientInfo> registerMaps;

    /**
     * 新建服务器对象并等待连接
     */
    public void getServer() {
    
    
        try {
    
    
            server = new ServerSocket(2006);
            registerMaps=new HashMap<>();
            while (true) {
    
    
                // 监听是否有客户端连接
                System.out.println("等待连接!!");
                socket = server.accept();
                System.out.println("连接成功!");
                // 创建并启动连接线程对象
                new ServerThread(socket,registerMaps).start();
            }
        } catch (Exception e) {
    
    
            e.printStackTrace(); // 输出异常信息
        }
    }

    public static void main(String[] args) {
    
    
        JJTalkServer jjTalkServer=new JJTalkServer();
        jjTalkServer.getServer();
    }
}

逻辑如下:创建服务器套接字后对其进行监听,当监听到一个时创建一个连接线程进行交互。

连接线程代码如下:

package InternetCode.Socket.JJTalk.ServerUtil;

import InternetCode.Socket.JJTalk.pojo.ClientInfo;

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.Socket;
import java.util.Map;

public class ServerThread extends Thread{
    
    
    // 创建流对象
    private ObjectOutputStream out = null;
    // 创建流对象
    private ObjectInputStream in = null;
    Socket socket;
    //客户端注册表
    Map<String,ClientInfo> registerMaps;

    public ServerThread(Socket socket, Map<String,ClientInfo>registerMaps) throws IOException {
    
    
        this.socket = socket;
        this.registerMaps=registerMaps;
        in=new ObjectInputStream(socket.getInputStream());
        out=new ObjectOutputStream(socket.getOutputStream());
    }

    public void run() {
    
    
        ClientInfo clientInfo = null;
        try {
    
    
            while (true){
    
    
                clientInfo=(ClientInfo) in.readObject();
                if(clientInfo!=null){
    
    
                    if(clientInfo.isQuery().equals("N")){
    
    
                        registerMaps.put(clientInfo.getClientId(),clientInfo);
                        System.out.println("注册成功:");
                        System.out.println(clientInfo.toString());
                    }else{
    
    
                        ClientInfo clientInfo1=registerMaps.get(clientInfo.getClientId());
                        if(clientInfo1!=null){
    
    
                            out.writeObject(clientInfo1);
                            System.out.println("查询完成:");
                            System.out.println(registerMaps.get(clientInfo.getClientId()).toString());
                        }else{
    
    
                            clientInfo1=new ClientInfo();
                            clientInfo1.setActive("F");
                            out.writeObject(clientInfo1);
                        }
                    }
                }
            }
        } catch (Exception e) {
    
    
            e.printStackTrace();
            System.out.println(socket + "已经退出。\n");
        }
    }
}

客户端信息对象如下:

package InternetCode.Socket.JJTalk.pojo;

import java.io.Serializable;

public class ClientInfo implements Serializable {
    
    

    /**
     * 客户端id
     */
    private String clientId;
    /**
     * 客户端ip
     */
    private String ip;
    /**
     * 客户端端口号
     */
    private String port;
    /**
     * 是否活跃
     */
    private String isActive;
    /**
     * 是否查询
     */
    private String isQuery;

    public String getClientId() {
    
    
        return clientId;
    }

    public void setClientId(String clientId) {
    
    
        this.clientId = clientId;
    }

    public String getIp() {
    
    
        return ip;
    }

    public void setIp(String ip) {
    
    
        this.ip = ip;
    }

    public String getPort() {
    
    
        return port;
    }

    public void setPort(String port) {
    
    
        this.port = port;
    }

    public String isActive() {
    
    
        return isActive;
    }

    public void setActive(String active) {
    
    
        isActive = active;
    }

    public String isQuery() {
    
    
        return isQuery;
    }

    public void setQuery(String query) {
    
    
        isQuery = query;
    }
    @Override
    public String toString() {
    
    
        return "ClientInfo{" +
                "clientId='" + clientId + '\'' +
                ", ip='" + ip + '\'' +
                ", port='" + port + '\'' +
                ", isActive='" + isActive + '\'' +
                ", isQuery='" + isQuery + '\'' +
                '}';
    }
}

线程建立后便对客户端进行消息监听,一旦有消息传来便进行判断处理,来为各个客户端服务。
测试结果图:
在这里插入图片描述

由于此综合Demo代码量有点大,所以客户端代码将在下篇博客展示,并给出项目地址供大家学习优化!!

猜你喜欢

转载自blog.csdn.net/c1776167012/article/details/121191178