最近在看webSocket,忽然想到以前学的Socket和ServerSocket,那么他们之间有什么不同呢?还有来回忆下Socket,和学习下webSocket
(天真的我以为写一个ServerSocket,再写一个webSocket就能实现通信了)
首先来说下区别吧,
Socket和ServerSocket 指传输层网络接口协议,是基于套接字的服务端和客户端实现。
而WebScoket是应用层协议,是客户端-服务器的异步通信方法,用于双向推送消息。
ServerSocket server = null;
Socket client = null;
try {
server = new ServerSocket(8888);
client = server.accept();
BufferedReader input = new BufferedReader(new InputStreamReader(client.getInputStream()));
boolean flag = true;
while (flag) {
String line = input.readLine();
System.out.println("客户端:" + line);
if (line.equals("exit")) {
flag = false;
}
}
} catch (IOException e) {
e.printStackTrace();
}finally {
try {
if(client!=null){
client.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
Socket client =null;
try {
client = new Socket("127.0.0.1", 8888);
PrintWriter output =
new PrintWriter(client.getOutputStream(), true);
Scanner cin = new Scanner(System.in);
String words;
while (cin.hasNext()) {
words = cin.nextLine();
output.println(words);
System.out.println("写出了数据:" + words);
}
cin.close();
} catch (UnknownHostException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}finally{
try {
if(client!=null){
client.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
Java NIO 由以下几个核心部分组成:
Channels
Buffers
Selectors
Channel 和 Buffer
基本上,所有的 IO 在NIO 中都从一个Channel 开始。Channel 有点象流。 数据可以从Channel读到Buffer中,也可以从Buffer 写到Channel中。这里有个图示:
Selector允许单线程处理多个 Channel。如果你的应用打开了多个连接(通道),但每个连接的流量都很低,使用Selector就会很方便。例如,在一个聊天服务器中。
try {
Selector selector = Selector.open();
// 创建一个套接字通道,注意这里必须使用无参形式
SocketChannel channel = SocketChannel.open();
// 设置为非阻塞模式,这个方法必须在实际连接之前调用(所以open的时候不能提供服务器地址,否则会自动连接)
channel.configureBlocking(false);
channel.connect(new InetSocketAddress("127.0.0.1", 8888));
channel.register(selector, SelectionKey.OP_CONNECT);
while (true) {
// 测试等待事件发生,分为直接返回的selectNow()和阻塞等待的select(),另外也可加一个参数表示阻塞超时
// 停止阻塞的方法有两种: 中断线程和selector.wakeup(),有事件发生时,会自动的wakeup()
// 方法返回为select出的事件数(参见后面的注释有说明这个值为什么可能为0).
// 另外务必注意一个问题是,当selector被select()阻塞时,其他的线程调用同一个selector的register也会被阻塞到select返回为止
// select操作会把发生关注事件的Key加入到selectionKeys中(只管加不管减)
if (selector.select() == 0) { //
System.out.println("跳过");
continue;
}
// 获取发生了关注时间的Key集合,每个SelectionKey对应了注册的一个通道
Set keys = selector.selectedKeys();
Iterator it = keys.iterator();
while (it.hasNext()) {
SelectionKey key = it.next();
// OP_CONNECT 两种情况,链接成功或失败这个方法都会返回true
if (key.isConnectable()) {
// 由于非阻塞模式,connect只管发起连接请求,finishConnect()方法会阻塞到链接结束并返回是否成功
// 另外还有一个isConnectionPending()返回的是是否处于正在连接状态(还在三次握手中)
if (channel.finishConnect()) {
System.out.println("完成连接");
// 链接成功了可以做一些自己的处理,略
Scanner cin = new Scanner(System.in);
SocketChannel serverchannel = (SocketChannel) key.channel();
String words = "";
ByteBuffer buffer = null;
while (cin.hasNext()) {
words = cin.nextLine();
buffer = ByteBuffer.wrap(words.getBytes("UTF8"));
System.out.println(serverchannel.equals(channel));//同一对象
channel.write(buffer);
System.out.println("写出了数据:" + words);
}
// 处理完后必须吧OP_CONNECT关注去掉,改为关注OP_READ
key.interestOps(SelectionKey.OP_READ|SelectionKey.OP_WRITE);
}
if(channel.isConnectionPending()){
System.out.println("正在连接状态");
}
}
// 后略 和服务器端的类似
// ...
if (key.isReadable()) {
System.out.println("读事件");
key.interestOps(SelectionKey.OP_READ|SelectionKey.OP_WRITE);
}
if (key.isWritable()) {
System.out.println("写事件");
key.interestOps(SelectionKey.OP_READ);
}
if(key.isConnectable()){
System.out.println("连接状态");
key.interestOps(SelectionKey.OP_READ|SelectionKey.OP_WRITE);
}
it.remove();
}
}
} catch (IOException e) {
e.printStackTrace();
}
try {
// 创建一个选择器,可用close()关闭,isOpen()表示是否处于打开状态,他不隶属于当前线程
Selector selector = Selector.open();
// 创建ServerSocketChannel,并把它绑定到指定端口上
ServerSocketChannel server = ServerSocketChannel.open();
server.bind(new InetSocketAddress( 8888));
// 设置为非阻塞模式, 这个非常重要
server.configureBlocking(false);
// 在选择器里面注册关注这个服务器套接字通道的accept事件
// ServerSocketChannel只有OP_ACCEPT可用,OP_CONNECT,OP_READ,OP_WRITE用于SocketChannel
server.register(selector, SelectionKey.OP_ACCEPT);
while(true){
// 测试等待事件发生,分为直接返回的selectNow()和阻塞等待的select(),另外也可加一个参数表示阻塞超时
// 停止阻塞的方法有两种: 中断线程和selector.wakeup(),有事件发生时,会自动的wakeup()
// 方法返回为select出的事件数(参见后面的注释有说明这个值为什么可能为0).
// 另外务必注意一个问题是,当selector被select()阻塞时,其他的线程调用同一个selector的register也会被阻塞到select返回为止
// select操作会把发生关注事件的Key加入到selectionKeys中(只管加不管减)
if (selector.select() == 0) { //
continue;
}
// 获取发生了关注时间的Key集合,每个SelectionKey对应了注册的一个通道
Set keys = selector.selectedKeys();
// 多说一句selector.keys()返回所有的SelectionKey(包括没有发生事件的)
Iterator it = keys.iterator();
while (it.hasNext()) {
SelectionKey key = it.next();
// OP_ACCEPT 这个只有ServerSocketChannel才有可能触发
if (key.isAcceptable()) {
System.out.println("套接字通道 ");
// 得到与客户端的套接字通道
SocketChannel channel = ((ServerSocketChannel) key.channel()).accept();
// 同样设置为非阻塞模式
channel.configureBlocking(false);
// 同样将于客户端的通道在selector上注册,OP_READ对应可读事件(对方有写入数据),可以通过key获取关联的选择器
ByteBuffer buffer = ByteBuffer.allocate(1024);
channel.register(key.selector(), SelectionKey.OP_READ, buffer);
}
// OP_READ 有数据可读
if (key.isReadable()) {
System.out.println("正在读");
SocketChannel channel = (SocketChannel) key.channel();
// 得到附件,就是上面SocketChannel进行register的时候的第三个参数,可为随意Object
ByteBuffer buffer = (ByteBuffer) key.attachment();
// 读数据
channel.read(buffer);
buffer.flip();
byte[] arr = new byte[buffer.limit()];
while (buffer.hasRemaining()) {
int i = 0;
arr[i]=buffer.get();
System.out.println("B:"+arr[i]);
i++;
}
System.out.println("C:"+new String(arr,"utf-8"));
buffer.clear();
// 改变自身关注事件,可以用位或操作|组合时间
key.interestOps( SelectionKey.OP_WRITE|SelectionKey.OP_READ);
}
// OP_WRITE 可写状态 这个状态通常总是触发的,所以只在需要写操作时才进行关注
if (key.isWritable()) {
System.out.println("正在写");
// 写数据掠过,可以自建buffer,也可用附件对象(看情况),注意buffer写入后需要flip
// ......
// 写完就吧写状态关注去掉,否则会一直触发写事件
key.interestOps(SelectionKey.OP_READ);
}
// 由于select操作只管对selectedKeys进行添加,所以key处理后我们需要从里面把key去掉
it.remove();
}
//for (SelectionKey key : keys) {
// }
}
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
再来看看webSocket,spring集成实现
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-websocketartifactId>
<version>${spring.version}version>
dependency>
<dependency>
<groupId>javax.websocketgroupId>
<artifactId>javax.websocket-apiartifactId>
<version>1.0version>
<scope>providedscope>
dependency>
其他核心jar自行添加,另spring需4.0以上版本
package com.fp.controller.websocket;
import java.util.ArrayList;
import java.util.List;
import org.springframework.web.socket.CloseStatus;
import org.springframework.web.socket.WebSocketHandler;
import org.springframework.web.socket.WebSocketMessage;
import org.springframework.web.socket.WebSocketSession;
public class MyWebSocketHandler implements WebSocketHandler{
private List sessions = new ArrayList();
//建立
@Override
public void afterConnectionEstablished(WebSocketSession session) throws Exception {
System.out.println("afterConnectionEstablished");
sessions.add(session);
}
//发送消息
@Override
public void handleMessage(WebSocketSession session, WebSocketMessage> message) throws Exception {
System.out.println("handleMessage");
for(WebSocketSession se : sessions){
se.sendMessage(message);
}
System.out.println(message);
session.sendMessage(message);
}
//异常
@Override
public void handleTransportError(WebSocketSession session, Throwable exception) throws Exception {
System.out.println("handleTransportError");
}
//关闭
@Override
public void afterConnectionClosed(WebSocketSession session, CloseStatus closeStatus) throws Exception {
System.out.println("afterConnectionClosed");
}
@Override
public boolean supportsPartialMessages() {
System.out.println("supportsPartialMessages");
return false;
}
}
package com.fp.controller.websocket;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import org.apache.http.HttpMessage;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.web.socket.WebSocketHandler;
import org.springframework.web.socket.server.support.HttpSessionHandshakeInterceptor;
public class MyHttpSessionHandshakeInterceptor extends HttpSessionHandshakeInterceptor{
@Override
public boolean beforeHandshake(ServerHttpRequest request,
ServerHttpResponse response, WebSocketHandler wsHandler,
Map attributes) throws Exception {
// 解决The extension [x-webkit-deflate-frame] is not supported问题
if (request.getHeaders().containsKey("Sec-WebSocket-Extensions")) {
request.getHeaders().set("Sec-WebSocket-Extensions",
"permessage-deflate");
}
System.out.println("Before Handshake");
/* String domain = request.getHeaders().getOrigin();
System.out.println(domain);
HttpHeaders headers = response.getHeaders();
headers.setAccessControlAllowOrigin(domain);
List list = new ArrayList();
list.add(HttpMethod.GET);
list.add(HttpMethod.POST);
list.add(HttpMethod.DELETE);
list.add(HttpMethod.POST);
list.add(HttpMethod.OPTIONS);
headers.setAccessControlAllowMethods(list);
headers.setAccessControlAllowCredentials(true);*/
/*headers
response.setHeader("Access-Control-Allow-Headers", "Content-Type,Access-Control-Expose-Headers");
response.setHeader("Access-Control-Expose-Headers", "Content-Type"); */
return super.beforeHandshake(request, response, wsHandler, attributes);
}
@Override
public void afterHandshake(ServerHttpRequest request,
ServerHttpResponse response, WebSocketHandler wsHandler,
Exception ex) {
System.out.println("After Handshake");
/*String domain = request.getHeaders().getOrigin();
HttpHeaders headers = response.getHeaders();
headers.setAccessControlAllowOrigin(domain);
List list = new ArrayList();
list.add(HttpMethod.GET);
list.add(HttpMethod.POST);
list.add(HttpMethod.DELETE);
list.add(HttpMethod.POST);
list.add(HttpMethod.OPTIONS);
headers.setAccessControlAllowMethods(list);
headers.setAccessControlAllowCredentials(true);*/
ex.printStackTrace();
super.afterHandshake(request, response, wsHandler, ex);
}
}
<bean id="websocket" class="com.fp.controller.websocket.MyWebSocketHandler"/>
<websocket:handlers allowed-origins="*">
<websocket:mapping path="/websocket" handler="websocket"/>
<websocket:handshake-interceptors>
<bean class="com.fp.controller.websocket.MyHttpSessionHandshakeInterceptor"/>
websocket:handshake-interceptors>
websocket:handlers>
如果前端用sockjs,websocket:handlers需加节点
(这个还没有试过,只是别处的方法)
<websocket:sockjs/>
<html>
<head>
<meta charset="UTF-8">
<script src="js/jquery-1.7.2.min.js" type="text/javascript" charset="utf-8">script>
<title>title>
head>
<body>
<input type="" name="" id="sendtext" value="" />
<input type="button" id="send" value="发送" />
<div id="return">
div>
body>
<script type="text/javascript">
var url = 'ws://localhost:8080/ws/websocket';
//var url = 'ws://echo.websocket.org/';
var socket = new WebSocket(url);
socket.onopen = function( ){
console.log("open ");
$("#return").text("open");
}
socket.onclose = function( ){
console.log("close ");
$("#return").text("close");
}
socket.onmessage = function(e){
console.log("onmessage ");
console.log(e.data);
$("#return").text(e.data);
}
$("#send").click(function(){
var text = $("#sendtext").val();
socket.send(text);
});
script>
html>
如果报403错误,是跨域没有配置allowed-origins="*"
属性
https://www.cnblogs.com/zhjh256/p/6052102.html
http://ifeve.com/java-nio-all/