天天看點

java nio socket 例子_Java NIO socket程式設計執行個體

晚上学习了下Java 的 NIO Socket编程,写了下面这个小程序,包括服务器端与客户端。实现的功能为客户端向服务器端发送随即数目的消息,服务器端一条一条的回应。消息内容保存在talks.properties文件中,内容为:

Hi=Hi

Bye=Bye

床前明月光=疑是地上霜

举头望明月=低头思故乡

少小离家老大回=乡音无改鬓毛衰

天王盖地虎=宝塔镇河妖

我是甲=我是乙

我是客户端=我是服务器

我是周星驰=我是周润发

客户端会随即发送“=”左边的消息,服务器端会回应客户端“=”右边的消息。如果客户端想断开连接,会向服务器发送一个"Bye",然后服务器会回应一个"Bye“。收到服务器端的"Bye"后,客户端会断开连接。

当然,java的properties文件不接受中文内容,你需要native2ascii一下。talks.properties 的实际文件内容为:

Hi=Hi

Bye=Bye

\u5E8A\u524D\u660E\u6708\u5149=\u7591\u662F\u5730\u4E0A\u971C

\u4E3E\u5934\u671B\u660E\u6708=\u4F4E\u5934\u601D\u6545\u4E61

\u5C11\u5C0F\u79BB\u5BB6\u8001\u5927\u56DE=\u4E61\u97F3\u65E0\u6539\u9B13\u6BDB\u8870

\u5929\u738B\u76D6\u5730\u864E=\u5B9D\u5854\u9547\u6CB3\u5996

\u6211\u662F\u7532=\u6211\u662F\u4E59

\u6211\u662F\u5BA2\u6237\u7AEF=\u6211\u662F\u670D\u52A1\u5668

\u6211\u662F\u5468\u661F\u9A70=\u6211\u662F\u5468\u6DA6\u53D1

看下服务器端的代码。此例中的服务器端只有一个主线程,用于selector操作,并处理多个客户端的消息。在常规的socket编程中,每个客户端都需要单独开一个线程,效率比较低。代码为:

package helloweenpad;

import java.io.FileInputStream;

import java.net.InetSocketAddress;

import java.net.Socket;

import java.nio.ByteBuffer;

import java.nio.CharBuffer;

import java.nio.channels.SelectionKey;

import java.nio.channels.Selector;

import java.nio.channels.ServerSocketChannel;

import java.nio.channels.SocketChannel;

import java.nio.charset.Charset;

import java.nio.charset.CharsetDecoder;

import java.nio.charset.CharsetEncoder;

import java.util.Iterator;

import java.util.Properties;

public class MyFirstNIOServer {

public static final int PORT = 12315;

protected Selector selector;

protected Charset charset = Charset.forName("UTF-8");

protected CharsetEncoder charsetEncoder = charset.newEncoder();

protected CharsetDecoder charsetDecoder = charset.newDecoder();

protected Properties talks = new Properties();

int clientCount;

public MyFirstNIOServer() throws Exception {

talks.load(new FileInputStream("E:\\talk.properties"));

selector = Selector.open();

ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();

serverSocketChannel.socket().bind(new InetSocketAddress(PORT)); // port

serverSocketChannel.configureBlocking(false);

serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);// register

p("Server localhost:" + PORT + " started. waiting for clients. ");

while (true) {

// selector 线程。select() 会阻塞,直到有客户端连接,或者有消息读入

selector.select();

Iterator iterator = selector.selectedKeys().iterator();

while (iterator.hasNext()) {

SelectionKey selectionKey = iterator.next();

iterator.remove(); // 删除此消息

// 并在当前线程内处理。(为了高效,一般会在另一个线程中处理此消息,例如使用线程池等)

handleSelectionKey(selectionKey);

}

}

}

public void handleSelectionKey(SelectionKey selectionKey) throws Exception {

if (selectionKey.isAcceptable()) {

// 有客户端进来

clientCount++;

ServerSocketChannel serverSocketChannel = (ServerSocketChannel) selectionKey.channel();

SocketChannel socketChannel = serverSocketChannel.accept();

socketChannel.configureBlocking(false);

Socket socket = socketChannel.socket();

// 立即注册一个 OP_READ 的SelectionKey, 接收客户端的消息

SelectionKey key = socketChannel.register(selector, SelectionKey.OP_READ);

key.attach("第 " + clientCount + " 个客户端 [" + socket.getRemoteSocketAddress() + "]: ");

p(key.attachment() + "\t[connected] =========================================");

} else if (selectionKey.isReadable()) {

// 有消息进来

ByteBuffer byteBuffer = ByteBuffer.allocate(100);

SocketChannel socketChannel = (SocketChannel) selectionKey.channel();

try {

int len = socketChannel.read(byteBuffer);

// 如果len>0,表示有输入。如果len==0, 表示输入结束。需要关闭 socketChannel

if (len > 0) {

byteBuffer.flip();

String msg = charsetDecoder.decode(byteBuffer).toString();

// 根据客户端的消息,查找到对应的输出

String newMsg = talks.getProperty(msg);

if (newMsg == null)

newMsg = "Sorry? I don't understand your message. ";

// UTF-8 格式输出到客户端,并输出一个'n'

socketChannel.write(charsetEncoder.encode(CharBuffer.wrap(newMsg + "\n")));

p(selectionKey.attachment() + "\t[recieved]: " + msg + " ----->\t[send]: " + newMsg);

} else {

// 输入结束,关闭 socketChannel

p(selectionKey.attachment() + "read finished. close socketChannel. ");

socketChannel.close();

}

} catch (Exception e) {

// 如果read抛出异常,表示连接异常中断,需要关闭 socketChannel

e.printStackTrace();

p(selectionKey.attachment() + "socket closed? ");

socketChannel.close();

}

} else if (selectionKey.isWritable()) {

p(selectionKey.attachment() + "TODO: isWritable() ???????????????????????????? ");

} else if (selectionKey.isConnectable()) {

p(selectionKey.attachment() + "TODO: isConnectable() ????????????????????????? ");

} else {

p(selectionKey.attachment() + "TODO: else. ");

}

}

public static void p(Object object) {

System.out.println(object);

}

public static void main(String[] args) throws Exception {

new MyFirstNIOServer();

}

}

再看下客户端代码。这个客户端使用了常规的socket编程,没有使用NIO。是否使用NIO对另一方是透明的,对方看不见,也不关心。无论使用NIO还是使用常规socket,效果都是一样的,只是NIO的效率要高一些。代码为:

package helloweenpad;

import java.io.BufferedReader;

import java.io.FileInputStream;

import java.io.InputStream;

import java.io.InputStreamReader;

import java.io.OutputStream;

import java.net.Socket;

import java.util.Properties;

import java.util.Random;

public class MyFirstNIOClientTest extends Thread {

public static final String HOST = "localhost";

public static final int PORT = 12315;

boolean exist = false;

Properties talks = new Properties();

Random random = new Random();

String[] keys;

int messageCount = 0;

public void run() {

try {

// 对话内容

talks.load(new FileInputStream("E:\\talk.properties"));

// 客户端发送 "=" 左边的内容

keys = new String[talks.size()];

talks.keySet().toArray(keys);

Socket socket = new Socket(HOST, PORT);

OutputStream ous = socket.getOutputStream();

InputStream ins = socket.getInputStream();

// 接收线程,接收服务器的回应

RecieverThread reciever = new RecieverThread(ins);

reciever.start();

while (!exist) {

messageCount++;

// 选择一个随机消息

String msg = chooseMessage();

synchronized (ins) {

// 发送给服务器端

ous.write(msg.getBytes("UTF-8"));

System.out.println("[send]\t" + messageCount + ": " + msg);

// 然后等待接收线程

ins.wait();

}

if (msg.equals("Bye")) {

break;

}

}

ins.close();

ous.close();

socket.close();

} catch (Exception e) {

e.printStackTrace();

}

}

public String chooseMessage() {

int index = random.nextInt(keys.length);

String msg = keys[index];

// 如果 10 次就选中 Bye,则重新选择,为了使对话内容多一些

if (messageCount < 10 && msg.equalsIgnoreCase("Bye")) {

return chooseMessage();

}

return msg;

}

// 接收线程

class RecieverThread extends Thread {

private InputStream ins;

public RecieverThread(InputStream ins) {

this.ins = ins;

}

@Override

public void run() {

try {

String line = null;

BufferedReader r = new BufferedReader(new InputStreamReader(

ins, "UTF-8"));

// readLine()会阻塞,直到服务器输出一个 '\n'

while ((line = r.readLine()) != null) {

System.out.println("[Recieved]: " + line);

synchronized (ins) {

// 接收到服务器的消息,通知下主线程

ins.notify();

}

if (line.trim().equals("Bye")) {

exist = true;

break;

}

}

} catch (Exception e) {

e.printStackTrace();

}

}

}

public static void main(String[] args) throws Exception {

// 开三个客户端线程

for (int i = 0; i < 3; i++) {

try {

new MyFirstNIOClientTest().start();

} catch (Exception e) {

e.printStackTrace();

}

}

}

}

=============================================================================

服务器端的输出:

Server localhost:12315 started. waiting for clients.

第 1 个客户端 [/127.0.0.1:1890]: [connected] =========================================

第 2 个客户端 [/127.0.0.1:1865]: [connected] =========================================

第 3 个客户端 [/127.0.0.1:1866]: [connected] =========================================

第 2 个客户端 [/127.0.0.1:1865]: [recieved]: 床前明月光 -----> [send]: 疑是地上霜

第 3 个客户端 [/127.0.0.1:1866]: [recieved]: 我是周星驰 -----> [send]: 我是周润发

第 1 个客户端 [/127.0.0.1:1890]: [recieved]: 少小离家老大回 -----> [send]: 乡音无改鬓毛衰

第 1 个客户端 [/127.0.0.1:1890]: [recieved]: 床前明月光 -----> [send]: 疑是地上霜

第 1 个客户端 [/127.0.0.1:1890]: [recieved]: 我是客户端 -----> [send]: 我是服务器

第 3 个客户端 [/127.0.0.1:1866]: [recieved]: 举头望明月 -----> [send]: 低头思故乡

第 3 个客户端 [/127.0.0.1:1866]: [recieved]: 床前明月光 -----> [send]: 疑是地上霜

第 2 个客户端 [/127.0.0.1:1865]: [recieved]: 举头望明月 -----> [send]: 低头思故乡

第 1 个客户端 [/127.0.0.1:1890]: [recieved]: 我是甲 -----> [send]: 我是乙

第 1 个客户端 [/127.0.0.1:1890]: [recieved]: 我是周星驰 -----> [send]: 我是周润发

第 3 个客户端 [/127.0.0.1:1866]: [recieved]: 举头望明月 -----> [send]: 低头思故乡

第 2 个客户端 [/127.0.0.1:1865]: [recieved]: 床前明月光 -----> [send]: 疑是地上霜

第 1 个客户端 [/127.0.0.1:1890]: [recieved]: 床前明月光 -----> [send]: 疑是地上霜

第 3 个客户端 [/127.0.0.1:1866]: [recieved]: 床前明月光 -----> [send]: 疑是地上霜

第 2 个客户端 [/127.0.0.1:1865]: [recieved]: Hi -----> [send]: Hi

第 2 个客户端 [/127.0.0.1:1865]: [recieved]: 举头望明月 -----> [send]: 低头思故乡

第 2 个客户端 [/127.0.0.1:1865]: [recieved]: 举头望明月 -----> [send]: 低头思故乡

第 2 个客户端 [/127.0.0.1:1865]: [recieved]: 少小离家老大回 -----> [send]: 乡音无改鬓毛衰

第 3 个客户端 [/127.0.0.1:1866]: [recieved]: 我是甲 -----> [send]: 我是乙

第 2 个客户端 [/127.0.0.1:1865]: [recieved]: 床前明月光 -----> [send]: 疑是地上霜

第 1 个客户端 [/127.0.0.1:1890]: [recieved]: 我是客户端 -----> [send]: 我是服务器

第 2 个客户端 [/127.0.0.1:1865]: [recieved]: Hi -----> [send]: Hi

第 3 个客户端 [/127.0.0.1:1866]: [recieved]: Hi -----> [send]: Hi

第 2 个客户端 [/127.0.0.1:1865]: [recieved]: 我是周星驰 -----> [send]: 我是周润发

第 1 个客户端 [/127.0.0.1:1890]: [recieved]: 我是周星驰 -----> [send]: 我是周润发

第 3 个客户端 [/127.0.0.1:1866]: [recieved]: 举头望明月 -----> [send]: 低头思故乡

第 2 个客户端 [/127.0.0.1:1865]: [recieved]: Bye -----> [send]: Bye

第 1 个客户端 [/127.0.0.1:1890]: [recieved]: Hi -----> [send]: Hi

第 2 个客户端 [/127.0.0.1:1865]: read finished. close socketChannel.

第 1 个客户端 [/127.0.0.1:1890]: [recieved]: Bye -----> [send]: Bye

第 3 个客户端 [/127.0.0.1:1866]: [recieved]: 我是客户端 -----> [send]: 我是服务器

第 1 个客户端 [/127.0.0.1:1890]: read finished. close socketChannel.

第 3 个客户端 [/127.0.0.1:1866]: [recieved]: 我是客户端 -----> [send]: 我是服务器

第 3 个客户端 [/127.0.0.1:1866]: [recieved]: Bye -----> [send]: Bye

第 3 个客户端 [/127.0.0.1:1866]: read finished. close socketChannel.

客户端的输出:

[send] 1: 我是周星驰

[send] 1: 少小离家老大回

[Recieved]: 乡音无改鬓毛衰

[send] 2: 床前明月光

[Recieved]: 疑是地上霜

[send] 3: 我是客户端

[Recieved]: 我是服务器

[Recieved]: 疑是地上霜

[Recieved]: 我是周润发

[Recieved]: 低头思故乡

[send] 2: 举头望明月

[Recieved]: 疑是地上霜

[send] 3: 床前明月光

[send] 1: 床前明月光

[send] 2: 举头望明月

[Recieved]: 低头思故乡

[Recieved]: 我是乙

[send] 4: 我是甲

[Recieved]: 我是周润发

[send] 5: 我是周星驰

[send] 3: 床前明月光

[send] 6: 床前明月光

[send] 4: 举头望明月

[Recieved]: 低头思故乡

[send] 5: 床前明月光

[Recieved]: 疑是地上霜

[Recieved]: 疑是地上霜

[Recieved]: 疑是地上霜

[Recieved]: Hi

[send] 4: Hi

[Recieved]: 低头思故乡

[send] 5: 举头望明月

[Recieved]: 低头思故乡

[Recieved]: 低头思故乡

[send] 6: 举头望明月

[send] 7: 少小离家老大回

[s

2

顶

1

踩

分享到:

java nio socket 例子_Java NIO socket程式設計執行個體
java nio socket 例子_Java NIO socket程式設計執行個體

2010-09-28 15:38

浏览 22236

评论

5 楼

iHelios

2012-11-29

最近开始学习Java NIO Socket编程,有些地方不太明白:

1. 开始我以为Server端和Client端共用一个Selector,后来感觉似乎不是共用的吧,谁要读或写,就自己往selector里注册键值,再循环处理自己selector中的事件,不知道对不对?

2. Server端,注册了ACCEPT启动

Client端注册了CONNECT事件连Server并write(data)  (不太明白这里我还没注册WRITE事件怎么就可以写了)

Server知道Client连上,并注册了READ事件,取得READ键值, remove READ键值,处理READ事件(关键是这里 我明明已经remove了READ键值了,我客户端执行写操作关闭后,服务器端还在循环处理isReadable事件,这是怎么回事啊?)

希望您帮忙解惑!

4 楼

somatezyc

2011-02-16

java nio socket 例子_Java NIO socket程式設計執行個體

例子是写的不错,但是你那个同步块看的我一头雾水。。。。为什么你那个同步块没起作用,要起作用了不是就锁死了么。。。一个等待唤醒,一个等待释放锁。。。。。。

3 楼

fortianwei

2011-01-19

lost_alien 写道

// 并在当前线程内处理。(为了高效,一般会在另一个线程中处理此消息,例如使用线程池等)

handleSelectionKey(selectionKey);

在另一个线程中处理此消息 ? 那这跟不用nio有啥区别呃。。

处理消息开线程和接受客户端开线程又不同。传统的给客户端开的那些线程由于经常阻塞所以效率低。这里是要开线程处理消息,这个线程又不用等待,直接工作了,跟阻塞沾不上边的了。

话说这property文件我看的也喜欢。。。

2 楼

lost_alien

2010-12-01

// 并在当前线程内处理。(为了高效,一般会在另一个线程中处理此消息,例如使用线程池等)

handleSelectionKey(selectionKey);

在另一个线程中处理此消息 ? 那这跟不用nio有啥区别呃。。

1 楼

yangguo

2010-09-28

哥们的property文件比较有特色。