您的位置:首页 > 理论基础 > 计算机网络

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。先看服务器端的:
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吧,后边接着说。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: