相关文章推荐
豪气的煎鸡蛋  ·  Java ...·  1 年前    · 
爱跑步的感冒药  ·  MessagePack-CSharp ...·  1 年前    · 
稳重的咖啡  ·  Android ...·  1 年前    · 
帅呆的四季豆  ·  python ...·  1 年前    · 

1)那什么是同步编程?

什么是同步,就是在发出一个功能调用时,在没有得到结果之前,该调用就不返回。根据这个定义,android中绝大多数函数都是同步调用。但是一般而言,我们在谈论同步、异步的时候,特指那些需要其他部件协作或者需要一定时间完成的任务。在android中,由于主线程(UI线程的不安全性),我们经常会用到handler的 SendMessage函数,就是一个同步线程,它将数据传送给某个窗口后,在对方处理完消息后,这个函数是不会返回的,当处理完毕的时候才返回相应的返回值。

2)那什么是异步编程?

异步的概念和同步相反的。当一个调用者异步发出一个功能调用时,调用者不能立刻得到结果。实际处理这个调用的部件在完成后,通过状态、通知和回调来通知调用者。以 android中AsyncTask类为例,顾名思义异步执行任务,在doInBackground 执行完成后,onPostExecute 方法将被UI 线程调用,后台的计算结果将通过该方法传递到UI 线程,并且在界面上展示给用户.。在android或者java异步编程中需要注意以下几个知识点:回调,监听者模式,观察者模式。这几点在之后另外几篇文章中会提及。

3)什么是阻塞式编程?

阻塞调用是指调用结果返回之前,当前线程会被挂起。函数只有在 得到结果之后才会返回。因为这点定义跟同步编程的定义很相像,所以很多人认为同步编程就等阻塞式编程。 对于同步调用来说,很多时候当前线程还是激活的,只是从逻辑上当前函数没有返回而已。例如,我们在 socket编程中调用Receive函数,如果缓冲区中没有数据,这个函数就会一直等待,直到有数据才返回。而此时,当前线程还会继续处理各种各样的消 息。如果主窗口和调用函数在同一个线程中,除非你在特殊的界面操作函数中调用,其实主界面还是应该可以刷新。但是在android中,由于主线程(UI线程)的不安全性,特别到4.0版本后,系统已经不允许在主线程中进行耗时的同步编程。所以android才出现了 AsyncTask类用于异步编程。

4)什么是非阻塞式编程?

非阻塞和阻塞的概念相对应,指在不能立刻得到结果之前,该函数不会阻塞当前线程,而会立刻返回。从这个定义上来说,非阻塞编程可以说是异步编程的一种,但是异步编程并不等于非阻塞式编程。

5)区别大概

我们用买票的案例去理解它,当我们去买票的时候,如果还在排队,一直排着,直到买到票再离开,这个就是同步编程( 所谓同步就是当一个进程发起一个函数(任务)调用的时候,一直会到函数(任务)完成 )。那还有另外一方式,你可以叫一个人(监听者,观察者)帮你看着,直接你买票了,再通知你,你可以先去别的事情( 而异步这不会这样,异步情况下是当一个进程发 起一个函数(任务)调用的时候,不会等函数返回 )。阻塞是就是等排队,非阻塞就是直接走开。

2、几个关键知识点

1)java.net.InetSocketAddress

此类实现 IP 套接字地址(IP 地址 + 端口号)。它还可以是一个对(主机名 + 端口号),在此情况下,将尝试解析主机名。如果解析失败,则该地址将被视为未解析 地址,但是其在某些情形下仍然可以使用,比如通过代理连接。
需注意接口: public InetSocketAddress(InetAddress addr,int port) 根据 IP 地址和端口号创建套接字地址。
有效端口值介于 0 和 65535 之间。端口号 zero 允许系统在 bind 操作中挑选暂时的端口。

2)java.nio.channels.Selector

可通过调用此类的 open 方法创建选择器,该方法将使用系统的默认选择器提供者创建新的选择器。也可通过调用自定义选择器提供者的 openSelector 方法来创建选择器。通过选择器的 close 方法关闭选择器之前,它一直保持打开状态。 需注意接口: public static Selector open()throws IOException
打开一个选择器。
public abstract void close()throws IOException
关闭此选择器。
如果某个线程目前正阻塞在此选择器的某个选择方法中,则中断该线程,如同调用该选择器的 wakeup 方法那样。
所有仍与此选择器关联的未取消键已无效、其通道已注销,并且与此选择器关联的所有其他资源已释放。
如果此选择器已经关闭,则调用此方法无效。
关闭选择器后,除了调用此方法或 wakeup 方法外,以任何其他方式继续使用它都将导致抛出 ClosedSelectorException。 注:选择器的关闭是关键点,特别需要注意上述第二条

3)java.nio.channels.SocketChannel

针对面向流的连接套接字的可选择通道。
套接字通道不是连接网络套接字的完整抽象。必须通过调用 socket 方法所获得的关联 Socket 对象来完成对套接字选项的绑定、关闭和操作。不可能为任意的已有套接字创建通道,也不可能指定与套接字通道关联的套接字所使用的 SocketImpl 对象。
通过调用此类的某个 open 方法创建套接字通道。新创建的套接字通道已打开,但尚未连接。试图在未连接的通道上调用 I/O 操作将导致抛出 NotYetConnectedException。可通过调用套接字通道的 connect 方法连接该通道;一旦连接后,关闭套接字通道之前它会一直保持已连接状态。可通过调用套接字通道的 isConnected 方法来确定套接字通道是否已连接。
套接字通道支持非阻塞连接:可创建一个套接字通道,并且通过 connect 方法可以发起到远程套接字的连接,之后通过 finishConnect 方法完成该连接。可通过调用 isConnectionPending 方法来确定是否正在进行连接操作。
可单独地关闭 套接字通道的输入端和输出端,而无需实际关闭该通道。调用关联套接字对象的 shutdownInput 方法来关闭某个通道的输入端将导致该通道上的后续读取操作返回 -1(指示流的末尾)。调用关联套接字对象的 shutdownOutput 方法来关闭通道的输出端将导致该通道上的后续写入操作抛出 ClosedChannelException。
套接字通道支持异步关闭,这与 Channel 类中所指定的异步 close 操作类似。如果一个线程关闭了某个套接字的输入端,而同时另一个线程被阻塞在该套接字通道上的读取操作中,那么处于阻塞线程中的读取操作将完成,而不读取任何字节且返回 -1。I如果一个线程关闭了某个套接字的输出端,而同时另一个线程被阻塞在该套接字通道上的写入操作中,那么阻塞线程将收到 AsynchronousCloseException。
多个并发线程可安全地使用套接字通道。尽管在任意给定时刻最多只能有一个线程进行读取和写入操作,但数据报通道支持并发的读写。connect 和 finishConnect 方法是相互同步的,如果正在调用其中某个方法的同时试图发起读取或写入操作,则在该调用完成之前该操作被阻塞。

3、实例代码演示

连接核心代码: SocketChannel client = null; InetSocketAddress isa = null; SocketEventListener mSocketEventListener = null; private boolean Connect(String site, int port) if (mSocketEventListener != null) mSocketEventListener.OnSocketPause(); boolean ret = false; mSelector = Selector.open(); client = SocketChannel.open(); client.socket().setSoTimeout(5000); isa = new InetSocketAddress(site, port); boolean isconnect = client.connect(isa); // 将客户端设定为异步 client.configureBlocking(false); // 在轮讯对象中注册此客户端的读取事件(就是当服务器向此客户端发送数据的时候) client.register(mSelector, SelectionKey.OP_READ); long waittimes = 0; if(!isconnect) while (!client.finishConnect()) EngineLog.redLog(TAG, "等待非阻塞连接建立...."); Thread.sleep(50); if(waittimes < 100) waittimes++; break; Thread.sleep(500); haverepaired(); startListener(); ret = true; catch (Exception e) EngineLog.redLog(TAG + " - Connect error", e != null ? e.toString() : "null"); Thread.sleep(1000 * 10); catch (Exception e1) EngineLog.redLog(TAG + " - Connect error", e1 != null ? e1.toString() : "null"); ret = false; return ret; 在上述代码中,我们可以看到有一个SocketEventListener监听接口,这个接口用于监听socket事件,将其回调给调用者 SocketEventListener接口:
public interface SocketEventListener
                 * Socket正在接收数据
                public void OnStreamRecive();
                 * Socket接收数据完成
                public void OnStreamReciveFinish();
                 * Socket有新的消息返回
                public void OnStreamComing(byte[] aStreamData);
                 * Socket出现异常
                public void OnSocketPause();
                 * Socket已修复,可用
                public void OnSocketAvaliable();
        }
监听接口的使用:
rivate void startListener()
                if (mReadThread == null || mReadThread.isInterrupted())
                        mReadThread = null;
                        mReadThread = new Thread()
                                @Override
                                public void run()
                                        while (!this.isInterrupted() && mRunRead)
                                                MyLineLog.redLog(TAG,"startListener:" + mSendMsgTime);
                                                     // 如果客户端连接没有打开就退出循环
                                                        if (!client.isOpen())
                                                                break;
                                                        // 此方法为查询是否有事件发生如果没有就阻塞,有的话返回事件数量
                                                        int eventcount = mSelector.select();
                                                        // 如果没有事件返回循环
                                                        if (eventcount > 0)
                                                        	starttime = CommonClass.getCurrentTime();
                                                                // 遍例所有的事件
                                                                for (SelectionKey key : mSelector.selectedKeys())
                                                                        // 删除本次事件
                                                                        mSelector.selectedKeys().remove(key);
                                                                        // 如果本事件的类型为read时,表示服务器向本客户端发送了数据
                                                                        if (key.isValid() && key.isReadable())
                                                                                if (mSocketEventListener != null)
                                                                                        mSocketEventListener.OnStreamRecive();
                                                                                boolean readresult = ReceiveDataBuffer((SocketChannel) key.channel());
                                                                                if (mSocketEventListener != null)
                                                                                        mSocketEventListener.OnStreamReciveFinish();
                                                                                if(readresult)
                                                                                        key.interestOps(SelectionKey.OP_READ);
                                                                                        sleep(200);
                                                                                        throw new Exception();
                                                                        key = null;
                                                                mSelector.selectedKeys().clear();
                                                catch (Exception e)
                                                        mRunRead = false;
                                                        mReadThread = null;
                                                        if(e instanceof InterruptedException)
                                                                MyLineLog.redLog(TAG, "startListener:" + e.toString());
                                                                break;
                        mReadThread.setName(TAG + " Listener, " + CommonClass.getCurrentTime());
                        mRunRead = true;
                        mReadThread.start();
  连接完之后就是发送数据和接收数据,下面是发送数据的核心代码: 
                        sendBuffer.clear();
                        sendBuffer = ByteBuffer.wrap(aMessage);
                        int sendsize = client.write(sendBuffer);
                        sendBuffer.flip();
                        sendBuffer.clear();
                        mSendMsgTime = CommonClass.getCurrentTime();
                        MyLineLog.redLog(TAG, "SendSocketMsg:" + mSendMsgTime + ", sendsize:" + sendsize);
                        ret = true;
                catch (Exception e)
                        MyLineLog.redLog(TAG,  "发送数据失败。");
                        if (mSocketEventListener != null)
                                mSocketEventListener.OnSocketPause();
//                        crash();
                return ret;
 
因为实际工作需要,我们需要经常会碰到两个问题,无效数据和大数据,如何去解决这个问题呢,无效数据用过滤,大数据用分块接收,下面是接收数据的方法:
private boolean ReceiveDataBuffer(SocketChannel aSocketChannel)
//              n 有数据的时候返回读取到的字节数。
//              0 没有数据并且没有达到流的末端时返回0。
//              -1 当达到流末端的时候返回-1。
                boolean ret = false;
                ByteArrayBuffer bab = new ByteArrayBuffer(8*1024);
                while(true)
                                ByteBuffer readBuffer = ByteBuffer.allocate(1024 * 1);
                                readBuffer.clear();
                                int readsize = aSocketChannel.read(readBuffer);
                                if(readsize > 0)
                                    MyLineLog.redLog(TAG, "aSocketChannel.read=>" + readsize);
                                        byte[] readbytes = readBuffer.array();
                                        bab.append(readbytes, 0, readsize);
                                        readBuffer.clear();
                                        readBuffer.flip();
                                        ret = true;
                                else if(readsize == 0)
                                        int buffersize = bab.length();                                        
                                        byte[] readdata = bab.buffer();
                                        int readdataoffset = 0;
                                        boolean parsedata = true;
                                        while(readdataoffset < buffersize && parsedata)
                                                byte datatype = readdata[readdataoffset];
                                                if (datatype == PushUtils.PACKAGETYPE_HEARTBEAT || datatype == PushUtils.PACKAGETYPE_HEARTBEAR_NODATA)
                                                        byte[] blockdata = new byte[] { datatype };
                                                        ReceiveData(blockdata);
                                                        readdataoffset += 1;
                                                        blockdata = null;            
                                                        byte[] blocklength = new byte[4];
                                                        System.arraycopy(readdata, readdataoffset + 5, blocklength, 0, 4);
                                                        int blocksize = CommonClass.bytes2int(CommonClass.LitteEndian_BigEndian(blocklength));
                                                        blocklength = null;
                                                        int blockdatasize = 5 + blocksize + 4;
                                                        if(blockdatasize <= buffersize)
                                                                MyLineLog.redLog(TAG, "块数据大小:" + blockdatasize);
                                                                byte[] blockdata = new byte[blockdatasize];
                                                                System.arraycopy(readdata, readdataoffset, blockdata, 0, blockdatasize);
                                                                long starttime = CommonClass.getCurrentTime();
                                                                ReceiveData(blockdata);
                                                                long endtime = CommonClass.getCurrentTime();
                                                                MyLineLog.redLog(TAG, "解析数据用时:" + (endtime - starttime) + "ms");
                                                                readdataoffset += blockdatasize;
                                                                blockdata = null;
                                                        else if(blockdatasize < 10240)
                                                        {//小于10k,则属于正常包
                                                                MyLineLog.redLog(TAG, "块数据大小:" + blockdatasize + ",小于10k,说明数据不完整,继续获取。");
                                                                //将未解析数据存到临时buffer
                                                                int IncompleteSize = buffersize - readdataoffset;
                                                                if(IncompleteSize > 0)
                                                                        byte[] Incompletedata = new byte[IncompleteSize];
                                                                        System.arraycopy(readdata, readdataoffset, Incompletedata, 0, IncompleteSize);
                                                                        bab.clear();
                                                                        bab.append(Incompletedata, 0, IncompleteSize);
                                                                        parsedata = false;
                                                                        Incompletedata = null;
                                                        {//异常包
                                                                MyLineLog.yellowLog(TAG, "块数据错误大小:" + blockdatasize);
                                                                MyLineLog.redLog(TAG,"blockdatasize error:" + blockdatasize);
                                                                ret = true;
                                                                break;
                                        if(parsedata)
                                                ret = true;
                                                break;
                                else if(readsize == -1)
                                        ret = false;
                                        break;
                                        ret = true;
                                        break;
                        catch (IOException e)
                            MyLineLog.redLog(TAG, "aSocketChannel IOException=>" + e.toString());
                                ret = false;
                                break;
                bab.clear();
                bab = null;
                return ret;
 
如果数据量过大的话,还会使用压缩方法进行传输,那应该如何接收呢,下面是一段接收压缩数据的方法:
private void ReceiveData(byte[] aDataBlock)
                        MyLineLog.redLog(TAG, "ReceiveData:" + mSendMsgTime);
                        if (mSendMsgTime != 0)
                                mSendMsgTime = 0;
                        byte[] ret = null;
                        int offset = 0;
                        byte datatype = aDataBlock[offset];
                        offset += 1;
                        if (datatype != -1)
                                if (datatype == PushUtils.PACKAGETYPE_HEARTBEAT)
                                        ret = new byte[] { datatype };
                                else if (datatype == PushUtils.PACKAGETYPE_HEARTBEAR_NODATA)
                                        ret = new byte[] { datatype };
                                else if (datatype == PushUtils.PACKAGETYPE_NORMAL || datatype == PushUtils.PACKAGETYPE_HEARTBEAR_HAVEDATA)
                                        byte[] databytelength = new byte[4];
                                        System.arraycopy(aDataBlock, offset, databytelength, 0, 4);
                                        offset += 4;
                                        int header = CommonClass.bytes2int(CommonClass.LitteEndian_BigEndian(databytelength));
                                        databytelength = null;
                                        if (header == PushUtils.PACKAGEHEADER)
                                                byte[] datalengthbyte = new byte[4];
                                                System.arraycopy(aDataBlock, offset, datalengthbyte, 0, 4);
                                                offset += 4;
                                                int datalength = CommonClass.bytes2int(CommonClass.LitteEndian_BigEndian(datalengthbyte));
                                                datalengthbyte = null;
                                                if (datalength > 4)
                                                        // compressed bit 暂时不压缩
                                                        byte compressed = aDataBlock[offset];
                                                        offset += 1;
                                                        if (compressed == 1)
                                                        {//解压缩
                                                                //跳过头4个字节,此处用于解压缩后的数据大小,暂时不需要
                                                                offset += 4;
                                                                int contentlength = datalength - 1 - 4;
                                                                byte[] datacontentbyte = new byte[contentlength];
                                                                System.arraycopy(aDataBlock, offset, datacontentbyte, 0, contentlength);
                                                                offset += contentlength;                                                               
                                                                byte[] compressdata = new byte[contentlength - 4];
                                                                System.arraycopy(datacontentbyte, 0, compressdata, 0, contentlength - 4);
                                                                long starttime = CommonClass.getCurrentTime();
                                                                byte[] decompressdatacontentbyte = CommonClass.decompress(compressdata);
                                                                long endtime = CommonClass.getCurrentTime();
                                                                MyLineLog.redLog(TAG, "解压缩数据用时:" + (endtime - starttime) + "ms");
                                                                int decompressdatacontentbytelength = decompressdatacontentbyte.length;
                                                                compressdata = null;
                                                                int footer = PushUtils.getInt(datacontentbyte, contentlength - 4);
                                                                if (footer == PushUtils.PACKAGEFOOTER)
                                                                        ret = new byte[decompressdatacontentbytelength + 1];
                                                                        ret[0] = datatype;
                                                                        System.arraycopy(decompressdatacontentbyte, 0, ret, 1, decompressdatacontentbytelength);
                                                                        datacontentbyte = null;
                                                                        decompressdatacontentbyte = null;
                                                        {//数据未压缩
                                                                int contentlength = datalength - 1;
                                                                byte[] datacontentbyte = new byte[contentlength];
                                                                System.arraycopy(aDataBlock, offset, datacontentbyte, 0, contentlength);
                                                                offset += contentlength;
                                                                int footer = PushUtils.getInt(datacontentbyte, contentlength - 4);
                                                                if (footer == PushUtils.PACKAGEFOOTER)
                                                                        ret = new byte[contentlength + 1 - 4];
                                                                        ret[0] = datatype;
                                                                        System.arraycopy(datacontentbyte, 0, ret, 1, contentlength - 4);
                                                                        datacontentbyte = null;
                                if (mSocketEventListener != null)
                                        mSocketEventListener.OnStreamComing(ret);
                catch (Exception e)
                        MyLineLog.redLog(TAG + " - ReceiveData error", e.toString());
 
在介绍SocketChannel的时候,api提到关闭需要注意事项,下面一段关闭SocketChannel的示例代码:
public void closeSocket()
                mRunRead = false;
                if (mReadThread != null)
                        if (!mReadThread.isInterrupted())
                                mReadThread.interrupt();
                                mReadThread = null;
                if (mSelector != null && mSelector.isOpen())
                                mSelector.close();
                        catch (IOException e)
                                MyLineLog.redLog(TAG + " - closeSocket error", e.toString());
                        mSelector = null;
                if (client != null)
                                client.close();
                                client = null;
                        catch (IOException e)
                                MyLineLog.redLog(TAG + " - closeSocket2 error", e.toString());
                System.gc();
 
这篇文章讲解部分大量参照JavaApi,其实很多问题的答案就在Api里面,当你不知道如何去做的时候,回头看一下Api,仔细思考一下,就能解决大部分问题。 Ps:感谢我大学舍友阿钢为我提供的代码 有关Android NIO的精髓主要用于高负载的Socket网络传输,相对于传统I/O模型的Socket传输方式的优势,我们已经在 Android开发进阶之NIO非阻塞包(一) 中讲到了,这里不再赘述,一起来看看Android NIO有关Socket操作提供的类吧: 一、ServerSocketChannel 服务器套接字通道在Android SDK中查找package名为... 首先我们要明确搞明确:同步就等于堵塞?异步就等于非堵塞?这是不正确的,同步不等于阻 塞。而异步也不等于非堵塞。 1)那什么是同步编程? 什么是同步,就是在发出一个功能调用时,在没有得到结果之前,该调用就不返回。依据这个定义,android中绝大多数函数都是同步调用。可是一般而言,我们在谈论... SocketChannel 用于 Tcp 网络连接的通道,类似于非阻塞Socket,它可以通过以下两种方式创建: 通过 SocketChannel 的open()方法,并 connect 网络上的服务器 当 ServerSocketChannel 连接到一个请求,也会创建 SocketChannel。 创建 SocketChannel 通过... NIO(Non-Blocking I/O或叫New I/O)是一种同步非阻塞的I/O模型,主要用于服务端解决高并发或者大量连接的情况的IO处理。它是JDK1.4中引入的,位于java.nio包中,主要用于弥补原来同步阻塞I/O(Blocking I/O或叫BIO)的不足。在NIO出现之前大多服务端主要使用BIO通过新建线程的方式来解决并发请求,如上一篇博文《Android网络编程(十三... AsyncTask异步线程简单讲解 在这里呢简单的讲一下刚刚学过的Android的异步线程,以及简单的示例: 以下就是AsyncTask的标准定义方法了,可以通过new一个对象来调用此方法,本文章仅仅讲述极为简单的方法 public abstract class AsyncTask<Params, Progress, Result> 三种泛型类型分别代表“启动任务执行的输入参数”、“... 这里写目录标题netty的原理及使用——AIO初体验AIO:服务端demo代码客户端demo代码执行结果扩展确认异常猜想推理异常原因结论 netty的原理及使用——AIO初体验 JDK1.7 才开始真正的实现异步I/O,AIO、把I/O的读写全交给了操作系统来完成 在java AIO 的处理API中有三个重要的类: AsynchronousServerSocketChannel(服务端)、 AsynchronousSocketChannel(客户端)、 AndroidAsync是一个基于nio的异步socket ,http(客户端服务器端),websocketsocket.io库,AndroidAsync 是一个底层的网络协议库,如果你想要一个容易使用,高级的,http请求库,请使用Ion(它是基于AndroidAsync 的),正常来说开发者更倾向于使用 Ion。如果你需要一个未被封装的Android的raw Socket,HTTP ... 一 概述:是一种面向流连接只sockets套接字的可选择通道。是基于TCP连接传输,主要用来处理网络I/O的通道,实现了可选择通道,可以被多路复用。二 特征:1 对于已经存在的socket不能创建SocketChannel2 SocketChannel中提供的open接口创建的Channel并没有进行网络级联,需要使用connect接口连接到指定地址3 未进行连接的SocketChannle执行I...