上一篇文章是已经完成了搜索区域网内的所有设备,那么搜索出来后就是进行通讯了。
其实,这几篇文章说是wifi的应用也不准确,准确的说都是socket的应用。只要基础应用那篇是涉及wifi的。
之前说了,在发送UDP广播时,我们为了大家都能相互搜索,所以大家都有一个客户端和服务端,但是到了TCP这么做不太好,平时我们玩游戏不就是房主创建房间,其他人加入么?难道每个人都创建自己的房间,然后又可以加入别人的房间?这么做是可以,但是感觉没这个必要,所以就涉及到了上一篇文章遗留的问题:
问题:到底是由谁来建立服务端?
这个问题我是这么解决的:
我们扫描出区域网内的设备时:
1.如果设备已经创立了房间,那么收到搜索广播时,把自己已经建立服务器的信息和服务器的端口号回应过去
2.搜索端收到回应,然后记录收到的设备详细信息
3.搜索方选择需要进行通讯的设备发起建立TCP连接请求(此时应关闭搜索线程),连接请求使用一个连接线程以udp的形式发送
3.1 如果对方还没建立房间,则自己建立一个房间后,将约定的端口号发送给目标设备。目标设备受到请求之后,若同意连接,则回应一个消息同时建立TCP客户端连接到房间
3.2如果对方已经建立房间,在提示对方已经建立房间,是否请求加入。如果是,则建立TCP客户端连接到房间
4.如果自己已经建立自己的房间,但是想加入对方的房间,需要先关闭自己的服务端,然后新建客户端连接到房间
5.成功建立TCP之后,接下来就可以进行聊天和文件传输等了,具体见:Socket实现多人聊天以及文件传输
这只是做一个基本通讯DEMO还有很多逻辑未处理,大家作为参考就行了。作为小白,错误是家常便饭。希望大家指出我的错误和可以优化之处。
下面直接贴代码:
首先,点击该设备时,需要手动关闭搜索线程,建立发起连接线程。发起建立连接线程是这样子的:
public class ConnectThread extends Thread { private JSONObject mJson; private byte[] recvDate = null; private byte[] sendDate = null; private DatagramPacket recvDP; private DatagramSocket recvDS = null; private DatagramSocket sendDS = null; private String name; private String port;//假如创建房间,则作为房间服务端口 private Handler mHandler; public ConnectThread(JSONObject json,String name,String port,Handler handler) { recvDate = new byte[256]; recvDP = new DatagramPacket(recvDate, 0, recvDate.length); mHandler = handler; this.name=name; this.port =port; mJson = json; } @Override public void run() { try { if (mJson.getBoolean(JsonUtil.DATA_SERVER)) { Message message =Message.obtain(); message.what =1003; message.obj = mJson; mHandler.sendMessage(message); } else { mHandler.sendEmptyMessage(1006); recvDS = new DatagramSocket(54000); sendDS = new DatagramSocket(); recvDS.setSoTimeout(5000);//超时5秒 sendDate = JsonUtil.generateData(name, "connect", port, true); DatagramPacket sendDP = new DatagramPacket(sendDate, sendDate.length, (InetAddress) mJson.get(JsonUtil.DATA_ADDRESS), 53000); sendDS.send(sendDP); //等待回应 recvDS.receive(recvDP); //收到回应 mHandler.sendEmptyMessage(1004); } } catch (IOException e) { e.printStackTrace(); } catch (JSONException e) { e.printStackTrace(); } finally { if (recvDS != null) recvDS.close(); if (sendDS != null) sendDS.close(); } } }
大概逻辑是:判断对方是否已经建立房间,如果已经建立,则通知UI,询问用户是否加入对方房间;如果对方没建立,则自己建立一个房间,并且发送一个邀请连接请求给对方;最后等待回应
然后是TCP客户端的代码:
public class TCPClientThread extends Thread {
private String mAddr;
private int mPort;
private PrintWriter writer;
private Handler mHandler;
private boolean isRun;
public TCPClientThread(String address ,String port,Handler handler){
mAddr =address;
mPort = Integer.valueOf(port);
mHandler = handler;
}
@Override
public void run() {
isRun = true;
try {
boolean flag = true;
Socket socket = new Socket(mAddr, mPort);
InputStream is = socket.getInputStream();
OutputStream os = socket.getOutputStream();
BufferedReader reader = new BufferedReader(new InputStreamReader(is, "UTF-8"));
writer = new PrintWriter(new OutputStreamWriter(os,"UTF-8"), true);
while (flag) {
String content = reader.readLine();//阻塞接收消息
if (content==null){
//已经断开链接 应通知UI
break;
}
//将消息通知UI
Message msg = Message.obtain();
msg.obj =new String[]{content,"/"+mAddr};
msg.what=10010;
mHandler.sendMessage(msg);
}
reader.close();
writer.close();
socket.close();
} catch (UnknownHostException e) {
isRun=false;
e.printStackTrace();
Message message =Message.obtain();
message.what=1009;
message.obj = "客户端异常:"+e.getMessage();
mHandler.sendMessage(message);
} catch (IOException e) {
isRun=false;
e.printStackTrace();
Message message =Message.obtain();
message.what=1009;
message.obj = "客户端异常:"+e.getMessage();
}
}
@Override
public synchronized void start() {
if (!isRun) {
super.start();
}
}
//发送消息
public void sendMessage(final String msg){
new Thread(){
@Override
public void run() {
if (writer!=null){
writer.println(msg);
writer.flush();
}
}
}.start();
}
}
大概逻辑是,收到连接请求之后,开始开启这个线程,建立和对方的TCP连接接下来就可以通讯了
最后是TCP服务端的代码:
public class TCPServerThread extends Thread { private ServerSocket ss; private int port;//约定的端口 private Map<String,SocketThread> clients;//保存加入房间的客户端 private Handler mHandler;//用于通知UI更新 private boolean isRun;//标记 线程是否已经运行,避免重复启动 public TCPServerThread(String port,Handler handler) { this.port = Integer.valueOf(port); clients = new HashMap<>(); mHandler = handler; } @Override public void run() { isRun=true; try { ss = new ServerSocket(Integer.valueOf(port)); ss.setReuseAddress(true);//设置端口重用,避免TIME_WAIT引起Address already in use异常 mHandler.sendEmptyMessage(1007);//通知服务已经创建成功 for(;;) { Sock