Socket,ServerSocket,WebSocket
2017-12-13 16:53
302 查看
最近在看webSocket,忽然想到以前学的Socket和ServerSocket,那么他们之间有什么不同呢?还有来回忆下Socket,和学习下webSocket
(天真的我以为写一个ServerSocket,再写一个webSocket就能实现通信了)
Socket和ServerSocket 指传输层网络接口协议,是基于套接字的服务端和客户端实现。
而WebScoket是应用层协议,是客户端-服务器的异步通信方法,用于双向推送消息。
Channels
Buffers
Selectors
Channel 和 Buffer
基本上,所有的 IO 在NIO 中都从一个Channel 开始。Channel 有点象流。 数据可以从Channel读到Buffer中,也可以从Buffer 写到Channel中。这里有个图示:
Selector允许单线程处理多个 Channel。如果你的应用打开了多个连接(通道),但每个连接的流量都很低,使用Selector就会很方便。例如,在一个聊天服务器中。
其他核心jar自行添加,另spring需4.0以上版本
如果前端用sockjs,websocket:handlers需加节点
(这个还没有试过,只是别处的方法)
如果报403错误,是跨域没有配置
(天真的我以为写一个ServerSocket,再写一个webSocket就能实现通信了)
一 区别
首先来说下区别吧,Socket和ServerSocket 指传输层网络接口协议,是基于套接字的服务端和客户端实现。
而WebScoket是应用层协议,是客户端-服务器的异步通信方法,用于双向推送消息。
二 Socket和ServerSocket
ServerSocket简单实现
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简单实现
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(); } }
NIO回顾
Java NIO 由以下几个核心部分组成:Channels
Buffers
Selectors
Channel 和 Buffer
基本上,所有的 IO 在NIO 中都从一个Channel 开始。Channel 有点象流。 数据可以从Channel读到Buffer中,也可以从Buffer 写到Channel中。这里有个图示:
Selector允许单线程处理多个 Channel。如果你的应用打开了多个连接(通道),但每个连接的流量都很低,使用Selector就会很方便。例如,在一个聊天服务器中。
SocketChannel
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<SelectionKey> keys = selector.selectedKeys(); Iterator<SelectionKey> 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 10fb8 .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(); }
ServerSocketChannel
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<SelectionKey> keys = selector.selectedKeys(); // 多说一句selector.keys()返回所有的SelectionKey(包括没有发生事件的) Iterator<SelectionKey> 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
再来看看webSocket,spring集成实现1.添加jar包
<dependency> <groupId>org.springframework</groupId> <artifactId>spring-websocket</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>javax.websocket</groupId> <artifactId>javax.websocket-api</artifactId> <version>1.0</version> <scope>provided</scope> </dependency>
其他核心jar自行添加,另spring需4.0以上版本
2.实现WebSocketHandler,用于处理信息
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<WebSocketSession> sessions = new ArrayList<WebSocketSession>(); //建立 @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; } }
3.实现拦截器HttpSessionHandshakeInterceptor
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<String, Object> 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<HttpMethod> list = new ArrayList<HttpMethod>(); 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<HttpMethod> list = new ArrayList<HttpMethod>(); 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); } }
4.spring配置
<bean id="websocket" class="com.fp.controller.websocket.MyWebSocketHandler"/> <websocket:handlers allowed-origins="*"> <!-- path表示对应的连接 --> <websocket:mapping path="/websocket" handler="websocket"/> <!-- 这个class是连接的流程控制方法,这个重写HttpSessionHandshakeInterceptor的方法--> <websocket:handshake-interceptors> <bean class="com.fp.controller.websocket.MyHttpSessionHandshakeInterceptor"/> </websocket:handshake-interceptors> </websocket:handlers>
如果前端用sockjs,websocket:handlers需加节点
(这个还没有试过,只是别处的方法)
<websocket:sockjs/>
5.前端页面
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <!--<script src="http://libs.baidu.com/jquery/1.10.0/jquery.min.js"></script>--> <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/[/code]
相关文章推荐
- WebSocket(5)-- WebSocket Server
- AndroidAsync :异步Socket,http(client+server),websocket和socket.io的Android类库
- Socket、SocketServer与WebSocket的关系与区别
- Can't connect to local MySQL server through socket '/tmp/mysql.sock'
- Mac下Mysql启动异常["ERROR 2002 (HY000): Can't connect to local MySQL server through socket '/tmp/mysql.so
- 【Java NIO的深入研究】 ServerSocketChannel
- 【Java NIO的深入研究】 ServerSocketChannel
- websocket的那些事 - socket.io-client-java源码浅析
- C# Socket TCP Server & Client & nodejs client
- Error 2002 (HY000): Can't connect to local MySOL server through socket '/var/lib/mysql.sock' (2 "No
- python版websocket server在handshake时返回数据的处理
- centos下mysql问题记录--ERROR 2002 (HY000): Can't connect to local MySQL server through socket '/var/lib/
- day⑧:socket 和socketserver
- HTML5之WebSocket入门3 -通信模型socket.io
- HTML5之WebSocket入门3 -通信模型socket.io
- Android版多线程聊天室——ServerSocket和Socket的使用
- Can’t connect to local MySQL server through socket ‘/tmp/mysql/mysql.sock’解决方法
- 完成端口通讯服务器(IOCP Socket Server)设计(二)内存管理(AWE)
- Socket -Server
- android 跨进程通信 LocalServerSocket LocalSocket Address already in use