这是一个从”单挑“,到”我要打十个“,再到”万人敌“的故事。
基于TCP/IP网络,对基础网络操作的封装。
三个核心类:InetAddress,Socket,ServerSocket
客户端(InetAddress + Socket)
1:socket通过IP和PORT,寻找服务器并建立连接
2:getInputStream() 获取服务器返回的流数据:
3:getOutputStream 向服务器写入要传递的数据流
4:最后关闭流合连接。
服务器端:ServerSocket
1:绑定端口进行监听
2:服务器进入无限循环,开始accpt()等待,直到获取Socket,此Socket绑定客户端的Ip和端口
3:getInputStream() 获取服务器返回的流数据:
4:getOutputStream 向服务器写入要传递的数据流
5:继续循环Accept,即:重复步骤2
--------------------------------------------------------------------------------
在客户端和服务器过程中
因为服务器端的主线程中accept是阻塞的,所以,普通的实现,只能是一对一的交互。
为了能够使服务器同时响应更多的客户端,服务器端引入多线程技术。
1:server.accept()得到的Socket
2:新创建线程t1,并将1中产生的Socket传递给t1,t1.start()开始执行
3:重复1~2,这样就同时会有多个线程在服务器端运行。
--------------------------------------------------------------------------------
一对多的问题解决之后,就是性能问题。
server.socket().bind(new InetSocketAddress(port)); //绑定端口
server.configureBlocking(false);//非阻塞模型
server.register(sel, SelectionKey.OP_ACCEPT); //注册ACCEPT操作
selector.select();
Iterator iter = selector.selectedKeys().iterator();
while (iter.hasNext()) {
SelectionKey key = (SelectionKey) iter.next();
iter.remove();
process(key);
}
}
} catch (IOException e) {
e.printStackTrace();
}
//设置非阻塞模式
channel.configureBlocking(false);
channel.register(selector, SelectionKey.OP_READ);
if (count > 0) {
clientBuffer.flip();
CharBuffer charBuffer = decoder.decode(clientBuffer);
name = charBuffer.toString();
System.out.println(name);
SelectionKey sKey = channel.register(selector,
SelectionKey.OP_WRITE);
sKey.attach(name);
} else {
channel.close();
}
clientBuffer.clear();
性能问题集中在两个地方
1:IO --由BIO升级为NIO
2:网络连接 -- 添加事件模型通过Selector获取和分发网络操作事件。
3:业务逻辑(根据实际业务逻辑,不是必然的)
阻塞IO:简单,但是Socket的accept和read()方法都是阻塞的,即使没有数据也会阻塞。并且每个客户端都需要一个线程,当服务器有大量客户端时,不考虑处理器和内存的时候,线程数就会成为瓶颈。
NIO:可以使一个或者几个线程,处理大量的客户端。
1:打开ServerSocketChannel,打开Selector,serverSocketChannel绑定端口,设置为非阻塞模式,注册OP_ACCEPT事件。
ServerSocketChannel server = ServerSocketChannel.open();
Selector sel = Selector.open();
server.socket().bind(new InetSocketAddress(port)); //绑定端口
server.configureBlocking(false);//非阻塞模型
server.register(sel, SelectionKey.OP_ACCEPT); //注册ACCEPT操作
2:Server开始监听,OP_ACCEPT事件(此监听同时监听了可读,可写等事件)。
try {
for (;;) {
selector.select();
Iterator iter = selector.selectedKeys().iterator();
while (iter.hasNext()) {
SelectionKey key = (SelectionKey) iter.next();
iter.remove();
process(key);
}
}
} catch (IOException e) {
e.printStackTrace();
}
3:监听到OP_ACCEPT事件,获取SocketChannel,注册OP_READ事件。
ServerSocketChannel server = (ServerSocketChannel) key.channel();
SocketChannel channel = server.accept();
//设置非阻塞模式
channel.configureBlocking(false);
channel.register(selector, SelectionKey.OP_READ);
4:监听到OP_READ,读取数据,同时注册OP_WRITE事件。
SocketChannel channel = (SocketChannel) key.channel();
int count = channel.read(clientBuffer);
if (count > 0) {
clientBuffer.flip();
CharBuffer charBuffer = decoder.decode(clientBuffer);
name = charBuffer.toString();
System.out.println(name);
SelectionKey sKey = channel.register(selector,
SelectionKey.OP_WRITE);
sKey.attach(name);
} else {
channel.close();
}
clientBuffer.clear();
5:监听到OP_WRITE,写入数据。 SocketChannel channel = (SocketChannel) key.channel();
String name = (String) key.attachment();
ByteBuffer block = encoder.encode(CharBuffer
.wrap("Hello !" + name));
channel.write(block);
ByteBuffer block = encoder.encode(CharBuffer
.wrap("Hello !" + name));
channel.write(block);
channel.close();//通道关闭
--------------------------------------------------------------------------------------------------
总之,我们从“单挑”升级为“我要打十个”,一直走到了“万人敌”。IO,线程,内存,CPU。
注:源码摘自某文章(纯为了说明一些问题),细节处可能需要斟酌