public class Clientxian implements Runnable
//该线程负责处理的Socket
private Socket s;
//该线程所处理的Socket对应的输入流
BufferedReader br = null;
public Clientxian(Socket s)
throws IOException
this.s = s;
br = new BufferedReader(
new InputStreamReader(s.getInputStream()));
public void run()
String content = null;
//不断读取Socket输入流中的内容,并将这些内容打印输出
while ((content = br.readLine()) != null)
System.out.println(content);
catch (Exception e)
e.printStackTrace();
上述代码能够不断获取Socket输入流中的内容,当获取Socket输入流中的内容后,直接将这些内容打印在控制台。先运行上面程序中的类IServer,该类运行后作为本应用的服务器,不会看到任何输出。接着可以运行多个IClient——相当于启动多个聊天室客户端登录该服务器,此时可以看到,在任何一个客户端通过键盘输入一些内容后,按回车键,所有客户端(包括自己)都会在控制台收到它刚刚输入的内容,这就简单地实现了一个聊天室的功能。
6.2.4 实现非阻塞Socket通信
在Java应用程序中,可以使用NIO API来开发高性能网络服务器。当程序执行输入、输出操作后,在这些操作返回之前会一直阻塞该线程,服务器必须为每个客户端都提供一条独立的线程进行处理。这说明前面的程序是基于阻塞式API的,当服务器需要同时处理大量客户端时,这种做法会降低性能。
在Java应用程序中可以用NIO API让服务器使用一个或有限几个线程来同时处理连接到服务器上的所有客户端。在Java的NIO中,为非阻塞式的Socket通信提供了下面的特殊类。
① Selector。它是SelectableChannel对象的多路复用器,所有希望采用非阻塞方式进行通信的Channel都应该注册到 Selector对象。可通过调用此类的静态open()方法来创建Selector实例,该方法将使用系统默认的Selector来返回新的Selector。Selector可以同时监控多个SelectableChannel的I/O状况,是非阻塞I/O的核心。一个Selector实例有如下3个SelectionKey的集合。
a.所有SelectionKey集合。它代表了注册在该Selector上的Channel,这个集合可以通过keys()方法返回。
b.被选择的SelectionKey集合。它代表了所有可通过select()方法监测到、需要进行I/O处理的Channel,这个集合可以通过selectedKeys()返回。
c.被取消的SelectionKey集合。它代表了所有被取消注册关系的Channel,在下一次执行select()方法时,这些Channel对应的SelectionKey会被彻底删除,程序通常无须直接访问该集合。
除此之外,Selector还提供了如下和select()相关的方法。
a.int select()。监控所有注册的Channel,当它们中间有需要处理的I/O操作时,该方法返回,并将对应的SelectionKey加入被选择的SelectionKey集合中,该方法返回这些Channel的数量。
b.int select(long timeout)。可以设置超时时长的select()操作。
c.int selectNow()。执行一个立即返回的select()操作,相对于无参数的select()方法而言,该方法不会阻塞线程。
Selector wakeup()。使一个还未返回的select()方法立刻返回。
② SelectableChannel。它代表可以支持非阻塞I/O操作的Channel对象,可以将其注册到Selector上,这种注册的关系由SelectionKey实例表示。在Selector对象中,可以使用select()方法设置允许应用程序同时监控多个I/O Channel。Java程序可调用SelectableChannel中的register()方法将其注册到指定Selector上,当该Selector上某些SelectableChannel上有需要处理的I/O操作时,程序可以调用Selector实例的select()方法获取它们的数量,并通过selectedKeys()方法返回它们对应的SelectKey集合。这个集合的作用巨大,因为通过该集合就可以获取所有需要处理I/O操作的SelectableChannel集。
对象SelectableChannel支持阻塞和非阻塞两种模式,其中所有Channel默认都是阻塞模式,我们必须使用非阻塞式模式才可以利用非阻塞I/O操作。
在SelectableChannel中提供了如下两个方法来设置和返回该Channel的模式状态。
a.SelectableChannel configureBlocking(boolean block)。设置是否采用阻塞模式。
b.boolean isBlocking()。返回该Channel是否是阻塞模式。
不同的SelectableChannel所支持的操作不一样,如ServerSocketChannel代表一个ServerSocket,它就只支持OP_ACCEPT操作。在SelectableChannel中提供了如下方法来返回它支持的所有操作。
int validOps():返回一个bit mask,表示这个Channel上支持的I/O操作。
除此之外,SelectableChannel还提供了如下方法获取它的注册状态。
① boolean isRegistered()。返回该Channel是否已注册在一个或多个Selector上。
② SelectionKey keyFor(Selector sel)。返回该Channel和sel Selector之间的注册关系,如果不存在注册关系,则返回null。
③ SelectionKey。该对象代表SelectableChannel和Selector之间的注册关系。
④ ServerSocketChannel。支持非阻塞操作,对应于java.net.ServerSocket这个类,提供了TCP协议I/O接口,只支持OP_ACCEPT操作。该类也提供了accept()方法,功能相当于ServerSocket提供的accept()方法。
⑤ SocketChannel。支持非阻塞操作,对应于java.net.Socket这个类,提供了TCP协议I/O接口,支持OP_CONNECT、OP_READ和OP_WRITE操作。这个类还实现了ByteChannel接口、ScatteringByteChannel接口和GatheringByteChannel接口,所以可以直接通过SocketChannel来读写ByteBuffer对象。
服务器上所有Channel都需要向Selector注册,包括ServerSocketChannel和SocketChannel。该Selector则负责监视这些Socket的I/O状态,当其中任意一个或多个Channel具有可用的I/O操作时,该Selector的select()方法将会返回大于0的整数,该整数值就表示该Selector上有多少个Channel具有可用的I/O操作,并提供了selectedKeys()方法来返回这些Channel对应的SelectionKey集合。正是通过Selector才使得服务器端只需要不断地调用Selector实例的select()方法,这样就可以知道当前所有Channel是否有需要处理的I/O操作。当Selector上注册的所有Channel都没有需要处理的I/O操作时,将会阻塞select()方法,此时调用该方法的线程被阻塞。
我们继续以聊天室为例,讲解非阻塞Socket通信在Java应用项目中的实现过程。我们的目标是,在服务器端使用循环不断获取Selector的select()方法返回值,当该返回值大于0时就处理该Selector上被选择SelectionKey所对应的Channel。在具体实现时,服务器端使用ServerSocketChannel来监听客户端的连接请求,程序先调用它的socket()方法获得关联ServerSocket对象,再用该ServerSocket对象绑定到来指定监听IP和端口。最后在服务器端调用Selector的select()方法来监听所有Channel上的I/O操作。
接下来开始具体编码,其中服务器端的主要代码如下。
源码路径:daima\6\tcpudp\src\feizu\feizuServer.java。
public class feizuServer
//用于检测所有Channel状态的Selector
private Selector selector = null;
//定义实现编码、解码的字符集对象
private Charset charset = Charset.forName("UTF-8");
public void init()throws IOException
selector = Selector.open();
//通过open方法来打开一个未绑定的ServerSocketChannel实例
ServerSocketChannel server = ServerSocketChannel.open();
InetSocketAddress isa = new InetSocketAddress(
"127.0.0.1", 30000);
//将该ServerSocketChannel绑定到指定IP地址
server.socket().bind(isa);
//设置ServerSocket以非阻塞方式工作
server.configureBlocking(false);
//将server注册到指定Selector对象
server.register(selector, SelectionKey.OP_ACCEPT);
while (selector.select() > 0)
//依次处理selector上每个已选择的SelectionKey
for (SelectionKey sk : selector.selectedKeys())
//从selector上的已选择Key集中删除正在处理的SelectionKey
selector.selectedKeys().remove(sk);
//如果sk对应的通道包含客户端的连接请求
if (sk.isAcceptable())
//调用accept方法接收连接,产生服务器端对应的SocketChannel
SocketChannel sc = server.accept();
//设置采用非阻塞模式
sc.configureBlocking(false);
//将该SocketChannel也注册到selector
sc.register(selector, SelectionKey.OP_READ);
//将sk对应的Channel设置成准备接收其他请求
sk.interestOps(SelectionKey.OP_ACCEPT);
//如果sk对应的通道有数据需要读取
if (sk.isReadable())
//获取该SelectionKey对应的Channel,该Channel中有可读的数据
SocketChannel sc = (SocketChannel)sk.channel();
//定义准备执行读取数据的ByteBuffer
ByteBuffer buff = ByteBuffer.allocate(1024);
String content = "";
//开始读取数据
while(sc.read(buff) > 0)
buff.flip();
content += charset.decode(buff);
//打印从该sk对应的Channel里读取到的数据
System.out.println("=====" + content);
//将sk对应的Channel设置成准备下一次读取
sk.interestOps(SelectionKey.OP_READ);
//如果捕捉到该sk对应的Channel出现了异常,即表明该Channel
//对应的Client出现了问题,所以从Selector中取消sk的注册
catch (IOException ex)
//从Selector中删除指定的SelectionKey
sk.cancel();
if (sk.channel() != null)
sk.channel().close();
//如果content的长度大于0,则聊天信息不为空
if (content.length() > 0)
//遍历该selector里注册的所有SelectKey
for (SelectionKey key : selector.keys())
//获取该key对应的Channel
Channel targetChannel = key.channel();
//如果该channel是SocketChannel对象
if (targetChannel instanceof SocketChannel)
//将读到的内容写入该Channel
SocketChannel dest = (SocketChannel)targetChannel;
dest.write(charset.encode(content));
public static void main(String[] args)
throws IOException
new feizuServer().init();
通过上述代码,在启动时马上建立一个可监听连接请求的ServerSocketChannel,并将该Channel注册到指定的Selector,接着程序直接采用循环不断监控Selector对象的select()方法返回值,当该返回值大于0时处理该Selector上所有被选择的 SelectionKey。在处理指定SelectionKey之后立即从该Selector中的被选择的SelectionKey集合中删除该SelectionKey。服务器端的Selector仅需要监听连接和读数据这两种操作,在处理连接操作时只需将接受连接后产生的SocketChannel注册到指定Selector对象即可。当处理读数据操作后,系统先从该Socket中读取数据,再将数据写入Selector上注册的所有Channel。
接下来开始编写客户端的代码,本应用的客户端程序需要如下两个线程。
① 负责读取用户的键盘输入,并将输入的内容写入SocketChannel。
② 不断查询Selector对象的select()方法的返回值。
客户端的主要代码如下。
源码路径:daima\6\tcpudp\src\feizu\feizuClient.java。
public class feizuClient{
//定义检测SocketChannel的Selector对象
private Selector selector = null;
//定义处理编码和解码的字符集
private Charset charset = Charset.forName("UTF-8");
//客户端SocketChannel
private SocketChannel sc = null;
public void init()throws IOException
selector = Selector.open();
InetSocketAddress isa = new InetSocketAddress("127.0.0.1", 30000);
//调用open静态方法创建连接到指定主机的SocketChannel
sc = SocketChannel.open(isa);
//设置该sc以非阻塞方式工作
sc.configureBlocking(false);
//将SocketChannel对象注册到指定Selector
sc.register(selector, SelectionKey.OP_READ);
//启动读取服务器端数据的线程
new ClientThread().start();
//创建键盘输入流
Scanner scan = new Scanner(System.in);
while (scan.hasNextLine())
//读取键盘输入
String line = scan.nextLine();
//将键盘输入的内容输出到SocketChannel
sc.write(charset.encode(line));
//定义读取服务器数据的线程
private class ClientThread extends Thread
public void run()
while (selector.select() > 0)
//遍历每个有可用I/O操作Channel对应的SelectionKey
for (SelectionKey sk : selector.selectedKeys())
//删除正在处理的SelectionKey
selector.selectedKeys().remove(sk);
//如果该SelectionKey对应的Channel中有可读的数据
if (sk.isReadable())
//使用NIO读取Channel中的数据
SocketChannel sc = (SocketChannel)sk.channel();
ByteBuffer buff = ByteBuffer.allocate(1024);
String content = "";
while(sc.read(buff) > 0)
sc.read(buff);
buff.flip();
content += charset.decode(buff);
//打印输出读取的内容
System.out.println("聊天信息:" + content);
//为下一次读取做准备
sk.interestOps(SelectionKey.OP_READ);
catch (IOException ex)
ex.printStackTrace();
public static void main(String[] args)
throws IOException
new feizuClient().init();
上述客户端代码只有一条SocketChannel,当此SocketChannel注册到指定的Selector后,程序会启动另一条线程来监测该Selector。
在使用NIO来实现服务器时,甚至无须使用ArrayList来保存服务器中所有SocketChannel,因为所有的SocketChannel都需要注册到指定的Selector对象。除此之外,当客户端关闭时会导致服务器对应的Channel也抛出异常,而且本程序只有一条线程,如果该异常得不到处理将会导致整个服务器退出,所以程序捕捉了这种异常,并在处理异常时从Selector删除异常Channel的注册。
【Android】使用Android开发应用过程中遇到ViewGroup的简单效以及aw和assets文件夹下的文件(Http协议的底层工作)
【Android】使用Android开发应用过程中遇到ViewGroup的简单效以及aw和assets文件夹下的文件(Http协议的底层工作)
Android | TCP的C(Java|Android)/S(Java)通信实战经典聊天室案例(文末附本案例代码实现概述、观察者模式实现小结)
Android | TCP的C(Java|Android)/S(Java)通信实战经典聊天室案例(文末附本案例代码实现概述、观察者模式实现小结)
【Android 逆向】Android 逆向通用工具开发 ( Android 端远程命令工具 | Android 端可执行程序的 main 函数操作 | TCP 协议服务器建立 | 接收客户端数据 )
【Android 逆向】Android 逆向通用工具开发 ( Android 端远程命令工具 | Android 端可执行程序的 main 函数操作 | TCP 协议服务器建立 | 接收客户端数据 )
【Android Protobuf 序列化】Protobuf 服务器与客户端通信 ( TCP 通信中使用 Protobuf )
【Android Protobuf 序列化】Protobuf 服务器与客户端通信 ( TCP 通信中使用 Protobuf )
一文详解 Android进程及TCP动态心跳保活
面对国内GCM推送服务不可用,也未出现一个统一市场PUSH平台的现状。早期的第三方软件一般通过维持一个终端与远端服务器之间的TCP长连接,达到PUSH拉活和消息及时送达的目的。
而为了维持这个`TCP长连接`不断开,前提条件就是保证自己APP的后台服务进程,不会被杀死(因为只有活着的终端进程才能定期与远端服务器通信,保证长连接不断连)。
安卓开发学习笔记(五):史上最简单且华丽地实现Android Stutio当中Webview控件https/http协议的方法
一.我们先在XML当中自定义一个webview(Second_layout.xml)
代码如下:
可以看到,这里我们使用了现行布局以及WebView控件,tools:context=".SecondActivity"告诉我们这个控件是定义在第二个主活动当中的。