Android网络编程简介

一、Android网络编程简介

       我们在平时的开发中,不可避免的会使用到网络编程方面的知识;在我的理解中,App好比是一个壳,它的作用是将用户和服务器联系起来,用户通过操作App向服务器发送或者接收数据,并且以友好的方式反馈给用户,而App要和服务器交互数据,就会设计到网络编程。在Android App的开发中,用到最多的通信方式是基于Http协议,如果涉及到消息推送,可能会使用到WebSocket等,不管是Http还是WebSocket,其底层实现都是基于Socket;Socket即套接字,是一个对TCP/IP协议进行封装的编程调用接口(API)。我们在开发App时,一般会直接使用比较成熟稳定的网络框架,比如OkHttp、Volley、Glide、Retrofit等,这些框架可以方便的进行网络请求,让我们不必关心网络通信的具体实现原理,但是作为一个优秀的开发者,我们不能仅仅停留在使用阶段,还必须对其实现有所了解,后面我们将一一分析其源码。

二、网络分层

       网络分层就是将网络节点所要完成的数据的发送或转发、打包或拆包、以及控制信息的加载或拆出等工作,分别由不同的硬件和软件模块来完成。这样可以将通信和网络互联这一复杂的问题变得较为简单。说的直白一点,就是拆分任务,将网络通信这一复杂的任务拆分开,分别交给不同的层处理,每一层只处理指定的功能,而不用关心整个网络通信的流程,这样就使问题变得较为简单。网络分层有不同的模型,有OSI的7层参考模型,有TCP/IP的5层分层模型,其对比如下:

网络分层的每一层都是为了完成一种功能而设的,为了实现这些功能,就需要遵守共同的规则,这个规则就叫作“协议”,我们常说的Http协议,就是基于应用层的协议。为了方便起见,我们这里主要分析TCP/IP分层模型:

物理层

该层负责比特流在节点间的传输,即负责物理传输。该层的协议即与链路有关,也与传输介质有关。其通俗来讲就是把计算机连接起来的物理手段。

数据链路层

该层控制网络层和物理层之间的通信,其主要功能是如何在不可靠的物理线路上进行数据的可靠传递。为了保证传输,从网络层接收到的数据被分割成特定的可被物理层传输的帧。帧是用来移动数据的结构包,它不仅包括原始数据,还包括发送方和接收方的物理地址以及纠错地址和控制信息。其中的地址确定了帧将发送到何处,而纠错和控制信息则确保帧无差错到达。如果在传输数据是,接收点检测所传数据中有差错,就要通知发送方重发这一帧。

网络层

该层决定如何将数据从发送方路由到接收方。网络层通过综合考虑发送优先权、网络拥塞程度、服务质量以及可选路由的花费来决定从一个网络中的节点A到另一个网络中的节点B的最佳路径。

传输层

该层为两台主机上的应用程序提供端到端的通信。相比之下,网络层的功能是建立主机到主机的通信。传输层有两个传输协议:TCP(传输控制协议)和UDP(用户数据报协议)。其中,TCP是一个可靠的面向连接的协议,UDP是不可靠的或者说无连接的协议。

应用层

应用程序接收到传输层的数据后,接下来就要进行解读。解读必须事先规定好规则,而应用层就是规定应用程序的数据格式的。它的主要协议有HTTP、FTP、Telnet、SMTP、POP3等。

网络通信的图解

我们在平时的开发过程中,一般接触到的是应用层和传输层,这也是我们之后要介绍的重点。

三、传输层

传输层为两台主机上的应用程序提供端到端的通信。其主要有两个协议:TCP和UDP。其中TCP是可靠的面向连接的协议,而UDP是不可靠的无连接协议。通俗一点说,就是TCP开始通信前首先要建立两台主机上端到端的连接,确保连接正常才发送数据,就好比打电话,首先要拨号,电话通了才能进行对话;而UDP发送数据前不会建立连接,直接就开始发送数据,至于数据能否成功到达,就要看当前的网络状况了,这就好比写信,我们写完信后封装好直接交给邮局处理,至于信件能否成功到达,就要看邮局的处理能力了。在实际应用中,TCP和UDP有着各自的应用场景,并不是说TCP一定就优于UDP,UDP由于不需要建立连接,确保数据可靠性等,相比于TCP,数据传输更快,占用资源更少,对于一些对数据可靠性要求不高但是对实时性要求较高的场景,特别适用,比如多人视频会议、多人网络游戏等,这些场略微的丢包对整体体验并没有太大的影响。下面我们看一下TCP和UDP的对比:

1、TCP面向连接;UDP是无连接的,即发送数据前不需要建立连接;

2、TCP提供可靠的服务,也就是说,通过TCP协议传输的数据,数据无差错、不丢失、不重复、且按序到达;UDP是尽最大努力交付,但是不保证可靠交付;

3、UDP具有较好的实时性,工作效率比TCP要高,适用于需要高速传输或者对实时性要求较高的通信;而TCP为了确保数据的可靠性,需要通过校验和,重传控制,序号标识,滑动窗口、确认应答实现可靠传输,丢包时的重发控制,对次序乱掉的分包进行顺序控制等一系列的手段,这就必然导致了其实时性和工作效率低于UDP;

4、每一条TCP连接只能是点对点的;而UDP支持一对一、一对多、多对一、多对多的交互通信;

5、TCP相比于UDP要占用更多的系统资源。

TCP的三次握手

TCP为了确保数据能够正确传输,在传输数据之前要进行三次握手来建立连接,三次握手的过程如下:

第一次握手:建立连接。客户端发送连接请求报文段,将SYN位置为1,Sequence Number为x;然后,客户端进入SYN_SEND状态,等待服务器的确认;即A发送信息给B。
第二次握手:服务器收到客户端的SYN报文段,需要对这个SYN报文段进行确认,即B收到连接信息后向A返回确认信息。
第三次握手:客户端收到服务器的(SYN+ACK)报文段,并向服务器发送ACK报文段,即A收到确认信息后再次向B返回确认连接信息 。

经过三次握手建立连接后,通信双方就可以正常发送和接收数据了,图解如下:

为什么要进行三次握手呢?主要是为了避免服务端因为收到无效的连接请求报文从而一直等待客户端发送数据导致资源浪费,考虑这样一种情况,客户端首先向服务端发送一个连接请求报文,这个时候由于网络问题,导致这个报文滞留在了某个网络结点中,服务端没有收到这个报文,自然也不会给客户端回复确认报文,客户端因为等待超时,就会认为连接请求报文发送失败,然后重新发送一个连接请求报文,这次连接请求报文顺利到达服务端,服务端给客户端回复确认报文后就建立连接了,然后开始正常通信,通信结束后关闭连接。而此时网络通畅了,之前滞留在网络结点中的请求报文顺利到达服务端,服务端不知道这个请求报文已经失效了,就给客户端发送确认报文并分配资源等待客户端发送数据,而当客户端收到确认报文时,因为请求连接报文已经失效,所有并不会对确认报文做任何处理,这样服务端就会一直等待客户端发送数据,从而导致资源浪费。

TCP的四次挥手

TCP在通信结束后,需要进行四次挥手关闭客户端和服务端的连接,四次挥手的过程如下:

第一次挥手:A发送“请求释放连接”信息到B;
第二次挥手:B收到A的释放信息之后,回复确认释放的信息:我同意你的释放连接请求
第三次挥手:B发送“请求释放连接“信息给A
第四次挥手:A收到B发送的信息后向B发送确认释放信息:我同意你的释放连接请求

B收到确认信息后就会正式关闭连接;A等待2MSL后依然没有收到回复,则证明B端已正常关闭,于是A关闭连接

经过四次挥手后,客户端和服务端就都断开了连接,通信结束,图解如下:

为什么要进行四次挥手呢?

主要是因为TCP连接是全双工的,通信双方都可以发送数据和接收数据,当A向B发送请求释放连接报文给B时,只是表示A没有数据给B发送了,这个时候B可能还有数据发送给A,所以B要等数据发送完毕后才会发送请求释放连接给A,在收到A的确认报文后才开始释放连接。

为什么A要等待2MSL才释放连接?

主要是为了确保A给B发送的确认释放连接报文成功到达B,B能够成功是否连接,以免造成资源浪费。考虑这样一种情况,当A给B发送确认释放连接报文后,由于网络阻塞,这个报文没有成功到达B,那么这个时候如果A直接释放连接,就会造成B无法释放连接。等待2MSL就是为了解决这个问题,当B在发出请求释放连接后,如果迟迟没有收到A的确认回复,就会再次向A发送请求释放连接报文,所以A在收到B的释放连接请求后不能马上释放连接,它还需要等待一段时间确保B不会重新发送请求关闭连接的报文,也就是确保B收到自己发送的确认报文,关闭连接。

四、Android Socket编程简介

Socket即套接字,它是一个对TCP/IP协议进行封装的编程调用接口(API),通过它,我们能实现两个程序之间端对端的通信;Socket有两种类型,即流套接字(streamsocket)和数据报套接字(datagramsocket),流套机制是基于TCP协议的,而数据报套机制是基于UDP协议的。

Socket基本通信模型:

下面我们已流套接字举例,看一下在Android中如何使用Socket,为了方便起见,服务端Socket我们用Python编写,其实流程都是一样的,只是不同的系统编程方式可能不一样。

服务端代码:

# coding=utf-8
import socket
import threading,time

def tcplink(sock, addr):
	print 'Accept new connection from %s:%s...' % addr
	sock.send('Welcome!\n') # 向客户端发送数据
	while True: # 循环等待客户端发送数据并处理
		data = sock.recv(1024) # 接收客户端发送的数据
		print '收到客户端数据:' + data # 打印客户端发送的数据
		if data == 'exit' or not data: # 如果客户端发送的数据为exit则退出循环
			break
		responseData = 'Hello,' + data
		print '回复客户端数据:' + responseData
		sock.send(responseData + '\n') # 向客户端回应数据

	sock.close() # 关闭连接
	print 'Connection from %s:%s closed' % addr

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # 创建Socket,基于字节流的形式,也就是基于TCP协议
s.bind(("", 9999)) #绑定ip和端口号
s.listen(5) #指定最多可以同时连接五个客户端
print 'Wating for connection..'

while True: # 通过循环让服务端一直处于等待客户端连接的状态
	sock, addr = s.accept() # 等待客户端连接
	t = threading.Thread(target=tcplink, args=(sock, addr)) # 连接成功后单独开启一个线程处理和客户端的通信
	t.start() # 启动和客户端通信的线程

服务端Socket编程步骤如下:

1、创建Socket,绑定本地IP和端口;

2、开始监听客户端连接;

3、进入循环,不断接收客户端的连接请求;

4、连接成功后单独开启一个线程来处理和客户端的通信,传输完毕关闭Socket;

客户端代码:

public class SocketTestActivity extends AppCompatActivity {
    private ExecutorService mThreadPool;
    Button mSendButton;
    EditText mSendEditText;
    TextView mReceiveTextView;
    Socket mSocket;
    OutputStream mOutputStream;
    BufferedReader mBufferedReader;
    MyHandler mMyHandler;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_socket_test);
        mMyHandler = new MyHandler(this);
        mSendButton = (Button) findViewById(R.id.send_button);
        mSendEditText = (EditText) findViewById(R.id.send_edit_text);
        mReceiveTextView = (TextView) findViewById(R.id.receive_text_view);
        mSendButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                String sendText = mSendEditText.getText().toString();
                mSendEditText.setText("");
                sendData(sendText);
            }
        });
        mThreadPool = Executors.newCachedThreadPool(); // 创建一个线程池处理异步任务
        createConnection();
    }

    private void createConnection() { // 连接服务端
        mThreadPool.execute(new Runnable() {
            @Override
            public void run() {
                try {
                    mSocket = new Socket("10.41.3.17", 9999); // 创建一个Socket,指定服务端的IP和端口
                    if (mSocket.isConnected()) { // 如果连接成功则在线程池中执行接收服务端数据的任务
                        receiveData();
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        });
    }

    private void receiveData() { // 在线程池中执行接收服务端数据的任务
        mThreadPool.execute(new Runnable() {
            @Override
            public void run() {
                while (true) { // 循环等待服务端发送数据
                    if (mSocket != null && mSocket.isConnected()) {
                        InputStream inputStream = null;
                        try {
                            inputStream = mSocket.getInputStream();
                            InputStreamReader isr = new InputStreamReader(inputStream);
                            mBufferedReader = new BufferedReader(isr);
                            // 通过输入流读取器对象 接收服务器发送过来的数据
                            String responseString = mBufferedReader.readLine();
                            if (responseString != null) { // 将收到的服务端数据通过Handler发送给主线程
                                Message message = Message.obtain();
                                message.what = MyHandler.RECEIVE_MESSAGE_TYPE;
                                message.obj = responseString;
                                mMyHandler.sendMessage(message);
                            }
                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                    } else {
                        break;
                    }
                }
            }
        });
    }

    private void sendData(final String sendText) { // 在线程池中执行发送数据任务
        if (sendText != null && !sendText.contentEquals("")) {
            mThreadPool.execute(new Runnable() {
                @Override
                public void run() {
                    if (mSocket != null && mSocket.isConnected()) {
                        try {
                            mOutputStream = mSocket.getOutputStream();
                            mOutputStream.write(sendText.getBytes());
                            mOutputStream.flush();
                            if (sendText.contentEquals("exit")) {
                                closeConnection();
                            }
                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                    }
                }
            });
        }
    }

    private void closeConnection() { // 关闭连接
        if (mSocket != null) {
            try {
                mSocket.shutdownInput();
                mSocket.shutdownOutput();
                mSocket.close();
                mSocket = null;
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

        if (mBufferedReader != null) {
            try {
                mBufferedReader.close();
                mBufferedReader = null;
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        if (mOutputStream != null) {
            try {
                mOutputStream.close();
                mOutputStream = null;
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        sendData("exit");
    }

    // Handler类,声明为static,避免持有外部类的直接引用,防止内存泄露
    private static class MyHandler extends Handler {
        public static final int RECEIVE_MESSAGE_TYPE = 1;
        private WeakReference<SocketTestActivity> activityWeakReference; // 持有外部类的弱引用
        public MyHandler(SocketTestActivity activity) {
            activityWeakReference = new WeakReference<SocketTestActivity>(activity);
        }

        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case MyHandler.RECEIVE_MESSAGE_TYPE: // 将接收到的服务端数据更新到UI界面上
                    SocketTestActivity activity = activityWeakReference.get();
                    if (activity != null) {
                        String responseString = (String)msg.obj;
                        if (!TextUtils.isEmpty(responseString)) {
                            activity.mReceiveTextView.setText(responseString);
                        }
                    }
                    break;
            }
        }
    }
}

xml布局:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">
    <TextView
        android:id="@+id/receive_text_view"
        android:layout_centerInParent="true"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />
    <LinearLayout
        android:layout_alignParentBottom="true"
        android:layout_width="match_parent"
        android:layout_height="wrap_content">
        <EditText
            android:id="@+id/send_edit_text"
            android:layout_width="0dip"
            android:layout_weight="1"
            android:layout_height="wrap_content" />
        <Button
            android:id="@+id/send_button"
            android:text="send"
            android:layout_centerInParent="true"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content" />
    </LinearLayout>

</RelativeLayout>

最后不要忘记在AndroidManifest中加入网络权限:

<uses-permission android:name="android.permission.INTERNET" />

客户端Socket编程步骤:

1、创建Socket,并指定服务端的IP和端口号,和服务端建立连接;

2、获得Socket的输入流和输出流,利用输入流向服务端发送数据,利用输出流接收服务端数据;

3、通信完毕使用关闭输入输出流,并关闭Socket连接。

运行效果

1、首先运行服务端,等待客户端连接

2、然后运行客户端,建立连接,并向服务端发送一条数据“你好”

可以看到,客户端向服务端发送一条数据“你好”后,收到了服务端的回复“Hello,你好”,此时服务端打印如下:

客户端再向服务端发送一条数据“exit”,表示通信结束要断开连接,服务端打印如下:

五、总结

       在这篇文章中,我们大致介绍了Android编程的基本知识,其中包括网络分层、传输层TCP/IP、Socket编程等,这些都是网络通信的基础,我们平时接触到最多的通信方式应该是基于Http协议的,Http协议是基于最上层的应用层协议,其底层的实现也离不开底层的网络知识,后面我们会分析Http协议的基础知识,以及在Android中常见的Http网络请求框架。

猜你喜欢

转载自blog.csdn.net/xiao_nian/article/details/81475964
今日推荐