nio java tcp_Java网络编程基础(六)— 基于TCP的NIO简单聊天系统

在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();

}

}

}

运行结果

服务端

d1f433f2d98ae4aad1e90cc3324825c8.png

客户端

00608898f3f1b356c2f4af4a8d2898c8.png

d8e20754bd0a30802bd2f4d5031aacf1.png

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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值