早起终于基本完成了工作材料准备。
《全职高手》我是最喜欢的看的网络小说,看过很多遍。其实没什么内涵,可能因为是爽文喜欢主线的情节,或者喜欢里面主角的人物性格。总之,看的很舒服。
昨天看了电视剧,对电视剧本身无感,但足以勾起我的回忆。
大不了,从头再来。
2019年7月28日 晨
早起终于基本完成了工作材料准备。
《全职高手》我是最喜欢的看的网络小说,看过很多遍。其实没什么内涵,可能因为是爽文喜欢主线的情节,或者喜欢里面主角的人物性格。总之,看的很舒服。
昨天看了电视剧,对电视剧本身无感,但足以勾起我的回忆。
大不了,从头再来。
2019年7月28日 晨
要做的事情很多:
但,我却先写了日记,呵呵。
2019年7月27日 午
不知道为什么,基于gitment的评论框完全失效。看不惯这么荒废下去,找到一个替代品:基于LeanCloud的valine。理由很简单,LeanCloud我正在用,博客中的阅读量统计就是基于LeanCloud的做的。刚回家就迫不急的折腾一番,目前看应该替换成功了,未做详细测试,暂且如此吧。
刚刚,第一次喝了夺命乌苏+大凯龙,还真是有点晕,但估计是心里作用。
今天是个特殊的日子,因为昨天是我妈的生日。
2019年7月26日 晚
看了会书,本想在这个差点荒废的博客上写点总结,却发现以前写的一段创建新博文的python脚本失效。
不知道脑子里在想什么,我开始读起自己2016年有段时间在博客里写的日记。还挺有趣,除了错别字有点多。
想想早期做博客还很在意流量和SEO,整日折腾。现在在意的,恐怕是懒了吧。
既然回头看看这么有趣,就再给自己储备点食粮吧。
最近,在看数据中台和SQL。
2019年7月25日 晚
看到一段好看的代码注释。想写在自己的程序里:
/***
* ┌───┐ ┌───┬───┬───┬───┐ ┌───┬───┬───┬───┐ ┌───┬───┬───┬───┐ ┌───┬───┬───┐
* │Esc│ │ F1│ F2│ F3│ F4│ │ F5│ F6│ F7│ F8│ │ F9│F10│F11│F12│ │P/S│S L│P/B│ ┌┐ ┌┐ ┌┐
* └───┘ └───┴───┴───┴───┘ └───┴───┴───┴───┘ └───┴───┴───┴───┘ └───┴───┴───┘ └┘ └┘ └┘
* ┌───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───────┐ ┌───┬───┬───┐ ┌───┬───┬───┬───┐
* │~ `│! 1│@ 2│# 3│$ 4│% 5│^ 6│& 7│* 8│( 9│) 0│_ -│+ =│ BacSp │ │Ins│Hom│PUp│ │N L│ / │ * │ - │
* ├───┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─────┤ ├───┼───┼───┤ ├───┼───┼───┼───┤
* │ Tab │ Q │ W │ E │ R │ T │ Y │ U │ I │ O │ P │{ [│} ]│ | \ │ │Del│End│PDn│ │ 7 │ 8 │ 9 │ │
* ├─────┴┬──┴┬──┴┬──┴┬──┴┬──┴┬──┴┬──┴┬──┴┬──┴┬──┴┬──┴┬──┴─────┤ └───┴───┴───┘ ├───┼───┼───┤ + │
* │ Caps │ A │ S │ D │ F │ G │ H │ J │ K │ L │: ;│" '│ Enter │ │ 4 │ 5 │ 6 │ │
* ├──────┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴────────┤ ┌───┐ ├───┼───┼───┼───┤
* │ Shift │ Z │ X │ C │ V │ B │ N │ M │< ,│> .│? /│ Shift │ │ ↑ │ │ 1 │ 2 │ 3 │ │
* ├─────┬──┴─┬─┴──┬┴───┴───┴───┴───┴───┴──┬┴───┼───┴┬────┬────┤ ┌───┼───┼───┐ ├───┴───┼───┤ E││
* │ Ctrl│ │Alt │ Space │ Alt│ │ │Ctrl│ │ ← │ ↓ │ → │ │ 0 │ . │←─┘│
* └─────┴────┴────┴───────────────────────┴────┴────┴────┴────┘ └───┴───┴───┘ └───────┴───┴───┘
*/
更新节奏缓慢,因为每晚学习注意力不够集中,学习进展缓慢。本还给自己找了一大堆其他理由,但摸着良心问自己,似乎只有这个理由说的通。
想搞懂的太多,却始终没搞明白。先看一个用Netty编写的NIO Server的样例。
package com.coderli.nettylab.guide;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
/**
* @author lihongzhe 2018/7/24 23:19
*/
public class NettyNioServer {
public static void main(String[] args) throws InterruptedException {
EventLoopGroup bossGroup = new NioEventLoopGroup(); // (1)
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
ServerBootstrap b = new ServerBootstrap(); // (2)
b.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class) // (3)
.childHandler(new ChannelInitializer<SocketChannel>() { // (4)
@Override
public void initChannel(SocketChannel ch) throws Exception {
ch.pipeline().addLast(new ServerHandler());
}
})
.option(ChannelOption.SO_BACKLOG, 128)
.childOption(ChannelOption.SO_KEEPALIVE, true);
ChannelFuture f = b.bind(7060).sync(); // (5)
f.channel().closeFuture().sync();
} finally {
workerGroup.shutdownGracefully();
bossGroup.shutdownGracefully();
}
}
}
ServerHanler代码
package com.coderli.nettylab.guide;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
/**
* @author lihongzhe 2018/7/24 23:58
*/
public class ServerHandler extends ChannelInboundHandlerAdapter {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) {
System.out.println("Receive Msg.");
((ByteBuf) msg).release();
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
cause.printStackTrace();
ctx.close();
}
}
上述代码改自Netty官方手册。NettyNioServer代码中做了几处标记,分别对应我们在Netty4 自学笔记(2)中讨论的关键点,简析如下:
从样例代码的直观感觉来说,Netty提供了良好的封装,无论是Server还是事件处理的Handler,Netty几乎帮我们做好了一切。对于开发人员来说,只需要关注于Netty提供的Handler的回调时机,开发自己的业务逻辑,比直接使用Java NIO的API节约了很多的开工作量,而且保证了代码的健壮性和多线程支持。
因此,我下一步的思路就是去研究一下Netty中Handler的回调机制,真正掌握才可开发逻辑正确的代码。
本文的最后,把Client端的代码补充完整,以便调试,
NettyNioClient
package com.coderli.nettylab.guide;
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
/**
* @author lihongzhe 2018/8/6 22:55
*/
public class NettyNioClient {
public static void main(String[] args) throws InterruptedException {
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
Bootstrap b = new Bootstrap();
b.group(workerGroup);
b.channel(NioSocketChannel.class);
b.option(ChannelOption.SO_KEEPALIVE, true);
b.handler(new ChannelInitializer<SocketChannel>() {
@Override
public void initChannel(SocketChannel ch) throws Exception {
ch.pipeline().addLast(new ClientHandler());
}
});
ChannelFuture f = b.connect("127.0.0.1", 7060).sync();
// Wait until the connection is closed.
f.channel().closeFuture().sync();
} finally {
workerGroup.shutdownGracefully();
}
}
}
ClientHandler
package com.coderli.nettylab.guide;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import java.util.Date;
/**
* @author lihongzhe 2018/8/6 23:13
*/
public class ClientHandler extends ChannelInboundHandlerAdapter {
private ByteBuf buf;
@Override
public void channelRegistered(ChannelHandlerContext ctx) throws Exception {
System.out.println("Channel Registered, Client.");
ctx.fireChannelRegistered();
}
@Override
public void handlerAdded(ChannelHandlerContext ctx) {
System.out.println("Handler added.");
buf = ctx.alloc().buffer(4);
}
@Override
public void handlerRemoved(ChannelHandlerContext ctx) {
buf.release();
buf = null;
}
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) {
ByteBuf m = (ByteBuf) msg;
buf.writeBytes(m);
m.release();
if (buf.readableBytes() >= 4) {
long currentTimeMillis = (buf.readUnsignedInt() - 2208988800L) * 1000L;
System.out.println(new Date(currentTimeMillis));
ctx.close();
}
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
cause.printStackTrace();
ctx.close();
}
}
后续的研究,我也会基于上述代码加以改造和调试。
距离上一篇博文已经过去了半个多月。这期间有一周多的时间用在了准备单位举办的英语竞赛上。余下的时间沉迷于陪孩子玩耍和睡觉,日复一日。
当然,我也抽空学习了Java NIO(None-Blocking / New IO) 一些知识,现总结如下。
Java的非阻塞IO的原理是采用了操作系统的多路复用器机制,即在一个通道(channel)上,注册一个事件选择器(selector)及各种事件(读、写等),当有事件到达时,事件选择器返归对应的事件,然后可对事件进行处理,这样即可实现在单一线程上对来自不同客户的请求进行交替处理,服务端处理返回后即可处理下一事件,而不会受制于客户端的响应速度,提高了并发访问的效率。
Java NIO的样例代码如下:
服务端
package com.coderli.nettylab.nio;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Iterator;
/**
* @author lihongzhe 2018/7/11 10:59
*/
public class NioServer {
public static void main(String[] args) {
try {
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
serverSocketChannel.bind(new InetSocketAddress("127.0.0.1", 7090));
serverSocketChannel.configureBlocking(false);
Selector selector = Selector.open();
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
for (; ; ) {
selector.select();
Iterator<SelectionKey> keysItor = selector.selectedKeys().iterator();
while (keysItor.hasNext()) {
SelectionKey selectionKey = keysItor.next();
keysItor.remove();
if (selectionKey.isAcceptable()) {
ServerSocketChannel ssChannel = (ServerSocketChannel) selectionKey.channel();
SocketChannel socketChannel = ssChannel.accept();
socketChannel.configureBlocking(false);
ByteBuffer buffer = ByteBuffer.allocate(17);
socketChannel.read(buffer);
System.out.println("Receive msg from client:" + new String(buffer.array()));
socketChannel.write(ByteBuffer.wrap(new String("Server: op_accept").getBytes()));
}
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
客户端
package com.coderli.nettylab.nio;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
import java.util.Iterator;
/**
* @author lihongzhe 2018/7/12 15:54
*/
public class NioClient {
public static void main(String[] args) {
try {
SocketChannel socketChannel = SocketChannel.open();
socketChannel.configureBlocking(false);
socketChannel.connect(new InetSocketAddress("127.0.0.1", 7090));
Selector selector = Selector.open();
socketChannel.register(selector, SelectionKey.OP_CONNECT);
selector.select();
Iterator<SelectionKey> itor = selector.selectedKeys().iterator();
while (itor.hasNext()) {
SelectionKey key = itor.next();
if (key.isConnectable()) {
System.out.println("Connectable...");
while (socketChannel.isConnectionPending()) {
socketChannel.finishConnect();
socketChannel.register(selector, SelectionKey.OP_READ);
}
SocketChannel channel = (SocketChannel) key.channel();
channel.write(ByteBuffer.wrap("I am client".getBytes()));
}
}
for (; ; ) {
selector.select();
Iterator<SelectionKey> keyIterator = selector.selectedKeys().iterator();
while (keyIterator.hasNext()) {
SelectionKey key = keyIterator.next();
if (key.isConnectable()) {
System.out.println("Connectable...");
}
if (key.isReadable()) {
SocketChannel channel = (SocketChannel) key.channel();
ByteBuffer buffer = ByteBuffer.allocate(17);
channel.read(buffer);
System.out.println("Receive msg from server:" + new String(buffer.array()));
keyIterator.remove();
}
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
上述代码谈不上合理与严谨,仅是我实验中的代码,但可表述出Java NIO中的channel、select、selectionKey(事件)等基本要素,仅供参考。
对代码做一简单说明:
对比BIO(OIO)来看,如要实现并发,BIO模式下的每一个客户端请求需要用一个线程与之对应,显然无法实现大规模并发;而NIO模式下,因为是事件驱动,一个selector可以处理所有客户端的事件,只有当有事件到达时才会返回处理,只需要启动一定数量的事件处理线程去异步处理客户端事件即可。因此,NIO模式从理论上具备应对高并发的条件。
至此,我暂不再去深究操作系统层面epoll等技术细节,带着对Java BIO、NIO的初步认识,下一步打算去了解一下使用Netty如何去创建和访问一个BIO、NIO的服务以及Netty带给我们的封装和基本的设计思想。
2012年,由于项目的需要我第一次接触到了Netty,当时Netty还处于3.x版本。我用十几篇博文记录了自己自学Netty的过程,虽然内容浅薄,但没想到被各处转载,我想主要是因为当时Netty的资料确实较少的缘故。
五六年过去了,Netty早已发展到了4.x系列,好奇也好,求知也罢,我打算重学Netty,虽然严格来说,我已不是IT从业人员,但我仍希望保留对技术的热爱与追求。
学习Netty,就免不了先去了解Java中的几种通信模型。我不想先去学习很多概念,单刀直入,就先从最容易理解的BIO(阻塞I/O)学起。
阻塞I/O,顾名思义,就是服务端在接受到客户端的请求时,在当前线程下是阻塞执行的。只有当一个客户端请求关闭后,才能接受其他客户端的请求。阻塞I/O的JDK原生实现代码如下:
服务端
package com.coderli.nettylab.bio;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;
/**
* @author lihongzhe 2018-06-19 11:06
*/
public class BioServer {
public static void main(String[] args) throws IOException {
ServerSocket socketServer = new ServerSocket();
socketServer.bind(new InetSocketAddress("127.0.0.1", 7080));
for (; ; ) {
Socket socket = socketServer.accept();
System.out.println("接收到新的连接请求。");
InputStream is = socket.getInputStream();
BufferedReader reader = new BufferedReader(new InputStreamReader(is));
String line = reader.readLine();
System.out.println(line);
socket.getOutputStream().write("Hello, I'm server.".getBytes());
socket.shutdownOutput();
}
}
}
客户端
package com.coderli.nettylab.bio;
import java.io.*;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.net.SocketImpl;
/**
* @author lihongzhe 2018-06-21 23:24
*/
public class BioClient {
public static void main(String[] args) throws IOException {
Socket socket = new Socket();
socket.connect(new InetSocketAddress("127.0.0.1", 7080));
OutputStream os = socket.getOutputStream();
InputStream is = socket.getInputStream();
os.write("Hello, I'm client.".getBytes());
socket.shutdownOutput();
BufferedReader br = new BufferedReader(new InputStreamReader(is));
System.out.println(br.readLine());
socket.close();
}
}
简单解释一下上述代码:
由此可见,服务端同一时刻、同一线程只能处理来自一个客户端的请求,显然这种连接模式的并发效率并不能令人满意。
我暂不深究更多的细节,接下来先去了解一下Java NIO模式的特点和开发方式。
Implement the following operations of a queue using stacks.
push(x) -- Push element x to the back of queue.
pop() -- Removes the element from in front of queue.
peek() -- Get the front element.
empty() -- Return whether the queue is empty.
Example:
MyQueue queue = new MyQueue();
queue.push(1);
queue.push(2);
queue.peek(); // returns 1
queue.pop(); // returns 1
queue.empty(); // returns false
Notes:
You must use only standard operations of a stack – which means only push to top, peek/pop from top, size, and is empty operations are valid. Depending on your language, stack may not be supported natively. You may simulate a stack by using a list or deque (double-ended queue), as long as you use only standard operations of a stack. You may assume that all operations are valid (for example, no pop or peek operations will be called on an empty queue).false
即用stack来实现queue
# Implement the following operations of a queue using stacks.
#
# push(x) -- Push element x to the back of queue.
# pop() -- Removes the element from in front of queue.
# peek() -- Get the front element.
# empty() -- Return whether the queue is empty.
# Example:
#
# MyQueue queue = new MyQueue();
#
# queue.push(1);
# queue.push(2);
# queue.peek(); // returns 1
# queue.pop(); // returns 1
# queue.empty(); // returns false
# Notes:
#
# You must use only standard operations of a stack -- which means only push to top, peek/pop from top, size, and is
# empty operations are valid.
# Depending on your language, stack may not be supported natively. You may simulate a stack by using a list or
# deque (double-ended queue), as long as you use only standard operations of a stack.
# You may assume that all operations are valid (for example, no pop or peek operations will be called on an empty queue).
class MyQueue:
def __init__(self):
"""
Initialize your data structure here.
"""
self._data = []
self._temp_stack = []
def push(self, x):
"""
Push element x to the back of queue.
:type x: int
:rtype: void
"""
self._data.append(x)
def pop(self):
"""
Removes the element from in front of queue and returns that element.
:rtype: int
"""
if len(self._temp_stack) == 0:
while len(self._data) != 0:
self._temp_stack.append(self._data.pop())
return self._temp_stack.pop()
def peek(self):
"""
Get the front element.
:rtype: int
"""
if len(self._temp_stack) == 0:
while len(self._data) != 0:
self._temp_stack.append(self._data.pop())
return self._temp_stack[-1]
def empty(self):
"""
Returns whether the queue is empty.
:rtype: bool
"""
return len(self._data) == 0 and len(self._temp_stack) == 0
# Your MyQueue object will be instantiated and called as such:
# obj = MyQueue()
# obj.push(x)
# param_2 = obj.pop()
# param_3 = obj.peek()
# param_4 = obj.empty()
无话可说……
Given an integer, write a function to determine if it is a power of two.
Example 1:
Input: 1
Output: true
Explanation: 2^0 = 1
Example 2:
Input: 16
Output: true
Explanation: 2^4 = 16
Example 3:
Input: 218
Output: false
判断一个整数是否是2的幂次方
# Given an integer, write a function to determine if it is a power of two.
#
# Example 1:
#
# Input: 1
# Output: true
# Explanation: 20 = 1
# Example 2:
#
# Input: 16
# Output: true
# Explanation: 24 = 16
# Example 3:
#
# Input: 218
# Output: false
class Solution:
def isPowerOfTwo(self, n):
"""
:type n: int
:rtype: bool
"""
if n == 0:
return False
binary_str = bin(n)[3:]
for c in binary_str:
if c is not '0' and not '':
return False
return True
def isPowerOfTwo_bit_operation(self, n):
"""
:type n: int
:rtype: bool
"""
if n <= 0:
return False
while n > 0:
if n & 1 and n != 1:
return False
n = n >> 1
return True
给出两种办法。一种是解析二进制的字符串。因为2的次幂都是1后面接n个0的形式的,因此判断是否有0以外的字符串即可。
另一种是利用与运算,利用的性质是一样的。个人推荐第二种思考方式。