基于TCP协议的网络编程
TCP/IP通信协议是一种可靠的网络协议,它在通信的两端各建立一个Socket,从而在通信的两端之间形成网络虚拟链路。一旦建立了虚拟的网络链路,两端的成员就可以通过虚拟链路进行通信。Java是对基于TCP协议的网络通信提供了良好的封装,Java使用Socket对象来代表两端的通信端口,并通过Socket产生IO流来进行网络通信。
使用ServerSocket创建TCP服务器端
Java中能接收其他通信实体连接请求的类是ServerSocket,ServerSocket对象用于监听来自客户端Socket连接,如果没有连接,它将一直处于等待状态。
接收客户端Socket的请求时,服务器端也对应产生一个Socket:Socket socket = serverSocket.accept();
使用Socket进行通信
创建连接到主机、1205端口的Socket:Socket socket = new Socket("127.0.0.1", 1205);
Socket提供了两个方法获取输入流和输出流
InputStream getInputStream()
:返回该Socket对象对应的输入流,让程序通过该输入流从Socket中取出数据。OutputStream getOutputStream()
:返回该Socket对象对应的输出流,让程序通过该输出流向Socket中输出数据。
传统BIO实现的Socket通信
服务器端
/**
* @author 小铭
* Description: BIO实现Socket通信服务器端
*/
public class Server {
public static void main(String[] args) throws IOException {
// 用于监听1205端口的Socket连接请求
ServerSocket serverSocket = new ServerSocket(1205);
// 进行端口监听
Socket socket = serverSocket.accept();
// 从Socket中读取信息
InputStreamReader inputStreamReader = new InputStreamReader(socket.getInputStream());
BufferedReader bufferedReader = new BufferedReader(inputStreamReader);
String line;
PrintStream printStream = new PrintStream(socket.getOutputStream(), true);
while (true) {
line = bufferedReader.readLine();
if (line == null) {
break;
}
System.out.println("接收到客户端的信息:" + line);
// 向Socket中写入信息
printStream.println("已接收到客户端的信息");
}
// 关闭资源
printStream.flush();
printStream.close();
bufferedReader.close();
inputStreamReader.close();
socket.close();
serverSocket.close();
}
}
客户端
/**
* @author 小铭
* Description: BIO实现Socket通信客户端
*/
public class Client {
public static void main(String[] args) throws IOException {
// 服务器ip和地址
Socket socket = new Socket("127.0.0.1", 1205);
// 向Socket写入信息,即发信息发至服务器
PrintStream printStream = new PrintStream(socket.getOutputStream());
printStream.println("我是客户端");
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
System.out.println("收到服务器的数据:" + bufferedReader.readLine());
printStream.close();
bufferedReader.close();
socket.close();
}
}
传统BIO实现的Socke通信最大的问题就是一个客户端接入,服务器就为它分配一条线程,降低了系统的性能,所以有了伪异步IO
伪异步IO实现Socket通信
维护了一个线程池,线程池的资源占用是可控的,可以支持客户端并发访问。
服务器端启动类
public class TimeServer {
public static void main(String[] args) throws IOException {
ServerSocket serverSocket = null;
try {
serverSocket = new ServerSocket(1205);
Socket socket;
// 创建一个线程池
TimeServerHandlerExecutePool pool = new TimeServerHandlerExecutePool(50, 10000);
while (true) {
socket = serverSocket.accept();
pool.execute(new TimeServerHandler(socket));
}
} finally {
if (serverSocket != null) {
System.out.println("The time server close");
serverSocket.close();
}
}
}
}
线程池
public class TimeServerHandlerExecutePool {
private ExecutorService executorService;
TimeServerHandlerExecutePool(int maxPoolSize, int queueSize) {
executorService = new ThreadPoolExecutor(Runtime.getRuntime().availableProcessors(), maxPoolSize, 120L, TimeUnit.SECONDS,
new ArrayBlockingQueue<>(queueSize));
}
public void execute(Runnable task) {
executorService.execute(task);
}
}
服务器端业务逻辑
public class TimeServerHandler implements Runnable {
private Socket socket;
TimeServerHandler(Socket socket) {
this.socket = socket;
}
@Override
public void run() {
BufferedReader bufferedReader = null;
PrintWriter printWriter = null;
try {
bufferedReader = new BufferedReader(new InputStreamReader(this.socket.getInputStream()));
printWriter = new PrintWriter(this.socket.getOutputStream());
String currentTime;
String body;
while (true) {
body = bufferedReader.readLine();
if (body == null) {
break;
}
System.out.println("The time server receive order: " + body);
currentTime = "QUERY TIME ORDER".equalsIgnoreCase(body) ? new Date(System.currentTimeMillis()).toString() : "BAO ORDER";
printWriter.println(currentTime);
}
} catch (IOException e) {
if (bufferedReader != null) {
try {
bufferedReader.close();
} catch (IOException ioException) {
ioException.printStackTrace();
}
}
if (printWriter != null) {
printWriter.close();
}
if (this.socket != null) {
try {
this.socket.close();
} catch (IOException e1) {
e1.printStackTrace();
}
}
}
}
}
客户端
public class TimeClient {
public static void main(String[] args) {
int port = 1205;
if (args != null && args.length > 0) {
try {
port = Integer.valueOf(args[0]);
} catch (NumberFormatException e) {
e.printStackTrace();
}
}
Socket socket = null;
BufferedReader bufferedReader = null;
PrintWriter printWriter = null;
try {
socket = new Socket("127.0.0.1", port);
printWriter = new PrintWriter(socket.getOutputStream(), true);
bufferedReader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
printWriter.println("QUERY TIME ORDER");
System.out.println("Send succeed");
String line;
while ((line = bufferedReader.readLine()) != null) {
System.out.println("Now is : " + line);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (printWriter != null) {
printWriter.close();
}
if (bufferedReader != null) {
try {
bufferedReader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (socket != null) {
try {
socket.close();
} catch (IOException e1) {
e1.printStackTrace();
}
}
}
}
}
NIO实现Socket通信
NIO的知识可以直接看之前的笔记。
服务器端启动类
/**
* @author 小铭
* Date: 2018/3/19
* No struggle, talent how to match the willfulness.
* Description: <p>NIO实现Socket服务器端</p>
*/
public class TimeServer {
public static void main(String[] args) {
MultiplexerTimeServer timeServer = new MultiplexerTimeServer(1205);
new Thread(timeServer, "NIO-MultiplexerTimeServer-001").start();
}
}
服务器端业务逻辑
/**
* @author 小铭
* Date: 2018/3/19
* No struggle, talent how to match the willfulness.
* Description: <p>NIO实现Socket通信服务器端业务逻辑</p>
*/
public class MultiplexerTimeServer implements Runnable {
private Selector selector;
private volatile boolean stop;
MultiplexerTimeServer(int port) {
try {
// 创建Selector
selector = Selector.open();
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
// 设为非阻塞
serverSocketChannel.configureBlocking(false);
serverSocketChannel.socket().bind(new InetSocketAddress(port), 1024);
// 设置监听
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
} catch (IOException e) {
e.printStackTrace();
System.exit(1);
}
}
@Override
public void run() {
while (!stop) {
try {
selector.select(1000);
Set<SelectionKey> selectionKeys = selector.selectedKeys();
Iterator<SelectionKey> iterator = selectionKeys.iterator();
SelectionKey key;
while (iterator.hasNext()) {
key = iterator.next();
iterator.remove();
try {
handleInput(key);
} catch (Exception e) {
if (key != null) {
key.cancel();
if (key.channel() != null) {
key.channel().close();
}
}
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
if (selector != null) {
try {
selector.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
private void handleInput(SelectionKey key) throws IOException {
if (key.isValid()) {
if (key.isAcceptable()) {
ServerSocketChannel serverSocketChannel = (ServerSocketChannel) key.channel();
SocketChannel socketChannel = serverSocketChannel.accept();
socketChannel.configureBlocking(false);
socketChannel.register(selector, SelectionKey.OP_READ);
}
if (key.isReadable()) {
// 读取数据
SocketChannel socketChannel = (SocketChannel) key.channel();
ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
int readBytes = socketChannel.read(byteBuffer);
if (readBytes > 0) {
byteBuffer.flip();
byte[] bytes = new byte[byteBuffer.remaining()];
byteBuffer.get(bytes);
String body = new String(bytes, "UTF-8");
System.out.println("The time server receive order: " + body);
String currentTime = "QUERY TIME ORDER".equalsIgnoreCase(body) ? new Date(System.currentTimeMillis()).toString() : "BAD ORDER";
doWrite(socketChannel, currentTime);
} else if (readBytes < 0) {
key.cancel();
socketChannel.close();
}
}
}
}
private void doWrite(SocketChannel socketChannel, String currentTime) throws IOException {
if (currentTime != null && currentTime.trim().length() > 0) {
byte[] bytes = currentTime.getBytes();
ByteBuffer buffer = ByteBuffer.allocate(bytes.length);
buffer.put(bytes);
buffer.flip();
socketChannel.write(buffer);
}
}
}
客户端启动类
/**
* @author 小铭
* Date: 2018/3/19
* No struggle, talent how to match the willfulness.
* Description: <p>NIO实现Socket通信客户端</p>
*/
public class TimeClient {
public static void main(String[] args) {
new Thread(new TimeClientHandle("127.0.0.1", 1205), "TimeClient-001").start();
}
}
客户端业务逻辑
/**
* @author 小铭
* Date: 2018/3/19
* No struggle, talent how to match the willfulness.
* Description: <p>NIO实现Socket客户端业务逻辑</p>
*/
public class TimeClientHandle implements Runnable {
private String host;
private int port;
private Selector selector;
private SocketChannel socketChannel;
private volatile boolean stop;
TimeClientHandle(String host, int port) {
this.host = host;
this.port = port;
try {
selector = Selector.open();
socketChannel = SocketChannel.open();
socketChannel.configureBlocking(false);
} catch (IOException e) {
e.printStackTrace();
System.exit(1);
}
}
@Override
public void run() {
try {
doConnect();
} catch (IOException e) {
e.printStackTrace();
System.exit(1);
}
while (!stop) {
try {
selector.select(1000);
Set<SelectionKey> selectionKeys = selector.selectedKeys();
Iterator<SelectionKey> iterator = selectionKeys.iterator();
SelectionKey key;
while (iterator.hasNext()) {
key = iterator.next();
iterator.remove();
try {
handleInput(key);
} catch (IOException e) {
e.printStackTrace();
key.cancel();
if (key.channel() != null) {
key.channel().close();
}
}
}
} catch (IOException e) {
e.printStackTrace();
System.exit(1);
}
}
}
private void handleInput(SelectionKey key) throws IOException {
if (key.isValid()) {
SocketChannel socketChannel = (SocketChannel) key.channel();
if (key.isConnectable()) {
if (socketChannel.finishConnect()) {
socketChannel.register(selector, SelectionKey.OP_READ);
doWrite(socketChannel);
} else {
System.exit(1);
}
}
if (key.isReadable()) {
ByteBuffer buffer = ByteBuffer.allocate(1024);
int readBytes = socketChannel.read(buffer);
if (readBytes > 0) {
buffer.flip();
byte[] bytes = new byte[buffer.remaining()];
buffer.get(bytes);
String body = new String(bytes, "UTF-8");
System.out.println("Now is : " + body);
this.stop = true;
} else if (readBytes < 0) {
key.cancel();
socketChannel.close();
}
}
}
}
private void doConnect() throws IOException {
if (socketChannel.connect(new InetSocketAddress(host, port))) {
socketChannel.register(selector, SelectionKey.OP_READ);
doWrite(socketChannel);
} else {
socketChannel.register(selector, SelectionKey.OP_CONNECT);
}
}
private void doWrite(SocketChannel socketChannel) throws IOException{
byte[] req = "QUERY TIME ORDER".getBytes();
ByteBuffer buffer = ByteBuffer.allocate(req.length);
buffer.put(req);
buffer.flip();
socketChannel.write(buffer);
if (!buffer.hasRemaining()) {
System.out.println("Send order 2 server succeed.");
}
}
}
AIO实现Socket通信
服务器端启动类
/**
* @author 小铭
* Date: 2018/3/20
* No struggle, talent how to match the willfulness.
* Description: <p>AIO实现Socket通信服务器端启动类</p>
*/
public class TimeServer {
public static void main(String[] args) {
AsyncTimeServerHandler timeServerHandler = new AsyncTimeServerHandler(1205);
new Thread(timeServerHandler, "AIO-AsyncTimeServerHandler-001").start();
}
}
服务器端业务逻辑
/**
* @author 小铭
* Date: 2018/3/20
* No struggle, talent how to match the willfulness.
* Description: <p>AIO实现Socke通信服务器端业务逻辑</p>
*/
public class AsyncTimeServerHandler implements Runnable {
CountDownLatch latch;
AsynchronousServerSocketChannel serverSocketChannel;
AsyncTimeServerHandler(int port) {
try {
serverSocketChannel = AsynchronousServerSocketChannel.open();
serverSocketChannel.bind(new InetSocketAddress(port));
System.out.println("The time server is start in port: " + port);
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
public void run() {
latch = new CountDownLatch(1);
doAccept();
try {
latch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
private void doAccept() {
serverSocketChannel.accept(this, new AcceptCompletionHandler());
}
}
/**
* @author 小铭
* Date: 2018/3/20
* No struggle, talent how to match the willfulness.
* Description: <p>AIO实现Socke通信服务器端业务逻辑</p>
*/
public class AcceptCompletionHandler implements CompletionHandler<AsynchronousSocketChannel, AsyncTimeServerHandler> {
@Override
public void completed(AsynchronousSocketChannel result, AsyncTimeServerHandler attachment) {
attachment.serverSocketChannel.accept(attachment, this);
ByteBuffer buffer = ByteBuffer.allocate(1024);
result.read(buffer, buffer, new ReadCompletionHandler(result));
}
@Override
public void failed(Throwable exc, AsyncTimeServerHandler attachment) {
exc.printStackTrace();
attachment.latch.countDown();
}
}
/**
* @author 小铭
* Date: 2018/3/19
* No struggle, talent how to match the willfulness.
* Description: <p>AIO实现Socke通信服务器端业务逻辑</p>
*/
public class ReadCompletionHandler implements CompletionHandler<Integer, ByteBuffer> {
private AsynchronousSocketChannel channel;
ReadCompletionHandler(AsynchronousSocketChannel channel) {
if (this.channel == null) {
this.channel = channel;
}
}
@Override
public void completed(Integer result, ByteBuffer attachment) {
attachment.flip();
byte[] body = new byte[attachment.remaining()];
attachment.get(body);
try {
String req = new String(body, "UTF-8");
System.out.println("The time server receive order : " + req);
String currentTime = "QUERY TIME ORDER".equalsIgnoreCase(req) ?
new Date(System.currentTimeMillis()).toString() : "BAD ORDER";
doWrite(currentTime);
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
}
private void doWrite(String currentTime) {
if (currentTime != null && currentTime.trim().length() > 0) {
byte[] bytes = currentTime.getBytes();
ByteBuffer buffer = ByteBuffer.allocate(bytes.length);
buffer.put(bytes);
buffer.flip();
channel.write(buffer, buffer, new CompletionHandler<Integer, ByteBuffer>() {
@Override
public void completed(Integer result, ByteBuffer attachment) {
if (buffer.hasRemaining()) {
channel.write(buffer, buffer, this);
}
}
@Override
public void failed(Throwable exc, ByteBuffer attachment) {
try {
channel.close();
} catch (IOException e) {
e.printStackTrace();
}
}
});
}
}
@Override
public void failed(Throwable exc, ByteBuffer attachment) {
try {
this.channel.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
客户端启动类
/**
* @author 小铭
* Date: 2018/3/20
* No struggle, talent how to match the willfulness.
* Description: <p>AIO实现Socket通信客户端启动类</p>
*/
public class TimeClient {
public static void main(String[] args) {
new Thread(new AsyncTimeClientHandler("127.0.0.1", 1205), "AIO-AsyncTimeClientHanler-001").start();
}
}
客户端业务逻辑
/**
* @author 小铭
* Date: 2018/3/20
* No struggle, talent how to match the willfulness.
* Description: <p>AIO实现Socke通信客户端业务逻辑</p>
*/
public class AsyncTimeClientHandler implements CompletionHandler<Void, AsyncTimeClientHandler>, Runnable {
private AsynchronousSocketChannel socketChannel;
private String host;
private int port;
private CountDownLatch latch;
AsyncTimeClientHandler(String host, int port) {
this.host = host;
this.port = port;
try {
socketChannel = AsynchronousSocketChannel.open();
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
public void run() {
latch = new CountDownLatch(1);
socketChannel.connect(new InetSocketAddress(host, port), this, this);
try {
latch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
@Override
public void completed(Void result, AsyncTimeClientHandler attachment) {
byte[] req = "QUERY TIME ORDER".getBytes();
ByteBuffer buffer = ByteBuffer.allocate(req.length);
buffer.put(req);
buffer.flip();
socketChannel.write(buffer, buffer, new CompletionHandler<Integer, ByteBuffer>() {
@Override
public void completed(Integer result, ByteBuffer attachment) {
if (buffer.hasRemaining()) {
socketChannel.write(buffer, buffer, this);
} else {
ByteBuffer readBuffer = ByteBuffer.allocate(1204);
socketChannel.read(readBuffer, readBuffer, new CompletionHandler<Integer, ByteBuffer>() {
@Override
public void completed(Integer result, ByteBuffer attachment) {
attachment.flip();
byte[] bytes = new byte[attachment.remaining()];
attachment.get(bytes);
String body;
try {
body = new String(bytes, "UTF-8");
System.out.println("Now is : " + body);
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
} finally {
latch.countDown();
}
}
@Override
public void failed(Throwable exc, ByteBuffer attachment) {
try {
socketChannel.close();
} catch (IOException e) {
e.printStackTrace();
} finally {
latch.countDown();
}
}
});
}
}
@Override
public void failed(Throwable exc, ByteBuffer attachment) {
try {
socketChannel.close();
} catch (IOException e) {
e.printStackTrace();
} finally {
latch.countDown();
}
}
});
}
@Override
public void failed(Throwable exc, AsyncTimeClientHandler attachment) {
exc.printStackTrace();
try {
socketChannel.close();
} catch (IOException e) {
e.printStackTrace();
} finally {
latch.countDown();
}
}
}
Netty实现Socket通信
Netty是一个强大的NIO框架
服务器端配置启动类
/**
* @author 小铭
* Date: 2018/3/20
* No struggle, talent how to match the willfulness.
* Description: <p>使用Netty进行Socket通信服务器端</p>
*/
public class TimeServer {
private void bind(int port) throws Exception {
// 配置服务器端的NIO线程组
// 用于接受客户端的连接
EventLoopGroup bossGroup = new NioEventLoopGroup();
// 用户进行SocketChannel的网络读写
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
ServerBootstrap bootstrap = new ServerBootstrap();
bootstrap.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
.option(ChannelOption.SO_BACKLOG, 1024)
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel socketChannel) {
socketChannel.pipeline().addLast(new TimeServerHandler());
}
});
// 绑定端口,同步等待成功
ChannelFuture future = bootstrap.bind(port).sync();
// 等待服务器端监听端口关闭
future.channel().closeFuture().sync();
} finally {
// 释放线程组资源
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
public static void main(String[] args) throws Exception {
new TimeServer().bind(1205);
}
}
服务器端业务逻辑
/**
* @author 小铭
* Date: 2018/3/20
* No struggle, talent how to match the willfulness.
* Description: <p>Netty实现Socket通信服务器端业务逻辑</p>
* <p>以下的ChannelInboundHandlerAdapter是netty4下的类,如果使用netty5(已被舍弃迭代)需要继承ChannelHandlerAdapter,代码内容不变。</p>
*/
public class TimeServerHandler extends ChannelInboundHandlerAdapter {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
ByteBuf buf = (ByteBuf) msg;
byte[] req = new byte[buf.readableBytes()];
buf.readBytes(req);
String body = new String(req, "UTF-8");
System.out.println("the time server receive order : " + body);
String currentTime = "QUERY TIME ORDER".equalsIgnoreCase(body) ?
new Date(System.currentTimeMillis()).toString() : "BAD ORDER";
ByteBuf resp = Unpooled.copiedBuffer(currentTime.getBytes());
ctx.write(resp);
}
@Override
public void channelReadComplete(ChannelHandlerContext ctx) {
ctx.flush();
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
cause.printStackTrace();
ctx.close();
}
}
客户端配置启动类
/**
* @author 小铭
* Date: 2018/3/20
* No struggle, talent how to match the willfulness.
* Description: <p>Netty实现Socket通信客户端</p>
*/
public class TimeClient {
private void connect(int port, String host) throws Exception {
EventLoopGroup group = new NioEventLoopGroup();
try {
Bootstrap bootstrap = new Bootstrap();
bootstrap.group(group).channel(NioSocketChannel.class)
.option(ChannelOption.TCP_NODELAY, true)
.handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel socketChannel) {
socketChannel.pipeline().addLast(new TimeClientHandler());
}
});
ChannelFuture future = bootstrap.connect(host, port).sync();
future.channel().closeFuture().sync();
} finally {
group.shutdownGracefully();
}
}
public static void main(String[] args) throws Exception {
new TimeClient().connect(1205, "127.0.0.1");
}
}
客户端业务逻辑
/**
* @author 小铭
* Date: 2018/3/20
* No struggle, talent how to match the willfulness.
* Description: <p>Netty实现Socket通信客户端业务逻辑</p>
*/
public class TimeClientHandler extends ChannelInboundHandlerAdapter {
private static final Logger LOGGER = Logger.getLogger(TimeClientHandler.class.getName());
private final ByteBuf buf;
TimeClientHandler() {
byte[] req = "QUERY TIME ORDER".getBytes();
buf = Unpooled.buffer(req.length);
buf.writeBytes(req);
}
@Override
public void channelActive(ChannelHandlerContext ctx) {
ctx.writeAndFlush(buf);
}
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) {
ByteBuf buf = (ByteBuf) msg;
byte[] req = new byte[buf.readableBytes()];
buf.readBytes(req);
try {
String body = new String(req, "UTF-8");
System.out.println("Now is : " + body);
}catch (Exception e){
e.printStackTrace();
}
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
LOGGER.warning("Unexpected exception from downstream : " + cause.getMessage());
ctx.close();
}
}
参考书籍:Netty权威指南。