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建立常连接的思路和部分代码。
- android Socket长连接及多客户端管理
- socket长连接客户端管理
- Android客户端 C#服务端 实现socket长连接
- android socket 长连接
- android socket长连接
- android socket长连接
- socket 客户端长连接(C++)
- android socket长连接问题
- Android 使用Socket实现服务器与手机客户端的长连接二:多Client对一Server聊天
- Android 使用Socket实现服务器与手机客户端的长连接一:一对一聊天
- Android 使用Socket实现服务器与手机客户端的长连接五:使用队列封装请求
- Android 使用Socket实现服务器与手机客户端的长连接六:二次封装
- Android 使用Socket实现服务器与手机客户端的长连接八
- android——Socket长连接
- android端 socket长连接 架构
- Android 基于mina 实现 Socket 长连接
- android端 socket长连接 架构
- iOS上的TCP-Socket长连接,Demo客户端
- android setPadding()跟setMargins()的区别是什么
- CBitmap,HBitmap,Bitmap区别及联系
- 项目启动报错 content of element type "class" must match "(meta*,subselect?,cache?,synchronize*,...)"
- UISegmentedControl
- source、sh、bash、./执行脚本的区别
- android Socket长连接及多客户端管理
- Winform FlatStyle可以改变按钮外形
- mongoDB的shell的基本操作
- 控制线程---后台线程
- c++读取某文件夹下的所有文件并查找.txt类型文件名
- 大数据计算:如何仅用1.5KB内存为十亿对象计数
- Hibernate 更新部分更改的字段 hibernate update
- 修改系统默认时间
- Android 4.3 Back key not work.