上学期学校开设了创新项目的课程,我选择了《基于手机定位的Android考勤系统》,在整个开发过程中,总的来说,真的是学到了很多,尤其是客户端和服务器端通信这一块。对Socket通信,多线程等有了一定的认识,所以在此记录一下,一起学习,我的认识还是很浅的,如有错误,欢迎指出。

服务器端

(我这里是把自己的电脑当做服务器,也可以申请云服务器)

主要步骤:
  • 1、在服务器端,用一个端口来实例化一个ServerSocket对象。当服务器端开始运行时,就可以用这个端口时刻监听从客户端发来的连接请求。
  • 2、调用ServerSocket的accept方法,接收从端口上发送来的连接请求,返回客户端的socket对象,用来进行读写IO的操作。
  • 3、利用客户端socket的isConnected()方法,来获取客户端连接的状态,连接成功可以做相应的操作,比如支持多用户并发访问的时候,可以将客户端的socket添加到线程池中。(isConnected()方法获取的并不是实时的客户端的连接状态,可以通过心跳包机制来获取实时的连接状态)。
  • 4、通讯完成后,关闭打开的流和Socket对象。
服务器代码(Server.java)
public class Server {
    private ExecutorService executorService;// 线程池
    private ServerSocket serverSocket = null;
    private Socket socket = null;
    private boolean isStarted = true;//判断服务是否启动

    public Server() {
        try {
            // 创建线程池,池中具有(cpu个数*50)条线程
            executorService = Executors.newFixedThreadPool(Runtime.getRuntime()
                    .availableProcessors() * 50);
            //实例化ServerSocket对象 注意客户端端口号需要和服务器端端口号保持一致 
            serverSocket = new ServerSocket(8090);
            } catch (IOException e) {
            e.printStackTrace();
            //quit();
        }
    }

    public void start() {

        try {
            while (isStarted) {
                System.out.println("等待连接");
                socket = serverSocket.accept();
                String ip = socket.getInetAddress().toString();
                System.out.println("客户端已连接");
                // 为支持多用户并发访问,采用线程池管理每一个用户的连接请求
                if (socket.isConnected())
                {   
                    //new Thread(new HeartBeatMonitor(socket)).start();
                    executorService.execute(new SocketTask(socket));// 添加到线程池
                }
            }
            if (socket != null)
                socket.close();
            if (serverSocket != null)
                serverSocket.close();
        } catch (IOException e) {
            e.printStackTrace();

        }
    }
    /**
     * 退出
     */
    public void quit() {
        try {
            this.isStarted = false;
            serverSocket.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
        new Server().start();//启动服务
       //   new Server().quit();
    }
    //单个Socket线程
    final class SocketTask implements Runnable {
        private Socket socket = null;
        private ServerThread  in;
        private OutputThread out;
        private OutputThreadMap outputThreadMap;


        public SocketTask(Socket socket) {
            this.socket = socket;
            outputThreadMap = OutputThreadMap.getInstance();
            //socketThreadMap = SocketThreadMap.getInstance();
        }

        @Override
        public void run() {
            out = new OutputThread(socket, outputThreadMap);// 先实例化写消息线程,(把对应用户的写线程存入map缓存器中)
            in = new ServerThread(socket, out, outputThreadMap);// 再实例化读消息线程
            out.setStart(true);
            in.setStart(true);
            in.start();
            out.start();
        }
    }
}

服务器读消息线程代码

private Socket socket;
    private Gson gson;
    private OutputThread out;// 传递进来的写消息线程,因为我们要给用户回复消息啊
    private OutputThreadMap map;// 写消息线程缓存器
    private SocketThreadMap socketThreadMap;// 写消息线程缓存器
    private DataInputStream inputStream;// 对象输入流
    private InputStreamReader iReader;
    private boolean isStart = true;// 是否循环读消息
    public long lastReceiveHeart;//上次接收心跳包时间

    public ServerThread(Socket socket, OutputThread out, OutputThreadMap map) {
        // TODO Auto-generated constructor stub
        this.socket = socket;
        this.out = out;
        this.map = map;
        try {
            inputStream = new DataInputStream(socket.getInputStream());// 实例化对象输入流
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public void setStart(boolean isStart) {// 提供接口给外部关闭读消息线程
        this.isStart = isStart;
    }

    @Override
    public void run() {
        // TODO Auto-generated method stub
        try {
            while (isStart) {
                iReader = new InputStreamReader(inputStream, "UTF-8");
                char[] buffer = new char[1024];
                int count = 0;
                String phone = null;
                StringBuffer sBuilder = new StringBuffer();
                while ((count = iReader.read(buffer, 0, buffer.length)) > -1) {
                    sBuilder.append(buffer, 0, count);
                    if (count < 1024 && count != 0) {
                        break;
                    }
                }

                gson = new GsonBuilder().setPrettyPrinting() // 格式化输出(序列化)
                        .setDateFormat("yyyy-MM-dd HH:mm:ss") // 日期格式化输出
                        .create();
                JsonReader jsonReader = new JsonReader(new StringReader(sBuilder.toString()));// 其中jsonContext为String类型的Json数据
                jsonReader.setLenient(true);
                TranObject readObject = gson.fromJson(jsonReader, TranObject.class);
                if (readObject != null )
                {
                    lastReceiveHeart = System.currentTimeMillis();
                    phone = readObject.getFromUser();//手机号作为用户的标识
                }
                //如果距离接收心跳包的时间超过5分钟 说明用户掉线
                if(System.currentTimeMillis() - lastReceiveHeart > 300000) {
                    try {
                        if (phone != null) {//更新用户状态
                            new UserDao().updateStatus(0, phone);
                        }

                        socket.close();
                    } catch (IOException e) {
                        // TODO: handle exception
                        e.printStackTrace();
                    }
                }
                TranObject serverResult = execute(readObject);
                pushMessage(readObject);// 执行推送的消息
                if (serverResult != null) {
                    out.setMessage(serverResult);
                }
            }
            if (iReader != null) {
                iReader.close();
            }
            if (inputStream != null) {
                inputStream.close();
            }
            if (socket != null) {
                socket.close();
            }
        } catch (IOException e) {
            // TODO: handle exception
        }

    }
    // 处理客户端发送过来的消息
    private TranObject execute(TranObject readObject) {

            //...省略代码...
    }
    /**
     * 处理需要互相推送的消息
     * 
     * @param readObject
     */
    private void pushMessage(TranObject readObject) {
          // ...省略代码...
    }

服务器写消息线程

public class OutputThread extends Thread{
    @SuppressWarnings("unused")
    private OutputThreadMap map;
    private SocketThreadMap socketThreadMap;
    //private ObjectOutputStream oos;
    private OutputStreamWriter oStreamWriter;
    private DataOutputStream dataOutputStream;
    private TranObject object;
    private boolean isStart = true;// 循环标志位
    private Socket socket;

    public OutputThread(Socket socket, OutputThreadMap map) {
        this.socket = socket;
        this.map = map;
        try {           
            dataOutputStream = new DataOutputStream(socket.getOutputStream());// 在构造器里面实例化对象输出流
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public void setStart(boolean isStart) {
        this.isStart = isStart;
    }

    // 调用写消息线程,设置了消息之后,唤醒run方法,可以节约资源
    public void setMessage(TranObject object) {
        this.object = object;
        synchronized (this) {
            notify();
        }
    }

    @Override
    public void run() {
        try {

            while (isStart) {
                // 没有消息写出的时候,线程等待
                synchronized (this) {
                    wait();
                }
                if (object != null) {
                     Gson gson = new GsonBuilder()
                                 .setPrettyPrinting()  //格式化输出(序列化)
                                 .setDateFormat("yyyy-MM-dd HH:mm:ss") //日期格式化输出
                                 .create();
                       oStreamWriter = new OutputStreamWriter(dataOutputStream, "UTF-8");
                       String outputString = gson.toJson(object);
                       //dataOutputStream.writeInt(outputString.length());
                       //dataOutputStream.write(outputString.getBytes());
                       //dataOutputStream.flush();
                       StringBuffer sBuilder = new StringBuffer();
                        sBuilder.append(outputString);
                       oStreamWriter.write(sBuilder.toString());
                       oStreamWriter.flush();
                       if(object != null && object.getType()!=TranObjectType.HEART_TEST)
                       {
                           System.out.println(outputString);
                       }

                }
            }
            if(oStreamWriter != null)
            {
                oStreamWriter.close();
            }
            if (dataOutputStream != null)// 循环结束后,关闭流,释放资源
                dataOutputStream.close();
            if (socket != null)
                socket.close();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

}