├── .gitignore
├── README.md
├── benchmark.py
├── client.html
├── pom.xml
├── src
└── main
│ └── java
│ └── net
│ └── mengkang
│ ├── WebSocketServer.java
│ ├── WebSocketServerHandler.java
│ ├── WebSocketServerInitializer.java
│ ├── dto
│ └── Response.java
│ ├── entity
│ └── Client.java
│ └── service
│ ├── MessageService.java
│ └── RequestService.java
└── websocket.iml
/.gitignore:
--------------------------------------------------------------------------------
1 | /websocket.iml
2 | /.idea
3 | /netty-websocket.iml
4 | ### Java template
5 | *.class
6 |
7 | # Mobile Tools for Java (J2ME)
8 | .mtj.tmp/
9 |
10 | # Package Files #
11 | *.jar
12 | *.war
13 | *.ear
14 |
15 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
16 | hs_err_pid*
17 |
18 | # Created by .ignore support plugin (hsz.mobi)
19 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # netty-websocket
2 |
3 | ## launch
4 |
5 | At first, java 8 should be supported in your server.
6 |
7 | Run `net.mengkang.WebSocketServer` in IDE, Then you can open the `client.html` in your browser for testing.
8 |
9 | ## benchmark
10 |
11 | you can use the script `banchmark.py` in command line.
12 |
13 | ## demo
14 |
15 | https://mengkang.net/demo/websocket/2.html
16 |
--------------------------------------------------------------------------------
/benchmark.py:
--------------------------------------------------------------------------------
1 | # -*- encoding: utf-8 -*-
2 | #
3 |
4 | import sys
5 | import time
6 | import struct
7 | import logging
8 | import socket
9 | import threading
10 | import random
11 | import StringIO
12 |
13 | HOST = "127.0.0.1"
14 | PORT = 8083
15 |
16 | # 并发连接数
17 | CONNECTIONS = 200
18 |
19 | # 每个连接发送消息间隔时间,单位秒
20 | MSG_INTERVAL = 5
21 |
22 | logging.basicConfig(format='%(levelname)s %(asctime)-15s %(thread)-8d %(message)s', level=logging.DEBUG)
23 | log = logging.getLogger("SocketTest")
24 |
25 | def encodeFrame(d):
26 | """
27 | @param d dict, keys maybe:
28 | FIN: FIN
29 | opCode: type of payloadData
30 | length: length of payloadData
31 | payloadData: the real data to send
32 | maskingKey: list of 4 unsigned chars, optional
33 |
34 | See http://tools.ietf.org/html/rfc6455
35 |
36 | """
37 | k = (1 if 'maskingKey' in d else 0)
38 | s = StringIO.StringIO()
39 | s.write(struct.pack('B', (d['FIN'] << 7) + d['opCode']))
40 | l = d['length']
41 | if l < 126:
42 | s.write(struct.pack('B', (k << 7) + l))
43 | elif (l < 0x10000):
44 | s.write(struct.pack('B', (k << 7) + 126))
45 | s.write(struct.pack('>H', l))
46 | else:
47 | s.write(struct.pack('B', (k << 7) + 127))
48 | s.write(struct.pack('>Q', l))
49 |
50 | if k:
51 | i = 0
52 | while i < 4:
53 | s.write(struct.pack('B', d['maskingKey'][i]))
54 | i += 1
55 | i = 0
56 | while i < l:
57 | s.write(struct.pack('B', struct.unpack('B', d['payloadData'][i])[0] ^ d['maskingKey'][i % 4]))
58 | i += 1
59 | else:
60 | s.write(d['payloadData'])
61 | s.seek(0)
62 | content = s.read()
63 | s.close()
64 | return content
65 |
66 | def decodeFrame(d):
67 | return d
68 |
69 | class PluginThread(threading.Thread):
70 |
71 | def __init__(self):
72 | threading.Thread.__init__(self, name="SocketTest")
73 | try:
74 | self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
75 | except Exception as msg:
76 | self.sock = None
77 | log.error("Error create socket: %s", msg)
78 | if self.sock is None:
79 | return
80 | try:
81 | self.sock.connect((HOST, PORT))
82 | except Exception as msg:
83 | self.sock = None
84 | log.error("Error connect socket: %s", msg)
85 |
86 | def run(self):
87 | count = 0
88 | log.debug("SocketTest thread started")
89 | self.connect()
90 | while (True):
91 | self.sendmsg(('测试消息' * 10) + str(random.randint(0,10000)))
92 | time.sleep(MSG_INTERVAL)
93 | count += 1
94 | if count >= 10:
95 | break
96 | self.sendclose()
97 | self.sock.close()
98 | log.debug("SocketTest thread finished")
99 |
100 | def connect(self):
101 | content = "GET /websocket/?request=eyJpZCI6MTg1NjYyMjQxMjA2MDMxOSwiYWRtaW4iOjEsIm5hbWUiOiJcdTRlNjBcdThmZDFcdTVlNzMiLCJ0b2tlbiI6ImFmMjFhZDhhZmIxMjhiNmU1ZjdkNDgxNzQ4NTJiYjg1MWZhMmJmOGMwNGZmY2FmMmExMzQ3MzZhZGQ2MTUwYzYxIn0= HTTP/1.1\r\nUpgrade: WebSocket\r\nConnection: Upgrade\r\nHost: "+HOST+':'+str(PORT)+"\r\nOrigin: https://yq.aliyun.com\r\nSec-WebSocket-Version: 13\r\nSec-WebSocket-Key: AQIDBAUGBwgJCgsMDQ4PEC==\r\n\r\n"
102 | self.sock.sendall(content)
103 | log.debug("SocketTest send handshake msg")
104 | self.recvmsg()
105 |
106 | def sendmsg(self, msg):
107 | log.debug("SocketTest send msg: %s", msg)
108 | log.debug('%r', encodeFrame({'length': len(msg),
109 | 'opCode': 1, 'FIN': 1, 'payloadData': msg, 'maskingKey': [0x25, 0x98, 0x67, 0x99]}))
110 | self.sock.sendall(encodeFrame({'length': len(msg),
111 | 'opCode': 1, 'FIN': 1, 'payloadData': msg, 'maskingKey': [0x25, 0x98, 0x67, 0x99]}))
112 |
113 | def sendclose(self):
114 | log.debug("SocketTest send close frame")
115 | code = 1000 # a normal closure
116 | msg = struct.pack('>H', code) + '关闭连接'
117 | self.sock.sendall(encodeFrame({'length': len(msg),
118 | 'opCode': 8, 'FIN': 1, 'payloadData': msg, 'maskingKey': [0x25, 0x98, 0x67, 0x99]}))
119 |
120 | def recvmsg(self):
121 | buf = []
122 | s = self.sock.recv(1024)
123 | buf.append(s)
124 | msg = "".join(buf)
125 | log.debug("SocketTest recv msg: %r", msg)
126 |
127 | if __name__ == '__main__':
128 | tasks = []
129 | for i in range(CONNECTIONS):
130 | tasks.append(PluginThread())
131 | for task in tasks:
132 | task.start()
133 |
134 | sys.exit(0)
135 |
--------------------------------------------------------------------------------
/client.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
36 |
40 |
41 |
--------------------------------------------------------------------------------
/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 | 4.0.0
6 |
7 | mengkang.net
8 | websocket
9 | 1.0-SNAPSHOT
10 |
11 |
12 |
13 | UTF-8
14 | 5.0.0.Alpha2
15 |
16 |
17 |
18 |
19 |
20 | org.apache.maven.plugins
21 | maven-compiler-plugin
22 |
23 | 1.7
24 | 1.7
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 | io.netty
34 | netty-all
35 | 5.0.0.Alpha2
36 |
37 |
38 | org.slf4j
39 | slf4j-api
40 | 1.7.13
41 |
42 |
43 | ch.qos.logback
44 | logback-classic
45 | 1.1.3
46 |
47 |
48 | com.jcraft
49 | jzlib
50 | 1.1.2
51 |
52 |
53 | org.json
54 | json
55 | 20141113
56 |
57 |
58 | commons-codec
59 | commons-codec
60 | 1.10
61 |
62 |
63 |
--------------------------------------------------------------------------------
/src/main/java/net/mengkang/WebSocketServer.java:
--------------------------------------------------------------------------------
1 | package net.mengkang;
2 |
3 | import io.netty.bootstrap.ServerBootstrap;
4 | import io.netty.channel.Channel;
5 | import io.netty.channel.EventLoopGroup;
6 | import io.netty.channel.nio.NioEventLoopGroup;
7 | import io.netty.channel.socket.nio.NioServerSocketChannel;
8 | import io.netty.handler.logging.LogLevel;
9 | import io.netty.handler.logging.LoggingHandler;
10 |
11 |
12 | public final class WebSocketServer {
13 |
14 | private static final int PORT = 8083;
15 |
16 | public static void main(String[] args) throws Exception {
17 |
18 | EventLoopGroup bossGroup = new NioEventLoopGroup(1);
19 | EventLoopGroup workerGroup = new NioEventLoopGroup();
20 | try {
21 | ServerBootstrap b = new ServerBootstrap();
22 | b.group(bossGroup, workerGroup)
23 | .channel(NioServerSocketChannel.class)
24 | .handler(new LoggingHandler(LogLevel.INFO))
25 | .childHandler(new WebSocketServerInitializer());
26 |
27 | Channel ch = b.bind(PORT).sync().channel();
28 | ch.closeFuture().sync();
29 | } finally {
30 | bossGroup.shutdownGracefully();
31 | workerGroup.shutdownGracefully();
32 | }
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/src/main/java/net/mengkang/WebSocketServerHandler.java:
--------------------------------------------------------------------------------
1 | package net.mengkang;
2 |
3 | import io.netty.buffer.ByteBuf;
4 | import io.netty.buffer.Unpooled;
5 | import io.netty.channel.*;
6 | import io.netty.channel.group.ChannelGroup;
7 | import io.netty.channel.group.DefaultChannelGroup;
8 | import io.netty.handler.codec.http.*;
9 | import io.netty.handler.codec.http.websocketx.*;
10 | import io.netty.util.CharsetUtil;
11 | import io.netty.util.concurrent.GlobalEventExecutor;
12 | import net.mengkang.dto.Response;
13 | import net.mengkang.entity.Client;
14 | import net.mengkang.service.MessageService;
15 | import net.mengkang.service.RequestService;
16 | import org.json.JSONObject;
17 |
18 | import java.util.List;
19 | import java.util.Map;
20 | import java.util.concurrent.ConcurrentHashMap;
21 |
22 | import static io.netty.handler.codec.http.HttpHeaderNames.HOST;
23 | import static io.netty.handler.codec.http.HttpMethod.GET;
24 | import static io.netty.handler.codec.http.HttpResponseStatus.*;
25 | import static io.netty.handler.codec.http.HttpVersion.HTTP_1_1;
26 |
27 | public class WebSocketServerHandler extends SimpleChannelInboundHandler