java高性能 tcp协议代码示例(一)
2016-02-19 22:35
459 查看
注册csdn这么久,今天是第一次写博客。学了两三年的java,从以前的大菜鸟到如今的小菜鸟的水平,这其中的技术成长与网上各个大神们的博客又分不开的关系。
今天写一下关于使用java nio中的类来实现高性能网络编程。小弟初来乍到,若有错误,望各位大神哥哥们指正。
参考:https://www.ibm.com/developerworks/java/library/j-javaio/
在正式贴出代码之前呢,我想先聊一聊linux下的c语言socket编程。java中的nio与linux下c语言的网络编程有非常多的相似之处。
我们知道linux下网络编程一般会采用3种(或者四种方式);
1.多进程模型: socket + bind+ linsten+accept+fork,通过 这几个函数可以实现一个简单的多进程模型。
其中的原理就是,当通过accept的文件描述符读写数据的时候,默认情况下read/write函数会阻塞,知道读写完成或者读写失败返回,而在阻塞的过程中,服务器无法接受新的客户端请求。这与java中通过 socket获取输入输出流读写 数据如出一辙。于是,可以通过fork函数创建一个新的进程来处理read/write,而父进程继续accept,等待新的客户端请求。但是java一般情况下只有一个进程,所以无法通过 这种方式来实现网络编程。
2.多线程模型:socket + bind+ linsten+accept+pthread_create,这种方式也是一种解决之道,通过子线程来完成read/write文件描述符,而主线程继续监听新的客户端请求。这种方式与多进程模型相比有更多的优势,但是也优劣势。优势是可以操作系统管理线程比管理一个进程的代价要来的小(线程间共享虚拟内存段可以节约内存,线程在内核中的信息比进程在内核中保存的信息要少,等等),并且可以通过线程池来减少创建线程所带来的开销,劣势就是当一个线程发生异常会导致整个应用程序退出,所以多进程模型来的更加稳定。这种模型是java网络编程中最常用的模型,并且java中也可以实现线程池来减少开销。
3.多路io模型:我们知道linux/unix下默认read/write函数在对文件描述符进行操作时默认是阻塞的,但是可以通过fcntl来改变文件描述符的属性,使对它的操作变为非阻塞(包括accept linstenfd),于是我们可以不断遍历和操作所有打开的文件描述符,如果发现有就绪的文件描述符,我们就可以在一个线程中来完成所有的操作,避免操作系统内核在大量线程间来回切换增加系统开销,而不会停止响应新的客户端请求。linux内核已经为我们提供了这样的函数,select/poll。这在linux下并不是最好的解决方案,因为循环遍历各个连接也会产生不小的系统开销,而linux单独提供了epoll函数(不与unix,mac兼容),采用事件驱动模型,将已就绪的客户端连接加入一个红黑树当中,从而减少了系统循环遍历的开销,这是目前linux下高性能服务器的开发方案。传说nginx服务器采用这种方案。根据参考文档,java
nio也采用事件驱动模型,虽然没有去仔细看源码,但是听名字就知道这是一个高性能的编程方案。
好了,说了这么多废话,直接上代码。不仅是服务器端,客户端也能使用nio。先看服务器端的:
今天写一下关于使用java nio中的类来实现高性能网络编程。小弟初来乍到,若有错误,望各位大神哥哥们指正。
参考:https://www.ibm.com/developerworks/java/library/j-javaio/
在正式贴出代码之前呢,我想先聊一聊linux下的c语言socket编程。java中的nio与linux下c语言的网络编程有非常多的相似之处。
我们知道linux下网络编程一般会采用3种(或者四种方式);
1.多进程模型: socket + bind+ linsten+accept+fork,通过 这几个函数可以实现一个简单的多进程模型。
其中的原理就是,当通过accept的文件描述符读写数据的时候,默认情况下read/write函数会阻塞,知道读写完成或者读写失败返回,而在阻塞的过程中,服务器无法接受新的客户端请求。这与java中通过 socket获取输入输出流读写 数据如出一辙。于是,可以通过fork函数创建一个新的进程来处理read/write,而父进程继续accept,等待新的客户端请求。但是java一般情况下只有一个进程,所以无法通过 这种方式来实现网络编程。
2.多线程模型:socket + bind+ linsten+accept+pthread_create,这种方式也是一种解决之道,通过子线程来完成read/write文件描述符,而主线程继续监听新的客户端请求。这种方式与多进程模型相比有更多的优势,但是也优劣势。优势是可以操作系统管理线程比管理一个进程的代价要来的小(线程间共享虚拟内存段可以节约内存,线程在内核中的信息比进程在内核中保存的信息要少,等等),并且可以通过线程池来减少创建线程所带来的开销,劣势就是当一个线程发生异常会导致整个应用程序退出,所以多进程模型来的更加稳定。这种模型是java网络编程中最常用的模型,并且java中也可以实现线程池来减少开销。
3.多路io模型:我们知道linux/unix下默认read/write函数在对文件描述符进行操作时默认是阻塞的,但是可以通过fcntl来改变文件描述符的属性,使对它的操作变为非阻塞(包括accept linstenfd),于是我们可以不断遍历和操作所有打开的文件描述符,如果发现有就绪的文件描述符,我们就可以在一个线程中来完成所有的操作,避免操作系统内核在大量线程间来回切换增加系统开销,而不会停止响应新的客户端请求。linux内核已经为我们提供了这样的函数,select/poll。这在linux下并不是最好的解决方案,因为循环遍历各个连接也会产生不小的系统开销,而linux单独提供了epoll函数(不与unix,mac兼容),采用事件驱动模型,将已就绪的客户端连接加入一个红黑树当中,从而减少了系统循环遍历的开销,这是目前linux下高性能服务器的开发方案。传说nginx服务器采用这种方案。根据参考文档,java
nio也采用事件驱动模型,虽然没有去仔细看源码,但是听名字就知道这是一个高性能的编程方案。
好了,说了这么多废话,直接上代码。不仅是服务器端,客户端也能使用nio。先看服务器端的:
InetSocketAddress address = new InetSocketAddress(80);//设置监听的网卡和端口, ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();//构建ServerSocketChannel对象,相当于java.io中的Serversocket,相当于linux c语言中的,socket()函数 serverSocketChannel.configureBlocking(false); //设置serverSocketChannel为非阻塞,相当于linux中fcntl(socketfd,NO_BLOCKING); serverSocketChannel.bind(address);//绑定,相当于linux中的bind函数 Selector selector = Selector.open();//selector相当于一个监听器 serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);//注册监听事件,poll/epoll while (selector.select() > 0) {//基于事件驱动的channel,当执行到这里会阻塞,直到至少有一个就绪的channel //获取就绪事件列表,循环迭代,注意,一个selectionKey携带了一个channel,但是一个channel可能有多种事件 // 就绪,所以分别判断,由于在进入循环之前,只注册了一个serversocketchannel的accept事件,所以满足key.isAcceptable()一定是serversocketchannel Set<SelectionKey> selectionKeys = selector.select 92c5 edKeys(); for (SelectionKey key : selectionKeys) { if (key.isAcceptable()) { SocketChannel socket = serverSocketChannel.accept(); socket.configureBlocking(false); socket.register(selector, SelectionKey.OP_READ | SelectionKey.OP_WRITE, ByteBuffer.allocate(1024)); continue; } if (key.isReadable()) { SocketChannel client = (SocketChannel) key.channel(); ByteBuffer bb= (ByteBuffer) key.attachment(); String msg = read(client,bb); System.out.println(msg); if ("close".equals(msg)) { client.close(); } } if (key.isWritable()) { write(key.channel(), "这是我写出的消息"); } } }暂时先到这里,大家领悟一下channel和selector吧,后边接着说。
相关文章推荐
- Java TCP 简单实例
- HttpServlet类详解
- IOS alamofire网络请求
- 深度学习系列(3)——使用神经网络去辨识手写数字
- 软考之路(1)——浅解网络基础知识
- Https(SSL/TLS)原理详解
- BZOJ_P1412 [ZJOI2009]狼和羊的故事(网络流+最大流最小割)
- Android开发学习之路--网络编程之初体验
- Android开发学习之路--网络编程之初体验
- 深入理解Linux网络技术内幕——中断与网络驱动程序
- 深度学习系列(2)——神经网络与深度学习
- 神经网络的学习 机器学习基础(4)
- 加载网络图片(若SDCard有,则本地加载)
- 网络流24题 飞行员配对方案问题
- Redhat 7.0使用CentOS 7 的Yum 网络源
- 神经网络的表达式 机器学习基础(3)
- BZOJ_P2324 [ZJOI2011]营救皮卡丘(网络流+最小费用最大流+Floyd)
- HttpClientUtil
- BZOJ_P1221 [HNOI2001] 软件开发(网络流+最小费用最大流)
- iOS开发之网络编程--6、NSURLSessionConfiguration笔记