分为服务端和客户端,服务端采用NIO的模式,由于传统的模式可能存在消息堵塞的情况,可能需要用到多线程技术,实现起来难度较大,且耗cpu性能,没有特别要求的,或者对多线程技术不是很了解的,不建议使用,这里NIO可以很好解决这种问题JDK1.4以上就支持NIO了,我这里的程序都是在JDK1.8的环境下测试过,请大家参考,另外如果用到和前端页面的消息交互的话可以用websocket,例如网页聊天,请看我的另一篇博客https://blog.csdn.net/u011267841/article/details/103611624
1.服务端
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.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Iterator;
import java.util.Set;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.PropertySource;
import org.springframework.stereotype.Component;
import lombok.extern.slf4j.Slf4j;
@Slf4j
@Component
@PropertySource("classpath:socket.properties")
public class NIOServer {
@Value("${port}")
private Integer port;
public void startNIO() throws IOException{
//Integer port=8080;
// 创建网络服务端ServerSocketChannel
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
// 设置为非阻塞模式
serverSocketChannel.configureBlocking(false);
// 构建一个Selector选择器,并且将channel注册上去
Selector selector = Selector.open();
// 将serverSocketChannel注册到selector
SelectionKey selectionKey = serverSocketChannel.register(selector, 0, serverSocketChannel);
// 对serverSocketChannel上面的accept事件感兴趣(serverSocketChannel只能支持accept操作)
selectionKey.interestOps(SelectionKey.OP_ACCEPT);
// 绑定端口
serverSocketChannel.socket().bind(new InetSocketAddress(port));
log.info("启动成功,占用端口号:"+port);
while (true) {
// 不再轮询通道,改用下面轮询事件的方式.select方法有阻塞效果,直到有事件通知才会有返回
selector.select();
// 获取事件
Set keys = selector.selectedKeys();
// 遍历查询结果
Iterator iterator = keys.iterator();
while (iterator.hasNext()) {
// 被封装的查询结果
SelectionKey key = iterator.next();
iterator.remove();
// 关注 Read 和 Accept两个事件
if (key.isAcceptable()) {
ServerSocketChannel server = (ServerSocketChannel) key.attachment();
// 将拿到的客户端连接通道,注册到selector上面
SocketChannel clientSocketChannel = server.accept();
clientSocketChannel.configureBlocking(false);
clientSocketChannel.register(selector, SelectionKey.OP_READ, clientSocketChannel);
log.info("收到新连接 : " + clientSocketChannel.getRemoteAddress());
}
if (key.isReadable()) {
SocketChannel socketChannel = (SocketChannel) key.attachment();
try {
ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
while (socketChannel.isOpen() && socketChannel.read(byteBuffer) != -1) {
// 长连接情况下,需要手动判断数据有没有读取结束 (此处做一个简单的判断: 超过0字节就认为请求结束了)
if (byteBuffer.position() > 0) break;
}
if (byteBuffer.position() == 0) continue;
byteBuffer.flip();
byte[] content = new byte[byteBuffer.limit()];
byteBuffer.get(content);
log.info(new String(content));
log.info("收到数据,来自:" + socketChannel.getRemoteAddress());
// 响应结果 200
String response = "HTTP/1.1 200 OK\r\n" + "Content-Length: 11\r\n\r\n" + "Hello World";
ByteBuffer buffer = ByteBuffer.wrap(response.getBytes());
while (buffer.hasRemaining()) {
socketChannel.write(buffer);
}
} catch (Exception e) {
e.printStackTrace();
key.cancel(); // 取消事件订阅
}
}
selector.selectNow();
}
}
}
}
注意port就是你的配置文件的端口号,在resources目录下,我这里配置的是8080
2.客户端程序采用普通的socket的写法即可
package com.example.nio;
import lombok.extern.slf4j.Slf4j;
import java.io.*;
import java.net.Socket;
import java.util.Scanner;
import java.util.UUID;
@Slf4j
public class Client {
public static void main(String[] args) throws IOException {
String host = "localhost";
int port = 8080;
//与服务端建立连接
Socket socket = new Socket(host, port);
socket.setOOBInline(true);
Scanner scanner = new Scanner(System.in);
while (scanner.hasNextLine()) {
String sendData = scanner.nextLine();
log.info("输入内容为:"+sendData);
socket.getOutputStream().write(sendData.getBytes("UTF-8"));
if("end".equals(sendData)) {
//结束会话
break;
}
}
}
}
3.SpringBoot启动程序
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) throws BeansException, IOException {
//SpringApplication.run(DemoApplication.class, args);
//起socket服务
ApplicationContext applicationContext = SpringApplication.run(DemoApplication.class, args);
applicationContext.getBean(NIOServer.class).startNIO();//在spring容器启动后,取到已经初始化的NIOServer,启动Socket服务
}
}