NIO(Non-Blocking I/O或叫New I/O)是一种同步非阻塞的I/O模型,主要用于服务端解决高并发或者大量连接的情况的IO处理。它是JDK1.4中引入的,位于java.nio包中,主要用于弥补原来同步阻塞I/O(Blocking I/O或叫BIO)的不足。在NIO出现之前大多服务端主要使用BIO通过新建线程的方式来解决并发请求,如上一篇博文
《Android网络编程(十三) 之 Socket和长连接》
中的长连接Demo,在每个客户端请求连接后都会创建一个新的Socket对象并内部创建线程来处理相关连接,这样就很容易因线程瓶颈而造成很多限制。
NIO在处理读写是采用了内存映射文件的方式,它基于通道(Channel)和缓冲区(Buffer)进行操作,数据从通道读取到缓冲区或者从缓冲区写入到通道,再通过选择器(Selector)进行监听多个通道的事件,所以区别于BIO的面向流方式,NIO可更加高效地进行文件的读写操作。
2 NIO的组件
2.1 Buffer(缓冲区)
BIO的操作是面向数据流的读写,而NIO所有的数据都是用Buffer缓冲区处理的,缓冲区其实就是一块连续的内存空间,这块内存空间就像一个数据容器般,可以重复的读取数据和写入数据。
2.2 Channel(通道)
Channel通道跟BIO中的Stream类似,都是用于跟连接的对象进行IO操作。它们区别于,Stream是阻塞的单向操作的,即要么读要么写,比如InputStream和OutputStream;而Channel是非阻塞且是线程安全的双向操作的,通过一个Channel既可以进行读也可进行写操作,其所有数据都是映射到内存中通过Buffer来处理,Server端逻辑中,一个Client端会对应一个Channel。
2.3 Selector(选择器)
在BIO中当一个Server端连接着多个Client端时,Server端会为其创建一个线程来提升并发吞吐量,但是一旦并发量上升就会出现明显的弊端。在这情况Selector的优势就出现了。Selector叫做选择器,或者叫做多路复用器,Selector运行在单个线程中但可同时管理一个或多个Channel。它通过不断地轮询进行Channel的状态的检查处理其连接、读、写等操作。意味着可以使用更少的线程来处理多个Client端的请求,避免了使用线程的开销。Server逻辑中,仅需要一个Selector,但它可以管理多个Channel。
3 Socket与NIO
我们还是用一个简单的Demo来实现一个Socket,不过这次使用了NIO的方式。Demo中服务端在App的Service中进行,而客户端在App的Activity中进行,为了展示出服务端可以同时接收多个客户端,Activity的界面特意做了两套客户端,如下图所示。
3.1 服务端代码
TCPServerService.java
public class TCPServerService extends Service {
private static final String TAG = "TCPServerService----------";
public final static int SERVER_PORT = 9527; // 跟客户端绝定的端口
private TCPServer mTCPServer;
private ThreadPoolExecutor mConnectThreadPool; // 总的连接线程池
@Override
public void onCreate() {
super.onCreate();
init();
initTcpServer();
@Override
public IBinder onBind(Intent intent) {
return null;
@Override
public void onDestroy() {
super.onDestroy();
unInitTcpServer();
private void init() {
mConnectThreadPool = new ThreadPoolExecutor(
TimeUnit.MILLISECONDS,
new SynchronousQueue<Runnable>(),
new ThreadFactory() {
@Override
public Thread newThread(Runnable r) {
return new Thread(r, "server_thread_pool");
new RejectedExecutionHandler() {
@Override
public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
Log.e(TAG, "已启动连接,请免重复操作");
* 初始化TCP服务
private void initTcpServer() {
mConnectThreadPool.execute(new Runnable() {
@Override
public void run() {
mTCPServer = new TCPServer();
mTCPServer.init();
* 反初始化TCP服务
private void unInitTcpServer() {
mTCPServer.close();
服务端的实现在TCPServerService中,TCPServerService服务启动后,便创建一个线程来创建一个TCPServer对象并执行初始化。
TCPServer.java
public class TCPServer {
private final static String TAG = "TCPServer----------";
private String mSendMsg;
private Selector mSelector;
public void init() {
ServerSocketChannel serverSocketChannel = null;
try {
serverSocketChannel = ServerSocketChannel.open();
// 设置非阻塞
serverSocketChannel.configureBlocking(false);
// 获取与此Channel关联的ServerSocket并绑定端口
serverSocketChannel.socket().bind(new InetSocketAddress(TCPServerService.SERVER_PORT));
// 注册到Selector,等待连接
mSelector = Selector.open();
serverSocketChannel.register(mSelector, SelectionKey.OP_ACCEPT);
while (mSelector != null && mSelector.isOpen()) {
// 选择一组对应Channel已准备好进行I/O的Key
int select = mSelector.select();
if (select <=0) {
continue;
// 获得Selector已选择的Keys
Set<SelectionKey> selectionKeys = mSelector.selectedKeys();
Iterator<SelectionKey> iterator = selectionKeys.iterator();
while (iterator.hasNext()) {
SelectionKey selectionKey = iterator.next();
// 移除当前的key
iterator.remove();
if (selectionKey.isValid() && selectionKey.isAcceptable()) {
handleAccept(selectionKey);
if (selectionKey.isValid() && selectionKey.isReadable()) {
handleRead(selectionKey);
if (selectionKey.isValid() && selectionKey.isWritable()) {
handleWrite(selectionKey);
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (mSelector != null) {
mSelector.close();
mSelector = null;
if (serverSocketChannel != null) {
serverSocketChannel.close();
} catch (IOException e) {
e.printStackTrace();
private void handleAccept(SelectionKey selectionKey) throws IOException {
ServerSocketChannel server = (ServerSocketChannel) selectionKey.channel();
SocketChannel client = server.accept();
client.configureBlocking(false);
// 注册读就绪事件
client.register(mSelector, SelectionKey.OP_READ);
Log.d(TAG, "服务端 同意 客户端(" + client.getRemoteAddress() + ") 的连接请求");
private void handleRead(SelectionKey selectionKey) throws IOException {
SocketChannel client = (SocketChannel) selectionKey.channel();
//读取服务器发送来的数据到缓冲区中
ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
int bytesRead = client.read(byteBuffer);
if (bytesRead > 0) {
String inMsg = new String(byteBuffer.array(), 0, bytesRead);
// 处理数据
responseMsg(selectionKey, inMsg);
else {
Log.d(TAG, "服务端 断开跟 客户端(" + client.getRemoteAddress() + ") 的连接");
client.close();
private void handleWrite(SelectionKey selectionKey) throws IOException {
if (TextUtils.isEmpty(mSendMsg)) {
return;
SocketChannel client = (SocketChannel) selectionKey.channel();
ByteBuffer sendBuffer = ByteBuffer.allocate(1024);
sendBuffer.put(mSendMsg.getBytes());
sendBuffer.flip();
client.write(sendBuffer);
mSendMsg = null;
client.register(mSelector, SelectionKey.OP_READ);
* 处理数据
* @param selectionKey
* @param inMsg
* @throws IOException
private void responseMsg(SelectionKey selectionKey, String inMsg) throws IOException {
SocketChannel client = (SocketChannel) selectionKey.channel();
Log.d(TAG, "服务端 收到 客户端(" + client.getRemoteAddress() + ") 数据:" + inMsg);
// 估计1亿的AI代码
String outMsg = inMsg;
outMsg = outMsg.replace("吗", "");
outMsg = outMsg.replace("?", "!");
outMsg = outMsg.replace("?", "!");
sendMsg(selectionKey, outMsg);
* 发送数据
* @param selectionKey
* @param msg
* @throws IOException
public void sendMsg(SelectionKey selectionKey, String msg) throws IOException {
mSendMsg = msg;
SocketChannel client = (SocketChannel) selectionKey.channel();
client.register(mSelector, SelectionKey.OP_WRITE);
Log.d(TAG, "服务端 给 客户端(" + client.getRemoteAddress() + ") 发送数据:" + msg);
* 断开连接
public void close() {
try {
Log.d(TAG, "服务端中断所有连接");
mSelector.close();
} catch (IOException e) {
e.printStackTrace();
TCPServer类核心代码就是init方法,可见方法内存在ServerSocketChannel和Selector,它们便是我们上面介绍的通道和选择器。除此外还有一个SelectionKey,它是用于维护Channel和Selector的对应关系。而且可见TCPServer类内不再需要对每个客户端的连接再开启新线程。
SelectionKey里头有四个常量:SelectionKey.OP_CONNECT、SelectionKey.OP_ACCEPT、SelectionKey.OP_READ、SelectionKey.OP_WRITE,它们表示Channel注册到Selectort感兴趣的事件。对应selectionKey.isConnectable()、selectionKey.isAcceptable()、selectionKey.isReadable()、selectionKey.isWritable()方法会返回true,所以可以理解成,主要注册了相应的事件,上述循环中便会执行相应返回true的动作。
3.2 客户端代码
activity_main.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"
tools:context=".MainActivity">
<Button
android:id="@+id/btn_connection1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="80dp"
android:layout_marginLeft="30dp"
android:text="连接1" />
<Button
android:id="@+id/btn_send1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="80dp"
android:layout_centerHorizontal="true"
android:text="发送1" />
<Button
android:id="@+id/btn_disconnect1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="80dp"
android:layout_alignParentRight="true"
android:layout_marginRight="30dp"
android:text="断开1" />
<Button
android:id="@+id/btn_connection2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="180dp"
android:layout_marginLeft="30dp"
android:text="连接2" />
<Button
android:id="@+id/btn_send2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="180dp"
android:layout_centerHorizontal="true"
android:text="发送2" />
<Button
android:id="@+id/btn_disconnect2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="180dp"
android:layout_alignParentRight="true"
android:layout_marginRight="30dp"
android:text="断开2" />
</RelativeLayout>
MainActivity.java
public class MainActivity extends AppCompatActivity {
private TCPClient mTcpClient1;
private TCPClient mTcpClient2;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Intent service = new Intent(this, TCPServerService.class);
startService(service);
mTcpClient1 = new TCPClient("客户端A");
mTcpClient2 = new TCPClient("客户端B");
Button btnConnection1 = findViewById(R.id.btn_connection1);
btnConnection1.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
mTcpClient1.requestConnectTcp();
Button btnSend1 = findViewById(R.id.btn_send1);
btnSend1.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
mTcpClient1.sendMsg("2_你好吗?");
Button btnDisconnect1 = findViewById(R.id.btn_disconnect1);
btnDisconnect1.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
mTcpClient1.disconnectTcp();
Button btnConnection2 = findViewById(R.id.btn_connection2);
btnConnection2.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
mTcpClient2.requestConnectTcp();
Button btnSend2 = findViewById(R.id.btn_send2);
btnSend2.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
mTcpClient2.sendMsg("2_吃饭了吗?");
Button btnDisconnect2 = findViewById(R.id.btn_disconnect2);
btnDisconnect2.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
mTcpClient2.disconnectTcp();
客户端的实现在MainActivity中,MainActivity主要是创建了两个TCPClient对象,然后对应界面中的按钮作相应的逻辑。
TCPClient.java
public class TCPClient {
private static final String TAG = "TCPClient**********";
private String mSendMsg;
private String mClientName; // 客户端命名
private Selector mSelector;
private SocketChannel mSocketChannel;
private ThreadPoolExecutor mConnectThreadPool; // 消息连接和接收的线程池
public TCPClient(String clientName) {
init(clientName);
* 基本初始化
* @param clientName
private void init(String clientName) {
mClientName = clientName;
mConnectThreadPool = new ThreadPoolExecutor(
TimeUnit.MILLISECONDS,
new SynchronousQueue<Runnable>(),
new ThreadFactory() {
@Override
public Thread newThread(Runnable r) {
return new Thread(r, "client_connection_thread_pool");
new RejectedExecutionHandler() {
@Override
public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
Log.e(TAG, mClientName + " 已启动连接,请免重复操作");
* 请求连接服务端
public void requestConnectTcp() {
mConnectThreadPool.execute(new Runnable() {
@Override
public void run() {
initSocketAndReceiveMsgLoop();
private void initSocketAndReceiveMsgLoop() {
try {
mSocketChannel = SocketChannel.open();
// 设置为非阻塞方式
mSocketChannel.configureBlocking(false);
// 连接服务端地址和端口
mSocketChannel.connect(new InetSocketAddress("127.0.0.1", TCPServerService.SERVER_PORT));
// 注册到Selector,请求连接
mSelector = Selector.open();
mSocketChannel.register(mSelector, SelectionKey.OP_CONNECT);
while (mSelector != null && mSelector.isOpen() && mSocketChannel != null && mSocketChannel.isOpen()) {
// 选择一组对应Channel已准备好进行I/O的Key
int select = mSelector.select(); // 当没有消息时,这里也是会阻塞的
if (select <= 0) {
continue;
Set<SelectionKey> selectionKeys = mSelector.selectedKeys();
Iterator<SelectionKey> iterator = selectionKeys.iterator();
while (iterator.hasNext()) {
SelectionKey selectionKey = iterator.next();
// 移除当前的key
iterator.remove();
if (selectionKey.isValid() && selectionKey.isConnectable()) {
handleConnect();
if (selectionKey.isValid() && selectionKey.isReadable()) {
handleRead();
if (selectionKey.isValid() && selectionKey.isWritable()) {
handleWrite();
} catch (Exception e) {
e.printStackTrace();
} finally {
close();
private void handleConnect() throws IOException {
// 判断此通道上是否正在进行连接操作。
if (mSocketChannel.isConnectionPending()) {
mSocketChannel.finishConnect();
mSocketChannel.register(mSelector, SelectionKey.OP_READ);
Log.d(TAG, mClientName + " 请求跟服务端建立连接");
private void handleRead() throws IOException {
ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
int bytesRead = mSocketChannel.read(byteBuffer);
if (bytesRead > 0) {
String inMsg = new String(byteBuffer.array(), 0, bytesRead);
Log.d(TAG, mClientName + " 收到服务端数据: " + inMsg);
} else {
Log.d(TAG, mClientName + " 断开跟 服务端的连接");
disconnectTcp();
private void handleWrite() throws IOException {
if (TextUtils.isEmpty(mSendMsg)) {
return;
ByteBuffer sendBuffer = ByteBuffer.allocate(1024);
sendBuffer.put(mSendMsg.getBytes());
sendBuffer.flip();
mSocketChannel.write(sendBuffer);
Log.d(TAG, "--------------------------------------");
Log.d(TAG, mClientName + " 发送数据: " + mSendMsg);
mSendMsg = null;
mSocketChannel.register(mSelector, SelectionKey.OP_READ);
* 发送数据
* @param msg
* @throws IOException
public void sendMsg(String msg) {
if (mSelector == null || !mSelector.isOpen() || mSocketChannel == null || !mSocketChannel.isOpen()) {
return;
try {
mSendMsg = msg;
mSocketChannel.register(mSelector, SelectionKey.OP_WRITE);
mSelector.wakeup();
} catch (IOException e) {
e.printStackTrace();
* 断开连接
public void disconnectTcp() {
Log.d(TAG, "--------------------------------------");
Log.d(TAG, mClientName + " 主动断开跟服务端连接");
close();
* 断开连接
private void close() {
try {
if (mSelector != null && mSelector.isOpen()) {
mSelector.close();
if (mSocketChannel != null && mSocketChannel.isOpen()) {
mSocketChannel.close();
} catch (IOException e) {
e.printStackTrace();
TCPClient类对外就是对应三种按钮事件:连接服务端requestConnectTcp、发送数据sendMsg、断开连接disconnectTcp,基本上跟服务端TCPServer类的逻辑很像。
3.3 输出日志
运行程序后,相应执行连接和断开按钮会能输出以下日志:
2020-12-31 16:59:44.558 31789-31851/com.zyx.myapplication D/TCPClient**********: 客户端A 请求跟服务端建立连接
2020-12-31 16:59:44.558 31789-31829/com.zyx.myapplication D/TCPServer----------: 服务端 同意 客户端(/127.0.0.1:42250) 的连接请求
2020-12-31 16:59:47.315 31789-31789/com.zyx.myapplication D/ContentCapture: checkClickAndCapture, voiceRecorder=disable, collection=disable
2020-12-31 16:59:47.318 31789-31851/com.zyx.myapplication D/TCPClient**********: --------------------------------------
2020-12-31 16:59:47.318 31789-31851/com.zyx.myapplication D/TCPClient**********: 客户端A 发送数据: 2_你好吗?
2020-12-31 16:59:47.318 31789-31829/com.zyx.myapplication D/TCPServer----------: 服务端 收到 客户端(/127.0.0.1:42250) 数据:2_你好吗?
2020-12-31 16:59:47.318 31789-31829/com.zyx.myapplication D/TCPServer----------: 服务端 给 客户端(/127.0.0.1:42250) 发送数据:2_你好!
2020-12-31 16:59:47.321 31789-31851/com.zyx.myapplication D/TCPClient**********: 客户端A 收到服务端数据: 2_你好!
2020-12-31 16:59:49.689 31789-31789/com.zyx.myapplication D/ContentCapture: checkClickAndCapture, voiceRecorder=disable, collection=disable
2020-12-31 16:59:49.689 31789-31789/com.zyx.myapplication D/TCPClient**********: --------------------------------------
2020-12-31 16:59:49.689 31789-31789/com.zyx.myapplication D/TCPClient**********: 客户端A 主动断开跟服务端连接
2020-12-31 16:59:49.690 31789-31851/com.zyx.myapplication D/FlymeTrafficTracking: untag(69) com.zyx.myapplication client_connection_thread_pool uid 10472 5134ms
2020-12-31 16:59:49.692 31789-31829/com.zyx.myapplication D/TCPServer----------: 服务端 断开跟 客户端(/127.0.0.1:42250) 的连接
好了,到此Socket的使用包括长连接、NIO都已通过上篇和本篇博文介绍完毕,有兴趣的朋友可以将两篇文章中的两个Demo结合来搭建一个属于自己长连接框架。
http://blog.csdn.net/lcllcl987/archive/2007/04/16/1566114.aspxnio学习:最近花了点时间研究了一下nio,及其开源框架MINA,现把心得总结如下:1:传统socket:阻塞式通信每建立一个Socket连接时,同时创建一个新线程对该Socket进行单独通信(采用阻塞的方式通信)。这种方式具有很高的响应速度,并且控制起来也很简单,在连接数较...
Android Socket 系列更新计划
Android Socket通信(一) – 初识与相遇
Android Socket通信(二) --UDP,单播,广播和多播(组播)
Android Socket通信(三) – TCP 配置和传递基础数据
Android Socket通信(四) – UDP与TCP结合传输数据
Android Socket通信(五) – 实现一个多人聊天室...
NioSocket 客户端与服务端交互实现java Nio是jdk1.4新增的io方式—–nio(new IO),这种方式在目前来说算不算new,更合适的解释应该是non-block IO。non-block是相对于传统的io方式来讲的。传统的Io方式是阻塞的,我们拿网络io来举例,传统的io模型如下:服务端主线程负责不断地server.accept(),如果没有客户端请求主线程就会阻塞,当客户端...
Socket的使用在Android的网络编程中非常重要
今天我将带大家全面了解Socket及其使用方法 目录1.网络基础1.1 计算机网络分层计算机网络分为五层:物理层、数据链路层、网络层、运输层、应用层其中:
网络层:负责根据IP找到目的地址的主机
运输层:通过端口把数据传到目的主机的目的进程,来实现进程与进程之间的通信
1.2 端口号(PORT)端口号规定为16位,即允许一个IP主机有2的
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetSocketAddress;
import j
服务器代码:import java.net.*;import java.nio.*;import java.nio.channels.*;import java.util.*;public class server{ServerSocketChannel ssc ;public void start(){try{Selector selector = Selector.open();ServerS...
之前自己写了socket建立的连接,但是涉及到一些需要完善的地方,表示自己有点懒。然后就在网上找了一个socket框架, 拿来直接用。
阅读测试后,个人认为NettyClient 主要分为几个部分:
1,异步端口连接 Bootstrap
2,NettyClient的解码方式等 pipeline
3,配套的HandlerAdapter : ChannelInboundHandlerAdapter等...
1 Socket的简介
Socket字面翻译是“插座”,通常也称作“套接字”,是对TCP/IP的封装的编程接口。Socket把复杂的TCP/IP 协议族隐藏在Socket 接口后面。Socket 用于描述IP地址和端口,是一个通信链的句柄。应用程序通常通过Socket向网络发出请求或者应答网络请求。就像一台服务器可能会提供很多服务,每种服务对应一个Socket,并绑定到一个端口上,不同的端口对应...
Android App开发基础篇—实现非阻塞Socket通信
前言:Android开发中可以使用Java API提供的Socket和ServerSocket类来实现Socket通信。但是,通过这两个类实现的Socket通信是阻塞式的,当程序执行输入/输出操作后,在这些操作返回之前会一直阻塞线程。当有大量任务需要处理时,这种方式会降低性能。在Java中提供了另一种NIO API,可以实现非阻塞的Socket通信,该NIO API主要提供了以下两种特殊类:Selector和SelectableChannel。
socket服务端之前笔记里面记录的比较乱,最后我写了一个类,试着封装成一个模块的样子。使用的时候通过继承生成一个子类,然后调用run执行。你应该需要重构其中的部分方法,另外可能还需要在子类中创建新的方法。至少需要重构onrecv方法,接收到数据后的处理。另外要发数据,调用send_data接口,把conn连接和bytes类型的data传入。import loggingimport queueim...