/*******************************************************************************
* Copyright (c) 2011 Intalio, Inc.
* ======================================================================
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* and Apache License v2.0 which accompanies this distribution.
*
* The Eclipse Public License is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* The Apache License v2.0 is available at
* http://www.opensource.org/licenses/apache2.0.php
*
* You may elect to redistribute this code under either of these licenses.
*******************************************************************************/
package org.eclipse.jetty.websocket;
import java.net.InetSocketAddress;
import java.net.URI;
import java.util.Arrays;
import java.util.Random;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
import org.eclipse.jetty.util.StringUtil;
import org.eclipse.jetty.util.TypeUtil;
/**
* This is not a general purpose websocket client.
* It's only for testing the websocket server and is hardwired to a specific draft version of the protocol.
*/
public class TestClient implements WebSocket.OnFrame
{
private static WebSocketClientFactory __clientFactory = new WebSocketClientFactory();
private static boolean _verbose=false;
private static final Random __random = new Random();
private final String _host;
private final int _port;
private final String _protocol;
private final int _timeout;
private static boolean __quiet;
private static int __framesSent;
private static int __messagesSent;
private static AtomicInteger __framesReceived=new AtomicInteger();
private static AtomicInteger __messagesReceived=new AtomicInteger();
private static AtomicLong __totalTime=new AtomicLong();
private static AtomicLong __minDuration=new AtomicLong(Long.MAX_VALUE);
private static AtomicLong __maxDuration=new AtomicLong(Long.MIN_VALUE);
private static long __start;
private BlockingQueue<Long> _starts = new LinkedBlockingQueue<Long>();
int _messageBytes;
int _frames;
byte _opcode=-1;
private volatile WebSocket.FrameConnection _connection;
private final CountDownLatch _handshook = new CountDownLatch(1);
public void onOpen(Connection connection)
{
if (_verbose)
System.err.printf("%s#onHandshake %s %s\n",this.getClass().getSimpleName(),connection,connection.getClass().getSimpleName());
}
public void onClose(int closeCode, String message)
{
_handshook.countDown();
}
public boolean onFrame(byte flags, byte opcode, byte[] data, int offset, int length)
{
try
{
if (_connection.isClose(opcode))
return false;
__framesReceived.incrementAndGet();
_frames++;
_messageBytes+=length;
if (_opcode==-1)
_opcode=opcode;
if (_connection.isControl(opcode) || _connection.isMessageComplete(flags))
{
int recv =__messagesReceived.incrementAndGet();
Long start=_starts.poll();
if (start!=null)
{
long duration = System.nanoTime()-start.longValue();
long max=__maxDuration.get();
while(duration>max && !__maxDuration.compareAndSet(max,duration))
max=__maxDuration.get();
long min=__minDuration.get();
while(duration<min && !__minDuration.compareAndSet(min,duration))
min=__minDuration.get();
__totalTime.addAndGet(duration);
if (!__quiet)
System.out.printf("%d bytes from %s: frames=%d req=%d time=%.1fms opcode=0x%s\n",_messageBytes,_host,_frames,recv,((double)duration/1000000.0),TypeUtil.toHexString(_opcode));
}
_frames=0;
_messageBytes=0;
_opcode=-1;
}
}
catch(Exception e)
{
e.printStackTrace();
}
return false;
}
public void onHandshake(FrameConnection connection)
{
_connection=connection;
_handshook.countDown();
}
public TestClient(String host, int port,String protocol, int timeoutMS) throws Exception
{
_host=host;
_port=port;
_protocol=protocol;
_timeout=timeoutMS;
}
private void open() throws Exception
{
WebSocketClient client = new WebSocketClient(__clientFactory);
client.setProtocol(_protocol);
client.setMaxIdleTime(_timeout);
client.open(new URI("ws://"+_host+":"+_port+"/"),this).get(10,TimeUnit.SECONDS);
}
public void ping(byte opcode,byte[] data,int fragment) throws Exception
{
_starts.add(System.nanoTime());
int off=0;
int len=data.length;
if (fragment>0&& len>fragment)
len=fragment;
__messagesSent++;
while(off<data.length)
{
__framesSent++;
byte flags= (byte)(off+len==data.length?0x8:0);
byte op=(byte)(off==0?opcode:WebSocketConnectionRFC6455.OP_CONTINUATION);
if (_verbose)
System.err.printf("%s#sendFrame %s|%s %s\n",this.getClass().getSimpleName(),TypeUtil.toHexString(flags),TypeUtil.toHexString(op),TypeUtil.toHexString(data,off,len));
_connection.sendFrame(flags,op,data,off,len);
off+=len;
if(data.length-off>len)
len=data.length-off;
if (fragment>0&& len>fragment)
len=fragment;
}
}
public void disconnect() throws Exception
{
if (_connection!=null)
{
_connection.close();
}
}
private static void usage(String[] args)
{
System.err.println("ERROR: "+Arrays.asList(args));
System.err.println("USAGE: java -cp CLASSPATH "+TestClient.class+" [ OPTIONS ]");
System.err.println(" -h|--host HOST (default localhost)");
System.err.println(" -p|--port PORT (default 8080)");
System.err.println(" -b|--binary");
System.err.println(" -v|--verbose");
System.err.println(" -q|--quiet");
System.err.println(" -c|--count n (default 10)");
System.err.println(" -s|--size n (default 64)");
System.err.println(" -f|--fragment n (default 4000) ");
System.err.println(" -P|--protocol echo|echo-assemble|echo-fragment|echo-broadcast");
System.err.println(" -C|--clients n (default 1) ");
System.err.println(" -d|--delay n (default 1000ms) ");
System.exit(1);
}
public static void main(String[] args) throws Exception
{
__clientFactory.start();
String host="localhost";
int port=8080;
String protocol=null;
int count=10;
int size=64;
int fragment=4000;
boolean binary=false;
int clients=1;
int delay=1000;
for (int i=0;i<args.length;i++)
{
String a=args[i];
if ("-p".equals(a)||"--port".equals(a))
port=Integer.parseInt(args[++i]);
else if ("-h".equals(a)||"--host".equals(a))
host=args[++i];
else if ("-c".equals(a)||"--count".equals(a))
count=Integer.parseInt(args[++i]);
else if ("-s".equals(a)||"--size".equals(a))
size=Integer.parseInt(args[++i]);
else if ("-f".equals(a)||"--fragment".equals(a))
fragment=Integer.parseInt(args[++i]);
else if ("-P".equals(a)||"--protocol".equals(a))
protocol=args[++i];
else if ("-v".equals(a)||"--verbose".equals(a))
_verbose=true;
else if ("-b".equals(a)||"--binary".equals(a))
binary=true;
else if ("-C".equals(a)||"--clients".equals(a))
clients=Integer.parseInt(args[++i]);
else if ("-d".equals(a)||"--delay".equals(a))
delay=Integer.parseInt(args[++i]);
else if ("-q".equals(a)||"--quiet".equals(a))
__quiet=true;
else if (a.startsWith("-"))
usage(args);
}
TestClient[] client = new TestClient[clients];
try
{
__start=System.currentTimeMillis();
protocol=protocol==null?"echo":protocol;
for (int i=0;i<clients;i++)
{
client[i]=new TestClient(host,port,protocol==null?null:protocol,60000);
client[i].open();
}
System.out.println("Jetty WebSocket PING "+host+":"+port+
" ("+ new InetSocketAddress(host,port)+") "+clients+" clients "+protocol);
for (int p=0;p<count;p++)
{
long next = System.currentTimeMillis()+delay;
byte opcode=binary?WebSocketConnectionRFC6455.OP_BINARY:WebSocketConnectionRFC6455.OP_TEXT;
byte data[]=null;
if (opcode==WebSocketConnectionRFC6455.OP_TEXT)
{
StringBuilder b = new StringBuilder();
while (b.length()<size)
b.append('A'+__random.nextInt(26));
data=b.toString().getBytes(StringUtil.__UTF8);
}
else
{
data= new byte[size];
__random.nextBytes(data);
}
for (int i=0;i<clients;i++)
client[i].ping(opcode,data,opcode==WebSocketConnectionRFC6455.OP_PING?-1:fragment);
while(System.currentTimeMillis()<next)
Thread.sleep(10);
}
}
finally
{
for (int i=0;i<clients;i++)
if (client[i]!=null)
client[i].disconnect();
long duration=System.currentTimeMillis()-__start;
System.out.println("--- "+host+" websocket ping statistics using "+clients+" connection"+(clients>1?"s":"")+" ---");
System.out.printf("%d/%d frames sent/recv, %d/%d mesg sent/recv, time %dms %dm/s %.2fbps%n",
__framesSent,__framesReceived.get(),
__messagesSent,__messagesReceived.get(),
duration,(1000L*__messagesReceived.get()/duration),
1000.0D*__messagesReceived.get()*8*size/duration/1024/1024);
System.out.printf("rtt min/ave/max = %.3f/%.3f/%.3f ms\n",__minDuration.get()/1000000.0,__messagesReceived.get()==0?0.0:(__totalTime.get()/__messagesReceived.get()/1000000.0),__maxDuration.get()/1000000.0);
__clientFactory.stop();
}
}
}