在Java网络编程基础(四)中提到了基于Socket的TCP/IP简单聊天系统实现了一个多客户端之间护法消息的简单聊天系统。其服务端采用了多线程来处理多个客户端的消息发送,并转发给目的用户。但是由于它是基于Socket的,因此是阻塞的。

本节我们将通过SocketChannel和ServerSocketChannel来实现同样的功能。

1、客户端输入消息的格式

username:msg    username表示要发送的的用户名,msg为发送内容,以冒号分割

2、实现思路

实现思路与Java网络编程基础(四)的实现思路类似,服务端需要保存一份用户名的列表,以便在转发消息时能够查到对应的用户。对于客户端来说,客户端需要能够随时收取服务端转发来的消息,并能够随时通过键盘输入发送消息。

3、实现过程

根据以上思路,客户端需要有独立线程,因此需要实现3个类

(1)客户端主程序ChatClient.java

该程序根据启动时输入的用户名参数负责启动客户端子线程,由子线程建立与服务端的连接。在主线程中,需要循环读取键盘的输入,将输入的消息通过子线程发送给服务端,交由服务端来转发该消息给客户端

(2)客户端子线程ClientThread.java

该线程封装了客户端与服务端的所有操作

a、在构造函数中,根据创建与服务端的连接

b、在线程主函数中建立与服务端的连接事件时,发送用户名给服务器进行注册,格式为:username = XXX(启动参数)

c、发送消息函数:外部的主线程在接收到键盘输入后,调用该函数给服务端发送消息;消息格式为“to:content”(即username:message),为了服务端能够区分是谁发送给to用户的,需要将用户名usename也加进去。from:to:content

d、关闭函数:在客户端输入bye命令后,关闭客户端与服务器的连接

(3)服务端主程序ChatServer.java

该类负责启动服务端,并负责监听客户端的请求,并负责处理客户端的输入/输出和消息转发任务,因此包含以下功能

a、启动服务器,并监听客户端的连接

b、在读取到客户端发送的有“username = XXX”字样的消息时,表示客户端第一次建立的连接,将该用户添加到客户端列表中

c、读取客户端输入的数据,消息格式类似于"from:to:content",第一个冒号":"前的部分为发送者用户名,第二个冒号前的部分为接收者用户名,最后一部分为消息内容。根据该接受者用户名在客户端列表中找到对应的客户端,发送数据给该客户端。

【服务端代码】package org.test.nio.chat;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.channels.ClosedChannelException;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.nio.charset.CharacterCodingException;
import java.nio.charset.Charset;
import java.nio.charset.CharsetDecoder;
import java.nio.charset.CharsetEncoder;
import java.util.Hashtable;
import java.util.Iterator;
public class ChatServer {
public static void main(String[] args) {
// 客户端列表
Hashtable clietList = new Hashtable();
Selector selector = null;
ServerSocketChannel server = null;
try {
// 创建一个Selector
selector = Selector.open();
// 创建Socket并注册
server = ServerSocketChannel.open();
server.configureBlocking(false);
server.register(selector, SelectionKey.OP_ACCEPT);
// 启动监听端口
InetSocketAddress ip = new InetSocketAddress(12345);
server.socket().bind(ip);
System.out.println("成功启动服务端!");
// TODO 监听事件
while (true) {
// 监听事件
selector.select();
// 事件来源列表
Iterator it = selector.selectedKeys().iterator();
while (it.hasNext()) {
SelectionKey key = it.next();
// 删除该事件
it.remove();
// 判断事件类型
if (key.isAcceptable()) {
// 连接事件
ServerSocketChannel server2 = (ServerSocketChannel) key.channel();
SocketChannel channel = server2.accept();
channel.configureBlocking(false);
if (channel.isConnectionPending()) {
channel.finishConnect();
}
channel.register(selector, SelectionKey.OP_READ);
System.out.println("客户端连接:" + channel.socket().getInetAddress().getHostName()
+ channel.socket().getPort());
} else if (key.isReadable()) {
// 读取数据事件
SocketChannel channel = (SocketChannel) key.channel();
// 读取数据
CharsetDecoder decoder = Charset.forName("UTF-8").newDecoder();
ByteBuffer buffer = ByteBuffer.allocate(512);
channel.read(buffer);
buffer.flip();
String msg = decoder.decode(buffer).toString();
System.out.println("收到:" + msg);
if(msg.startsWith("username=")){
String username = msg.replaceAll("username=", "");
clietList.put(username, channel);
}else{
//转发消息给客户端
String[] arr = msg.split(":");
if(arr.length == 3){
String from = arr[0];//发送者
String to = arr[1];//接受者
String content = arr[2];//发送内容
if(clietList.containsKey(to)){
CharsetEncoder encoder = Charset.forName("UTF-8").newEncoder();
//给接收者发送消息
clietList.get(to).write(encoder.encode(CharBuffer.wrap(from+"】"+content)));
}
}else{
String from = arr[0];
String content = "来自服务器消息:您未指定接收人";
CharsetEncoder encoder = Charset.forName("UTF-8").newEncoder();
//给接收者发送消息
clietList.get(from).write(encoder.encode(CharBuffer.wrap(content)));
}
}
}
}
}
} catch (ClosedChannelException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (CharacterCodingException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} finally{
try {
selector.close();
server.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
客户端线程package org.test.nio.chat;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.channels.ClosedChannelException;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
import java.nio.charset.CharacterCodingException;
import java.nio.charset.Charset;
import java.nio.charset.CharsetDecoder;
import java.nio.charset.CharsetEncoder;
import java.util.Iterator;
public class ClientThread extends Thread {
private CharsetDecoder decoder = Charset.forName("UTF-8").newDecoder();
private CharsetEncoder encoder = Charset.forName("UTF-8").newEncoder();
private Selector selector = null;
private SocketChannel socket = null;
private SelectionKey clientKey = null;
private String username;
// TODO 启动客户端
public ClientThread(String username){
try {
// 创建Selector
selector = Selector.open();
// 创建并注册Socket
socket = SocketChannel.open();
socket.configureBlocking(false);
clientKey = socket.register(selector, SelectionKey.OP_CONNECT);
// 连接到远程地址
InetSocketAddress ip = new InetSocketAddress("localhost", 12345);
socket.connect(ip);
this.username = username;
} catch (ClosedChannelException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
@Override
public void run() {
try {
// 监听事件
while (true) {
selector.select(1);
// 事件来源列表
Iterator it = selector.selectedKeys().iterator();
while (it.hasNext()) {
SelectionKey key = it.next();
// 删除当前事件
it.remove();
// 判断当前事件类型
if (key.isConnectable()) {
// 连接事件
SocketChannel channel = (SocketChannel) key.channel();
if(channel.isConnectionPending()){
channel.finishConnect();
}
channel.register(selector, SelectionKey.OP_READ);
System.out.println("连接服务器端成功!");
//发送用户名
send("username="+this.username);
} else if (key.isReadable()) {
// 读取数据事件
SocketChannel channel = (SocketChannel) key.channel();
//channel.register(selector, SelectionKey.OP_WRITE);
// 读取数据
ByteBuffer buffer = ByteBuffer.allocate(512);
channel.read(buffer);
buffer.flip();
String msg = decoder.decode(buffer).toString();
System.out.println("【收到:" + msg);
}
}
}
} catch (ClosedChannelException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (CharacterCodingException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
void send(String msg) {
// TODO 发送消息
try {
SocketChannel channel = (SocketChannel) clientKey.channel();
channel.write(encoder.encode(CharBuffer.wrap(msg)));
} catch (CharacterCodingException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
// TODO 关闭客户端
public void close() {
try {
selector.close();
socket.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
客户端package org.test.nio.chat;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
public class ChatClient {
public static void main(String[] args) {
// TODO Auto-generated method stub
String username = args[0];
ClientThread client = new ClientThread(username);
client.start();
// 输入\输出流
BufferedReader sin = new BufferedReader(new InputStreamReader(System.in));
try {
// 循环读取键盘输入
String readLine;
while ((readLine = sin.readLine().trim()) != null) {
if (readLine.equals("bye")) {
client.close();
System.exit(0);
}
client.send(username + ":" + readLine);
}
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}

运行结果

服务端

java tcp封装 rtsp_java tcp封装 rtsp

客户端

java tcp封装 rtsp_java tcp封装 rtsp_02

java tcp封装 rtsp_客户端_03

相对还很简陋,但是确实是NIO的具体应用,喜欢请关注