Android中socket(tcp|udp),websocket基本使用
整个编码过程在Android studio 3.6.1中进行的,不要忘记申请网络权限哦,全篇文章都是聊天室为例
Android中常用的网络通信有http,https,socket,websocket,其中http和https是通信协议。socket和websocket是基于tcp/udp协议的编程接口。
一、相关知识
网络体系结构
网络体系结构有三种,OSI体系结构、TCP / IP体系结构、五层体系结构
- OSI体系结构:概念清楚 & 理念完整,但复杂 & 不实用。
- TCP / IP体系结构:包含了一系列构成互联网基础的网络协议,是Internet的核心协议被广泛应用于局域网和广域网。
- 五层体系结构:融合了OSI 与 TCP / IP的体系结构,目的是为了学习和计算机原理。
TCP/IP 是个协议族,是互联网的核心,可以分四个层次:链路层,网络层,传输层和应用层。
每个层对应的协议以及功能在表中已给出。
网络传输的过程
如何将一个数据从一个终端传到另一个终端的呢?下面是数据流转的过程:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-UMzVV2K8-1587015624687)(en-resource://database/4214:1)]
每次往下层传输都需要将本层的信息带上,并放在头部中,发送端是数据封装的过程,接收端是信息解封装的过程。
在传输过程中,涉及几个概念:
IP:用于找到网络中的主机和路由器
MAC:用于区分同一链路中不同的计算机。
Port:用于识别同一个计算机中的不同程序,相当于程序在网络的地址。端口范围0-65535,其中0-1023为系统端口。下面列举一些系统默认的端口号。
协议名称 | 协议功能 | 默认端口号 |
---|---|---|
HTTP(HypertextTransfer Protocol)超文本传输协 | 议浏览网页 | 80 |
FTP(File TransferProtocol) 文件传输协议 | 用于网络上传输文件 | 21 |
TELNET | 远程终端访问 | 23 |
POP3(Post OfficeProtocol) | 邮局协议版本 | 110 |
- TCP/UDP 对比
UDP | TCP | |
---|---|---|
是否连接 | 无连接 | 面向连接 |
是否可靠 | 不可靠传输,不使用流量控制和拥塞控制 | 可靠传输,使用流量控制和拥塞控制 |
连接对象个数 | 支持一对一,一对多,多对一和多对多交互通信 | 只能是一对一通信 |
传输方式 | 面向报文 | 面向字节流 |
首部开销 | 首部开销小,仅8字节 | 首部最小20字节,最大60字节 |
适用场景 | 适用于实时应用(IP电话、视频会议、直播等) | 适用于要求可靠传输的应用,例如文件传输 |
在Android中socket,websocket,tcp,udp
二、Socket使用
Socket不是通信协议,是一种基于TCP/UDP协议的编程接口。
1.采用TCP方式
若想实现两端的通信,两端必须需要使用Socket;客户端使用socket,服务端使用ServerSocket,两端约定好端口。socket端需要知道ServerSocket的tIP地址。
- 1.建立连接
客户端在建立连接时,需要在socket中约定好服务端IP地址和端口号(避免重复),如 Socket mSocket = new Socket(IP,Port);而服务端则只需要制定好端口,因为本机IP地址就是它的IP地址,如ServerSocket mServerSocket = new ServerSocket(8989); - 2.发送数据
因为socket是采用C/S结构,所以建立好连接之后,就可以相互发数据了。
mSocket.getOutputStream().write(msg); //获取socket中流,并将数据写入进去。仅支持byte[]这种格式。
mSocket.getOutputStream().flush(); //刷新
服务端和客户端本质上是一样的,只是服务端需要mSokcet = mServerSocket.accept() ,通过这个socket进行发送数据。 - 3.接收数据
要想接收来自其他端发来的数据,就必须获取流通道getInputStream();
InputStream inputStream = mSocket.getInputStream(); ,不停地从里面取数据,然后根据发送端发过来的数据转成我们需要的数据。
搭建TCP Client
public class SocketUtil { private Socket mSocket; private ExecutorService mExecutorService; public SocketUtil() { mExecutorService = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors()); } public void startClient(final String address, final int port) { if (address == null) { return; } if (mSocket == null) { Runnable mRunnable = new Runnable() { @Override public void run() { try { Log.i("tcp", "启动客户端"); mSocket = new Socket(address, port); Log.i("tcp", "客户端连接成功"); InputStream inputStream = mSocket.getInputStream(); byte[] buffer = new byte[1024]; int len = -1; while ((len = inputStream.read(buffer)) != -1) { String data = new String(buffer, 0, len); mIMessageCallback.callback(data); Log.i("tcp", "收到服务器的数据-------------------:" + data); } Log.i("tcp", "客户端断开连接"); } catch (Exception EE) { EE.printStackTrace(); Log.i("tcp", "客户端无法连接服务器" + EE.getMessage()); } finally { try { if (mSocket != null) { mSocket.close(); } } catch (IOException e) { e.printStackTrace(); } mSocket = null; } } }; mExecutorService.execute(mRunnable); } } public void sendTcpMessage(final byte[] msg) { if (mSocket != null && mSocket.isConnected()) { Runnable mRunnable = new Runnable() { @Override public void run() { try { mSocket.getOutputStream().write(msg); mSocket.getOutputStream().flush(); } catch (IOException e) { e.printStackTrace(); } } }; mExecutorService.execute(mRunnable); } } IMessageCallback mIMessageCallback; public interface IMessageCallback { void callback(String string); } public void setIMessageCallback(IMessageCallback messageCallback) { this.mIMessageCallback = messageCallback; }}
搭建TCP Server
public class SocketUtil { private ServerSocket mServerSocket; private ExecutorService mExecutorService; private Socket mSocket; public SocketUtil() { mExecutorService = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors()); try { Log.i("tcp", "启动服务端"); mServerSocket = new ServerSocket(8989); Log.i("tcp", "服务端连接成功"); } catch (Exception e) { Log.i("tcp", "启动服务端失败"); } } public void startSever() { Runnable mRunnable = new Runnable() { @Override public void run() { while (true) { try { mSocket = mServerSocket.accept(); Log.i("tcp", mSocket.getRemoteSocketAddress().toString() + ""); InputStream inputStream = mSocket.getInputStream(); byte[] buffer = new byte[1024]; int len = -1; while ((len = inputStream.read(buffer)) != -1) { String data = new String(buffer, 0, len); mIMessageCallback.callback(data); Log.i("tcp", "收到客户端的数据-------------------:" + data); } Log.i("tcp", "客户端断开连接"); } catch (Exception EE) { EE.printStackTrace(); Log.i("tcp", "服务端无法连接服务器" + EE.getMessage()); break; } finally { try { if (mServerSocket != null) { mServerSocket.close(); } } catch (IOException e) { e.printStackTrace(); } mServerSocket = null; } } } }; mExecutorService.execute(mRunnable); } public void sendTcpMessage(final byte[] msg) { Runnable mRunnable = new Runnable() { @Override public void run() { try { mSocket.getOutputStream().write(msg); mSocket.getOutputStream().flush(); } catch (IOException e) { e.printStackTrace(); } } }; mExecutorService.execute(mRunnable); } public void des() { try { mServerSocket.close(); } catch (Exception e) { } } public IMessageCallback mIMessageCallback; public interface IMessageCallback { void callback(String string); } public void setIMessageCallback(IMessageCallback messageCallback) { this.mIMessageCallback = messageCallback; }}
2.采用UDP方式
UDP其实没有服务端和客户端的概念,但是和TCP用法也是类似,在这里需要注意两个概念,DatagramSocket和DatagramPacket。UDP是面向数据包的传输层协议,所以在接收和发送都是以数据包的形式。
IP数据报的最大长度为 65535 字节 ,除去首字IP 的20 字节和 UDP首部8个字节,实际上,UDP 能传输的最大字节数为 65507,个字节;当我们的数据超过这个长度时,则需要考虑分包的问题,
-
1.接收数据
在接收UDP数据时,需要通过DatagramSocket监听指定端口,IP地址就是当前主机的IP地址,接收的数据格式为DatagramPacket。 -
2.发送数据
接收数据采用的数据为DatagramPacket,发送数据也是如此,只是在发送数据时需要指定IP和端口,因为它不是面向连接的,发送过去至于能不能接收,它是不知道的再不关注,只需要将数据发送指定的端口和IP就行了。高效率就体现在如此,没有tcp三次握手和四次挥手的过程。
搭建UDP Client
public class SocketUDPClientUtil { private DatagramSocket mDatagramSocket; private ExecutorService mExecutorService; private DatagramPacket sendPacket; private DatagramPacket receivePacket; public SocketUDPClientUtil() { mExecutorService = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors()); } public void startClient() { if (mDatagramSocket == null) { Runnable mRunnable = new Runnable() { @Override public void run() { try { Log.i("udp", "启动客户端"); mDatagramSocket = new DatagramSocket(8989);//本机监听的端口 mDatagramSocket.setReuseAddress(true); Log.i("udp", "客户端连接成功"); byte[] buffer = new byte[1024]; int len = -1; while (true) { receivePacket = new DatagramPacket(buffer, buffer.length); mDatagramSocket.receive(receivePacket); Log.i("udp", receivePacket.getAddress().toString() + receivePacket.getPort()); String data = new String(receivePacket.getData(), 0, receivePacket.getData().length, "utf-8");//针对中文要采用utf-8 mIMessageCallback.callback(data); Log.i("udp", "收到服务器的数据-------------------:" + data); }// Log.i("udp", "客户端断开连接"); } catch (Exception EE) { EE.printStackTrace(); Log.i("udp", "客户端无法连接服务器" + EE.getMessage()); } finally { try { if (mDatagramSocket != null) { mDatagramSocket.close(); } } catch (Exception e) { e.printStackTrace(); } mDatagramSocket = null; } } }; mExecutorService.execute(mRunnable); } } /** * @param msg 要发送的数据 * @param ip 目标IP地址 * @param port 目标端口号 */ public void sendTcpMessage(final byte[] msg, final String ip, final int port) { if (mDatagramSocket != null) { Runnable mRunnable = new Runnable() { @Override public void run() { try { sendPacket = new DatagramPacket(msg, msg.length, InetAddress.getByName(ip), port); mDatagramSocket.send(sendPacket); } catch (IOException e) { e.printStackTrace(); } } }; mExecutorService.execute(mRunnable); } } IMessageCallback mIMessageCallback; public interface IMessageCallback { void callback(String string); } public void setIMessageCallback(IMessageCallback messageCallback) { this.mIMessageCallback = messageCallback; }}
搭建UDP Server
public class SocketServerUtil { private DatagramSocket mDatagramSocket; private ExecutorService mExecutorService; private DatagramPacket sendPacket; private DatagramPacket receivePacket; public SocketUtil() { mExecutorService = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors()); try { Log.i("udp", "启动服务端"); mDatagramSocket = new DatagramSocket(8989); Log.i("udp", "服务端连接成功"); } catch (Exception e) { Log.i("udp", "启动服务端失败"); } } public void startClient() { Runnable mRunnable = new Runnable() { @Override public void run() { try { byte[] buffer = new byte[1024]; int len = -1; receivePacket = new DatagramPacket(buffer, buffer.length); while (true) { mDatagramSocket.receive(receivePacket); String data = new String(receivePacket.getData(), 0, buffer.length, "utf-8");//针对中文要采用utf-8 mIMessageCallback.callback(data); Log.i("udp", "收到服务器的数据-------------------:" + data); } } catch (Exception EE) { EE.printStackTrace(); Log.i("udp", "客户端无法连接服务器" + EE.getMessage()); } finally { try { if (mDatagramSocket != null) { mDatagramSocket.close(); } } catch (Exception e) { e.printStackTrace(); } mDatagramSocket = null; } } }; mExecutorService.execute(mRunnable); } public void sendTcpMessage(final byte[] msg, final String ip, final int port) { if (mDatagramSocket != null) { Runnable mRunnable = new Runnable() { @Override public void run() { try { sendPacket = new DatagramPacket(msg, msg.length, InetAddress.getByName(ip), port); mDatagramSocket.send(sendPacket); } catch (IOException e) { e.printStackTrace(); } } }; mExecutorService.execute(mRunnable); } } public void des() { try { mDatagramSocket.close(); } catch (Exception e) { Log.i(TAG, e.toString()); } } public IMessageCallback mIMessageCallback; public interface IMessageCallback { void callback(String string); } public void setIMessageCallback(IMessageCallback messageCallback) { this.mIMessageCallback = messageCallback; }
两端代码一样的,TCP还分Socket和ServerSocket,而UDP完全没有
单播,组播和广播实现
由于UDP是面向无连接的,所有才有组播和广播的概念。
- 1.单播
单播其实上面已经实现了,就是IP地址是确定的,实现一对一发送数据。 - 2.广播
一对所有,所有指的是可能到达网络节点,一般就是局域网中的所有节点。主要用于音视频分发,但是在公共网络中并没有用这个技术,因为容易造成网络中不必要的数据以及隐私问题。
发送时将目标地址设置为:255.255.255.255 即可实现广播功能,代码就不贴出来了。
广播自己也能收到,所以若是不需要可以根据IP地址进行过滤掉。
- 3.组播
组播介于单播和广播之间,即一对指定网络节点
多播的地址是特定的,D类地址用于多播。D类IP地址就是多播IP地址,即224.0.0.0至239.255.255.255之间的IP地址,并被划分为局部连接多播地址、预留多播地址和管理权限多播地址3类:
1、局部多播地址:在224.0.0.0~224.0.0.255之间,这是为路由协议和其他用途保留的地址,路由器并不转发属于此范围的IP包。
2、预留多播地址:在224.0.1.0~238.255.255.255之间,可用于全球范围(如Internet)或网络协议。
3、管理权限多播地址:在239.0.0.0~239.255.255.255之间,可供组织内部使用,类似于私有IP地址,不能用于Internet,可限制多播范围。
组播实现
组播的实现与单播和广播类似,可能在对象上不同,采用MulticastSocket,然后加入到组播地址中,凡是加入到这个地址的都可以收到数据。
MulticastSocket mDatagramSocket = new MulticastSocket(8989);//本机监听的端口mDatagramSocket.joinGroup(InetAddress.getByName("225.0.0.1"));
三、Websocket使用
WebSocket是为HTML5提供的一种在单个TCP连接上进行全双工通讯的编程接口。
添加依赖:
implementation “org.java-websocket:Java-WebSocket:1.4.0”
-
1.地址形式
它与之前的socket不同,地址的形式为“ws://ip:port" -
2.发送信息
服务端发送数据也是通过socket的,在建立连接时需要保持这个socket而已。见服务端代码。 -
3.接受数据
在onMessage中接收数据,但是在代码可以看出,它接收其实String,要是你想接收流,必须先转成string才行。
搭建Websocket Client
public class MyWebSocket extends WebSocketClient { public MyWebSocket(URI serverUri) { super(serverUri); setReuseAddr(true); } @Override public void onOpen(ServerHandshake handshakedata) { Log.e("haochen", "message " + handshakedata.toString()); } @Override public void onMessage(String message) { Log.e("haochen", "message" + message); mIMessageCallback.callback(message); } @Override public void onClose(int code, String reason, boolean remote) { Log.e("haochen", "message" + reason); } @Override public void onError(Exception ex) { Log.e("haochen", "message" + ex.toString()); } IMessageCallback mIMessageCallback; public interface IMessageCallback { void callback(String string); } public void setIMessageCallback(IMessageCallback messageCallback) { this.mIMessageCallback = messageCallback; }}
搭建WebSocket Server
public class MyWebSocketServer extends WebSocketServer { public WebSocket mWebSocket; public final static String TAG = MyWebSocketServer.class.getSimpleName(); public MyWebSocketServer(InetSocketAddress address) { super(address); setReuseAddr(true); } @Override public void onOpen(WebSocket conn, ClientHandshake handshake) { mWebSocket = conn; conn.send("server is opening" + conn.getRemoteSocketAddress()); Log.e(TAG, "open"); } @Override public void onClose(WebSocket conn, int code, String reason, boolean remote) { conn.close(); Log.e(TAG, "close" + reason); } @Override public void onMessage(WebSocket conn, String message) { mWebSocket = conn; mIMessageCallback.callback(message); Log.e(TAG, "message" + message + conn.getRemoteSocketAddress());// conn.send("iflytek123" + message); } @Override public void onError(WebSocket conn, Exception ex) { if (conn != null) { conn.closeConnection(1, "close"); Log.e(TAG, "ex " + ex.toString() + conn.getRemoteSocketAddress()); } else { Log.e(TAG, "cnn is null " + ex.getMessage()); } } @Override public void onStart() { Log.e(TAG, "onStart"); } public WebSocket getWebSocket() { return mWebSocket; } public void clearConnection() { try { stop(); } catch (Exception e) { } } IMessageCallback mIMessageCallback; public interface IMessageCallback { void callback(String string); } public void setIMessageCallback(IMessageCallback messageCallback) { this.mIMessageCallback = messageCallback; }}
websocket 主要解决H5与其他端的通信,若是有一端是H5,又想通过TCP实现通信,这时候你可以选websocket。
坑:需要设置setReuseAddr(true),不然你退出在进入的话,需要等2-4分钟才能绑定同一个地址。
参考
Android Socket使用详解
太厉害了,终于有人能把TCP/IP 协议讲的明明白白了
安卓网络知识总结(一)–网络基础知识
Android 你不得不学的HTTP相关知识
A Practical Guide to Differentiate Unicast, Broadcast & Multicast
Java UDP 单播、多播(组播)、广播、任播(未实现)
更多相关文章
- GreenDao 3.0 简介、使用及踩坑
- 一文详解Android(安卓)轻量级存储方案的前世今生
- Android(安卓)Camera数据流分析全程记录
- 【Android(安卓)开发】:UI控件之 ListView 列表控件的使用
- android 音乐视频播放器(github上十二款最著名的Android播放器开
- Android多个APK共享数据(Shared User ID)
- android访问远程数据库
- Android基础类之BaseAdapter
- mybatisplus的坑 insert标签insert into select无参数问题的解决