本文是对个人笔记中内容的整理,部分代码及图片来自互联网,由于不好找到原始出处,所以未加注明。 如有痛感,联系删除。
本文将介绍以下知识点:
TCP:Transmission Control Protocol(传输控制协议) TCP 是一种面向连接(连接导向)的、可靠的、基于字节流的运输层(Transport layer)通信协议,由 IETF 的 RFC 793 说明(specified)。TCP 建立连接之后,通信双方都同时可以进行数据的传输,是全双工的。
TCP 传输过程示意图:
Client 和 Server 建立连接之后,服务器处于监听状态,即:服务器端 Socket 并不定位具体的客户端套接字,而是处于等待连接的状态,实时监控网络状态,等待客户端的连接请求。
客户端 Socket 提出连接请求,要连接的目标是服务器端 Socket。为此,客户端 Socket 必须首先描述它要连接的服务器 Socket,指出服务端 Socket 的地址和端口号,然后就向服务器端 Socket 提出连接请求。
当服务器端 Socket 监听到或者说接收到客户端 Socket 的连接请求时,就响应客户端 Socket 的请求,建立一个新的线程,把服务器端 Socket 的描述发给客户端,一旦客户端确认了此描述,双方就正式通信。
而服务端 Socket 继续处于监听状态,继续接收其他客户端 Socket 的连接请求。
TCP 服务器端代码:
- try {
- Boolean endFlag = false;
- ServerSocket ss = new ServerSocket(12345);
- while (!endFlag) {
- // 等待客户端连接
- Socket s = ss.accept();
- BufferedReader input = new BufferedReader(newInputStreamReader(s.getInputStream()));
- //注意第二个参数据为true将会自动flush,否则需要需要手动操作output.flush()
- PrintWriter output = newPrintWriter(s.getOutputStream(),true);
- String message = input.readLine();
- Log.d("Tcp Demo", "message from Client:"+message);
- output.println("message received!");
- //output.flush();
- if("shutDown".equals(message)){
- endFlag=true;
- }
- s.close();
- }
- ss.close();
- } catch (UnknownHostException e) {
- e.printStackTrace();
- } catch (IOException e) {
- e.printStackTrace();
- }
TCP 客户端代码:
- try {
- Socket s = new Socket("localhost", 12345);
- // outgoing stream redirect to socket
- OutputStream out = s.getOutputStream();
- // 注意第二个参数据为true将会自动flush,否则需要需要手动操作out.flush()
- PrintWriter output = new PrintWriter(out, true);
- output.println("Hello World!");
- BufferedReader input = new BufferedReader(newInputStreamReader(s.getInputStream()));
- // read line(s)
- String message = input.readLine();
- Log.d("Tcp Demo", "message From Server:" + message);
- s.close();
- } catch (UnknownHostException e) {
- e.printStackTrace();
- } catch (IOException e) {
- e.printStackTrace();
- }
UDP:User Datagram Protocol(用户数据包协议) UDP 是 OSI 参考模型中一种无连接的传输层协议,提供面向事务的简单不可靠信息传送服务。它是 IETF RFC 768 是 UDP 的正式规范。
相比于 TCP,UDP 在通信之前并不建立连接,UDP 服务端 Socket 监听某个端口的流量,客户端 Socket 发送报文给服务端 Socket 指定端口,服务端 Socket 处理完信息之后也并不反馈信息给客户端 Socket。 即:客户端 Socket 发送报文后,不关心服务端是否收到报文;服务端 Socket 若收到报文,也并不告知客户端 Socket。
UDP 服务器端代码:
- // UDP服务器监听的端口
- Integer port = 12345;
- // 接收的字节大小,客户端发送的数据不能超过这个大小
- byte[] message = new byte[1024];
- try {
- // 建立Socket连接
- DatagramSocket datagramSocket = new DatagramSocket(port);
- DatagramPacket datagramPacket = new DatagramPacket(message, message.length);
- try {
- while (true) {
- // 准备接收数据
- datagramSocket.receive(datagramPacket);
- Log.d("UDP Demo", datagramPacket.getAddress()
- .getHostAddress().toString()
- + ":" + new String(datagramPacket.getData()));
- }
- } catch (IOException e) {
- e.printStackTrace();
- }
- } catch (SocketException e) {
- e.printStackTrace();
- }
UDP 客户端代码:
- public static void send(String message) {
- message = (message == null ? "Hello IdeasAndroid!" : message);
- int server_port = 12345;
- DatagramSocket s = null;
- try {
- s = new DatagramSocket();
- } catch (SocketException e) {
- e.printStackTrace();
- }
- InetAddress local = null;
- try {
- // 换成服务器端IP
- local = InetAddress.getByName("localhost");
- } catch (UnknownHostException e) {
- e.printStackTrace();
- }
- int msg_length = message.length();
- byte[] messagemessageByte = message.getBytes();
- DatagramPacket p = new DatagramPacket(messageByte, msg_length, local,
- server_port);
- try {
- s.send(p);
- } catch (IOException e) {
- e.printStackTrace();
- }
- }
总结下 TCP 和 UDP 的主要区别:
TCP | UDP | |
---|---|---|
是否连接 | 面向连接 | 面向非连接 |
传输是否可靠 | 可靠 | 不可靠 |
速度 | 慢 | 快 |
应用场景 | 要求准确性数据(例如金融、库存) | 不求准确,但求实时、快(语音、图像数据) |
以上内容部分出自单播,组播 (多播),广播以及任播。
几个关键的类:
如果把 DatagramSocket 比作创建的港口码头,那么 DatagramPacket 就是发送和接收数据的集装箱。
比如,要接收数据长度为 1024 的字节,构建字节缓存区
- public DatagramPacket(byte[] buf, int length) //接收数据
,创建 DatagramPacket 只需传入
- byte buf[] = new byte[1024]
和长度,实现接收长度为 length 的包。
- buf[]
- while (true) {
- byte buf[] = new byte[1024];
- // 接收数据
- DatagramPacket packet = new DatagramPacket(buf, buf.length);
- datagramSocket.receive(packet);
- String content = new String(packet.getData()).trim();
- // ……
- }
比如,要发送数据为
- public DatagramPacket(byte[] buf,int length,InetAddress address,int port)
,构造函数需要字节数组,数组长度,接收端地址(IP)和端口(Port),构造数据报文包用来把长度为 length 的包传送到指定宿主的指定的端口号。
- byte[] data
- byte[] data = paramVarArgs[0].getBytes();
- DatagramPacket dataPacket = new DatagramPacket(data,
- data.length, inetAddress, BROADCAST_PORT);
- try {
- datagramSocket.send(dataPacket);
- } catch (IOException e) {
- e.printStackTrace();
- return App.getInstance().getResources().getString(R.string.send_failed);
- }
- return App.getInstance().getResources().getString(R.string.send_success);
返回接收或发送此数据报文的机器的 IP 地址。
- getAddress()
返回接收的数据或发送出的数据。
- getData()
返回发送出的或接收到的数据的长度。
- getLength()
返回接收或发送该数据报文的远程主机端口号。
- getPort()
此类表示用来发送和接收数据报包的套接字。数据报套接字是包投递服务的发送或接收点。
创建数据报套接字。
- DatagramSocket()
用于发送报文的套接字,一般不指定特定端口及地址。
- try {
- datagramSocket = new DatagramSocket();
- datagramSocket.setBroadcast(true);
- } catch (Exception e) {
- e.printStackTrace();
- }
创建数据报套接字并将其绑定到本地主机上的指定端口。
- DatagramSocket(int port)
创建数据报套接字,将其绑定到指定的本地地址。
- DatagramSocket(int port, InetAddress laddr)
关于 0.0.0.0 的意义,可参考:全零网络 IP 地址 0.0.0.0 表示意义详谈
- // 保持一个套接字打开,监听该端口上所有UDP流量(0.0.0.0表示所有未处理的流量)
- datagramSocket = new DatagramSocket(BROADCAST_PORT, InetAddress.getByName("0.0.0.0"));
- datagramSocket.setBroadcast(true);
从此套接字接收数据报包。
- receive(DatagramPacket p)
从此套接字发送数据报包。
- void send(DatagramPacket p)
将此 DatagramSocket 绑定到特定的地址和端口。
- bind(SocketAddress addr)
关闭此数据报套接字。
- void close()
将套接字连接到此套接字的远程地址。
- void connect(InetAddress address, int port)
将此套接字连接到远程套接字地址(IP 地址 + 端口号)。
- void connect(SocketAddress addr)
断开套接字的连接。
- void disconnect()
返回此套接字连接的地址。
- getInetAddress()
获取套接字绑定的本地地址。
- InetAddress getLocalAddress()
NetworkInterface 是 JDK1.4 中添加的一个获取网络接口的类,该网络接口既可以是物理的网络接口,也可以是虚拟的网络接口,而一个网络接口通常由一个 IP 地址来表示。
既然 NetworkInterface 用来表示一个网络接口,那么如果可以获得当前机器所有的网络接口(包括物理的和虚拟的),然后筛选出表示局域网的那个网络接口,那就可以得到机器在局域网内的 IP 地址。
NetworkInterface 常用到的方法有两个:
用于获取当前机器上所有的网络接口;
- getNetworkInterfaces()
用于获取绑定到该网卡的所有的 IP 地址。
- getInetAddresses()
来看下这段代码,实现的功能是遍历所有本地网络接口,获取广播地址,并向它们发送广播报文。
- // 获取本地所有网络接口
- Enumeration<NetworkInterface> interfaces = NetworkInterface.getNetworkInterfaces();
- while (interfaces.hasMoreElements()) {
- NetworkInterface networkInterface = interfaces.nextElement();
- if (networkInterface.isLoopback() || !networkInterface.isUp()) {
- continue;
- }
- // getInterfaceAddresses()方法返回绑定到该网络接口的所有 IP 的集合
- for (InterfaceAddress interfaceAddress : networkInterface.getInterfaceAddresses()) {
- InetAddress broadcast = interfaceAddress.getBroadcast();
- // 不广播回环网络接口
- if (broadcast == null) {
- continue;
- }
- // 发送广播报文
- try {
- DatagramPacket sendPacket = new DatagramPacket(data,
- data.length, broadcast, BROADCAST_PORT);
- datagramSocket.send(sendPacket);
- } catch (Exception e) {
- e.printStackTrace();
- }
- Log.d("发送请求", getClass().getName() + ">>> Request packet sent to: " +
- broadcast.getHostAddress() + "; Interface: " + networkInterface.getDisplayName());
- }
- }
方法返回的是一个绑定到该网络接口的所有 InterfaceAddress 的集合。InterfaceAddress 是 JDK1.6 之后添加的类,包含 IP 地址(InetAddress),以及该地址对应的广播地址和掩码长度。
- getInterfaceAddresses
以上内容部分出自使用 NetworkInterface 获得本机在局域网内的 IP 地址。
在局域网内通过 UDP 广播实现 Peer Discovering 的方法非常简单:
整个流程如下图所示:
在 ServerSocket 的构造函数中实例化
- try {
- handler = new ReceiveMsgHandler(this);
- new ServerSocket(handler).start();
- } catch (IOException e) {
- e.printStackTrace();
- }
,指定端口,IP 设置为 0.0.0.0。
- DatagramSocket
在接收线程的
- public ServerSocket(Handler handler) throws IOException {
- // Keep a socket open to listen to all the UDP trafic that is destined for this port
- datagramSocket = new DatagramSocket(BROADCAST_PORT, InetAddress.getByName("0.0.0.0"));
- datagramSocket.setBroadcast(true);
- // handler
- this.handler = handler;
- }
方法中,接收所有广播消息:
- run()
如上图所示,接收线程需要接收两种广播消息:"我来了"(
- while (true) {
- byte buf[] = new byte[1024];
- // 接收数据
- DatagramPacket packet = new DatagramPacket(buf, buf.length);
- datagramSocket.receive(packet);
- String content = new String(packet.getData()).trim();
- if (content.equals("DISCOVER_REQUEST") &&
- !packet.getAddress().toString().equals("/" + IPUtil.getLocalIPAddress())) {
- byte[] feedback = "DISCOVER_RESPONSE".getBytes();
- // 发送数据
- DatagramPacket sendPacket = new DatagramPacket(feedback, feedback.length,
- packet.getAddress(), BROADCAST_PORT);
- datagramSocket.send(sendPacket);
- // 发送Handler消息
- sendHandlerMessage(packet.getAddress().toString());
- } else if (content.equals("DISCOVER_RESPONSE") &&
- !packet.getAddress().toString().equals("/" + IPUtil.getLocalIPAddress())) {
- // 发送Handler消息
- sendHandlerMessage(packet.getAddress().toString());
- }
- }
)和 "知道了"(
- DISCOVER_REQUEST
)。接收到
- DISCOVER_RESPONSE
后,发送
- DISCOVER_REQUEST
。需要注意的是:
- DISCOVER_RESPONSE
这里需要指定端口为
- // 发送数据
- DatagramPacket sendPacket = new DatagramPacket(feedback, feedback.length, packet.getAddress(), BROADCAST_PORT);
,因为
- BROADCAST_PORT
报文的的端口是随机的。不然无法在
- DISCOVER_REQUEST
端口接收到
- BROADCAST_PORT
报文,新加入局域网的设备就无法感知其他设备的存在。
- DISCOVER_RESPONSE
:
- DatagramSocket
在本 demo 中,发送通过
- private ClientSocket() {
- try {
- inetAddress = InetAddress.getByName(BROADCAST_IP);
- datagramSocket = new DatagramSocket();
- datagramSocket.setBroadcast(true);
- } catch (Exception e) {
- e.printStackTrace();
- }
- }
进行实现,在 background 中发送消息,发送完成后通过 Handler 在界面 Toast 提示。
- AsyncTask
- new AsyncTask<String, Integer, String>() {
- @Override
- protected String doInBackground(String... paramVarArgs) {
- byte[] data = paramVarArgs[0].getBytes();
- DatagramPacket dataPacket = new DatagramPacket(data,
- data.length, inetAddress, BROADCAST_PORT);
- try {
- datagramSocket.send(dataPacket);
- } catch (IOException e) {
- e.printStackTrace();
- return App.getInstance().getResources().getString(R.string.send_failed);
- }
- return App.getInstance().getResources().getString(R.string.send_success);
- }
- @Override
- protected void onPostExecute(String result) {
- super.onPostExecute(result);
- Message msg = new Message();
- msg.what = SendMsgHandler.STATUS;
- msg.obj = result;
- handler.sendMessage(msg);
- }
- }.execute(content);
代码已上传 github:yhthu / intercom,如有兴趣,可移步参考代码 Demo。
来源: http://www.cnblogs.com/younghao/p/6681264.html