说是简单聊天系统,压根不能算是一个系统,顶多算个雏形。本文重点不在聊天系统设计和实现上,而是通过实现类似效果,展示下NIO 和Socket两种编程方式的差异性。说是Socket与Nio的编程方式,不太严谨,
说是简单聊天系统,压根不能算是一个系统,顶多算个雏形。本文重点不在聊天系统设计和实现上,而是通过实现类似效果,展示下NIO 和Socket两种编程方式的差异性。说是Socket与Nio的编程方式,不太严谨,因为NIO的底层也是通过Socket实现的,但又想不出非常好的题目,就这样吧。
主要内容
Socket方式实现简易聊天效果
NIO方式实现简易聊天效果
两种方式的性能对比
前言
预期效果,是客户端之间进行“广播”式聊天,类似于QQ群聊天。希望以后有机会,以此简易版为基础,不断演进,演练下在线聊天系统。
1.Socket方式实现简易聊天效果
1.1服务端 Server.java
package com.example.socket.server;
import java.io.IOException;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.ArrayList;
import java.util.List;
public class Server {
private static int port =9999;
// 可接受请求队列的最大长度
private static int backlog=100;
// 绑定到本机的IP地址
private static final String bindAddr = "127.0.0.1";
//socket字典列表
private static List<Socket> nodes= new ArrayList<Socket>();
public static void main(String[] args) {
try {
ServerSocket ss = new ServerSocket(port, backlog,InetAddress.getByName(bindAddr));
for(;;){
//发生阻塞,等待客户端连接
Socket sc = ss.accept();
nodes.add(sc);
InetAddress addr = sc.getLocalAddress();
System.out.println("create new session from "+addr.getHostName()+":"+sc.getPort()+"\n");
//针对一个Socket 客户端 启动两个线程,分别是接收信息,发送信息
new Thread(new ServerMessageReceiver(sc,nodes)).start();
new ServerMessageSender(sc).start();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
1.2 消息接收端 ServerMessageReceiver.java
额外负责信息广播
package com.example.socket.server;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.net.Socket;
import java.util.ArrayList;
import java.util.List;
public class ServerMessageReceiver implements Runnable{
private Socket socket;
//socket字典列表
private List<Socket> nodes= new ArrayList<Socket>();
public ServerMessageReceiver(Socket sc,List<Socket> nodes){
this.socket=sc;
this.nodes=nodes;
}
@Override
public void run() {
try {
BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream(), "UTF-8"));
//接收到的消息
String content;
while (true) {
if(socket.isClosed()){
System.out.println("Socket已关闭,无法获取消息");
reader.close();
socket.close();
break;
}
content=reader.readLine();
if(content!=null && content.equals("bye")){
System.out.println("对方请求关闭连接,无法继续进行聊天");
reader.close();
socket.close();
break;
}
String message =socket.getPort()+":"+content;
//广播信息
for(Socket n:this.nodes){
if(n !=this.socket){
BufferedWriter writer=new BufferedWriter(new OutputStreamWriter(n.getOutputStream(),"UTF-8"));
writer.write(message);
writer.newLine();
writer.flush();
}
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
1.3消息发送服务端 ServerMessageSender.java
主要作用:发送欢迎信息
package com.example.socket.server;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.net.Socket;
public class ServerMessageSender extends Thread{
private Socket socket;
public ServerMessageSender(Socket socket) {
this.socket = socket;
}
@Override
public void run() {
try {
BufferedWriter writer=new BufferedWriter(new OutputStreamWriter(socket.getOutputStream(),"UTF-8"));
// BufferedReader inputReader=new BufferedReader(new InputStreamReader(System.in));
try {
String msg="server :welcome "+socket.getPort();
writer.write(msg);
writer.newLine();
writer.flush();
} catch (IOException e) {
e.printStackTrace();
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
1.4 客户端 Client.java
package com.example.socket.client;
import java.net.InetAddress;
import java.net.Socket;
public class Client {
// 监听端口号
private static final int port = 9999;
// 绑定到本机的IP地址
private static final String bindAddr = "127.0.0.1";
public static void main(String[] args) {
try {
System.out.println("正在连接Socket服务器");
Socket socket=new Socket(InetAddress.getByName(bindAddr),port);
System.out.println("已连接\n==================================");
new ClientMessageSender(socket).start();
new ClientMessageReceiver(socket).start();
} catch (Exception e) {
e.printStackTrace();
}
}
}
1.4 消息接收客户端 ClientMessageReceiver.java
仅仅是输出
package com.example.socket.client;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.net.Socket;
public class ClientMessageReceiver extends Thread {
private Socket socket;
public ClientMessageReceiver(Socket socket) {
this.socket=socket;
}
@Override
public void run() {
try {
// 获取socket的输 出\入流
BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream(), "UTF-8"));
//接收到的消息
String content;
while (true) {
if(socket.isClosed()){
System.out.println("Socket已关闭,无法获取消息");
reader.close();
socket.close();
break;
}
content=reader.readLine();
if(content.equals("bye")){
System.out.println("对方请求关闭连接,无法继续进行聊天");
reader.close();
socket.close();
break;
}
System.out.println(content+"\n");
}
reader.close();
socket.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
1.5 消息发送客户端 ClientMessageSender.java
通过输入流输入,将信息传入Socket
package com.example.socket.client;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.net.Socket;
public class ClientMessageSender extends Thread {
private Socket socket;
public ClientMessageSender(Socket socket) {
this.socket = socket;
}
@Override
public void run() {
try {
BufferedWriter writer=new BufferedWriter(new OutputStreamWriter(socket.getOutputStream(),"UTF-8"));
BufferedReader inputReader=new BufferedReader(new InputStreamReader(System.in));
try {
String msg;
for(;;){
msg=inputReader.readLine();
if(msg.toLowerCase().equals("exit")){
System.exit(0);
}
if(socket.isClosed()){
System.out.println("Socket已关闭,无法发送消息");
writer.close();
socket.close();
break;
}
writer.write(msg);
writer.newLine();
writer.flush();
System.out.println();
}
} catch (IOException e) {
e.printStackTrace();
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
1.6 效果
2.NIO方式实现简易聊天效果
2.1服务端 NServer.java
package com.example.nio;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.Channel;
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;
public class NServer {
private Selector selector;
private Charset charset = Charset.forName("UTF-8");
public void init() throws Exception {
selector = Selector.open();
ServerSocketChannel server = ServerSocketChannel.open();
InetSocketAddress isa = new InetSocketAddress("127.0.0.1", 3000);
server.socket().bind(isa);
server.configureBlocking(false);
server.reGISter(selector, SelectionKey.OP_ACCEPT);
while (selector.select() > 0) {
for (SelectionKey key : selector.selectedKeys()) {
selector.selectedKeys().remove(key);
if (key.isAcceptable()) {
SocketChannel sc = server.accept();
System.out.println("create new session from "+sc.getRemoteAddress()+"\n");
sc.configureBlocking(false);
sc.register(selector, SelectionKey.OP_READ);
key.interestOps(SelectionKey.OP_ACCEPT);
sc.write(charset.encode("welcome"+sc.getRemoteAddress()));
}
if (key.isReadable()) {
SocketChannel sc = (SocketChannel)key.channel();
ByteBuffer buff = ByteBuffer.allocate(1024);
String content = "";
try {
while (sc.read(buff) > 0) {
buff.flip();
content += charset.decode(buff);
buff.clear();
}
key.interestOps(SelectionKey.OP_READ);
} catch (IOException e) {
key.cancel();
if (key.channel() != null)
key.channel().close();
}
if (content.length() > 0) {
for (SelectionKey sk : selector.keys()) {
Channel targetchannel = sk.channel();
if (targetchannel instanceof SocketChannel && targetchannel!=sc) {
SocketChannel dest = (SocketChannel)targetchannel;
dest.write(charset.encode(content));
}
}
}
}
}
}
}
public static void main(String[] args) throws Exception {
new NServer().init();
}
}
2.2 客户端 NClient.java
package com.example.nio;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
import java.nio.charset.Charset;
import java.util.Scanner;
public class NClient {
private Selector selector;
private Charset charset = Charset.forName("UTF-8");
private SocketChannel sc = null;
public void init() throws IOException {
selector = Selector.open();
InetSocketAddress isa = new InetSocketAddress("127.0.0.1", 3000);
sc = SocketChannel.open(isa);
sc.configureBlocking(false);
sc.register(selector, SelectionKey.OP_READ);
new ClientThread().start();
@SuppressWarnings("resource")
Scanner scan = new Scanner(System.in);
while (scan.hasNextLine()) {
sc.write(charset.encode(scan.nextLine()));
}
}
private class ClientThread extends Thread {
public void run() {
try {
while (selector.select() > 0) {
for (SelectionKey sk : selector.selectedKeys()) {
selector.selectedKeys().remove(sk);
if (sk.isReadable()) {
SocketChannel sc = (SocketChannel)sk.channel();
ByteBuffer buff = ByteBuffer.allocate(1024);
String content = "";
while (sc.read(buff) > 0) {
sc.read(buff);
buff.flip();
content += charset.decode(buff);
buff.clear();
}
System.out.println("chat info: " + content);
sk.interestOps(SelectionKey.OP_READ);
}
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) throws IOException {
new NClient().init();
}
}
代码来自
https://GitHub.com/xeostream/chat
2.3 效果
3. 对比
从api操作上来看,NIO偏复杂,面向的是异步编程方式,重点围绕Selector,SelecTKEy操作。
性能对比,主要简单模拟下Echo情景:客户端连接成功,服务端返回一条信息。
3.1Socket性能测试入口
可以关闭ServerMessageReceiver线程
package com.example.socket.client;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.InetAddress;
import java.net.Socket;
import java.net.UnknownHostException;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
public class BenchmarkClient {
// 监听端口号
private static final int port = 9999;
// 绑定到本机的IP地址
private static final String bindAddr = "127.0.0.1";
public static <T> void main(String[] args) {
try {
long s=System.currentTimeMillis();
for (int i = 0; i < 1000; i++) {
final Socket socket = new Socket(
InetAddress.getByName(bindAddr), port);
Future<String> future = Executors.newFixedThreadPool(4).submit(
new Callable<String>() {
@Override
public String call() throws Exception {
BufferedReader reader = new BufferedReader(
new InputStreamReader(socket
.getInputStream(), "UTF-8"));
String content = reader.readLine();
return Thread.currentThread().getName()+"--->"+content;
}
});
System.out.println(i+":"+future.get());
socket.close();
}
long e=System.currentTimeMillis();
System.out.println(e-s);
} catch (UnknownHostException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
3.2 NIO性能测试入口
package com.example.nio;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
import java.nio.charset.Charset;
import java.util.Scanner;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
public class BenchMarkNClient {
private Selector selector;
private Charset charset = Charset.forName("UTF-8");
private SocketChannel sc = null;
public void init() throws IOException {
long s = System.currentTimeMillis();
selector = Selector.open();
InetSocketAddress isa = new InetSocketAddress("127.0.0.1", 3000);
for (int i = 0; i < 10000; i++) {
sc = SocketChannel.open(isa);
sc.configureBlocking(false);
sc.register(selector, SelectionKey.OP_READ);
Future<String> future = Executors.newFixedThreadPool(4).submit(new ClientTask());
try {
System.out.println(i+":"+future.get());
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
long e= System.currentTimeMillis();
System.out.println(e-s);
}
private class ClientTask implements Callable<String> {
public String call() {
try {
while (selector.select() > 0) {
for (SelectionKey sk : selector.selectedKeys()) {
selector.selectedKeys().remove(sk);
if (sk.isReadable()) {
SocketChannel sc = (SocketChannel)sk.channel();
ByteBuffer buff = ByteBuffer.allocate(1024);
String content = "";
while (sc.read(buff) > 0) {
sc.read(buff);
buff.flip();
content += charset.decode(buff);
buff.clear();
}
sk.interestOps(SelectionKey.OP_READ);
return content;
}
}
}
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
}
public static void main(String[] args) throws IOException {
new BenchMarkNClient().init();
}
}
3.3 性能对比
次数 | NIO | SOCKET(ms) |
1000 | 525 | 637 |
2000 | 1411 | 1215 |
2000(休眠时间为100毫秒) | 205928 | 206313 |
5000 | 6731 | 2976 |
次数较少时,NIO性能较好。但随着次数增加,性能下降非常厉害。(存疑)
当通讯时间变长时,发现NIO性能又相对提高了。
可见一个技术的好坏,是和业务场景分不开的。
--结束END--
本文标题: 简易版聊天系统实现 Socket VS NIO两种实现方式
本文链接: https://lsjlt.com/news/44660.html(转载时请注明来源链接)
有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341
2024-10-23
2024-10-22
2024-10-22
2024-10-22
2024-10-22
2024-10-22
2024-10-22
2024-10-22
2024-10-22
2024-10-22
回答
回答
回答
回答
回答
回答
回答
回答
回答
回答
0