手把手教你写一个极简版Netty

使用Netty编写一个极简的Http服务器 使用Netty编写一个极简的Http服务器 Netty是一个高性能的网络编程框架,有着简单易于使用的抽象模型。利用Netty自带的Http协议编解码器,我们可以快速地以较少的代码编写一个简单的Http服务器。 本文主要介绍如何使用Netty实现一个极简的Http服务器,主要功能是通过接收一个浏览器Http请求,Netty服务器返回一个静态资源页面。 为了实现Http服务器,我们只需要用到Netty自带的两个编解码器: HttpServerCodec:用于Netty服务端,该类其实是HttpRequestD 阅读详情

前言

Netty如今是使用最广泛的网络通信框架,许多人对此有强烈的学习需求。但是Netty本身代码量大、概念众多,单纯看代码学习容易一头雾水,看了后面忘了前面。这个问题的破解之道,就是以自己的思考重新写一遍,这是掌握一个复杂设计的最好方法。本文会带着你从零开始设计一个极简版的Netty,为你演示如何从最原始的BIO进化到NIO,再一步步添加Netty的Reactor线程模型和任务处理的。本文的源代码放在Github上(mini-netty),有兴趣的读者可以自己取阅。

BIO版本

先看最原始的BIO。BIO的特点是阻塞式的,它的API使用简单,但是缺点也很明显:一个连接对应一个线程,如果连接数量过多,那么线程数量也会很多,而CPU核数有限,造成大量不必要的线程切换开销。代码示例如下:

/**
 * 基于BIO的客户端
 */
public class BioClient {
    public static void main(String[] args) throws IOException {
        try (Socket socket = new Socket("127.0.0.1", 13)) {
			OutputStream os = socket.getOutputStream();
			PrintStream ps = new PrintStream(os);
			try (Scanner sc = new Scanner(System.in)) {
			    while(true){
			    	// 循环从系统输入获取消息并发送
			    	System.out.print("type in your msg:");
			        String msg = sc.nextLine();
			        ps.println(msg);
			        ps.flush();
			    }
			}
		}
    }
}
/**
 * 基于BIO的服务端
 */
public class BioServer {
    public static void main(String[] args) throws IOException{
        try (ServerSocket ss = new ServerSocket(13)) {
			while(true){
				// 循环接收新的连接,并启动新的线程处理对应连接的IO消息
			    Socket socket = ss.accept();
			    new Thread(){
			    	@Override
			    	public void run() {
			            try{
			                InputStream is = socket.getInputStream();
			                BufferedReader br = new BufferedReader(new InputStreamReader(is));
			                String msg;
			                while((msg = br.readLine())!=null){
			                    System.out.println("receive msg:" + msg);
			                }
			            }catch (Exception e){
			                e.printStackTrace();
			            }
			    	}
			    	
			    }.start();
			}
		}
    }
}

NIO版本

由于BIO的上述缺点,Java在后来的版本中推出了NIO,以获得非阻塞、更加可扩展的IO。NIO三大核心:Selector、Buffer、Channel。其中又以Selector为首,它仅需一个主线程循环,便可以通过收集和处理事件的方式,管理成千上万个连接,整个过程不会阻塞。示例代码如下:

/**
 * 基于NIO的客户端
 */
public class NioClient {
	public static void main(String[] args) throws Exception{
		// 启动16个socket分别发送消息
		for (int i = 1; i <= 16; i++) {
			InetSocketAddress inetSocketAddress = new InetSocketAddress("127.0.0.1", 13);
	        SocketChannel socketChannel = SocketChannel.open(inetSocketAddress);
	        socketChannel.configureBlocking(false);
	        String msg = "msg" + i;
	        ByteBuffer byteBuffer = ByteBuffer.wrap(msg.getBytes());
	        System.out.println("socket" + i + " send msg:" + msg);
	        socketChannel.write(byteBuffer);
	        socketChannel.close();
	        Thread.sleep(100);
		}
	}
}
/**
 * 基于BIO的服务端
 */
public class NioServer {
	
	public static void main(String[] args) throws Exception{
        ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
        InetSocketAddress inetSocketAddress = new InetSocketAddress(13);
        serverSocketChannel.socket().bind(inetSocketAddress);
        //设置成非阻塞
        serverSocketChannel.configureBlocking(false); 
        Selector selector = Selector.open();
        // 对serverSocket仅监听ACCEPT事件
        serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
 
        while(true) {
            selector.select();
            //遍历selectionKeys
            Set<SelectionKey> selectionKeys = selector.selectedKeys();
            Iterator<SelectionKey> iterator = selectionKeys.iterator();
            while (iterator.hasNext()) {
                SelectionKey key = iterator.next();
                //处理连接事件
                if(key.isAcceptable()) {  
                    SocketChannel socketChannel = serverSocketChannel.accept();
                    //设置为非阻塞
                    socketChannel.configureBlocking(false);  
                    System.out.println("client:" + socketChannel.getRemoteAddress() + " connected");
                    //注册socket的READ事件到selector
                    socketChannel.register(selector, SelectionKey.OP_READ); 
                    //处理读取事件
                } else if (key.isReadable()) {  
                    ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
                    SocketChannel channel = (SocketChannel) key.channel();
					int len = channel.read(byteBuffer);
					if (len >= 0) {
						byte[] array = byteBuffer.array();
						String msg = new String(array);
						System.out.println("receive " + msg + " from client:" + channel.getRemoteAddress());
					}
					// <0说明客户端断开(一定要处理,否则会一直收到READ事件)
					else {
						System.out.println("client:" + channel.getRemoteAddress() + " disconnected");
						channel.close();
					}
                }
                //事件处理完毕,要记得清除
                iterator.remove();  
            }
        }
    }
}

从上面代码我们可以看出NIO有两个缺点:一是代码编写远比BIO复杂,所以现在很难再看到裸用NIO编写代码的人,一般都会在封装好的框架上做二次开发,比如Netty就封装成了基于事件的开发模式,只需要在channelActive、channelRead等方法中编写业务逻辑即可;二是上面代码仅使用单线程,并未充分利用多核能力,完全可以让主线程专门处理连接接收,多个子线程处理各个channel的读写事件。这个思路实际上就是Netty的Reactor模型:使用bossGroup线程池处理连接接收,等channel建立好后,再绑定到workerGroup线程池中的一个线程,这个线程负责处理绑定在它上面的channel的读写事件。

mini-netty版本v1:Reactor线程模型

基于以上思路,我们改进之前的代码。首先我们创建NettyClient和NettyServer类,将已编写好的NioClient和NioServer类分别拷贝到这两个新类中。我们打算用NettyServer处理连接接收,那么就需要单独编写一个类,处理channel的读写事件。这个新类需要继承Thread类,并且把原NettyServer类中处理读写事件的逻辑移植过来。按照这个思路编写NioEventLoop类如下:

/**
 * worker线程池中的线程
 * 功能类似于Netty中的同名类
 */
public class NioEventLoop extends Thread {

	public NioEventLoop() {
		try {
			//构造方法中初始化自己的selector
			this.selector = Selector.open();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
	
	private Selector selector;
	
	public Selector getSelector() {
		return this.selector;
	}
	
	@Override
	public void run() {
	       while (true) {
	    	   int selectNum = 0;
	    	   try {
	    		    //select超时时间设为10ms,避免一直阻塞
		    	   	selectNum = selector.select();
				} catch (IOException e) {
					e.printStackTrace();
				}
	    	    if (selectNum > 0) {
		            //监听所有通道
		            //遍历selectionKeys
		            Set<SelectionKey> selectionKeys = selector.selectedKeys();
		            Iterator<SelectionKey> iterator = selectionKeys.iterator();
		            while (iterator.hasNext()) {
		                SelectionKey key = iterator.next();
		                //处理读取事件
		                if (key.isReadable()) {  
		                    ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
		                    SocketChannel channel = (SocketChannel) key.channel();
		                    try {
								int len = channel.read(byteBuffer);
								if (len >= 0) {
									byte[] array = byteBuffer.array();
									String msg = new String(array);
									System.out.println("receive " + msg + " from client:" + channel.getRemoteAddress());
								}
								// <0说明客户端断开(一定要处理,否则会一直收到READ事件)
								else {
									System.out.println("client:" + channel.getRemoteAddress() + " disconnected");
									channel.close();
								}
							} catch (IOException e) {
								e.printStackTrace();
							}
		                }
		                //事件处理完毕,要记得清除
		                iterator.remove();  
		            }
	    	    }
	        }
	}
}

在原来的NettyServer类中,我们需要移除处理读写事件的代码,但是需要在新连接建立后,将新channel绑定到其中一个NioEventLoop上。绑定的逻辑是:在worker线程池中挑选一个NioEventLoop,再将新channel上的读写事件注册到这个NioEventLoop内部的selector上。然后我们需要在NettyServer初始化时创建一个worker线程池,线程池中挑选NioEventLoop的逻辑可以是按顺序遍历,这样我们需要一个数据结构来保存所有NioEventLoop的引用,以及一个index来保存当前遍历到的下标。按照这些思路修改NettyServer类如下:

/**
 * mini-netty的服务端
 */
public class NettyServer {
	
	/**
	 * worker线程池
	 */
	private static List<NioEventLoop> eventLoops = new ArrayList<>();
	
	/**
	 * 下一次轮询到的下标
	 */
	private static int nextIndex = 0;
	
	/**
	 * worker线程池中线程数量
	 */
	private static final int WORKER_THREAD_NUM = 8;
	
	static {
		// 做一些worker线程池的初始化工作
		for (int i = 1; i <= WORKER_THREAD_NUM; i++) {
			NioEventLoop el = new NioEventLoop();
			el.start();
			eventLoops.add(el);
		}
	}
	
	/**
	 * 获取下一个event loop
	 */
	private static NioEventLoop getNextEventLoop() {
		NioEventLoop nextEventLoop = eventLoops.get(nextIndex);
		nextIndex++;
		if (nextIndex > eventLoops.size() - 1) {
			nextIndex = 0;
		}
		return nextEventLoop;
	}
	
	public static void main(String[] args) throws  Exception{
        ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
        InetSocketAddress inetSocketAddress = new InetSocketAddress(13);
        serverSocketChannel.socket().bind(inetSocketAddress);
        //设置成非阻塞
        serverSocketChannel.configureBlocking(false);
        Selector selector = Selector.open();
        serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
        int num = 0;
        while(true) {
        	//监听所有通道
            selector.select();
            //遍历selectionKeys
            Set<SelectionKey> selectionKeys = selector.selectedKeys();
            Iterator<SelectionKey> iterator = selectionKeys.iterator();
            while (iterator.hasNext()) {
                SelectionKey key = iterator.next();
                //处理连接事件
                if(key.isAcceptable()) {  
                    SocketChannel socketChannel = serverSocketChannel.accept();
                    System.out.println("client:" + socketChannel.getRemoteAddress() + " connected");
                    //设置为非阻塞
                    socketChannel.configureBlocking(false);  
                    System.out.println("bind new channel to event loop" + nextIndex);
                    NioEventLoop nextEventLoop = getNextEventLoop();
                    //注册socket的READ事件到所选eventLoop的selector
                    socketChannel.register(nextEventLoop.getSelector(), SelectionKey.OP_READ);
                    num++;
                    System.out.println(num + " clients connected");
                }
                //事件处理完毕,要记得清除
                iterator.remove();  
            }
        }
    }
}

看完上面代码的读者可能会有疑问:在NioEventLoop类的run方法中,为啥要写selector.select(10)?如果改成selector.select()行不行?之所以写成带超时时间而非一直阻塞的,是因为READ事件是在channel建立后再注册到这个selector上面的,在这之前selector没有任何channel事件注册,所以会一直阻塞。那你可能会说,我在注册完READ后再加一句selector.wakeup()把selector唤醒行不行?如果尝试一下,就会知道也不可行,因为当selector在select()方法上阻塞时,注册READ事件的register方法也会一直阻塞,无法继续向下执行。

所以selector.select(10)虽然性能不好,但是至少能保证程序正常运行。为了进一步优化性能,Netty的思路是将select的超时时间设为下一个即将执行任务的过期时间,而不是固定的值。为此Netty引入了优雅的任务系统来解决这个问题,同时进一步提升与event loop交互的灵活性。我们将在下节为mini-netty引入类似的系统。

mini-netty版本v2:任务处理

任务系统通常可把任务分为两类,一类是需要立即执行的,称为task(普通任务);另一类是指定时间在未来执行的,称为futureTask(定时任务)。这两类任务都需要在event loop的循环中得到执行。

先来考虑futureTask的实现。futureTask本身的结构比较简单,只需要用一个long变量存储执行时间,一个Runnable变量存储执行命令,再加以包装即可。重点需要考虑的是用什么数据结构来保存futureTask的集合。我们看下实际需求:需要频繁地获取或删除头节点,头节点是执行时间距今最近的futureTask;需要按时间顺序遍历所有节点。如果使用ArrayDeque来实现,需要保证存储时有序,虽然获取或删除头节点是O(1)时间复杂度,但是插入或删除任意节点是O(n)时间复杂度。更好的选择是PriorityQueue,它能保证插入或删除任意节点是O(logn)的时间复杂度。

现在我们有了底层是优先级队列的futureTaskQueue,再来考虑它是否需要同步。在futureTaskQueue上确实可能存在多线程竞争的问题,但是如何加锁却是个难题。我们知道ConcurrentHashMap通过分段锁大大提升了并发能力,1.8版本后甚至将加锁粒度细化到了哈希表的每个头节点上,但是同样的做法却难以对PriorityQueue实施,因为优先级队列的数据结构不像哈希表那样好做分段。可如果我们对futureTaskQueue整体加锁,并发性又不够好,聪明的Netty设计者肯定不会这么做。Netty的做法是:将taskQueue设计成线程安全的,futureTaskQueue设计成非线程安全的;保证只有一个线程(即event loop)能直接与futureTaskQueue交互;当需要多线程添加futureTask时,将添加futureTask本身包装成一个task添加到taskQueue中。我们用这种思路自己实现添加futureTask的方法:

	/**
	 * 添加定时任务
	 */
	public void scheduleFutureTask(FutureTask task) {
		if (inEventLoop()) {
			futureTaskQueue.offer(task);
		} else {
			final long execTime = task.getExecTime();
			if (execTime < nextWakeupMillis.get()) {
				this.addTask(() -> {
					scheduleFutureTask(task);
				});
			} else {
				this.addTask(() -> {
					scheduleFutureTask(task);
				}, false);
			}
		}
	} 

	/**
	 * 判断当前线程是否就是本event loop
	 */
    public boolean inEventLoop() {
        return Thread.currentThread() == this;
    }

在上面的代码中,先通过inEventLoop()方法判断当前线程是否就是本event loop,如果是的,那么无需考虑多线程问题,直接对futureTaskQueue添加即可;否则,将scheduleFutureTask方法本身包装成task,加入到taskQueue中,待这个task被event loop执行到时,就会将其包装的futureTask添加到futureTaskQueue中。

接下来再考虑taskQueue用何种线程安全的Queue来实现。JDK中常用的线程安全Queue有BlockingQueue和ConcurrentLinkedQueue,前者是阻塞的不适合当前场景,故排除掉;后者适合当前场景,基于链表和CAS实现,非阻塞无界,可以用。但是Netty使用了一种在高并发下性能更好的线程安全Queue,这就是JCTools提供的MpscQueue,它基于数组和CAS实现,Mpsc意思是多生产者单消费者,完美适合当前场景(多线程添加task,单线程——event loop消费task)。Netty在许多内部实现中使用了这种高效的数据结构,关于它的实现细节在此不再赘述。

现在两种任务的实现思路已定。接下来我们再回到上一节末尾提出的问题:如何使用任务系统来优化selector.select?上节提到,思路是select的超时时间取距离将来最近一个定时任务的时间间隔。但是这里有个问题,没有考虑到插任务的情况。就是说如果添加的futureTask执行时间晚于select超时时间,那么肯定没问题;但是如果执行时间早于select超时时间,或添加的是需要立即执行的task,那么我们必须用wakeup及时唤醒selector,让其不再阻塞。下面的代码包含唤醒方法的实现,以及带和不带唤醒两种情况下添加task方法的实现:

/**
	 * 添加任务(默认立即执行)
	 */
	public void addTask(Runnable task) {
		this.addTask(task, true);
	}
	
	/**
	 * 添加任务
	 * 
	 * 可指定是否立即执行,若立即执行,则尝试唤醒selector
	 */
	private void addTask(Runnable task, boolean immediate) {
		taskQueue.offer(task);
		if (immediate) {
			wakeup(inEventLoop());
		}
	}
	
	/**
	 * 唤醒selector
	 */
    protected void wakeup(boolean inEventLoop) {
        if (!inEventLoop && nextWakeupMillis.getAndSet(AWAKE) != AWAKE) {
            selector.wakeup();
        }
    }

当你看到这里,可能会提出疑问:唤醒selector是否会存在多线程竞争的问题,如何避免重复唤醒的问题?非常好!对于这种轻量级的状态竞争,我们通常会使用CAS的方案。可以设计一个原子类型的变量,当它已经被设置成AWAKE状态时,表明已有其他线程唤醒了selector,这时不再重复唤醒,这也就是我们在之前的wakeup方法中所展示的代码。进一步,我们可以考虑扩展这个原子类型的变量,当selector不处于唤醒状态时,那么必然处于被阻塞状态,那么这个原子变量还可以用来存储当前等待的最近futureTask的超时时间间隔。因此我们设计一个AtomicLong类型的变量:

    private static final long AWAKE = -1L;
    private static final long NONE = Long.MAX_VALUE;

	/**
	 * 下一个唤醒时间点(毫秒)
	 * 
	 * 可能的值有:
	 * AWAKE	当EL处于非阻塞状态
	 * NONE		当EL处于阻塞状态,且没有已安排的唤醒时间点
	 * 其他值	T	当EL处于阻塞状态,且已安排好唤醒时间点T
	 */
    private final AtomicLong nextWakeupMillis = new AtomicLong(AWAKE);

为了维护这个变量,我们修改NioEventLoop类中selector.select(10)这行代码为如下代码:

    		    //根据无定时任务、定时任务已过期、未过期三种情况做不同处理
	    	   	FutureTask ft = futureTaskQueue.peek();
	    	   	if (ft != null) {
	    	   		nextWakeupMillis.set(ft.getExecTime());
	    	   		long leftTime = ft.getExecTime() - System.currentTimeMillis();
	    	   		if (leftTime > 0) {
	    	   			selectNum = selector.select(leftTime);
	    	   		} else {
	    	   			selectNum = selector.selectNow();
	    	   		}
	    	   	} else {
	    	   		nextWakeupMillis.set(NONE);
	    	   		selectNum = selector.select();
	    	   	}

读者还可以再回顾下之前wakeup和scheduleFutureTask两个方法中对nextWakeupMillis的处理:

/**
	 * 唤醒selector
	 */
    protected void wakeup(boolean inEventLoop) {
        if (!inEventLoop && nextWakeupMillis.getAndSet(AWAKE) != AWAKE) {
            selector.wakeup();
        }
    }
	
	/**
	 * 添加定时任务
	 */
	public void scheduleFutureTask(FutureTask task) {
		if (inEventLoop()) {
			futureTaskQueue.offer(task);
		} else {
			final long execTime = task.getExecTime();
			if (execTime < nextWakeupMillis.get()) {
				this.addTask(() -> {
					scheduleFutureTask(task);
				});
			} else {
				this.addTask(() -> {
					scheduleFutureTask(task);
				}, false);
			}
		}
	} 

至此我们为mini-netty完成了任务系统的添加。

总结

这篇文章带着大家一步步从最简单最原始的BIO,进化到一个Netty的雏形(包括Reactor线程模型和任务系统)。主要想带给大家这样一个逐步深入的过程和设计思路的取舍。再复杂的系统,只要自己实际设计过一遍,就能懂得作者的核心思路所在,再看源码就容易多了。当然Netty的内容远不止本文提到的这些,如果以后有时间,笔者会撰写进一步的后续文章,敬请期待。

Netty实战-第一个Netty       本部分将简单介绍Netty的核心概念,核心概念就是学习Netty是如何拦截和处理异常。1 设置开发环境   设置开发环境的步骤包括如下三个部分:安装jdk下载netty包安装Ecplise2 Netty客户端和服务器概述    下面将构建一个完整的Netty服务器和客户端。一般情况下,你可能只关心编写服务器,如一个http服务器的客户端是浏览器。下面的例子中,同时实现了服务器和客户端,... 阅读详情

相关推荐

Netty入门教程文档

Netty入门教程

Netty优化指导文档

Netty优化指导文档

weixin_44842076的博客 957

【联邦学习】联邦平均(FedAvg)_附pytorch代码实现

其他代码包括1)数据集划分;2)训练;3)保存结果;4)绘图都可在 https://github.com/kt4ngw/fedavg-pytorch 找到。如果您对文章有建议及指定,也欢迎您留言评论,作者定当虚心修正。

kt4ngw的博客 5109

Netty 开发文档

Netty 开发文档 https://netty.io/4.1/api/index.html

weixin_44371237的博客 1661

Unity 游戏实例开发集合 之 Car Racing 2D (2D赛车) 休闲小游戏快速实现

Unity 游戏实例开发集合 之 Car Racing 2D (2D赛车) 休闲小游戏快速实现 目录 Unity 游戏实例开发集合 之 Car Racing 2D (2D赛车) 休闲小游戏快速实现 一、简单介绍 二、Car Racing 2D (2D赛车) 游戏内容与操作 三、相关说明 四、游戏代码框架 五、知识点 六、游戏效果预览 七、实现步骤 八、工程源码地址 九、延伸扩展 一、简单介绍 Unity 游戏实例开发集合,使用简单易懂的方式,讲解常见游戏的开发实现过程,方便

仙魁XAN 7702

使用openMVS对样例数据进行重建

openMVS根据github上的步骤进行编译。注意,如果没有GPU,用不了CUDA的话,那么需要在openMVS目录下找到CmakeLists.txt文件,把CUDA设置为OFF: SET(OpenMVS_USE_CUDA OFF CACHE BOOL "Enable CUDA library") 可执行的文件生成在openMVS_build/bin/里面,在openMVS_build文件夹下打开终端,按照顺序执行如下命令即可逐步重建。其中,-w 设置的是数据的总路径,-i 后接输入的文件名,-o

YuhsiHu的博客 2887

手把手构建Netty

Netty相关;以及简单的demo

TOMORROW6COME的博客 953

Python离线断网情况下安装numpy、pandas和matplotlib等常用第三方包

联网情况下在命令终端CMD中输入“pip install numpy”即可自动安装,pandas和matplotlib同理一样方法进行自动安装。 工作的电脑不能上外网,所以不能通过直接输入pip命令来安装。 环境:电脑上已经安装了Python(没有安装的请参考其他教程)。 Python离线断网情况下安装numpy、pandas和matplotlib等常用第三方包的步骤如下: ...

dayou7738的博客 4942

全面到哭!阿里内部疯传Netty实战文档程序员必须人手一份

Netty作为当前流行的 NIO 框架,在游戏、大数据通讯,云计算、物联网等领域都有广泛的应用,大家熟知的 Dubbo,底层用的就是 Netty。尤其在高并发、高性能 RPC 方面,Netty更是必不可少。因此在很多技术面试中,Netty多被问及。 然而,想要将 Netty 真正掌握并精通难度却不小。一些学习者可能会遇到这些问题: 多线程编程、Socket 通信、TCP/IP 协议栈等知识掌握不扎实,学习过程比较吃力; 学习了不少理论知识,但真正跟具体项目结合在一起解决实际问题时,又感觉比较棘手;

bieber007的博客 273

云模型在综合评价过程中的应用

目录 1 云模型标尺(标准云模型)的构建方法 1.1 通过正态云的“3En 规则”来表示评价论域 1.2 通过论域的边界值隶属度相等来表示评价论域 1.3 通过黄金分割法来表示评价论域 2 确定评价结果的过程 2.1 基本云发生器 2.2 X条件云发生器 2.3 两种方法的区别 综合评价问题一般分为以下六个步骤 1、明确评价目的; 2、确定被评价对象; 3、建立评价指标体系(包括收集评价指标的原始值、评价指标的若干预处理等); 4、确立与各项评价指标相对应的权重系数; 5、选..

weixin_62223576的博客 1万+

飞思卡尔16位单片机(二)——CodeWarrior开发环境搭建及新建、调试工程

一、安装CodeWarrior 飞思卡尔16位单片机的开发环境为CodeWarrior5.1,这个软件安装比较简单,软件的安装文件可以从相应的网站上下载。安装CodeWarrior所需要的电脑的硬件资源也比较宽泛,目前一般的电脑都可以满足这个要求,可以支持win10、win7等操作系统。安装过程按照按照软件的提示一步步操作即可。 二、安装USBDM驱动 USBDM是...

bigmarshal的博客 1万+

BIO、NIO、Netty演化总结之二(手撸一个极简版netty)

,里面给了一个示例AsyncNonBlockingServerWithThreadPool,最近想了想,发现这个代码跟netty的模型还是有一些出入,说是netty的雏形好像有点牵强,于是想了一下,还是决定写一个更接近netty的极简版代码,仅供交流,有不对的地方欢迎指正,不喜勿喷,直接上代码。2、每一个连接的客户端在绑定workergroup的时候实际上就是绑定了一个selector,这样每一个workergroup所负责管理的客户端连接的socket之间也相互不影响。

qq_17805707的博客 729

VMware虚拟机安装MS-DOS 7.10教程

dos安装教程dos版本7.10附下载链接。

Minecraft_xiaole的博客 1万+

Netty 笔记——手把手教你如何写一个 RPC 程序

互联网大厂比较喜欢的人才特点:对技术有热情,强硬的技术基础实力;主动,善于团队协作,善于总结思考。无论是哪家公司,都很重视高并发高可用技术,重视基础,所以千万别小看任何知识。面试是一个双向选择的过程,不要抱着畏惧的心态去面试,不利于自己的发挥。同时看中的应该不止薪资,还要看你是不是真的喜欢这家公司,是不是能真的得到锻炼。其实我写了这么多,只是我自己的总结,并不一定适用于所有人,相信经过一些面试,大家都会有这些感触。下面有部分截图希望能对大家有所帮助。

2401_84091663的博客 1059

Keil C51开发必看:详解data/xdata/bdata内存分配陷阱与避坑指南

本文深入解析Keil C51开发中的data/xdata/bdata内存分配陷阱,提供针对51芯片的高效避坑指南。通过真实案例揭示Keil编译器的隐藏规则,分享内存优化策略和调试技巧,帮助开发者解决data溢出、xdata访问异常等典型问题,提升嵌入式开发效率。

v6b7n8m9q0的博客 1072

win10 跳过U8环境检测的IE WebControls.rar

win10 安装u8的时候 提示IE WebControls 不通过,这个工具可以欺骗u8的检查。 跳过U8环境检测的IE WebControls。提示导入失败后,重新检测通过。

SpringBoot2+Netty打造通俗简版RPC通信框架

2019-07-19:完成基本RPC通信! 2019-07-22:优化此框架,实现单一长连接! 2019-07-24:继续优化此框架:1、增加服务提供注解(带版本号),然后利用Spring框架的在启动时立刻保存提供服务的实现类。2、优化NettyConfig(区分消费者和提供者配置)! 2019-07-27:优化此框架:增加注册中心,使用Zookeeper作为注册中心。 接下来:继续优化此框架,例如Netty方面的、还有增加Redis作为注册中心等等 2019-07-30:已完成。

Howinfun的博客 4461

假人有限元模型

假人模型,车辆碰撞假人有限元模型,适合做乘员安全的研究者利用

Netty学习3-简版Netty模型

Netty工作原理 图是之前的Reactor模型的真正模样 Netty模型三个版本 简单版 BossGroup拥有 selector,accpet组件 多个客户端向 BossGroup发送请求.BossGroup只处理Accpet事件. ...

DXH9701的博客 4570

lwip-2.0.3

lwip是瑞典计算机科学院(SICS)的Adam Dunkels 开发的一个小型开源的TCP/IP协议栈。实现的重点是在保持TCP协议主要功能的基础上减少对RAM 的占用。

精雕软件(JDpaint) 5.5 绿色版

精雕软件(JDpaint) 5.5 绿色版精雕软件(JDpaint) 5.5 绿色版精雕软件(JDpaint) 5.5 绿色版

【Netty】手把手教你实现自己的第一个 Netty 应用

下面,我会带着大家搭建自己的第一个 Netty 版的 Hello World 小程序。 首先,让我们来创建服务端。 服务端 我们可以通过 ServerBootstrap 来引导我们启动一个简单的 Netty 服务端,为此,你必须要为其指定下面三类属性: 线程组(一般需要两个线程组,一个负责处理客户端的连接,一个负责具体的 IO 处理) IO 模型(BIO/NIO) 自定义 ChannelHandler (处理客户端发过来的数据并返回数据给客户端) 创建服务端 public final class Hel

LoveSummer 1026

netty源码看不懂?试着写一个吧

很难看尝试以作者的角度理解netty的全貌到此,山寨版的netty写完了,累了一头汗,代码的命名和类命名尽量和源码保持一致,因为都是复制过来的,有些很复杂的地方做了简化处理,但个人感觉核心的代码除了bytebuf应该都写上了,使用方式开头有写基本和netty差不多,还是那句话,手写的目的是理解netty源码篇幅有限,这里面的事好多代码都没细讲,但代码也摘的很轻量,完全可以自行理解,回头再看netty,基本就差不多。

m0_73311735的博客 609

Netty是如何写数据的?

1 写数据的核心问题 快递场景(包裹) Netty写数据(数据) 揽收到仓库 write:写到一个buffer 从仓库发货 flush:把buffer里的数据发送出去 揽收到仓库并立马发货( 加急件) writeAndFlush: 写到buffer, 立马发送 揽收与发货之间有个缓冲的仓库 Write和Flush之间有个ChannelOutboundBuffer 1.1 写炸了 对方仓库爆仓时,送不了的时候,会停止送,协商等电话通知什么时候好了,再送。 Netty写数据,写

JavaEdge全是干货的技术号 1529

手把手教你学习netty源码及原理

手把手教你学习netty源码及原理 本文通过netty的简单例子,从源码视角分析netty工作原理。netty是基于reactor的高性能网络nio框架,对nio的阻塞、异步、reactor模式不熟悉的同学可以参考上一篇的博文 https://blog.youkuaiyun.com/Houson_c/article/details/86114771。 netty的核心组件 channel:对应jdkchan...

抬头看世界 1655

netty极简教程(一):从helloworld到编写一个聊天室

netty介绍 Nowadays we use general purpose applications or libraries to communicate with each other. For example, we often use an HTTP client library to retrieve information from a web server and to i...

jsbintask的博客 423

手把手教大家写书写一个Mqtt网关

物联网是现在比较热门的软件领域,众多物联网厂商都有自己的物联网平台,而物联网平台其中一个核心的模块就是Mqtt网关。

华为云官方博客 1566

Netty进阶:手把手教你如何编写一个NIO服务端

Netty是一款非常优秀的网络编程框架,是对NIO的二次封装,本文将重点剖析Netty服务端的启动流程,深入底层了解如何使用NIO编程服务端。 本文是笔者基于问题的启发式源码阅读技巧的展示,建议带着如下问题开始本文的阅读: ServerBootstrap 的 option 与 childOption 分别有什么作用 服务端IO通道如何绑定事件链。 1、Netty服务端启动示例 基于Netty的使用示例如下: 代码@1:创建主从多Reactor线程模型的Boss线程组,通常只需要设置一个线程,用于监听

中间件兴趣圈 1435
上一篇: 5天带你读完《Effective Java》(二)
下一篇: 5天带你读完《Effective Java》(三)
长三月
博客等级 码龄9年 228粉丝 80原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值