Socket 网络接口
大家在学习计算机网络的时候一定学习过 TCP/IP 协议以及最经典的 OSI 七层结构,简单的回忆一下这 7 层结构:
从下到上依次是:
- 物理层
- 数据链路层
- 互联层
- 网络层
- 会话层
- 表示层
- 应用层
TCP/IP 协议对这 7 层了做一点精简,变为了 4 层结构:
我们现在的网路通信模型基本上都是按照这个层级来分发的,当然也包括了 Android 中的网络模型,简单回顾一下基础之后,开始学习今天的网络接口——Socket。
1. TCP 与 UDP
在模型之下,又衍生出两种经典的传输层协议——TCP 和 UDP,我们分别看看这两个协议。
1.1 TCP 协议
TCP 协议是传输控制协议是一个面向连接的协议,所谓的面向连接表示的是通信双方在传输数据之前,需要搭建一个专用的通信线路,并且在结束的时候需要将其关闭。在有了这条专用线路作保障之后,就能准确无误的将数据传递给对方,所以 TCP 是一种可靠的通信方式,它能够准确知道对方是否成功接收了消息。
1.2 UDP 协议
UDP 又叫用户数据包协议,相对于 TCP,它是一种面向无连接的协议,也就是通信双方在交换数据之前无需建立一条专用通道,当然在通信结束前也无需释放通道。这样一来,通信的效率非常高,但缺点是我们无法确定发出去的消息对方是否能够准确收到,所以它是一个轻量不可靠的通信方式。
以上的定义描述了二者主要的差异,更多细致的内容可以参考其他资料。
2. Socket 的基础概念
Socket 翻译成中文是“套接字”,它是应用层和传输层中间的一个抽象中间件,它封装了底层的 TCP / IP 协议族,并向上暴露 API 给应用层,从而向上屏蔽底层协议细节。 所以 Socket 可以作为底层网络门面来让我们不在拘泥于复杂的底层传输协议,而将更多的重心放在自己的功能开发上。 下图是 Socket 的一个整体工作原理:
每个 Socket 对象都对应着一个 IP 地址和一个端口号,用来标识互联网上的唯一目的地址,然后就可以通过 TCP 或者 UDP 将数据发送给对方。
3. Socket 的工作流程
首先看看 Socket 的工作流程图:
3.1 Server 监听端口
首先由服务端初始化 Socket 接口,然后绑定并监听自己的端口号,此时服务端会阻塞式等待客户端连接。
3.2 Client 连接端口
客户端可以在需要发送消息的时候初始化 Socket 接口,设置服务端的 IP 地址和端口号就可以连接到服务器,接着在连接成功之后,双方就完成了连接的建立。
3.3 数据传递和连接断开
在连接建立好之后,客户端或者服务端双方就可以开始发送数据了,在数据传输完毕之后,双方任一方都可以申请断开连接,此后通道关闭,数据传输完成。
4. Socket 的基本用法
- 首先创建一个 ServerSocket 对象
public ServerSocket(int port) throws IOException
创建 Socket 服务只需要传入一个端口号即可。
- 阻塞监听端口
public Socket accept() throws IOException
通过调用accept()
方法,server 便会阻塞式的监听第 1 步设置的端口号,等待客户端连接。
- 客户度创建 Socket 对象
public Socket(String host, int port) throws UnknownHostException, IOException
和前面说的一样,创建 Socket 需要两个必要的参数:
- **host:**服务端的网络地址
- **port:**服务端开放的对应端口号
- 获取输入输出流
socket.getInputStream();
socket.getOutputStream();
服务端和客户端通过这两个方法分别拿到输入输出流,从而向流里面写消息或者从流里面读数据完成数据的发送和接收。
- 断开连接
在数据传输完毕之后,通过close()
方法断开连接,完成本次通信。
5. Socket 网络通信示例
本节在电脑上通过 Java 搭建一个 Socket Server,然后手机作为 Client 来连接 Server。这个需要保证手机和电脑在同一个 Wifi 网段下。
5.1 搭建 Server
大家在学习 Android 之前,应该都有学过纯 Java 程序,可以通过javac
命令来将 java 代码编译成字节码,然后通过java
命令运行,当然也可以在 Android Studio 里面直接运行带main()
方法的 Java 程序。
首先在工程里新建一个 Java Library,注意不是 Android Library。
然后创建一个带main()
函数的类,在里面完成 Socket 的创建和初始化:
package com.emercy.libsocket;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.Inet4Address;
import java.net.InetAddress;
import java.net.NetworkInterface;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Enumeration;
public class SocketServer {
public static void main(String[] args) throws IOException {
// 1. Create ServerSocket
ServerSocket serverSocket = new ServerSocket(8888);
// 2. monitoring
System.out.println("server start listen : " + getIpAddress());
Socket socket = serverSocket.accept();
System.out.println("accept");
// 3. input stream
InputStream is = socket.getInputStream();
InputStreamReader reader = new InputStreamReader(is);
BufferedReader br = new BufferedReader(reader);
String content;
StringBuffer sb = new StringBuffer();
while ((content = br.readLine()) != null) {
sb.append(content);
}
System.out.println("server receiver: " + sb.toString());
socket.shutdownInput();
br.close();
reader.close();
is.close();
socket.close();
serverSocket.close();
System.out.println("server receiver: ");
}
public static String getIpAddress() {
try {
Enumeration<NetworkInterface> allNetInterfaces = NetworkInterface.getNetworkInterfaces();
InetAddress ip = null;
while (allNetInterfaces.hasMoreElements()) {
NetworkInterface netInterface = (NetworkInterface) allNetInterfaces.nextElement();
if (netInterface.isLoopback() || netInterface.isVirtual() || !netInterface.isUp()) {
continue;
} else {
Enumeration<InetAddress> addresses = netInterface.getInetAddresses();
while (addresses.hasMoreElements()) {
ip = addresses.nextElement();
if (ip instanceof Inet4Address) {
return ip.getHostAddress();
}
}
}
}
} catch (Exception e) {
System.err.println("IP地址获取失败" + e.toString());
}
return "";
}
}
运行之后,开始等待连接并打印当前设备的 IP 地址,这里要特别注意,有些教程里面会用InetAddress.getLocalHost()
这种方式获取 IP 返回“127.0.0.1”,这个是 local host,在跨设备通信中无法使用。
5.2 Client 连接
接下来编写 Android 程序,xml 里面只放置一个 Button 用于触发连接,这里就不列出来了。点击 button 之后按照上面的步骤来依次创建 Socket,设置 IP 和 port,接着获取输入输出流即可。
**注意:**网络请求属于耗时操作, Android 要求网络请求必须在子线程中执行,所以我们需要在onClick()
中 new 一个 thread。
package com.emercy.myapplication;
import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import java.io.IOException;
import java.io.OutputStream;
import java.net.Socket;
public class MainActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
//1. Create Client
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
findViewById(R.id.button).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
new Thread(new Runnable() {
@Override
public void run() {
Socket socket;
try {
//1. create socket
socket = new Socket("10.64.210.51", 12345);
//2. output stream
OutputStream os = socket.getOutputStream();
//3. Send data
os.write("Hello world".getBytes());
System.out.println("send message");
os.flush();
socket.shutdownOutput();
os.close();
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}).start();
}
});
}
}
首先运行 Server,接着打开 App,点击“连接”Button 就可以和 Server 通信了。
6. 小结
本节学习了一个底层的网络接口——Socket,它内部实现了计算机网络中最基础的协议和模型,可以让我们不再关心那些繁琐复杂的协议规则,从而轻松的将数据传输出去。首先给大家回顾了 IOS 和 TCP / IP 的几层模型,然后工作在传输层的两个重要通信协议 TCP / UDP,接着按照步骤来分别创建 SocketServer 和 Socket,通过 InputStream 和 OutputStream 进行通信。