您的位置:首页 > 移动开发 > Android开发

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

2014-03-31 15:26 330 查看
一、简介

最近在做一个项目,需要用到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("t
da74
ag", "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建立常连接的思路和部分代码。

 

 

 

 
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
相关文章推荐