Quantcast
Channel: 懒得折腾
Viewing all articles
Browse latest Browse all 764

Pattern-Oriented Software Architectures for Concurrent and Networked Software Echo Server

$
0
0

ATTN: As with all peer-assessments, you will receive the average of your 4 scores.  For the programming assignments, you will receive the maximum score out of the ones you submit (so if you get 30/30 for Java and 20/30 for C++, you will get 30/30 for Programming Assignment 2).

The purpose of this assignment is to deepen your understanding of the Wrapper Facade pattern, the Reactor pattern and the (Acceptor role of the) Acceptor-Connector pattern in the context of Java Netty. In particular, you will write a platform-independent reactive server program that accepts a connection from a client and echos back what the client sent. In brackets below are some hints about what kinds of Netty classes you might want to look up to do these (see the Netty Javadocs and examples for more background).

In addition to the Netty NIO Socket wrappers, the component that you should use for this assignment are the ServerBootstrap and ServerSocketChannelFactory. The other hints are simply for your convenience.

The reactive server program should do the following activities:
  • Create an EchoServerHandler that inherits from SimpleChannelUpstreamHandler and implement its messageReceived() hook method so that it echos back back the client’s input either (a) a “chunk” at a time or (b) a “line” at a time (i.e., until the symbols “\n”, “\r”, or “\r\n” are read), rather than a character at a time.   You can additionally override methods such as channelOpen for example to show logging information.
  • Create a ChannelPipelineFactory which sets up a pipeline for processing the inbound messages.
  • Configure the ServerSocketChannelFactory that inherits from ServerChannelFactory, and binds the ServerBootstrap class to an appropriate InetSocketAddress.
  • Configure the ServerBootstrap class with appropriate thread pools to run the events. Configure the bootstrap object with a PipelineFactory.
  • When a client connection request arrives, the ServerChannelFactory and ChannelPipeline work together to pass events to the EchoServerHandler.
  • Note that implementing SimpleChannelUpstreamHandler takes care of the connection acceptance.
  • For this assignment, you can simply exit the server process (using Ctrl+C) when you’re done, which will close down all the open sockets.

Make sure your solution clearly indicates which classes play which roles in the Wrapper Facade, Reactor, and/or Acceptor-Connector patterns.

Please implement this server program in Java using the the Netty library–which you can download from http://netty.io/ (we recommend you use Netty 3.6.x) along with  documentation at http://docs.jboss.org/netty/3.2/guide/–and put the server code in a single file.  A second file may optionally be included for a simple console client (clearly named) if you write one during your solution development as a test program and wish to include it for the convenience of evaluators, but it is not required and will not be assessed as part of this exercise, just used as a test driver for your server.  You can also use a telnet client (such as putty) as a test driver for your server.

An evaluator should be able to compile it with a command such as “javac -cp netty.jar Program.java”  and execute it with ‘java -cp .:netty.jar Program port_number’ (and ’java -cp .;netty.jar Program port_number’ for Windows) and your program should successfully run! Note that the netty’s distribution jar (which can be downloaded here http://netty.io/downloads.html ) is provided by the evaluator.Rubric30 possible points
  •  5 points – code is well-structured and design makes sense
  • 15 points – Wrapper Facade, Reactor, and Acceptor-Connector patterns are used correctly (and the classes in the solution are clearly mapped to the appropriate roles in the patterns)
  • 5 points – code demonstrates the appropriate level of readability for others to understand the intent and execution of the program
  • 5 points – code compiles and runs smoothly

NOTE: Name your file something like “Program.txt” instead of “Program.java” so that the uploader will accept your code!

javac -cp netty-3.6.5.Final.jar;junit-4.8.2.jar *.java
java -cp .;netty-3.6.5.Final.jar EchoServer

java -cp .;netty-3.6.5.Final.jar EchoClient

java -cp .;netty-3.6.5.Final.jar;junit-4.8.2.jar EchoServiceTester localhost 8080

 

//package posacns;

import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertTrue;

import java.io.IOException;
import java.net.Socket;
import java.net.UnknownHostException;
import java.util.ArrayList;

import junit.framework.JUnit4TestAdapter;
import junit.textui.TestRunner;

import org.junit.Test;

public class EchoServiceTester {

    public static final String ERROR_BYTES = "Server sent wrong bytes after service reaction time. Try to increase the service reaction time if the response came truncated.";

    private static int portNumber = 2048;
    private static String hostName = "127.0.0.1";
    private static int serverReactionTime = 100;
    private static String EOL = System.getProperty("line.separator");

    public static void main(String[] args) {

        System.out.println("Stand alone textui JUnit test.");
        System.out.println();
        System.out.println("-------------------------------------------------------------------------------");
        System.out.println();
        System.out.println("Usage: [portNumber [hostName [serverReactionTime]]]");
        System.out.println();
        System.out.println("Arguments:");
        System.out.println();
        System.out.println("    portNumber           The server port.");
        System.out.println("    hostName             Server host name.");
        System.out.println("    serverReactionTime   The time in milliseconds that this test will wait");
        System.out.println("                         for the server to reply.");
        System.out.println();
        System.out.println("Default values are:");
        System.out.println();
        System.out.println("    portNumber           = " + portNumber);
        System.out.println("    hostName             = " + hostName);
        System.out.println("    serverReactionTime   = " + serverReactionTime);
        System.out.println();
        System.out.println("-------------------------------------------------------------------------------");
        System.out.println();

        if (args.length >= 1) {
            try {
                portNumber = Integer.parseInt(args[0]);
            } catch (NumberFormatException e) {
                System.err.println(String.format("Invalid argument: %s. Should be integer.", args[0]));
                System.exit(1);
            }
        }

        if (args.length >= 2) {
            hostName = args[1];
        }

        if (args.length == 3) {
            try {
                serverReactionTime = Integer.parseInt(args[2]);
            } catch (NumberFormatException e) {
                System.err.println(String.format("Invalid argument: %s. Should be integer.", args[2]));
                System.exit(2);
            }
        }

        System.out.println("Using these parameters:");
        System.out.println();
        System.out.println("    portNumber           = " + portNumber);
        System.out.println("    hostName             = " + hostName);
        System.out.println("    serverReactionTime   = " + serverReactionTime);
        System.out.println();
        System.out.println("-------------------------------------------------------------------------------");
        System.out.println();

        TestRunner.run(new JUnit4TestAdapter(EchoServiceTester.class));

    }

    @Test
    public void connectionToServer() throws UnknownHostException, IOException {

        /*
         * Description
         */

        System.out.println("Checking if it is possible to connect to the server...");

        /*
         * Test
         */

        Socket socket = new Socket(hostName, portNumber);
        assertTrue("Could not create a socket to the server.", socket.isConnected());
        socket.close();
        assertTrue("Successfully created a socket to the server, but failed to close this socket.", socket.isClosed());

    }

    @Test
    public void echoFromServer() throws UnknownHostException, IOException, InterruptedException {

        /*
         * Description
         */

        System.out.println("Checking if the server echoes a short message...");

        /*
         * Test
         */

        Socket socket = new Socket(hostName, portNumber);
        byte[] bytes = ("ABCDEFGHIJKLMNOPQRSTUVWXYZ" + EOL).getBytes();
        socket.getOutputStream().write(bytes);
        // Waits for the server to respond
        Thread.sleep(serverReactionTime);
        int available = socket.getInputStream().available();
        byte[] actual = new byte[available];
        socket.getInputStream().read(actual);
        socket.close();

        assertArrayEquals(ERROR_BYTES, bytes, actual);

    }

    @Test
    public void randomLongEchoFromServer() throws UnknownHostException, IOException, InterruptedException {

        /*
         * Test parameter
         */
        int messageSize = 1024;

        /*
         * Description
         */

        System.out.println("Checking if the server echoes a long random message...");

        /*
         * Test
         */

        byte[] bytes0 = new byte[messageSize];
        for (int i = 0; i < messageSize; i++) {
            bytes0[i] = (byte)(32 + Math.random() * (126 - 32));
        }

        byte[] bytes = (new String(bytes0) + EOL).getBytes();

        Socket socket = new Socket(hostName, portNumber);
        socket.getOutputStream().write(bytes);
        // Waits for the server to respond
        Thread.sleep(serverReactionTime);
        int available = socket.getInputStream().available();
        byte[] actual = new byte[available];
        socket.getInputStream().read(actual);
        socket.close();

        assertArrayEquals(ERROR_BYTES, bytes, actual);

    }

    @Test
    public void multipleEchoesFromServer() throws UnknownHostException, IOException, InterruptedException {

        /*
         * Test parameter
         */

        int messages = 300;

        /*
         * Description
         */

        System.out.println("Checking if the server echoes several messages from the same client...");

        /*
         * Test
         */

        Socket socket = new Socket(hostName, portNumber);

        for (int i = 0; i < messages; i++) {

            byte[] bytes = ("Message" + i + EOL).getBytes();
            socket.getOutputStream().write(bytes);
            // Waits for the server to respond
            Thread.sleep(serverReactionTime);
            int available = socket.getInputStream().available();
            byte[] actual = new byte[available];
            socket.getInputStream().read(actual);
            assertArrayEquals(ERROR_BYTES, bytes, actual);

            System.out.print(".");

        }

        System.out.println();

        socket.close();

    }

    @Test
    public void concurrentClients() throws UnknownHostException, IOException, InterruptedException {

        /*
         * Test parameter
         */

        int numConcurrentClients = 40;

        /*
         * Description
         */

        System.out.println("Checking if the server echoes several messages from several clients simultaneously connected (although the server is NOT required to pass this test)...");

        /*
         * Test
         */

        ArrayList sockets = new ArrayList(numConcurrentClients);

        for (int i = 0; i < numConcurrentClients; i++) {
            sockets.add(new Socket(hostName, portNumber));
        }

        // Random strings
        ArrayList testData = new ArrayList();
        testData.add("Rio");
        testData.add("Tokyo");
        testData.add("NYC");
        testData.add("Madrid");
        testData.add("Cairo");

        for (String testDatum : testData) {
            for (Socket socket : sockets) {
                byte[] bytes = (testDatum + EOL).getBytes();
                socket.getOutputStream().write(bytes);
                // Waits for the server to respond
                Thread.sleep(serverReactionTime);
                int available = socket.getInputStream().available();
                byte[] actual = new byte[available];
                socket.getInputStream().read(actual);

                assertArrayEquals(ERROR_BYTES, bytes, actual);

                System.out.print(".");
            }
        }

        System.out.println();

       for (Socket socket : sockets) {
            socket.close();
        }
    }

}

/*
 * Copyright 2012 The Netty Project
 *
 * The Netty Project licenses this file to you under the Apache License,
 * version 2.0 (the "License"); you may not use this file except in compliance
 * with the License. You may obtain a copy of the License at:
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations
 * under the License.
 */
//package org.jboss.netty.example.echo;

import java.net.InetSocketAddress;
import java.util.concurrent.Executors;

import org.jboss.netty.bootstrap.ServerBootstrap;
import org.jboss.netty.channel.ChannelPipeline;
import org.jboss.netty.channel.ChannelPipelineFactory;
import org.jboss.netty.channel.Channels;
import org.jboss.netty.channel.socket.nio.NioServerSocketChannelFactory;

/**
 * Echoes back any received data from a client.
 */
public class EchoServer {

    private final int port;

    public EchoServer(int port) {
        this.port = port;
    }

    public void run() {
        // Configure the server.
        ServerBootstrap bootstrap = new ServerBootstrap(
                new NioServerSocketChannelFactory(
                        Executors.newCachedThreadPool(),
                        Executors.newCachedThreadPool()));

        // Set up the pipeline factory.
        bootstrap.setPipelineFactory(new ChannelPipelineFactory() {
            public ChannelPipeline getPipeline() throws Exception {
                return Channels.pipeline(new EchoServerHandler());
            }
        });

        // Bind and start to accept incoming connections.
        bootstrap.bind(new InetSocketAddress(port));
    }

    public static void main(String[] args) throws Exception {
        int port;
        if (args.length > 0) {
            port = Integer.parseInt(args[0]);
        } else {
            port = 8080;
        }
        new EchoServer(port).run();
    }
}

/*
 * Copyright 2012 The Netty Project
 *
 * The Netty Project licenses this file to you under the Apache License,
 * version 2.0 (the "License"); you may not use this file except in compliance
 * with the License. You may obtain a copy of the License at:
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations
 * under the License.
 */
//package org.jboss.netty.example.echo;

import java.util.concurrent.atomic.AtomicLong;
import java.util.logging.Level;
import java.util.logging.Logger;

import org.jboss.netty.buffer.ChannelBuffer;
import org.jboss.netty.buffer.ChannelBuffers;
import org.jboss.netty.channel.ChannelHandlerContext;
import org.jboss.netty.channel.ExceptionEvent;
import org.jboss.netty.channel.MessageEvent;
import org.jboss.netty.channel.SimpleChannelUpstreamHandler;

/**
 * Handler implementation for the echo server.
 */
public class EchoServerHandler extends SimpleChannelUpstreamHandler {

    private static final Logger logger = Logger.getLogger(
            EchoServerHandler.class.getName());
	private static final int CHUNK_SIZE = 50;
    private final AtomicLong transferredBytes = new AtomicLong();

	private byte[] state = new byte[CHUNK_SIZE];
	private int stateSize = 0;

    public long getTransferredBytes() {
        return transferredBytes.get();
    }

    @Override
    public void messageReceived(
            ChannelHandlerContext ctx, MessageEvent e) {
        // Send back the received message to the remote peer.
		ChannelBuffer cb = (ChannelBuffer) e.getMessage();
		byte[] input = cb.array();
        //transferredBytes.addAndGet(((ChannelBuffer) e.getMessage()).readableBytes());
		for (int i = 0; i < input.length; i++)
			System.out.println(input[i]);
		System.out.println("");
		for (int i = 0; i < input.length; i++) {
			if (stateSize == CHUNK_SIZE - 1) { //It is full.
				e.getChannel().write(ChannelBuffers.wrappedBuffer(state));
				stateSize = 0;
			} else if (input[i] == 10 || input[i] == 13) {
				byte[] output = new byte[stateSize + 1];
				for (int j = 0; j < stateSize; j++) {
					output[j] = state[j];
				}
				output[stateSize] = input[i];
				e.getChannel().write(ChannelBuffers.wrappedBuffer(output));
				stateSize = 0;
			} else {
				state[stateSize] = input[i];
				stateSize++;
			}
		}
        //e.getChannel().write(e.getMessage());
		//e.getChannel().write(ChannelBuffers.wrappedBuffer(input));
    }

    @Override
    public void exceptionCaught(
            ChannelHandlerContext ctx, ExceptionEvent e) {
        // Close the connection when an exception is raised.
        logger.log(
                Level.WARNING,
                "Unexpected exception from downstream.",
                e.getCause());
        e.getChannel().close();
    }
}

/*
 * Copyright 2012 The Netty Project
 *
 * The Netty Project licenses this file to you under the Apache License,
 * version 2.0 (the "License"); you may not use this file except in compliance
 * with the License. You may obtain a copy of the License at:
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations
 * under the License.
 */
//p/ackage org.jboss.netty.example.echo;

import java.net.InetSocketAddress;
import java.util.concurrent.Executors;

import org.jboss.netty.bootstrap.ClientBootstrap;
import org.jboss.netty.channel.ChannelFuture;
import org.jboss.netty.channel.ChannelPipeline;
import org.jboss.netty.channel.ChannelPipelineFactory;
import org.jboss.netty.channel.Channels;
import org.jboss.netty.channel.socket.nio.NioClientSocketChannelFactory;

/**
 * Sends one message when a connection is open and echoes back any received
 * data to the server.  Simply put, the echo client initiates the ping-pong
 * traffic between the echo client and server by sending the first message to
 * the server.
 */
public class EchoClient {

    private final String host;
    private final int port;
    private final int firstMessageSize;

    public EchoClient(String host, int port, int firstMessageSize) {
        this.host = host;
        this.port = port;
        this.firstMessageSize = firstMessageSize;
    }

    public void run() {
        // Configure the client.
        ClientBootstrap bootstrap = new ClientBootstrap(
                new NioClientSocketChannelFactory(
                        Executors.newCachedThreadPool(),
                        Executors.newCachedThreadPool()));

        // Set up the pipeline factory.
        bootstrap.setPipelineFactory(new ChannelPipelineFactory() {
            public ChannelPipeline getPipeline() throws Exception {
                return Channels.pipeline(
                        new EchoClientHandler(firstMessageSize));
            }
        });

        // Start the connection attempt.
        ChannelFuture future = bootstrap.connect(new InetSocketAddress(host, port));

        // Wait until the connection is closed or the connection attempt fails.
        future.getChannel().getCloseFuture().awaitUninterruptibly();

        // Shut down thread pools to exit.
        bootstrap.releaseExternalResources();
    }

    public static void main(String[] args) throws Exception {
        // Print usage if no argument is specified.
        if (args.length < 2 || args.length > 3) {
            System.err.println(
                    "Usage: " + EchoClient.class.getSimpleName() +
                    "   []");
            return;
        }

        // Parse options.
        final String host = args[0];
        final int port = Integer.parseInt(args[1]);
        final int firstMessageSize;
        if (args.length == 3) {
            firstMessageSize = Integer.parseInt(args[2]);
        } else {
            firstMessageSize = 256;
        }

        new EchoClient(host, port, firstMessageSize).run();
    }
}

/*
 * Copyright 2012 The Netty Project
 *
 * The Netty Project licenses this file to you under the Apache License,
 * version 2.0 (the "License"); you may not use this file except in compliance
 * with the License. You may obtain a copy of the License at:
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations
 * under the License.
 */
//package org.jboss.netty.example.echo;

import java.util.concurrent.atomic.AtomicLong;
import java.util.logging.Level;
import java.util.logging.Logger;

import org.jboss.netty.buffer.ChannelBuffer;
import org.jboss.netty.buffer.ChannelBuffers;
import org.jboss.netty.channel.ChannelHandlerContext;
import org.jboss.netty.channel.ChannelStateEvent;
import org.jboss.netty.channel.ExceptionEvent;
import org.jboss.netty.channel.MessageEvent;
import org.jboss.netty.channel.SimpleChannelUpstreamHandler;

/**
 * Handler implementation for the echo client.  It initiates the ping-pong
 * traffic between the echo client and server by sending the first message to
 * the server.
 */
public class EchoClientHandler extends SimpleChannelUpstreamHandler {

    private static final Logger logger = Logger.getLogger(
            EchoClientHandler.class.getName());

    private final ChannelBuffer firstMessage;
    private final AtomicLong transferredBytes = new AtomicLong();

    /**
     * Creates a client-side handler.
     */
    public EchoClientHandler(int firstMessageSize) {
        if (firstMessageSize <= 0) {
            throw new IllegalArgumentException(
                    "firstMessageSize: " + firstMessageSize);
        }
        firstMessage = ChannelBuffers.buffer(firstMessageSize);
        for (int i = 0; i < firstMessage.capacity(); i ++) {
            firstMessage.writeByte((byte) i);
        }
		//firstMessage.writeByte('\n');
		//System.out.println(firstMessage);
    }

    public long getTransferredBytes() {
        return transferredBytes.get();
    }

    @Override
    public void channelConnected(
            ChannelHandlerContext ctx, ChannelStateEvent e) {
        // Send the first message.  Server will not send anything here
        // because the firstMessage's capacity is 0.
        e.getChannel().write(firstMessage);
    }

    @Override
    public void messageReceived(
            ChannelHandlerContext ctx, MessageEvent e) {
        // Send back the received message to the remote peer.
        //transferredBytes.addAndGet(((ChannelBuffer) e.getMessage()).readableBytes());
       // e.getChannel().write(e.getMessage());
		ChannelBuffer cb = (ChannelBuffer) e.getMessage();
		byte[] input = cb.array();
        //transferredBytes.addAndGet(((ChannelBuffer) e.getMessage()).readableBytes());
		for (int i = 0; i < input.length; i++)
			System.out.println(input[i]);
		System.out.println("");
        //e.getChannel().write(e.getMessage());
		e.getChannel().write(ChannelBuffers.wrappedBuffer(input));
    }

    @Override
    public void exceptionCaught(
            ChannelHandlerContext ctx, ExceptionEvent e) {
        // Close the connection when an exception is raised.
        logger.log(
                Level.WARNING,
                "Unexpected exception from downstream.",
                e.getCause());
        e.getChannel().close();
    }
}




Viewing all articles
Browse latest Browse all 764

Trending Articles