截图
1、Android 客户端
client.png
2、PC服务端:用的是SocketTool软件模拟
server.png
流程
1、连接DatagramSocket的服务端(ip和port):开启异步线程和socket
2、发送数据(DatagramPacket):异步
3、接收数据(DatagramPacket):注意连接状态,异步读取
4、关闭连接:关闭DatagramSocket和对应线程
注意
1、异常:android.os.NetworkOnMainThreadException。 socket需要在线程中使用
2、前后端统一传输或者接收协议 [requestcode size d1 d2 d3 ... ],在解析时候用得到
3、实施监控socket的连接状态,还是用心跳包发过去,然后返回数据,一段时间没有的话则代表socket连接失败。
4、注意receive接收数据后的有效长度(一个是预存的buffer,一个是有效结果buffer)
5、客户端连上去后不知道为何一定要先发送一次,才能接收?
6、UDP不安全,有长度限制64K
代码
1、UdpClient.java:udp-socket的客户端,略微做了通用封装,主要是连接,发送,接收,然后设置监听
/** * Created by wujn on 2019/2/15. * Version : v1.0 * Function: udp client 64k限制 */public class UdpClient { /** * single instance UdpClient * */ private static UdpClient mSocketClient = null; private UdpClient(){} public static UdpClient getInstance(){ if(mSocketClient == null){ synchronized (UdpClient.class) { mSocketClient = new UdpClient(); } } return mSocketClient; } String TAG_log = "Socket"; private DatagramSocket mSocket; private DatagramPacket sendPacket; //发送 private DatagramPacket receivePacket; //接受// private OutputStream mOutputStream;// private InputStream mInputStream; private SocketThread mSocketThread; private boolean isStop = false;//thread flag /** * 128 - 数据按照最长接收,一次性 * */ private class SocketThread extends Thread { private String ip; private int port; public SocketThread(String ip, int port){ this.ip = ip; this.port = port; } @Override public void run() { Log.d(TAG_log,"SocketThread start "); super.run(); //connect ... try { if (mSocket != null) { mSocket.close(); mSocket = null; } InetAddress ipAddress = InetAddress.getByName(ip); mSocket = new DatagramSocket(); mSocket.connect(ipAddress, port); //连接 //设置timeout //mSocket.setSoTimeout(3000); Log.d(TAG_log,"udp connect = "+isConnect()); if(isConnect()){ isStop = false; uiHandler.sendEmptyMessage(1); } /* 此处这样做没什么意义不大,真正的socket未连接还是靠心跳发送,等待服务端回应比较好,一段时间内未回应,则socket未连接成功 */ else{ uiHandler.sendEmptyMessage(-1); Log.e(TAG_log,"SocketThread connect fail"); return; } } catch (IOException e) { uiHandler.sendEmptyMessage(-1); Log.e(TAG_log,"SocketThread connect io exception = "+e.getMessage()); e.printStackTrace(); return; } Log.d(TAG_log,"SocketThread connect over "); //发送一次,否则不发送则收不到,不知道为啥。。。 sendByteCmd(new byte[]{00},-1);//send once //read ... while (isConnect() && !isStop && !isInterrupted()) { int size; try { byte[] preBuffer = new byte[4 * 1024];//预存buffer receivePacket = new DatagramPacket(preBuffer, preBuffer.length); mSocket.receive(receivePacket); if (receivePacket.getData() == null) return; size = receivePacket.getLength(); //此为获取后的有效长度,一次最多读64k,预存小的话可能分包 Log.d(TAG_log, "pre data size = "+receivePacket.getData().length + ", value data size = "+size); byte[] dataBuffer = Arrays.copyOf(preBuffer, size); if (size > 0) { Message msg = new Message(); msg.what = 100; Bundle bundle = new Bundle(); bundle.putByteArray("data",dataBuffer); bundle.putInt("size",size); bundle.putInt("requestCode",requestCode); msg.setData(bundle); uiHandler.sendMessage(msg); } Log.i(TAG_log, "SocketThread read listening"); //Thread.sleep(100);//log eof } catch (IOException e) { uiHandler.sendEmptyMessage(-1); Log.e(TAG_log,"SocketThread read io exception = "+e.getMessage()); e.printStackTrace(); return; } } } } //==============================socket connect============================ private String ip; private int port; /** * connect socket in thread * Exception : android.os.NetworkOnMainThreadException * */ public void connect(String ip, int port){ this.ip = ip; this.port = port; mSocketThread = new SocketThread(ip, port); mSocketThread.start(); } /** * socket is connect * */ public boolean isConnect(){ boolean flag = false; if (mSocket != null) { flag = mSocket.isConnected(); } return flag; } /** * socket disconnect * */ public void disconnect() { isStop = true; if (mSocket != null) { mSocket.close(); mSocket = null; } if (mSocketThread != null) { mSocketThread.interrupt();//not intime destory thread,so need a flag } } /** * send byte[] cmd * Exception : android.os.NetworkOnMainThreadException * */ public void sendByteCmd(final byte[] mBuffer,int requestCode) { this.requestCode = requestCode; new Thread(new Runnable() { @Override public void run() { try { InetAddress ipAddress = InetAddress.getByName(ip); sendPacket = new DatagramPacket(mBuffer, mBuffer.length, ipAddress, port); mSocket.send(sendPacket); } catch (IOException e) { e.printStackTrace(); } } }).start(); } Handler uiHandler = new Handler() { @Override public void handleMessage(Message msg) { super.handleMessage(msg); switch(msg.what){ //connect error case -1: if (null != onDataReceiveListener) { onDataReceiveListener.onConnectFail(); disconnect(); } break; //connect success case 1: if (null != onDataReceiveListener) { onDataReceiveListener.onConnectSuccess(); } break; //receive data case 100: Bundle bundle = msg.getData(); byte[] buffer = bundle.getByteArray("data"); int size = bundle.getInt("size"); int mequestCode = bundle.getInt("requestCode"); if (null != onDataReceiveListener) { onDataReceiveListener.onDataReceive(buffer, size, mequestCode); } break; } } }; /** * socket response data listener * */ private OnDataReceiveListener onDataReceiveListener = null; private int requestCode = -1; public interface OnDataReceiveListener { public void onConnectSuccess(); public void onConnectFail(); public void onDataReceive(byte[] buffer, int size, int requestCode); } public void setOnDataReceiveListener( OnDataReceiveListener dataReceiveListener) { onDataReceiveListener = dataReceiveListener; } }
MainActivity.java:使用,简单明了
private void initListener(){ //socket connect btn_connect.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { String ip = et_ip.getText().toString(); String port = et_port.getText().toString(); if(TextUtils.isEmpty(ip)){ Toast.makeText(UdpActivity.this,"IP地址为空",Toast.LENGTH_SHORT).show(); return; } if(TextUtils.isEmpty(port)){ Toast.makeText(UdpActivity.this,"端口号为空",Toast.LENGTH_SHORT).show(); return; } connect(ip, Integer.parseInt(port)); } }); //socket disconnect btn_disconnect.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { disconnect(); } }); //socket send btn_send.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { if (UdpClient.getInstance().isConnect()) { byte[] data = et_send.getText().toString().getBytes(); send(data); } else { Toast.makeText(UdpActivity.this,"尚未连接,请连接Socket",Toast.LENGTH_SHORT).show(); } } }); //clear receive btn_clear.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { tv_receive.setText(""); } }); } /** * socket data receive * */ private void initDataReceiver(){ UdpClient.getInstance().setOnDataReceiveListener(dataReceiveListener); } /** * socket connect * */ private void connect(String ip, int port){ UdpClient.getInstance().connect(ip, port); } /** * socket disconnect * */ private void disconnect(){ UdpClient.getInstance().disconnect(); tv_state.setText("未连接"); } /** * socket send * */ private void send(byte[] data){ String ip = et_ip.getText().toString(); String port = et_port.getText().toString(); UdpClient.getInstance().sendByteCmd(data,1001); } /** * socket data receive * data(byte[]) analyze * */ private UdpClient.OnDataReceiveListener dataReceiveListener = new UdpClient.OnDataReceiveListener() { @Override public void onConnectSuccess() { Log.i(TAG_log,"onDataReceive connect success"); tv_state.setText("已连接"); } @Override public void onConnectFail() { Log.e(TAG_log,"onDataReceive connect fail"); tv_state.setText("未连接"); } @Override public void onDataReceive(byte[] buffer, int size, int requestCode) { //获取有效长度的数据 byte[] data = new byte[size]; System.arraycopy(buffer, 0, data, 0, size); final String oxValue = Arrays.toString(HexUtil.Byte2Ox(data)); Log.i(TAG_log,"onDataReceive requestCode = "+requestCode + ", content = "+oxValue); tv_receive.setText(tv_receive.getText().toString() + oxValue + "\n"); } }; @Override protected void onDestroy() { UdpClient.getInstance().disconnect(); super.onDestroy(); }
作者:Kandy_JS
链接:https://www.jianshu.com/p/9dbb8ac146f6