(本文需有一定的tcp/ip 基础知识,并对java socket 以及java的多线程有一定的了解)

前段时间持续集成平台页面需展示每一个代理端执行控制台输出日志,为了减少主控端存储压力,代理端执行日志只存放在代理端所在主机,每次页面日志的获取直接从代理端获取。

主控和代理端的消息通讯使用java socket,网上关于java socket 基础编程已经存在很多,本文主要探讨 java socket 中的一些延伸性的话题:多代理端同步消息通讯、
socket的分包和粘包,socket 客户端连接管理。

一、socket 客户端连接管理

持续集成的主控端必须要时刻监控代理端的状态,如果代理端没有连接需给出告警,而java socket的在不进行心跳控制的情况下无法真正的获取到代理端的状态

java socket 提供的方法 isConnected() 和 isClosed 无法真正的检测出客户端的状态,请看代码:

socket 服务端代码:

package com.test.socket.state;

import java.net.ServerSocket;
import java.net.Socket;
import java.util.Date;
import java.util.concurrent.ConcurrentLinkedQueue;

import com.zsmart.core.common.DateHelper;


/**
 * socket 服务端
 * @author chm
 *
 */
public class ServerSocketTest {

    /**存放客户端连接集合*/
    public static ConcurrentLinkedQueue<Socket> socketList = new ConcurrentLinkedQueue<Socket>();
    /**
     * 程序入口函数
     * @param args
     */
    public static void main(String[] args) {
        ServerSocketTest sst = new ServerSocketTest();
        sst.startMonitor();
    }
    
    /**
     * 监控客户端连接
     */
    public  void startMonitor() {
        try {
            ServerSocket serverS = new ServerSocket(9912);
            Thread th = new Thread(new MonitorClientState());
            th.start();
            while (true) {
                Socket socket = serverS.accept();
                socketList.add(socket);
            }
        }
        catch (Exception e) {
            e.printStackTrace();
        }
    }
    
    /**
     * 客户端状态监控类
     * @author chm
     *
     */
    class MonitorClientState implements Runnable {

        public void run() {
            try {
                while(true){
                    
                    Thread.sleep(2000);
                    System.out.println("exec time: " + DateHelper.date2String(new Date()));
                    for(Socket socketTemp : socketList){
                        System.out.println("ip: " + socketTemp.getInetAddress().getHostAddress() + " -- remote port: " + socketTemp.getPort() 
                            + " is close: " +  socketTemp.isClosed() + " -- is connected: " + socketTemp.isConnected());
                    }
                    
                }
                
            }
            catch (Exception e) {
                e.printStackTrace();
            }
            
        }
        
    }
    
}


socket 客户端代码:

package com.test.socket.state;

import java.net.Socket;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

import com.zsmart.core.common.DateHelper;

/**
 * 客户端socket
 * @author chm
 *
 */
public class ClientSocket {

    public static void main(String[] args) {
        try {
            
            List<Socket> listSocket = new ArrayList<Socket>();
            for(int i=0 ;i < 5 ;i++){
                Socket socket = new Socket("localhost", 9912);
                listSocket.add(socket);
            }
            Thread.sleep(4000);
            Socket socket = listSocket.get(2);
            socket.close();
            
            Socket socket1 = listSocket.get(3);
            socket1.close();
            socket1 = null;
            System.out.println("Exec time: " + DateHelper.date2String(new Date()) + "  设置socket 连接为关闭状态");
            
        }
        catch (Exception e) {
            e.printStackTrace();
        }
        
        
    }
}



我们在server端开始监听客户端的连接状态,每2秒打印出当前的连接状态,在客户端我们启动5个socket连接,过4 秒后直接关闭其中一个同时销毁一个,


并且我们的客户端在进行关闭操作后直接退出了,此时socket应用已经不存在了。看一下输出结果:



exec time: 2014-01-23 15:49:29
ip: 127.0.0.1 -- remote port: 64538 is close: false -- is connected: true
ip: 127.0.0.1 -- remote port: 64539 is close: false -- is connected: true
ip: 127.0.0.1 -- remote port: 64540 is close: false -- is connected: true
ip: 127.0.0.1 -- remote port: 64541 is close: false -- is connected: true
ip: 127.0.0.1 -- remote port: 64542 is close: false -- is connected: true


exec time: 2014-01-23 15:49:31
ip: 127.0.0.1 -- remote port: 64538 is close: false -- is connected: true
ip: 127.0.0.1 -- remote port: 64539 is close: false -- is connected: true
ip: 127.0.0.1 -- remote port: 64540 is close: false -- is connected: true
ip: 127.0.0.1 -- remote port: 64541 is close: false -- is connected: true
ip: 127.0.0.1 -- remote port: 64542 is close: false -- is connected: true


exec time: 2014-01-23 15:49:33
ip: 127.0.0.1 -- remote port: 64538 is close: false -- is connected: true
ip: 127.0.0.1 -- remote port: 64539 is close: false -- is connected: true
ip: 127.0.0.1 -- remote port: 64540 is close: false -- is connected: true
ip: 127.0.0.1 -- remote port: 64541 is close: false -- is connected: true
ip: 127.0.0.1 -- remote port: 64542 is close: false -- is connected: true


Exec time: 2014-01-23 15:49:30  设置socket 连接为关闭状态


在客户端设置其中一个socket 连接状态为关闭同时应用程序退出了,此时从server 端获取到的socket 状态依旧为可用状态。


我们怎么办,如何能真正的获取到socket的连接状态,答案就是:心跳,我们可以这样,在代码中增加如下功能:


server 端监控代码如下:

public void run() {

            while (true) {
                Socket socketOut = null;
                try {
                    Thread.sleep(2000);
                    System.out.println("exec time: " + DateHelper.date2String(new Date()));
                    for (Socket socketTemp : socketList) {
                        socketOut = socketTemp;
                        System.out.println("ip: " + socketTemp.getInetAddress().getHostAddress() + " -- remote port: "
                            + socketTemp.getPort() + " is close: " + socketTemp.isClosed() + " -- is connected: "
                            + socketTemp.isConnected());
                        
                       
                       //心跳,发送消息用来真正的检验客户端是否存在
                       DataOutputStream dataOut = new DataOutputStream( socketTemp.getOutputStream());
                       dataOut.writeBytes("test");
                       dataOut.flush();
                       
                    }

                    System.out.println("\n");
                }
                catch (Exception e) {
                    //出现异常,删除掉出现错误的客户端
                    socketList.remove(socketOut);
                    System.out.println(e.getMessage());
                }
                    
            }

        }



客户端代码如下:


public static void main(String[] args) {
        try {
            
            List<Socket> listSocket = new ArrayList<Socket>();
            for(int i=0 ;i < 5 ;i++){
                Socket socket = new Socket("localhost", 9912);
                listSocket.add(socket);
            }
            Thread.sleep(4000);
            Socket socket = listSocket.get(2);
            socket.close();
            
            Socket socket1 = listSocket.get(3);
            socket1.close();
            socket1 = null;
            System.out.println("Exec time: " + DateHelper.date2String(new Date()) + "  设置socket 连接为关闭状态");
            
            Thread.sleep(8000);
            
        }
        catch (Exception e) {
            e.printStackTrace();
        }
        
    }



server 端增加心跳控制,向代理端发送心跳消息,客户端在设置socket 为关闭后,等待8秒钟然后退出



结果输出如下:



exec time: 2014-01-23 18:14:39


ip: 127.0.0.1 -- remote port: 52669 is close: false -- is connected: true


ip: 127.0.0.1 -- remote port: 52670 is close: false -- is connected: true


ip: 127.0.0.1 -- remote port: 52671 is close: false -- is connected: true


ip: 127.0.0.1 -- remote port: 52674 is close: false -- is connected: true


ip: 127.0.0.1 -- remote port: 52675 is close: false -- is connected: true




exec time: 2014-01-23 18:14:41


ip: 127.0.0.1 -- remote port: 52669 is close: false -- is connected: true


ip: 127.0.0.1 -- remote port: 52670 is close: false -- is connected: true


ip: 127.0.0.1 -- remote port: 52671 is close: false -- is connected: true


Software caused connection abort: socket write error


exec time: 2014-01-23 18:14:43


ip: 127.0.0.1 -- remote port: 52669 is close: false -- is connected: true


ip: 127.0.0.1 -- remote port: 52670 is close: false -- is connected: true


ip: 127.0.0.1 -- remote port: 52674 is close: false -- is connected: true


Software caused connection abort: socket write error


exec time: 2014-01-23 18:14:45


ip: 127.0.0.1 -- remote port: 52669 is close: false -- is connected: true


ip: 127.0.0.1 -- remote port: 52670 is close: false -- is connected: true


ip: 127.0.0.1 -- remote port: 52675 is close: false -- is connected: true



exec time: 2014-01-23 18:14:47


ip: 127.0.0.1 -- remote port: 52669 is close: false -- is connected: true


ip: 127.0.0.1 -- remote port: 52670 is close: false -- is connected: true


ip: 127.0.0.1 -- remote port: 52675 is close: false -- is connected: true



exec time: 2014-01-23 18:14:49


ip: 127.0.0.1 -- remote port: 52669 is close: false -- is connected: true


Connection reset by peer: socket write error


exec time: 2014-01-23 18:14:51


ip: 127.0.0.1 -- remote port: 52670 is close: false -- is connected: true


Connection reset by peer: socket write error


exec time: 2014-01-23 18:14:53


ip: 127.0.0.1 -- remote port: 52675 is close: false -- is connected: true


Connection reset by peer: socket write error


exec time: 2014-01-23 18:14:55


exec time: 2014-01-23 18:14:57




Exec time: 2014-01-23 18:14:40  设置socket 连接为关闭状态



从日志可以看出,在设置了第二个和第三个socket 为关闭状态后,server通过发送心跳消息,成功的判断客户端失去连接,这样就可以近似实时的判断


出代理端连接状态,当然你可以在查询某个连接的状态时,直接发送消息进行判断代理端的连接状态。