前言
最近javaSwing開發中遇到了需要用TCP的長連接來維持服務端和客戶端的連接,所以把自己項目中書寫的服務端接收心跳包並回復客戶端的方法記錄下來,以方便以后遇到相同問題能更快的解決。
服務端代碼

package tcp; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.ServerSocket; import java.net.Socket; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import homektv.Fangjian; import net.sf.json.JSONObject; import utils.DoPOST; /** * C/S架構的服務端對象。 * <p> * 創建時間:2010-7-18 上午12:17:37 * @author weiyiji * @since 1.0 */ public class Server { private int port = 65432; private volatile boolean running=false; private long receiveTimeDelay=180000; //接收時間限制為3分鐘 private Thread connWatchDog; public Map<String, Socket> map = new HashMap<String, Socket>(); private static Server record; private Server() {}; static { record = new Server(); } public Map<String, Socket> getMap() { return map; } public void setMap(Map<String, Socket> map) { this.map = map; } public static Server getInServer() { return record; } public void start(){ if(running) { return; } running=true; connWatchDog(); } public void connWatchDog() { new Thread() { @Override public void run() { try { ServerSocket ss = new ServerSocket(port,5); while(running){ Socket s = ss.accept(); map.put(s.getInetAddress()+"", s); new SocketAction(s).start(); } } catch (IOException e) { e.printStackTrace(); } } }.start(); } //利用內部類創建線程 class SocketAction extends Thread{ Socket s; boolean run=true; String roomid; long lastReceiveTime = System.currentTimeMillis(); public SocketAction(Socket s) { this.s = s; } public void run() { while(running && run){ if(System.currentTimeMillis()-lastReceiveTime>receiveTimeDelay){ overThis(); currentThread().interrupt(); }else{ try { InputStream in = s.getInputStream(); if(in.available()>0){ lastReceiveTime = System.currentTimeMillis(); byte[] data = new byte[1024]; int len =in.read(data); String out = new String(data,0,len,"gbk"); if(!"0.0.0".equals(out)) { roomid = insert(out); } OutputStream oos = s.getOutputStream(); if(out!=null){ oos.write("OK".getBytes("gbk")); oos.flush(); } }else{ Thread.sleep(10); } } catch (Exception e) { e.printStackTrace(); overThis(); } } } } private void overThis() { if(run)run=false; if(s!=null){ try { String ipString = (s.getInetAddress()+"").substring(1); List<String> list1 = new ArrayList<String>(); List<String> list2 = new ArrayList<String>(); list1.add("roomid"); list2.add(roomid); list1.add("roomstatus"); list2.add("已關房"); String doPOST = DoPOST.doPOST("/roomController/updateRoomConfig", list1, list2); s.close(); } catch (IOException e) { e.printStackTrace(); } } } /** * 插入房間信息 * @param news * @return */ public String insert(String news) { List<String> list1 = new ArrayList<String>(); List<String> list2 = new ArrayList<String>(); list1.add("news"); list2.add(news); String doPOST = DoPOST.doPOST("/roomController/insertRoom", list1, list2); JSONObject jsonObject = JSONObject.fromObject(doPOST); return jsonObject.getString("data"); } } /** * 向客戶端發送消息 * @param sendMessString * @param ip */ public void SendMessage(String sendMessString,String ip) { try { Socket socket = map.get("/"+ip); OutputStream oos = socket.getOutputStream(); oos.write(sendMessString.getBytes("gbk")); oos.flush(); } catch (IOException e) { e.printStackTrace(); } } }
服務端發送消息

public void sendMessage() { List<String> list1 = new ArrayList<String>(); List<String> list2 = new ArrayList<String>(); list1.add("filename"); list2.add("information.txt"); new Thread() { public void run() { try { while(true) { String doPOST = DoPOST.doPOST("/informationController/read", list1, list2); if("fail".equals(doPOST)) { //如果沒請求到則每隔半分鍾請求一次 currentThread().sleep(30000); continue; } JSONObject record = JSONObject.parseObject(doPOST); String time = record.getString("time"); String content = record.getString("content"); if("0".equals(time)) { continue; } if(StringUtils.isEmpty(record.getString("chckbxNewCheckBox"))) { continue; } if(StringUtils.isEmpty(content)) { continue; } List<String> list = Fangjian.getAllSendIp(); if(list.size() != 0) { //發送火警指令 for(String sendip : list) { String sendOrder = CommonUtils.SendOrder(content,sendip); } } currentThread().sleep(Integer.valueOf(time)*60000); } } catch (InterruptedException e) { e.printStackTrace(); } } }.start(); }
代碼說明
上面只是TCP協議中服務端的代碼,整體思路是,客戶端啟動后,客戶端向服務端發送信息,服務端向客戶端返回OK,以此建立連接,然后每隔一定時間客戶端向服務端發送一個心跳包,心跳包格式可自行定義,規定若是服務端3分鍾之內為收到客戶端的心跳包則認為客戶端已下線,服務端則關閉與對應的客戶端的連接。該案列中是一台服務器對應多個客戶端,同時與多個客戶端進行通話。
其中用一個Map<String, Socket> 的map來保存服務端監聽的每一個客戶端的ip和對應的Socket,若服務器需單獨與某個客戶端通信,只需根據ip到map中去獲取對應的Socket即可向客戶端發送信息。