android Socket长连接及多客户端管理

来源:互联网 发布:手机msa是什么软件 编辑:程序博客网 时间:2024/05/19 07:43

一、简介

最近在做一个项目,需要用到socket 。具体功能为:在同一wifi下,一个手机作为服务器端,另一个(或多个)手机作为客户端,客户端自动获取服务器的ip,通过socket建立常连接,用客户端手机发送指令控制服务器端的手机。

二、分析:

1、服务器端:

(1)首先服务器端需要告诉同wifi下的客户端自己的ip,所以使用UDP广播,可见我的上一篇文章:http://blog.csdn.net/suyiyang888/article/details/21446655

(2)服务端需要开启多线程任务,与多个客户端保持常连接。

2、客户端:

(1)使用udp,接收服务器发送过来的ip和端口号

(2)使用socket建立连接

(3)开启心跳测试,判断连接是否断开

3、完整项目下载地址http://download.csdn.net/detail/suyiyang888/7061259

三、具体实现:

1、服务器端

(1)使用后台服务,创建类SocketService,因为要随时接收客户端的信息,所以使用service更好一些,这个类主要实现的功能有:开启UDP广播,定时向同网段发送自己的ip地址、端口号和测试字符串;开启socket等待客户端的连接,并对多客户端进行管理。

public class SocketService extends Service { private UDPSocketBroadCast mBroadCast; private ServersSocket mServersSocket; @Override public IBinder onBind(Intent intent) {  // TODO Auto-generated method stub  return null; } @Override public void onCreate() {  // TODO Auto-generated method stub  super.onCreate();  try {   String ip = ConnectionManager.getLocalIP();   if (ip != null && !"".equals(ip)) {    Info.SERVERSOCKET_IP = ip;    mBroadCast = UDPSocketBroadCast.getInstance();    mServersSocket = ServersSocket.getInstance();    mBroadCast.startUDP(Info.SERVERSOCKET_IP,      Info.SERVERSOCKET_PORT);    mServersSocket.startServer(clientData);   } else {    Toast.makeText(getApplicationContext(), "请检查网络设置",      Toast.LENGTH_LONG).show();    stopSelf();   }  } catch (SocketException e) {   // TODO Auto-generated catch block   e.printStackTrace();  } } /**  * 客户端数据在这里处理  */ private ClientDataCallBack clientData = new ClientDataCallBack() {  @Override  public void getClientData(int connectMode, String str) {   switch (connectMode) {   case Info.CONNECT_SUCCESS:// 连接成功    sendCast(Info.CONNECT_SUCCESS, str);    break;   case Info.CONNECT_GETDATA:// 传输数据    sendCast(Info.CONNECT_SUCCESS, str);    break;   case Info.CONNECT_FAIL:    sendCast(Info.CONNECT_FAIL, str);    break;   }  }  private void sendCast(int flag, String str) {   Intent intent = new Intent();   intent.putExtra("flag", flag);   intent.putExtra("str", str);   intent.setAction("updata");   sendBroadcast(intent);  } };}


(2)UDP发送广播类UDPSocketBroadCast,上一篇文章已经介绍,这里不再详细解说

public class UDPSocketBroadCast { /**  * .要使用多点广播,需要让一个数据报标有一组目标主机地址,其思想便是设置一组特殊网络地址作为多点广播地址,第一个多点广播地址都被看作是一个组  * ,当客户端需要发送  * .接收广播信息时,加入该组就可以了.IP协议为多点广播提供这批特殊的IP地址,这些IP地址范围是224.0.0.0---239.255  * .255.255  * ,其中224.0.0.0为系统自用.下面BROADCAST_IP是自己声明的一个String类型的变量,其范围但是前面所说的IP范围  * ,比如BROADCAST_IP="224.224.224.225"  */ private static final String BROADCAST_IP = "224.224.224.225"; private static final int BROADCAST_PORT = 8681; private static byte[] sendData; private boolean isStop = false; private static UDPSocketBroadCast broadCast = new UDPSocketBroadCast(); private MulticastSocket mSocket = null; private InetAddress address = null; private DatagramPacket dataPacket; private UDPSocketBroadCast() { } /**  * 单例  *   * @return  */ public static UDPSocketBroadCast getInstance() {  if (broadCast == null) {   broadCast = new UDPSocketBroadCast();  }  return broadCast; } /**  * 开始发送广播  *   * @param ip  */ public void startUDP(String ip, int port) {  sendData = ("IAMZTSERVERSOCKET" + "-" + ip + "-" + port).getBytes();  ShowLogManager.outputDebug("tag", ip+";"+port);  new Thread(UDPRunning).start(); } /**  * 停止广播  */ public void stopUDP() {  isStop = true;  destroy(); } /**  * 销毁缓存的数据  */ public void destroy() {  broadCast = null;  mSocket = null;  address = null;  dataPacket = null;  sendData = null; } /**  * 创建udp数据  */ private void CreateUDP() {  try {   mSocket = new MulticastSocket(BROADCAST_PORT);   mSocket.setTimeToLive(1);// 广播生存时间0-255   address = InetAddress.getByName(BROADCAST_IP);   mSocket.joinGroup(address);   dataPacket = new DatagramPacket(sendData, sendData.length, address,     BROADCAST_PORT);  } catch (IOException e) {   e.printStackTrace();  } } /**  * 两秒发送一次广播  */ private Runnable UDPRunning = new Runnable() {  @Override  public void run() {   while (!isStop) {    if (mSocket != null) {     try {      mSocket.send(dataPacket);      Thread.sleep(5 * 1000);// 发送一次停5秒     } catch (IOException e) {      e.printStackTrace();     } catch (InterruptedException e) {      e.printStackTrace();     }    } else {     CreateUDP();    }   }  } };}


(3)客户端管理类ClientSocketManager,可实现客户端的连接,断开管理

 *多客户端列表、线程、活跃度管理类 这个程序可设置限制客户端的个数,使用setLimit(boolean isLimit)和setLimitNum(int limitNum)设置,默认五个客户端
 * 使用map来存储,使用客户端ip作为唯一的key

public class ClientSocketManager { private static ClientSocketManager mSocketManager; private Map<String, Socket> socketList = new HashMap<String, Socket>();// 保存客户端列表 private Map<String, Thread> threadList = new HashMap<String, Thread>();// 保存线程列表 private Map<String, Integer> actionFrequency = new HashMap<String, Integer>();// 标记每个socket的使用情况,但客户端过多时,优先关闭活动量小的客户端 private boolean isLimit = false; private int limitNum = 5; private ClientSocketManager() { } /**  * 单例模式  *   * @return  */ public static ClientSocketManager getInstence() {  if (mSocketManager == null) {   mSocketManager = new ClientSocketManager();  }  return mSocketManager; } /**  * 获取当前限制状态  *   * @return  */ public boolean isLimit() {  return isLimit; } /**  * 设置是否增加限制  *   * @param isLimit  */ public void setLimit(boolean isLimit) {  this.isLimit = isLimit; } public int getLimitNum() {  return limitNum; } /**  * 设置客户端限制的个数  *   * @return  */ public void setLimitNum(int limitNum) {  this.limitNum = limitNum; } /**  * 增加客户端socket  *   * @param key使用socket客户端的ip保证不重复  *            ,限制客户端limitNum个以内,当多于limitNum个时会根据  * @param mSocket  * @return  */ public boolean putSocket(String key, Socket mSocket, Thread thread) {  ShowLogManager.outputDebug("tag", "ClientSocketManager add client:"    + key);  if (socketList != null && threadList != null) {   if (socketList.get(key) != null) {// 是否是重复的数据    removeItem(key);   }   if (socketList.size() >= limitNum && threadList.size() >= limitNum     && isLimit) {    String minKey = getMinSocket();// 大于    removeItem(minKey);   }   addItem(key, mSocket, thread);//增加一个客户端   return true;  }  return false; } /**  * 增加一个客户端  *   * @param key  * @param mSocket  * @param thread  */ private void addItem(String key, Socket mSocket, Thread thread) {  socketList.put(key, mSocket);  threadList.put(key, thread);  actionFrequency.put(key, 0); } /**  * 删除一个客户端  *   * @param key  */ private void removeItem(String key) {  socketList.remove(key);  threadList.get(key).interrupt();// 线程断掉  threadList.remove(key);// 在列表中去掉  actionFrequency.remove(key); } /**  * 删除客户端连接  *   * @param key  * @return  */ public boolean removeSocket(String key) {  ShowLogManager.outputDebug("tag", "ClientSocketManager remove client:"    + key);  if (socketList != null && threadList != null) {   try {    socketList.get(key).close();    threadList.get(key).interrupt();   } catch (IOException e) {    e.printStackTrace();   }   removeItem(key);   return true;  }  return false; } /**  * 清除数据  */ public void closeSocket() {  // 断开所有的socket  for (Map.Entry<String, Socket> map : socketList.entrySet()) {   try {    if (map.getValue() != null) {     map.getValue().close();    }   } catch (IOException e) {    e.printStackTrace();   }  }  // 停止所有的线程  for (Map.Entry<String, Thread> map : threadList.entrySet()) {   if (map.getValue() != null && map.getValue().isAlive()) {    map.getValue().interrupt();   }  }  socketList = null;  threadList = null;  actionFrequency = null;  mSocketManager = null; } /**  * 用ip获得某个socket客户端  *   * @param key  * @return  */ public Socket getSocket(String key) {  if (mSocketManager != null && socketList != null) {   return socketList.get(key);  }  return null; } /**  * 返回key对应的线程  *   * @param key  * @return  */ public Thread getThread(String key) {  if (threadList != null && mSocketManager != null) {   return threadList.get(key);  }  return null; } /**  * 获得当前客户端个数,出错时返回-1  *   * @return  */ public int getClientNum() {  if (socketList != null) {   return socketList.size();  }  return -1; } /**  * 返回当前列表的所有socket的ip  *   * @return  */ public List<String> getSocketIPList() {  List<String> list = new ArrayList<String>();  if (socketList != null) {   for (Map.Entry<String, Socket> map : socketList.entrySet()) {    list.add(map.getKey());   }   return list;  }  return null; } /**  * 当客户端有有效的数据更新的时候把活动量增加  *   * @param key  */ public void setFrequency(String key) {  if (actionFrequency != null) {   Integer ins = actionFrequency.get(key);   ins++;   actionFrequency.put(key, ins);  } } /**  * 获得数据传输量最小的客户端的IP  *   * @return  */ public String getMinSocket() {  int cont = 10000;  String minSocketIP = "";  for (Map.Entry<String, Integer> map : actionFrequency.entrySet()) {   if (map.getValue() < cont) {    cont = map.getValue();    minSocketIP = map.getKey();   }  }  return minSocketIP; }}

(4)seriverSocket常连接管理类类ServersSocket,实现客户端的连接,客户端的心跳测试的回应等功能

public class ServersSocket { private static ServersSocket socketServer = null; private ServerSocket serverSocket = null; private ClientSocketManager mClientSocketManager; private boolean allThreadStop = false;// 所有线程循环条件,当需要停止所有线程的时候把这个标志置为true private boolean stopFlag = false;// 接收客户端信息线程的标志位,当false时,一直等待客户端输入 private static long time = 0;// 与timeflag一起判断客户端意外退出时造成的无限接收空情况 private static int timeFlag = 0;// private Handler mHandler = null; private ServersSocket() { } /**  * 单例  *   * @return  */ public synchronized static ServersSocket getInstance() {  if (socketServer == null) {   socketServer = new ServersSocket();  }  return socketServer; } /**  * 启动socketserver port端口号  */ public void startServer(final ClientDataCallBack callBack) {  mHandler = new Handler() {   @Override   public void handleMessage(Message msg) {    super.handleMessage(msg);    int flag = msg.what;    String str = (String) msg.obj;    callBack.getClientData(flag, str);   }  };  try {   mClientSocketManager = ClientSocketManager.getInstence();   mClientSocketManager.setLimit(true);// 设置限制最大客户端数   mClientSocketManager.setLimitNum(6);// 设置最大客户端数为6   serverSocket = new ServerSocket(Info.SERVERSOCKET_PORT);   ShowLogManager.outputDebug("tag", "Create ServerSocket success!");   new Thread(waitClientConnection).start();  } catch (IOException e) {   e.printStackTrace();  } } /**  * 停止socket连接  */ public void stopServer() {  stopFlag = true;  allThreadStop = true;  clear(); } /**  * 清除所有数据  */ private void clear() {  if (mClientSocketManager != null) {   mClientSocketManager.closeSocket();  }  if (serverSocket != null) {   try {    serverSocket.close();   } catch (IOException e) {    e.printStackTrace();   }  }  socketServer = null; } /**  * 服务端  *   * @return  */ public ServerSocket getServerSocket() {  return serverSocket; } /**  * 等待客户端连接  *   * @return  */ private Runnable waitClientConnection = new Runnable() {  @Override  public void run() {   while (!stopFlag && !allThreadStop) {    if (serverSocket != null) {     try {      ShowLogManager.outputDebug("tag",        "serverSocket waiting!");      Socket mSocket = serverSocket.accept();      MyThread thread = new MyThread(mSocket);      thread.start();// 有新客户端连接进来,有几个开几个线程      mClientSocketManager.putSocket(mSocket.getInetAddress()        .getHostAddress(), mSocket, thread);// 添加到客户端管理类中     } catch (IOException e) {      e.printStackTrace();     }    } else {     ShowLogManager.outputDebug("tag", "serverSocket is null!");    }   }   ShowLogManager.outputDebug("tag",     "Wating Thread is exit! and stopFlag:" + stopFlag);  } }; /**  * 接收客户端数据  */ private class MyThread extends Thread {  private Socket mSocket;  MyThread(Socket mSocket) {   this.mSocket = mSocket;  }  @Override  public void run() {   super.run();   InputStream mInputStream = null;   OutputStream outStream = null;   if (mSocket != null) {    boolean stopFlags = false;// 循环标志置为不停止    try {     mInputStream = mSocket.getInputStream();// 获得输入流     outStream = mSocket.getOutputStream();// 获得输出流     String clientIP = mSocket.getInetAddress().getHostAddress();// 获得客户端IP     sendMessages(Info.CONNECT_SUCCESS, clientIP);// 连接成功的回调     while (!stopFlags && !allThreadStop) {      byte[] buf = new byte[10240];      mInputStream.read(buf);// 读取客户端数据      // 当客户端非正常退出时,会无线发送空包,因此加入判断,避免造成无限接收空包      if (time != 0        && System.currentTimeMillis() - time < 200        && timeFlag > 5) {// 说明客户端断开,应断掉这个线程       stopFlags = true;       time = 0;       timeFlag = 0;       mClientSocketManager.removeSocket(clientIP);// 在管理列表中去除这个客户端的所有信息       sendMessages(Info.CONNECT_FAIL, clientIP);// 客户端退出回调IP       continue;      } else if (time != 0        && System.currentTimeMillis() - time >= 200) {// 偶尔一次空包,数据清零       timeFlag = 0;      } else if (time != 0) {// 确定是空包,把空包计数器增加       timeFlag++;      }      time = System.currentTimeMillis();      String str = new String(buf, "utf-8").trim();// 转码      if (str != null && !"".equals(str) && !" ".equals(str)) {       if ("IHAVEQUIT".equals(str)) {// 客户端正常退出时发送过来的数据        mClientSocketManager.removeSocket(clientIP);// 在管理列表中去除这个客户端的所有信息        sendMessages(Info.CONNECT_FAIL, clientIP);// 客户端退出回调       } else if ("IAMINTHETEST".equals(str)) {// 客户端的心跳测试        outStream.write("YOUSTAYONLINE"          .getBytes("utf-8"));        outStream.flush();       } else {// 正常的数据传输        sendMessages(Info.CONNECT_GETDATA, str);        mClientSocketManager.setFrequency(clientIP);// 此客户端活动量增加,用来记录次客户端的活动量       }      }      buf = null;     }    } catch (IOException e) {     e.printStackTrace();    } finally {     if (mInputStream != null) {      try {       mInputStream.close();      } catch (IOException e) {       e.printStackTrace();      }     }    }   }  }  /**   * 需回调数据   *    * @param flag   * @param message   */  private void sendMessages(int flag, String message) {   Message msg = mHandler.obtainMessage();   msg.what = flag;   msg.obj = message;   mHandler.sendMessage(msg);// 连接成功回调客户端IP  } } /**  * 客户端数据回调接口  *   * @author thinkpad  *   */ public interface ClientDataCallBack {  public void getClientData(int connectMode, String str); }}


2、客户端:

客户端在这里做的事情比较少,主要是接收服务端的ip和端口号,开启心跳测试的线程等

(1)UDP接收广播类,接收服务端的ip和端口号,接收到以后就断开这个广播,避免造成资源浪费和耗电。

public class UDPSocketBroadCast { private final String BROADCAST_IP = "224.224.224.225"; private final int BROADCAST_PORT = 8681; private byte[] getData = new byte[1024]; private boolean isStop = false; private MulticastSocket mSocket = null; private InetAddress address = null; private DatagramPacket dataPacket; private Thread mUDPThread = null; private UDPDataCallBack mCallBack = null; /**  * 开始接收广播  *   * @param ip  */ public void startUDP(UDPDataCallBack mCallBack) {  this.mCallBack = mCallBack;  mUDPThread = new Thread(UDPRunning);  mUDPThread.start(); } /**  * 重新启动,当接收到udp后会停掉广播,再次需要时使用reStartUDp()启动  *   * @param ip  */ public void reStartUDP() {  Log.d("tag", "UDP is reStart!");  mUDPThread = null;  isStop = false;  mUDPThread = new Thread(UDPRunning);  mUDPThread.start(); } /**  * 停止广播  */ public void stopUDP() {  isStop = true;  mUDPThread.interrupt(); } /**  * 创建udp数据  */ private void CreateUDP() {  try {   mSocket = new MulticastSocket(BROADCAST_PORT);   mSocket.setTimeToLive(1);// 广播生存时间0-255   address = InetAddress.getByName(BROADCAST_IP);   mSocket.joinGroup(address);   dataPacket = new DatagramPacket(getData, getData.length, address,     BROADCAST_PORT);   Log.d("tag", "udp is create");  } catch (IOException e) {   e.printStackTrace();  } } private Runnable UDPRunning = new Runnable() {  final Handler handler = new Handler() {   @Override   public void handleMessage(Message msg) {    super.handleMessage(msg);    String result = (String) msg.obj;    mCallBack.mCallback(result);    Log.d("tag", "handler get data:" + result);   }  };  @Override  public void run() {   CreateUDP();   Message msg = handler.obtainMessage();   while (!isStop) {    if (mSocket != null) {     try {      mSocket.receive(dataPacket);      String mUDPData = new String(getData, 0,        dataPacket.getLength());      /**       * 确定是否是这个客户端发过来的数据       */      if (mUDPData != null        && "IAMZTSERVERSOCKET".equals(mUDPData          .split("-")[0])) {       msg.obj = mUDPData;       handler.sendMessage(msg);       isStop = true;      }     } catch (IOException e) {      e.printStackTrace();     }    } else {     msg.obj = "error";     handler.sendMessage(msg);    }   }  } }; public interface UDPDataCallBack {  public void mCallback(String str); }}


(2)socket常连接管理类,当udp接收到服务端的ip和端口号,并验证是正确的情况下,开启线程,建立常连接,并另起一个线程,单独进行心跳测试。如果心跳测试判断连接断开,会重启udp接收服务端的最新ip和端口号。

public class SocketClientManager { /**  * 单例对象模式,不同的Activity共享同一个ScoketClientMgr  */ private static SocketClientManager instance = null; private Socket clientSocket = null; private OutputStream outStream = null; private InputStream inStream = null; private String serverIP = ""; private int port = 0; public boolean isStop = false; private SocketClientCallBack callBack; public synchronized static SocketClientManager getInstance() {  if (instance == null) {   instance = new SocketClientManager();  }  return instance; } /**  * 通过这方法回调数据  *   * @param callBack  */ public void getSocketMessage(SocketClientCallBack callBack) {  this.callBack = callBack; } private Handler mHandler = new Handler() {  @Override  public void handleMessage(Message msg) {   super.handleMessage(msg);   callBack.callBack(msg.what);  } }; /**  * 创建客户端  *   * @param serverIP  * @param port  */ public void startClientScoket(String serverIP, int port) {  this.serverIP = serverIP;  this.port = port;  isStop = false;  this.clientSocket = null;  this.outStream = null;  this.inStream = null;  new Thread(runSocket).start();  Log.d("tag", "clientSocket is create"); } private Runnable runSocket = new Runnable() {  @Override  public void run() {   if (clientSocket == null) {    try {     clientSocket = new Socket(serverIP, port);     outStream = clientSocket.getOutputStream();     inStream = clientSocket.getInputStream();     new Thread(runHeartbeat).start();    } catch (UnknownHostException e) {     e.printStackTrace();    } catch (IOException e) {     e.printStackTrace();    }   }  } }; /**  * 心跳测试线程  */ private Runnable runHeartbeat = new Runnable() {  @Override  public void run() {   while (!isStop) {    SendMessage("IAMINTHETEST");    byte[] buf = new byte[10240];    try {     inStream.read(buf);// 读取服务器端数据     String res = new String(buf, 0, buf.length, "utf-8").trim();     Log.d("tag", "return :" + res);     if ("".equals(res)) {// 当服务器接收空包时说明断开了      isStop = true;      Message msg = mHandler.obtainMessage();      msg.what = 1;// 与服务器断开      mHandler.sendMessage(msg);// 知道断开后发送消息     } else {      Thread.sleep(10 * 1000);// 正常     }    } catch (IOException e) {     e.printStackTrace();    } catch (InterruptedException e) {     e.printStackTrace();    }   }  } }; /**  * 客户端正常退出调用这个方法  */ public void clientQuit() {  SendMessage("IHAVEQUIT"); } /**  * 发送数据  *   * @param strMsg  * @return  */ public boolean SendMessage(String strMsg) {  if (clientSocket == null) {   return false;  }  byte[] msgBuffer = null;  try {   msgBuffer = strMsg.getBytes("UTF-8");   outStream.write(msgBuffer);   outStream.flush();   Log.d("tag", "send message is:" + strMsg);  } catch (UnsupportedEncodingException e1) {   e1.printStackTrace();  } catch (IOException e) {   e.printStackTrace();  }  return true; } public void Close() {  CloseSocket(); } private void CloseSocket() {  isStop = true;  try {   if (clientSocket != null) {    clientSocket.close();    clientSocket = null;   }  } catch (IOException e) {   e.printStackTrace();  } } public interface SocketClientCallBack {  public void callBack(int what); }}


以上便是我自己完成的同wifi下使用socket建立常连接的思路和部分代码。

 

 

 

 

0 1
原创粉丝点击