一、利用Socket建立网络连接的步骤
建立Socket连接至少需要一对套接字,其中一个运行于客户端,称为ClientSocket ,另一个运行于服务器端,称为ServerSocket 。套接字之间的连接过程分为三个步骤:服务器监听,客户端请求,连接确认。
1、服务器监听:服务器端套接字并不定位具体的客户端套接字,而是处于等待连接的状态,实时监控网络状态,等待客户端的连接请求。
2、客户端请求:指客户端的套接字提出连接请求,要连接的目标是服务器端的套接字。为此,客户端的套接字必须首先描述它要连接的服务器的套接字,指出服务器端套接字的地址和端口号,然后就向服务器端套接字提出连接请求。
3、连接确认:当服务器端套接字监听到或者说接收到客户端套接字的连接请求时,就响应客户端套接字的请求,建立一个新的线程,把服务器端套接字的描述发给 客户端,一旦客户端确认了此描述,双方就正式建立连接。而服务器端套接字继续处于监听状态,继续接收其他客户端套接字的连接请求。
二、HTTP链接的特点
HTTP协议即超文本传送协议(Hypertext Transfer Protocol ),是Web联网的基础,也是手机联网常用的协议之一,HTTP协议是建立在TCP协议之上的一种应用。HTTP连接最显著的特点是客户端发送的每次请求 都需要服务器回送响应,在请求结束后,会主动释放连接。从建立连接到关闭连接的过程称为“一次连接”。
三、TCP和UDP的区别
1、TCP是面向链接的,虽然说网络的不安全不稳定特性决定了多少次握手都不能保证连接的可靠性,但TCP的三次握手在最低限度上(实际上也很大程度上 保证了)保证了连接的可靠性;而UDP不是面向连接的,UDP传送数据前并不与对方建立连接,对接收到的数据也不发送确认信号,发送端不知道数据是否会正 确接收,当然也不用重发,所以说UDP是无连接的、不可靠的一种数据传输协议。
2、也正由于1所说的特点,使得UDP的开销更小数据传输速率更高,因为不必进行收发数据的确认,所以UDP的实时性更好。
四、Socket、SocketChannel有什么区别
Socket、SocketChannel二者的实质都是一样的,都是为了实现客户端与服务器端的连接而存在的,但是在使用上,却有很大的区别。具体如下:
1.所属包不同
Socket在java.net包中,而SocketChannel在java.nio包中。
2.异步方式不同
从包的不同,我们大体可以推断出他们主要的区别:Socket是阻塞连接(当然我们可以自己实现非阻塞),SocketChannel可以设置非阻塞连接。
使用ServerSocket、Socket类时,服务端Socket往往要为每一个客户端Socket分配一个线程,而每一个线程都有可能处于长时间的阻塞状态中。过多的线程也会影响服务器的性能(可以使用线程池优化,具体看这里:如何编写多线程Socket程序)。而使用SocketChannel、ServerSocketChannel类可以非阻塞通信,这样使得服务器端只需要一个线程就能处理所有客户端socket的请求。
3.性能不同
一般来说使用SocketChannel会有更好的性能。其实,Socket实际应该比SocketChannel更高效,不过由于使用者设计等原因,效率反而比直接使用SocketChannel低。
4.使用方式不同
Socket、ServerSocket类可以传入不同参数直接实例化对象并绑定ip和端口,如:
Socket socket = new Socket("127.0.0.1", "8000");
ServerSocket serverSocket = new ServerSocket("8000");
而SocketChannel、ServerSocketChannel类需要借助Selector类控制,如:
private SocketChannel mSocketChannel; private Selector mSelector; private static boolean sIsGoOn = true; private static final int MAX_BUFFER_SPACE = 2048; /** * 建立 Socket 连接 * @param host 服务器 ip * @param port 服务器端口号 * @return 返回建立 socket 连接的结果i */ public boolean open(String host, String port) { if (TextUtils.isEmpty(host) || TextUtils.isEmpty(port)) { LogUtil.e( "host or port error."); return false; } try { mSelector = Selector.open(); mSocketChannel = SocketChannel .open(new InetSocketAddress(host, Integer.parseInt(port))); mSocketChannel.configureBlocking(false);// 设置为非阻塞方式,如果为true 那么就为传统的阻塞方式 mSocketChannel.register(mSelector, SelectionKey.OP_READ | SelectionKey.OP_WRITE);//注册读就绪事件和写就绪事件 } catch (IOException e) { e.printStackTrace(); return false; } return true; } /** * 发送报文 * @param sendPacket 待发送的报文 * @return 返回发送结果 */ public int sendPacket(byte[] sendPacket) { int count = 0; try { sIsGoOn = true; while (sIsGoOn && mSelector.select() > 0) { Iterator iterator = mSelector.selectedKeys().iterator(); while (sIsGoOn && iterator.hasNext()) { SelectionKey selectionKey = (SelectionKey) iterator.next(); iterator.remove(); if (selectionKey.isWritable()) { SocketChannel socketChannel = (SocketChannel) selectionKey.channel(); ByteBuffer byteBuffer = ByteBuffer.wrap(sendPacket, 0, sendPacket.length); count = socketChannel.write(byteBuffer); LogUtil.i( "Send ok."); return count; } LogUtil.i( "sendPacket: "); mSelector.selectedKeys().remove(selectionKey); } } } catch (IOException e) { e.printStackTrace(); return -1; } return count; } /** * 接收来自服务器的报文 * @return 返回接收的报文 */ public byte[] receivePacket() { byte[] receivedPacket = null; int count; try { sIsGoOn = true; while (sIsGoOn && mSelector.select() > 0) { Iterator iterator = mSelector.selectedKeys().iterator(); while (sIsGoOn && iterator.hasNext()) { SelectionKey selectionKey = (SelectionKey) iterator.next(); iterator.remove(); try { if (selectionKey.isReadable()) { SocketChannel socketChannel = (SocketChannel) selectionKey.channel(); ByteBuffer byteBuffer = ByteBuffer.allocate(MAX_BUFFER_SPACE); byteBuffer.clear(); count = socketChannel.read(byteBuffer); byteBuffer.flip(); if (count > 0) { receivedPacket = new byte[count]; System.arraycopy(byteBuffer.array(), 0, receivedPacket, 0, count); } break; } } catch (CancelledKeyException e) { selectionKey.cancel(); LogUtil.e( e.getMessage()); } mSelector.selectedKeys().remove(selectionKey); } if (receivedPacket != null) { break; } } } catch (IOException e) { e.printStackTrace(); } return receivedPacket; } /** * 关闭 Socket 连接 */ public void close() { try { if (mSocketChannel != null && mSocketChannel.isConnected()) { mSocketChannel.finishConnect(); mSelector.close(); sIsGoOn = false; mSocketChannel.close(); LogUtil.i( "close: "); } } catch (IOException e) { e.printStackTrace(); } }这些操作均需要在子线程进行
private static final int RETRY_MAX_COUNT = 6; private static final String CARD_ERROR = "cardError"; private byte[] mReceivePacket; /** * 建立 Socket 连接,用于发送报文 */ private PacketSocket mPacketSocket; private IPacketModelInterface mModelInterface; /** * 临时使用 */ private CommunicationInfo mCommunicationInfo; /** * 尝试发送的次数 */ private int mSendNum; private BasePacket mPacket; public PacketTransferTask(Context context, IPacketModelInterface modelInterface, BasePacket packet) { mPacketSocket = new PacketSocket(); mCommunicationInfo = new CommunicationInfo(context); mPacket = packet; mModelInterface = modelInterface; } @Override protected String doInBackground(byte[]... sendPacket) { LogUtil.i( "doInBackground: sendPacket" + ConvertUtil.bytesToHexString(sendPacket[0])); while (ClientInfoManager.getConsumeData().getCardType() == ConsumeInfoUtil.CARD_TYPE_IC && !BasePacketPresenter.sIsReverse) { int flag = ClientInfoManager.getConsumeData().getSearchCardResponse(); if (flag != 0 && flag != 1) { return null; } if (ClientInfoManager.getConsumeData().isCanSendPacket()) { LogUtil.i( "generatePacket again"); mPacket.clearPacket(); sendPacket[0] = mPacket.generatePacket(); break; } } publishProgress(SocketEvent.SOCKET_STATUS_CONNECTING); String ip = mCommunicationInfo.getHostIP(); String port = mCommunicationInfo.getHostPort(); if (ip == null || port == null) { ip = mCommunicationInfo.getSpareHostIP(); port = mCommunicationInfo.getSpareHostPort(); mSendNum = 3; if (ip == null || port == null) { LogUtil.e( "ip or port is null"); return SocketEvent.SOCKET_ERROR_IP_PORT; } } while (mSendNum < RETRY_MAX_COUNT) { if (isCancelled()) { return null; } if (mSendNum == 3) { ip = mCommunicationInfo.getSpareHostIP(); port = mCommunicationInfo.getSpareHostPort(); } if (!mPacketSocket.open(ip, port)) { LogUtil.e( mSendNum + " open failed"); mSendNum++; } else { LogUtil.e( mSendNum + " open success"); break; } } if (mSendNum == 6) { LogUtil.e( "socket open failed."); return SocketEvent.SOCKET_ERROR_CONNECT; } publishProgress(SocketEvent.SOCKET_STATUS_SENDING); if (sendPacket[0] != null && mPacketSocket.sendPacket(sendPacket[0]) <= 0) { LogUtil.e( "send packet failed."); return SocketEvent.SOCKET_ERROR_SEND; } publishProgress(SocketEvent.SOCKET_STATUS_RECEIVERING); try { mReceivePacket = mPacketSocket.receivePacket(); } catch (ClosedSelectorException e) { LogUtil.e( e.getMessage()); } if ((mReceivePacket == null) || (mReceivePacket.length <= 0)) { LogUtil.e( "receive packet failed."); return SocketEvent.SOCKET_ERROR_RECEIVER; } int isCard = ClientInfoManager.getConsumeData().getCardType(); LogUtil.i( "isCard: " + isCard); LogUtil.i( "sIsReverse: " + BasePacketPresenter.sIsReverse); if (isCard == ConsumeInfoUtil.CARD_TYPE_IC && !BasePacketPresenter.sIsReverse) { String domain39Data = ConvertUtil .bytesToString(mModelInterface.getUnpackDomainData(39, mReceivePacket)); String domain55Data = ConvertUtil .bytesToHexString(mModelInterface.getUnpackDomainData(55, mReceivePacket)); int result; result = ClientInfoManager.getConsumeData().getSearchCardResponse(); if (result != 1) { CardManager.getInstance().setRequestOnline(true, domain39Data, domain55Data); } while (true) { if (!ReplyCodeUtil.REPLY_CODE_00.equals(domain39Data)) { ClientInfoManager.getConsumeData().setApproval(true); break; } result = ClientInfoManager.getConsumeData().getSearchCardResponse(); if (result != 1 && result != 0) { TradeDatabaseUtils.saveReverseInfo(ReplyCodeUtil.REPLY_CODE_96); return CARD_ERROR; } else if (result != 0) { break; } if (ClientInfoManager.getConsumeData().getConsumeType() == PacketTypes.PACKET_BALANCE_INQUIRY) { break; } } } return SocketEvent.SOCKET_SUCCESS; } @Override protected void onProgressUpdate(String... progress) { super.onProgressUpdate(progress); mModelInterface.updatePacketSocketProgress(progress[0]); } @Override protected void onPostExecute(String socketResult) { super.onPostExecute(socketResult); if (isCancelled()) { return; } LogUtil.i( "onPostExecute: socketResult" + socketResult); LogUtil.i( "onPostExecute: receivePacket" + ConvertUtil.bytesToHexString(mReceivePacket)); mModelInterface.onReceivePacket(socketResult, mReceivePacket); mPacketSocket.close(); }下面是SocketChannel方式需要用到的几个核心类:
ServerSocketChannel
ServerSocket的替代类, 支持阻塞通信与非阻塞通信。
SocketChannel
Socket的替代类, 支持阻塞通信与非阻塞通信。
Selector
为ServerSocketChannel监控接收客户端连接就绪事件, 为SocketChannel监控连接服务器读就绪和写就绪事件。
SelectionKey
代表ServerSocketChannel及SocketChannel向Selector注册事件的句柄。当一个
SelectionKey对象位于Selector对象的selected-keys集合中时,就表示与这个SelectionKey对象相关的事件发生了。
在SelectionKey类中有几个静态常量:
SelectionKey.OP_ACCEPT,客户端连接就绪事件,等于监听serversocket.accept(),返回一个socket。
SelectionKey.OP_CONNECT,准备连接服务器就绪,跟上面类似,只不过是对于socket的 相当于监听了socket.connect()。
SelectionKey.OP_READ,读就绪事件, 表示输入流中已经有了可读数据, 可以执行读操作。
SelectionKey.OP_WRITE,写就绪事件, 表示可以执行写操作。