美文网首页
Java笔记——Socket通信

Java笔记——Socket通信

作者: 麦香菌 | 来源:发表于2018-07-06 19:41 被阅读0次

    重要的类

    ServerSocket与Socket

    1.ServerSocket类

    ServerSocket类主要功能是在指定端口处建立监听服务。

    ServerSocket serverSocket = new ServerSocket(Configuration.PORT);
    

    一台计算机可以开启多个客户端,不同客户端用随机分配的端口区别,ServerSocket在指定端口阻塞监听客户端的请求。

    Socket socket = serverSocket.accept();
    

    若有客户端的socket请求连接,在服务端建立一个对应的Socket与之进行通信,再执行之后的语句。

    2.Socket类

    客户端可创建一个Socket对象连接服务端进行通信:

    Socket socket = new Socket(InetAddress.getLocalHost(), Configuration.PORT);
    

    Socket类的构造函数有两个参数,第一个参数是欲连接到的Server计算机的主机地址,第二个参数是该ServerSocket监听的端口号。
    Socket对象建立成功之后,就可以在Client和Server之间建立一个连接,利用Socket类的方法getOutputStream()和getInputStream()获取和传输数据。

    服务端

    Socket实现多线程通信

    为实现多个客户端连接服务器,解决ServerSocket.accept()阻塞的问题,把服务端对客户端的处理全部写到独立的线程里,并循环监听下一个客户端的连接请求。

    while (true) {
        // 接收客户端Socket,监听,阻塞等待客户端连接,创建socket实例
        Socket socket = serverSocket.accept();
        // 客户端IP
        String ip = socket.getInetAddress().getHostAddress();
        // 客户端端口
        int port = socket.getPort();
        System.out.println("客户端UID:"+ip+":"+port);
        // 建立新线程
        Runnable runnable=new HandleMessageRunnable(socket, ip, port);
        Thread handleClientThread=new Thread(runnable);
        handleClientThread.start();
    }
    

    服务端线程

    通过构造函数获取客户端的socket和clientUid

    public HandleMessageRunnable(Socket socket, String ip, int port) {
        this.socket = socket;
        this.ip = ip;
        this.port = port;
        this.clientUid = ip + ":" + port;
    }
    
    1.添加客户端

    将clientUid添加到ArrayList<String>,并通过HashMap实现clientUid与线程HandleMessageRunnable的映射关系。

    //添加客户端
    public void addClient() {
        clientUidArrayList.add(clientUid);
        hashMap.put(clientUid, this);
    }
    
    2.发送连接成功消息

    通过输出流将当前客户连接成功的消息发送给对应客户端

    public void sendConnectedMessage() throws IOException {
        // 获取输入流
        socketInputStream = socket.getInputStream();
        // 获取输出流
        socketOutputStream = socket.getOutputStream();
        // 向当前客户端传输连接成功信息
        String successMessage ="服务端提示信息:"+Configuration.NEWLINE+ 
                    Util.getCurrentTime() + Configuration.NEWLINE + "成功连接服务器" +
                    Configuration.NEWLINE+ "服务器IP: " + Util.getLocalHostAddress() + 
                    ", 端口: " + Configuration.PORT + Configuration.NEWLINE+ "客户端IP: " 
                    + ip + ", 端口: " + port + Configuration.NEWLINE;
        socketOutputStream.write(successMessage.getBytes());
    }
    
    3.更新所有在线客户端名单

    a.为StringBuilder的开头添加消息类型(Configuration.TYPE_UPDATEONLINELIST)与分隔符(Configuration.SEPARATOR)。

    StringBuilder(Configuration.TYPE_UPDATEONLINELIST + Configuration.SEPARATOR);
    

    b.遍历客户列表(clientUidArrayList),用StringBuilder将客户列表里所有的clientUid拼接在一起,并用逗号隔开。

    for (String clientUid : clientUidArrayList) {
        stringBuilder.append(clientUid);
        int index = clientUidArrayList.indexOf(clientUid);
        int size =  clientUidArrayList.size();
        if (index != size - 1) {
            stringBuilder.append(",");
        }
    }
    

    c.再次遍历客户列表,通过HashMap的映射关系将在线客户端名单一一传给每个客户端。

    String  onlineClients=stringBuilder.toString();
    for (String clientUid : clientUidArrayList) {
        OutputStream out = hashMap.get(clientUid).socket.getOutputStream();
        out.write(onlineClients.getBytes());
    }
    

    客户端

    初始化客户端界面

    a.创建对象,取消窗口关闭键。

    // 创建客户端窗口对象
    clientFrame = new ClientFrame();
    // 窗口关闭键无效,必须通过退出键退出客户端
    clientFrame.setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);
    

    b.通过获取本机的屏幕分辨率设置客户端窗口的位置

    int screenWidth = Util.getScreenWidth();
    int screenHeight = Util.getScreenHeight();
    int x = (screenWidth - Configuration.CLIENT_FRAME_WIDTH) / 2;
    int y = (screenHeight - Configuration.CLIENT_FRAME_HEIGHT) / 2;
    // 设置位置
    clientFrame.setLocation(x, y);
    // 设置窗口可见
    clientFrame.setVisible(true);
    

    连接服务端

    a.创建socket对象连接服务器

    Socket socket = new Socket(InetAddress.getLocalHost(), Configuration.PORT);
    

    b.将创建的socket对象传递给客户端界面类(ClientFrame)

    clientFrame.setSocket(socket);
    

    c.在聊天消息框显示连接成功的欢迎信息

    // 获取输入流
    socketInputStream = socket.getInputStream();
    // 获取输出流
    socketOutputStream = socket.getOutputStream();
    // 获取服务端发送的欢迎信息
    byte[] buf = new byte[1024 * 1];
    int len = socketInputStream.read(buf);
    // 将欢迎信息显示在聊天消息框
    String welcomeMessage=new String(buf, 0, len);
    clientFrame.messageJTextArea.append(welcomeMessage);
    clientFrame.messageJTextArea.append(Configuration.NEWLINE);
    

    数据的发送、处理与接收

    数据的发送、处理与接收

    客户端界面(发送数据)

    选择发送对象

    a.在线名单列表:用int[]获取鼠标选中的在线客户行数,遍历int[],用StringBuilder拼接所有选中的客户的clientUid,并用逗号隔开。

    int[] selectedIndex = onlineJTable.getSelectedRows();
    // 将所有消息目标的uid拼接成一个字符串,以逗号分隔
    receiverStringBuilder = new StringBuilder("");
    for (int i = 0; i < selectedIndex.length; i++) {
        //获取第i行第0列的ip数据
        String ip = (String) tableModel.getValueAt(selectedIndex[i], 0);
        //获取第i行第1列的端口数据
        String port = (String) tableModel.getValueAt(selectedIndex[i], 1);
        receiverStringBuilder.append(ip);
        receiverStringBuilder.append(":");
        receiverStringBuilder.append(port);
        if (i != selectedIndex.length - 1) {
            receiverStringBuilder.append(",");
        }
    }
    

    b.发送消息按钮:向发送的客户名单发送消息,在聊天窗显示发送的消息

    // 向服务器发送聊天信息
    OutputStream out = socket.getOutputStream();
    out.write((Configuration.TYPE_CHAT + Configuration.SEPARATOR + receivers +
              Configuration.SEPARATOR+ message).getBytes());                
    messageJTextArea.append(Util.getCurrentTime() + 
              Configuration.NEWLINE + "发往 " + receivers+ Configuration.NEWLINE);
    // 在聊天窗显示发送消息
    messageJTextArea.append(message + Configuration.NEWLINE);
    

    清空文本输入框

    //清空文本输入框
    sendJTextArea.setText("");
    

    c.退出与清屏

    // 向服务器发送退出信息
    OutputStream out = socket.getOutputStream();
    out.write((Configuration.TYPE_EXIT + Configuration.SEPARATOR).getBytes());
    System.exit(0);
    
    messageJTextArea.setText("");
    

    服务端(处理数据)

    循环监听并转发客户端的数据

    a.读取输入流数据

    len = socketInputStream.read(buf);
    String message = new String(buf, 0, len);
    

    b.获取分隔符在字符串的位置,将字符串拆分为消息类型与消息主题两部分

    int separatorIndex=message.indexOf(Configuration.SEPARATOR);
    // 消息类型:退出或者聊天
    String messageType = message.substring(0, separatorIndex);
    // 消息本体:空或者聊天内容
    String messageContent = message.substring(separatorIndex + 1);
    

    c.处理数据:若数据类型为退出客户端,则将客户端信息从clientUid列表和HashMap中移除

    // 更新ArrayList和HashMap, 删除退出的uid和线程
    removeClient();
    // 更新在线名单
    updateOnlineList();
    // 结束循环,结束该服务线程
    break;
    

    更新在线客户名单,结束循环与线程

    public void removeClient() {
        int index = clientUidArrayList.indexOf(clientUid);
        clientUidArrayList.remove(index);
        hashMap.remove(clientUid);
    }
    

    d.处理数据:若数据类型为聊天,则根据逗号位置拆分获取收信人地址与聊天内容

    int messageContentSeparatorIndex=messageContent.indexOf(Configuration.SEPARATOR);
    // 提取收信者地址
    String[] receiveClientUidArray = messageContent.substring(0, 
                                            messageContentSeparatorIndex).split(",");
    // 提取聊天内容
    String word = messageContent.substring(messageContentSeparatorIndex + 1);
    

    遍历所有要发送的客户端,通过输出流一一发送数据

    public void sendChatMessage(String sendClientUid, String[] receiveClientUidArray, 
        String word) throws IOException {
        for (String clientUid : receiveClientUidArray) {
            OutputStream out = hashMap.get(clientUid).socket.getOutputStream();
            String message=Configuration.TYPE_CHAT+Configuration.SEPARATOR + 
                        sendClientUid + Configuration.SEPARATOR + word;
            out.write(message.getBytes());
        }
    }
    

    客户端(接受数据)

    循环监听服务端处理完发来的消息

    a.读取输入流数据

    byte[] buf = new byte[1024 * 1];
    socketInputStream = socket.getInputStream();
    int len = socketInputStream.read(buf);
    // 处理服务器传来的消息
    String message = new String(buf, 0, len);
    

    b.获取分隔符在字符串的位置,将字符串拆分为消息类型与消息主体两部分

    int separatorIndex=message.indexOf(Configuration.SEPARATOR);
    // 消息类型:更新在线名单或者聊天(发送类型)
    String messageType = message.substring(0, separatorIndex);
    // 消息本体:最新的在线名单或者聊天内容(发送内容)
    String messageContent = message.substring(separatorIndex + 1);
    

    c.处理消息:若消息类型为更新列表,则将消息主体拆分为ip地址与端口号,清空更新在线名单界面


    在线名单

    d.处理消息:若消息类型为聊天,则获取分隔符在消息主体的位置,将消息主体拆分为发送者与发送内容两部分

    if (messageType.equals(Configuration.TYPE_CHAT)) {
        int messageContentSeparatorIndex=messageContent.indexOf(Configuration.SEPARATOR);
        String from = messageContent.substring(0, messageContentSeparatorIndex);//发送者
        String word = messageContent.substring(messageContentSeparatorIndex + 1);//发送内容
        clientFrame.messageJTextArea.append(Util.getCurrentTime() + Configuration.NEWLINE 
                    + "来自 "+ from+ Configuration.NEWLINE + word + Configuration.NEWLINE);
        clientFrame.messageJTextArea.setCaretPosition(clientFrame.messageJTextArea.
                    getDocument().getLength());
    }
    

    局域网内两台主机的通信

    主机1
    主机2

    相关文章

      网友评论

          本文标题:Java笔记——Socket通信

          本文链接:https://www.haomeiwen.com/subject/fzlbuftx.html