章节索引 :

Socket 网络接口

大家在学习计算机网络的时候一定学习过 TCP/IP 协议以及最经典的 OSI 七层结构,简单的回忆一下这 7 层结构:
从下到上依次是:

  • 物理层
  • 数据链路层
  • 互联层
  • 网络层
  • 会话层
  • 表示层
  • 应用层

osi

TCP/IP 协议对这 7 层了做一点精简,变为了 4 层结构:

tcp/ip

我们现在的网路通信模型基本上都是按照这个层级来分发的,当然也包括了 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

每个 Socket 对象都对应着一个 IP 地址和一个端口号,用来标识互联网上的唯一目的地址,然后就可以通过 TCP 或者 UDP 将数据发送给对方。

3. Socket 的工作流程

首先看看 Socket 的工作流程图:

socketworkflow

3.1 Server 监听端口

首先由服务端初始化 Socket 接口,然后绑定并监听自己的端口号,此时服务端会阻塞式等待客户端连接。

3.2 Client 连接端口

客户端可以在需要发送消息的时候初始化 Socket 接口,设置服务端的 IP 地址和端口号就可以连接到服务器,接着在连接成功之后,双方就完成了连接的建立。

3.3 数据传递和连接断开

在连接建立好之后,客户端或者服务端双方就可以开始发送数据了,在数据传输完毕之后,双方任一方都可以申请断开连接,此后通道关闭,数据传输完成。

4. Socket 的基本用法

  1. 首先创建一个 ServerSocket 对象
public ServerSocket(int port) throws IOException

创建 Socket 服务只需要传入一个端口号即可。

  1. 阻塞监听端口
public Socket accept() throws IOException

通过调用accept()方法,server 便会阻塞式的监听第 1 步设置的端口号,等待客户端连接。

  1. 客户度创建 Socket 对象
public Socket(String host, int port)  throws UnknownHostException, IOException

和前面说的一样,创建 Socket 需要两个必要的参数:

  • **host:**服务端的网络地址
  • **port:**服务端开放的对应端口号
  1. 获取输入输出流
socket.getInputStream();
socket.getOutputStream();

服务端和客户端通过这两个方法分别拿到输入输出流,从而向流里面写消息或者从流里面读数据完成数据的发送和接收。

  1. 断开连接
    在数据传输完毕之后,通过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。

libsocket

然后创建一个带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 进行通信。

环境搭建,开发相关
Android 系统背景及结构概述 Android 开发环境搭建 Genymotion 的安装与使用 Android 工程解析及使用 Android 程序签名打包
常用 UI 布局
Android 的 UI 根基 View与View Android 线性布局 LinearLayout Android相对布局RelativeLayout Android 表格布局 TableLayout Android 网格布局 GridLayout Android 帧布局 FrameLayout Android绝对布局AbsoluteLayout
基础控件
Android 文本框 TextView Android 文本输入框 EditText 按钮 Button/ImageButton 选择框 RadioButton/Check 开关控件ToggleButton/Switch Android 图片控件 ImageView Android 进度条 ProgressBar Android 拖动条 SeekBar Android 评分条 RatingBar Android 滚动条 ScrollView 轮播滚动视图 ViewFlipper
Adapter 相关控件
Android 适配器 Adapter Android 列表控件 ListView Android 网格视图 GridView Android 下拉选择框 Spinner 自动补全文本框 AutoCompleteText 折叠列表 ExpandableListView
提示类控件
吐司提示:Toast 的使用方法 状态栏通知:Notification 对话框:AlertDialog 悬浮窗:PopupWindow
菜单类控件
菜单:Menu
其他控件
视频页面:ViewPager 侧滑菜单:DrawerLayout
事件处理机制
基于监听的事件处理机制 Handler 消息传递机制 触摸事件分发处理 AsyncTask:异步任务 Android 手势处理
Android 四大组件
活动:Activity 服务:Service 广播接收器:Broadcast Receiver 内容提供者 - Content Provider
数据存储
文件存储 SharedPreferences 存储 数据库:SQLite 的使用
网络编程
HTTP 使用详解 xml 数据解析 JSON 数据解析 网页视图:WebView Socket 网络接口
绘图与动画
图片资源:Drawable 位图:Bitmap
多媒体开发
媒体播放器:MediaPlayer 相机:Camera 音频录制:MediaRecorder
并发编程
多线程