概述
前言:
上一篇文章中探讨了关于Socket与SocketChannel的使用,都是基于客户端的视角来分析的。本文中我们分析下服务端,也就是ServerSocket和ServerSocketChannel。
同样的需求,实现一个可简单对话的服务端即可。
1.基于ServerSocket的服务端
public class BIOServerSocket {
private String address;
private int port;
public BIOServerSocket(String address, int port) {
this.address = address;
this.port = port;
}
public void startServer() {
try {
ServerSocket serverSocket = new ServerSocket();
serverSocket.bind(new InetSocketAddress(address, port));
System.out.println("bio server start...");
while (true) {
Socket clientSocket = serverSocket.accept();
System.out.println("client connect...");
// 写入 thread
ServerWriteThread serverWriteThread = new ServerWriteThread(clientSocket);
serverWriteThread.start();
// 读取数据
read(clientSocket);
}
} catch (IOException e) {
e.printStackTrace();
}
}
private void read(Socket clientSocket) {
try {
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));
String msg = "";
while ((msg = bufferedReader.readLine()) != null) {
System.out.println("receive msg: " + msg);
}
} catch (IOException e) {
try {
clientSocket.close();
} catch (IOException ex) {
ex.printStackTrace();
}
e.printStackTrace();
}
}
public static void main(String[] args) {
String address = "localhost";
int port = 9999;
BIOServerSocket bioServerSocket = new BIOServerSocket(address, port);
bioServerSocket.startServer();
}
}
/**
* 从Scanner获取输入信息,并写回到client
*/
class ServerWriteThread extends Thread {
private Socket socket;
private PrintWriter writer;
private Scanner scanner;
public ServerWriteThread(Socket socket) throws IOException{
this.socket = socket;
scanner = new Scanner(System.in);
this.writer = new PrintWriter(new OutputStreamWriter(socket.getOutputStream()), true);
}
@Override
public void run() {
String msg = "";
try {
while ((msg = scanner.nextLine()) != null) {
if (msg.equals("bye")) {
socket.close();
break;
}
writer.println(msg);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
与上篇中的Socket类似,ServerSocket的使用也是比较简单的,笔者不再详述。
2.基于ServerSocketChannel的服务端
public class NIOServerSocket {
private String address;
private int port;
private Selector selector;
private ByteBuffer readBuffer = ByteBuffer.allocate(1024);
private ByteBuffer writeBuffer = ByteBuffer.allocate(1024);
private Scanner scanner = new Scanner(System.in);
public NIOServerSocket(String address, int port) throws IOException {
this.address = address;
this.port = port;
this.selector = Selector.open();
}
public void startServer() {
try {
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
serverSocketChannel.configureBlocking(false);
ServerSocket serverSocket = serverSocketChannel.socket();
serverSocket.bind(new InetSocketAddress(address, port));
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
System.out.println("nio server start...");
} catch (IOException e) {
System.out.println("nio server start error...");
e.printStackTrace();
}
// 监听连接
acceptClient();
}
private void acceptClient() {
while (true) {
try {
selector.select();
Set<SelectionKey> selectionKeys = selector.selectedKeys();
for (SelectionKey key : selectionKeys) {
if (key.isAcceptable()) {
ServerSocketChannel server = (ServerSocketChannel) key.channel();
SocketChannel clientChannel = server.accept();
clientChannel.configureBlocking(false);
clientChannel.register(selector, SelectionKey.OP_READ);
System.out.println("client connect...");
} else if (key.isReadable()) {
SocketChannel clientChannel = (SocketChannel) key.channel();
readBuffer.clear();
StringBuffer sb = new StringBuffer("receive msg: ");
while (clientChannel.read(readBuffer) > 0) {
readBuffer.flip();
sb.append(new String(readBuffer.array(), 0, readBuffer.limit()));
}
System.out.println(sb.toString());
clientChannel.register(selector, SelectionKey.OP_WRITE);
} else if (key.isWritable()) {
SocketChannel clientChannel = (SocketChannel) key.channel();
String msg = scanner.nextLine();
writeBuffer.clear();
writeBuffer.put(msg.getBytes());
writeBuffer.flip();
clientChannel.write(writeBuffer);
clientChannel.register(selector, SelectionKey.OP_READ);
}
}
selectionKeys.clear();
} catch (IOException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
String address = "localhost";
int port = 9999;
try {
new NIOServerSocket(address, port).startServer();
} catch (IOException e) {
e.printStackTrace();
}
}
}
同SocketChannel一样,我们也借助了Selector来实现连接事件的监听、接收客户端请求(read)事件的监听、发送响应(write)事件的监听。
当服务端接收到客户端请求后,先获取对应的SocketChannel,然后将SocketChannel注册Selector 读事件(默认客户端先发送请求)。
读取到客户端请求信息后,然后将SocketChannel注册Selector 写事件,将获取控制台(Scanner)的输出信息,并发送给客户端,之后注册读事件。(同样,也是与客户端一问一答)
3.ServerSocketChannel API
先来看下其类结构图
3.1 非阻塞模式
4.ServerSocket与ServerSocketChannel
// A server socket waits for requests to come in over the network.
public
class ServerSocket implements java.io.Closeable {
// Listens for a connection to be made to this socket and accepts
// it. The method blocks until a connection is made.
public Socket accept() throws IOException {}
public ServerSocketChannel getChannel() {
return null;
}
}
ServerSocket作为一个服务端点,其主要工作就是等待客户端的连接;
其accept方法用于监听连接的到来,当连接未建立成功时,accept方法会被阻塞。
当通过ServerSocket.getChannel方法来获取对应ServerSocketChannel时,直接返回一个null,说明对于手动创建的ServerSocket而言,没法获取其对应的channel,只能通过ServerSocketChannel.open方法来获取
// A selectable channel for stream-oriented listening sockets
public abstract class ServerSocketChannel
extends AbstractSelectableChannel
implements NetworkChannel
{
// Accepts a connection made to this channel's socket
/** If this channel is in non-blocking mode then this method will
* immediately return <tt>null</tt> if there are no pending connections.
* Otherwise it will block indefinitely until a new connection is available
* or an I/O error occurs. */
public abstract SocketChannel accept() throws IOException;
// Retrieves a server socket associated with this channel.
public abstract ServerSocket socket();
}
ServerSocketChannel作为一个可选择的通道(注册到Selector),用于监听socket连接;
通过accept方法来获取连接到的SocketChannel,看其注释,我们知道,若当前ServerSocketChannel是非阻塞的,且没有客户端连接上来,则直接返回null;若为阻塞类型的,则一直阻塞到有客户端连接上来为止。
ServerSocketChannel.socket方法则返回通道对应的ServerSocket
总结:虽然每个ServerSocketChannel都有一个对应的ServerSocket,但是不是每个ServerSocket都有一个对应的channel。
相对非阻塞的ServerSocketChannel,ServerSocket是阻塞的,通过accept方法就可以明显的区分开。
最后
以上就是执着刺猬为你收集整理的NIO源码解析-ServerSocketChannel的全部内容,希望文章能够帮你解决NIO源码解析-ServerSocketChannel所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复