Задание к курсовому проекту по дисциплине ”Спецразделы мультимедиатехнологий” Организовать сетевое взаимодействие между двумя мобильными устройствами У1 и У2. Четные варианты используют для обмена протокол TCP, нечетные – протокол UDP. Устройство У1 выступает в роли клиента, а У2 в роли сервера. Схема взаимодействия приведена на рис.1. При этом У1 подготавливает исходные данные и отображает результаты обработки, а вся обработка выполняется в У2. У2 У1 исходные данные Подготовка исходных данных и отображение результатов результаты Обработка Рис.1 – Схема сетевого взаимодействия Варианты заданий 1,2. У2 выполняет роль ОЗУ (оперативного запоминающего устройства) емкостью 16 байт, реализует операции чтения и записи, формирует сигнал готовности по завершении операции, отображает содержимое банка памяти. У1 выставляет адрес на шину адреса, данные на шину данных при операции запись, сигнал записи или чтения на шину управления, отображает содержимое шин адреса, данных и управления. 3,4. У2 выполняет роль переводчика чисел из систем счисления с основаниями (10), (8) и (16) в двоичную систему счисления и отображает реализуемое действие. У1 подготавливает исходные данные для перевода и отображает 8-битный результат на светодиодах. Исходные данные – числа без знака в диапазоне байта: 0 – 255(10), 0 – 377(8), 0 – FF(16). 5,6. У2 выполняет роль переводчика чисел из двоичной системы счисления в системы счисления с основаниями (10), (8) и (16) и отображает реализуемое действие. У1 подготавливает исходные данные для перевода и отображает результат на индикаторном табло (3 семисегментных индикатора).. Исходные данные – числа без знака в диапазоне байта: 00000000 – 11111111(2). 7,8. У2 выполняет роль устройства сортировки массива, отображает исходный массив. У1 подготавливает исходный массив для сортировки, задает направление сортировки и отображает результат сортировки. 9,10. У2 выполняет роль генератора случайных чисел. У1 может задавать сам, а может запрашивать у У2 следующую информацию: фигура (круг или квадрат); цвет фигуры; размер (радиус или сторона квадрата); координаты начального месторасположения; направление перемещения фигуры (влево, вправо, вверх, вниз); скорость перемещения; действие при достижении границы (выход, отражение). При этом могут запрашиваться или некоторые из параметров или все параметры. Отображение выполняется на экране устройства У1. 11,12. У2 отображает исходное изображение и выполняет роль устройства трансформации изображения. У1 готовит исходное изображение, задает тип трансформации и отображает повернутое изображение. Пример разработки Разработка сетевого мобильного приложения по архитектуре “клиент-сервер” между двумя мобильными устройствами. При этом любое устройство может выполнять как функции клиента, так функции сервера. Соединение между устройствами реализуется либо по протоколу UDP, либо по протоколу TCP. В качестве мобильных устройств выберем мобильные телефоны, как наиболее распространенное на сегодняшний день средство коммуникации. В качестве примера рассмотрим следующую задачу. Необходимо реализовать простейший калькулятор, выполняющий четыре элементарных арифметических действия: сложение, вычитание, умножение и деление. Вычисления производятся над четырехразрядными десятичными целыми числами со знаком. Результат может содержать до восьми разрядов со знаком. При этом задача клиента подготовить исходные операнды для вычислений и задать команду, которую необходимо выполнить. Для ввода используется клавиатура телефона, а результаты вычислений отображаются на экране телефона. Сервер выполняет роль арифметического устройства и реализует следующие функции: - получение операндов и операции от клиента; - выполнение требуемой операции; - передача результата операции клиенту. Для работы приложения необходимо определить форматы передаваемых сообщений. Формат сообщения, передаваемого клиентом серверу, представлен на рис.2. знак 1 операнд 1 знак операнд 2 операция 4 1 4 1 11 символов Рис.2 – Формат сообщения клиента Формат имеет фиксированную длину 11 символов, чтобы дополнительно не включать в сообщение длины полей операндов и упростить серверу разбор принятого сообщения. Если операнды имеют длину меньшую, чем 4 символа, то в старшие позиции поля вставляется символ “0”. Поле знака операнда содержит символ “-“, если операнд отрицательный и символ ”0”, в противном случае. Сообщение сервера содержит только результат выполнения арифметической операции и может содержать от 1 до 9 символов. Сокетное соединение Программа сетевого соединения с использованием протокола TCP/IP приведена в листинге 1. Приложение состоит из четырех классов. Класс NetCalcSocketMIDlet – класс мидлета, класс NCCanvas – класс холста устройств, класс NCClient – класс функционирования устройства сокетного клиента, NCServer – класс устройства сокетного сервера. Сервер мидлета NetCalcSocketMIDlet Класс NCServer реализует интерфейс Runnable. Он запускается в отдельном потоке, который отслеживает сетевое соединение с клиентом и отображает информацию в строке состояния холста мидлета. Класс имеет следующие переменные. Ссылка на холст хранится в переменной canvas. Переменная is – ссылка на входной поток InputStream, а os – на выходной поток OutputStream. sc и scn – ссылки на объекты интерфейсов SocketConnection ServerSocketConnection соответственно. Переменные closing_rcv и closing_snd сигнализируют о завершении соединения между сервером и клиентом. Сервер присваивает переменной closing_rcv значение true при получении сообщения ”stop” от клиента. Если соединение завершается по инициативе сервера, то он посылает сообщение ”stop” клиенту и записывает значение true в переменную closing_snd. Метод run() – метод, в котором реализуются основные функции сервера: в строку состояния холста выводится сообщение ”Waiting for peer client…” об ожидании подключения клиента; сервер открывает соединение, вызывая метод Connector.open(“socket://:5000”) возвращая объект scn интерфейса ServerSocketConnection; сервер прослушивает возможные запросы соединения от клиентов, используя метод scn.acceptAndOpen(), который блокирует операции до тех пор пока не появится запрос соединения от клиента. При появлении запроса этот метод принимает запрос, создает новый объект соединения sc , связывает соединение с неиспользуемым сокетом и уведомляет клиента о новом соединении; в строку состояния выводится cообщение об установленном соединении; открывает входной и выходной потоки для соединения; в бесконечном цикле принимаются сообщения клиента, используя метод receiveMessage(InputStream) класса NCCanvas и формируется ответное сообщение. Цикл работает до получения сообщения ”stop” от клиента. В методе decodeAndOperation(String) сервер разбирает полученное от клиента сообщение, выполняет операцию над заданными аргументами, результат преобразуеи в строку и отображает информацию в строке состояния холста мидлета. Метод sendMessage(OutputStream, String) преобразует строку в байтовый массив, который записывается в выходной поток outputStream. Метод close() проверяет значения переменных closing_rcv и closing_snd. Если обе переменные принимают значение true, то закрываются все открытые потоки. Клиент мидлета NetCalcSocketMIDlet Другая часть сетевого кода мидлета NetCalcSocketMIDlet – это класс NCClient, который очень похож на класс NCServer. Класс NCClient также реализует интерфейс Runnable. Переменные класса NCClient совпадают с переменными класса NCServer за исключением переменной scn, которая используется только в серверной части соединения. Метод run() класса клиента отличается от подобного метода в классе сервера и реализует следующие действия: клиент запрашивает соединение у известного сокета, создавая клиентский запрос при помощи метода Connector.open( "socket://localhost:5000" ). В запросе должно быть указано имя сервера (в примере, localhost) и номер порта (в примере, 5000), который обязательно должен совпадать с портом сервера; после установления соединения с сервером в строку состояния холста выводится сообщение "Connected to peer server." ; открывает входной и выходной потоки для соединения; в бесконечном цикле принимаются сообщения сервера, используя метод receiveMessage(InputStream) класса NCCanvas. Цикл работает до получения сообщения ”stop” от сервера. Для посылки сообщений серверу, используется метод sendMessage1( String), который вызывается из класса NCCanvas. Холст мидлета NetCalcSocketMIDlet После создания классов сервера и клиента создается класс NCCanvas. Он выводит в верхней части экрана картинку для сервера или клиента, а в нижней – строку состояния. Строка состояния отображает информацию об установлении и завершении соединения между устройствами, а также информацию о выполняемых вычислениях. Кратко опишем переменные и методы класса NCCanvas. Переменная d хранит ссылку на дисплей и используется для отображения объектов в методе commandAction(Command, Displayable). (ссылка на родительский класс мидлета) служит для Переменная parent вызова методов destroyApp(true) и notifyDestroyed() при завершении работы приложения. Переменные client и server – это объекты классов NCClient и NCServer соответственно. Они отвечают за работу мидлета с сетью. Переменная isClient определяет режим работы мидлета. Если она принимает значение true, то мидлет работает в режиме клиента, в противном случае – в режиме сервера. Для ввода значений аргументов в режиме клиента используется форма, на которую ссылается переменная form_num. Значения аргументов в строковом представлении сохраняются в переменных first_arg и second_arg. Переменная full_flag отвечает за заполнение полей ввода аргументов в форме. Для прекращения работы потока класса NCCanvas используется переменная sleeping. При старте потока в методе start() ей присваивается значение false, а в методе stop() (остановка потока) – значение true. Изображения для клиента и сервера содержатся в переменных ImageClient и ImageServer соответственно. В переменной выполняемой arguments хранится сообщение клиента без указания операции. Для текста, отображаемого в строке состояния, используется переменная status. В методе start(): инициализируются фоновые изображения клиента и сервера; в зависимости от значения переменной isClient создается экземпляр класса NCClient или NCServer ; в режиме “клиент” добавляется команда ”Numbers” (вызов формы для ввода чисел); вызывается метод start(), запускающий поток соединения; добавляются команды “Close” (закрытие соединения) и “Exit”(выход из приложения); стартует поток класса NCCanvas. Метод run() запускает поток класса NCCanvas на выполнение. Он содержит в себе игровой цикл, в котором вызываются методы update() (обработка пользовательского ввода с клавиатуры) и draw() (отображение информации на дисплее). Задержка потока на 1,5 секунды позволяет наблюдать процесс взаимодействия клиента и сервера по отображению строки состояния на холстах этих устройств. Метод update() проверяет нажатие клавиш и работает только в режиме “клиент” при заданных значениях аргументов. Пользователь задает выполняемую команду (операцию) нажимая соответствующую клавишу (табл.1). Таблица 1 - Соответствие клавиш и выполняемых операций Клавиша Действие Влево Сложение Вправо Вычитание Вверх Умножение Вниз Деление К строке arguments добавляется знак операции, и сформированное сообщение посылается серверу при помощи метода sendMessage1(String) класса NCClient. Информация о выполняемой операции отображается в строке состояния клиента. Метод draw() отображает картинку для сервера или клиента и выводит строку состояния. Для доступа к строке состояния объектов классов NCClient и NCServer используются открытые методы setStatus(String) – записать текст в строку состояния и getStatus() – прочитать содержимое строки состояния. В методе CommandAction(Command, Displayable) происходит обработка команд приложения. Команда ”Numbers” создает форму для ввода значений аргументов, добавляет к форме команды “Enter” и “Cancel” и отображает форму на дисплее. Команда ”Enter” проверяет заполнены ли поля ввода аргументов и не превышают ли значения аргументов величины 9999, т.к. в полях ввода возможен ввод 5 символов. Для отрицательных чисел проверка не выполняется, т.к. старшим символом является знак '-' и возможен ввод только четырех символов. При обнаружении ошибок выводятся соответствующие ошибки. При отсутствии ошибок вызывается метод formString(TextField, TextField), формирующий строку сообщения клиента из двух аргументов. Информация о введенных значениях аргументов отображается в строке состояния клиента. Команда ”Cancel” отменяет формирование новых аргументов в форме ”Numbers”. Команда ”Close” завершает соединение, вызывая метод stop(), удаляет команду приложения “Close” и команду “Numbers” в режиме “клиент”. Команда ”Exit” – команда выхода из приложения. Метод removeNumbersCommand() предназначен для удаления команды ”Numbers” из класса NCClient. Метод receiveMessage(InputStream) читает сообщение из входного потока и записывает его в строку. Метод работает одинаково как для клиента, так и для сервера. Чтобы не включать его как в класс NCClient, так и в класс NCServer, он содержится в классе NCCanvas, а вызывается из классов NCClient NCServer, используя ссылку на класс NCCanvas. Мидлет NetCalcSocketMIDlet Класс NetCalcSocketMIDlet реализует пользовательский интерфейс для выбора режима работы устройства (клиент или сервер). Для этого создается форма “Net calc”, в которую включается группа выбора ChoiceGroup, предлагающая два варианта выбора ”Server” или “Client”. К форме добавляются две команды “Peer Choose” (“Выбрать режим”) и ”Exit” (”Выход”) и обработчик событий. Команда “Peer Choose” запускает мидлет в выбранном режиме работы, а команда ”Exit” завершает работу приложения. В результате выполнения команды “Peer Choose” создается объект NCCanvas – основной холст мидлета и запускается поток холста. В качестве дополнительных параметров конструктору класса NCCanvas передаются ссылки на мидлет и дисплей для возможности обращения к мидлету и дисплею из класса NCCanvas, а также выбранный режим работы для задания режима фукционирования холста. Форма выводится на экран дисплея. Листинг 1. Программа сетевого соединения с использованием протокола TCP/IP /* class NetCalcSocketMIDlet */ import javax.microedition.midlet.*; import javax.microedition.lcdui.*; public class NetCalcSocketMIDlet extends MIDlet implements CommandListener { private Form form; private Display d; private ChoiceGroup choices; private NCCanvas ncCanvas; private Command exit = new Command( "Exit", Command.EXIT, 0 ); private Command peer = new Command( "Peer Choose", Command.OK, 1 ); public NetCalcSocketMIDlet() { // Create form form = new Form("Net calc"); // Add the peer choice group String[] peerNames = { "Server", "Client" }; choices = new ChoiceGroup("Please select peer type:", Choice.EXCLUSIVE, peerNames, null); form.append(choices); // Add the exit and peer commands form.addCommand(exit); form.addCommand(peer); form.setCommandListener(this); // Set the form as the current screen d = Display.getDisplay( this ); d.setCurrent( form ); } public void startApp() {} public void pauseApp() {} public void destroyApp(boolean unconditional) { ncCanvas.stop(); } public void commandAction( Command c, Displayable s) { if( c == exit ) { destroyApp(true); notifyDestroyed(); } if( c == peer ) { // Find out which type of peer connection is being used String name = choices.getString(choices.getSelectedIndex()); // Create the game canvas if (ncCanvas == null) { ncCanvas = new NCCanvas( this, d, name ); } // Start up the ncCanvas ncCanvas.start(); } } } /* class NCCanvas */ import javax.microedition.lcdui.*; import javax.microedition.lcdui.game.*; import java.io.*; import javax.microedition.io.*; public class NCCanvas extends GameCanvas implements Runnable, CommandListener { private Display d; private NetCalcSocketMIDlet parent; private NCClient client; private NCServer server; private Form form_num; private TextField first_arg, second_arg; private Image ImageServer, ImageClient; private boolean isClient; private boolean sleeping, full_flag; private String status, arguments; private Command numbers = new Command( "Numbers", Command.SCREEN, 1 ); private Command close = new Command( "Close", Command.SCREEN, 1 ); private Command exit = new Command( "Exit", Command.EXIT, 0 ); private Command enter = new Command( "Enter", Command.OK, 1 ); private Command cancel = new Command( "Cancel", Command.CANCEL, 1 ); public void removeNumbersCommand() { removeCommand( numbers ); } public NCCanvas( NetCalcSocketMIDlet m, Display display, String peerType ) { super(true); parent = m; d = display; // Remember the peer type isClient = peerType.equals("Client"); } public void start() { // Set the canvas as the current screen d.setCurrent(this); // Load background images try { ImageServer = Image.createImage( "/Server.png" ); ImageClient = Image.createImage( "/Client.png" ); } catch( IOException e ) { System.err.println("Loading images error"); } // Start the networking service if( isClient ) { client = new NCClient(this); client.start(); addCommand( numbers ); } else { server = new NCServer(this); server.start(); } addCommand( close ); addCommand( exit ); setCommandListener(this); full_flag = false; // Start the thread sleeping = false; Thread t = new Thread(this); t.start(); } public void stop() { // Stop the thread sleeping = true; } public void run() { Graphics g = getGraphics(); // The main game loop while( !sleeping ) { update(); draw(g); try { Thread.sleep( 1500 ); } catch( InterruptedException ie ) {} } } private void update() { // Process user input to issue operation int keyState = getKeyStates(); if( isClient && full_flag ) { if( ( keyState & LEFT_PRESSED ) != 0 ) { if( isClient ) client.sendMessage1( arguments.concat("+") ); status = "Addition"; } if( ( keyState & RIGHT_PRESSED ) != 0 ) { if( isClient ) client.sendMessage1( arguments.concat("-") ); status = "Substruct"; } if( ( keyState & UP_PRESSED ) != 0 ) { if( isClient ) client.sendMessage1( arguments.concat("*") ); status = "Multiplication"; } if( ( keyState & DOWN_PRESSED ) != 0 ) { if( isClient ) client.sendMessage1( arguments.concat("/") ); status = "Division"; } } } private void draw(Graphics g) { int x = g.getClipWidth(); int y = g.getClipHeight(); g.setColor( 0xFFFFFF ); g.fillRect( 0, 0, x, y ); g.setColor( 0, 0, 0 ); // black if( isClient ) { g.drawImage( ImageClient, 0, 0, Graphics.TOP | Graphics.LEFT ); g.drawString( "Client", getWidth() / 2, 15, Graphics.TOP | Graphics.HCENTER); } else { g.drawImage( ImageServer, 0, 0, Graphics.TOP | Graphics.LEFT ); g.drawString( "Server", getWidth() / 2, 15, Graphics.TOP | Graphics.HCENTER); } // Draw the status message if( status != null ) g.drawString(status, getWidth() / 2, 225, Graphics.TOP | Graphics.HCENTER); // Flush the offscreen graphics buffer flushGraphics(); } public void setStatus( String s ) { // Set the status message status = s; } public String getStatus() { return status; } public void commandAction( Command c, Displayable s) { if( c == exit ) { parent.destroyApp(true); parent.notifyDestroyed(); } if( c == close ) { if( isClient ) { client.stop(); removeCommand( numbers ); } else { server.stop(); } removeCommand( close ); } if( c == numbers ) { form_num = new Form( "Numbers" ); form_num.addCommand(enter); form_num.addCommand(cancel); form_num.setCommandListener(this); // first argument field first_arg = new TextField( "First argument", "", 5, TextField.NUMERIC) ; form_num.append( first_arg ); // second argument field second_arg = new TextField( "Second argument", "", 5, TextField.NUMERIC) ; form_num.append( second_arg ); d.setCurrent( form_num ); full_flag = false; } if( c == enter ) { if( ( first_arg.size() == 0 ) || ( second_arg.size() == 0 ) ) { Alert al = new Alert( "Warning", "Missing argument", null, AlertType.WARNING ); al.setTimeout( 2000 ); d.setCurrent( al ); } else if( ( Integer.parseInt( first_arg.getString()) > 9999 ) || ( Integer.parseInt( second_arg.getString()) > 9999 ) ) { Alert al = new Alert( "Warning", "Illegal argument", null, AlertType.WARNING ); al.setTimeout( 2000 ); d.setCurrent( al ); } else { arguments = formString( first_arg, second_arg ); full_flag = true; setStatus( "Numbers: " + first_arg.getString() + " , " + second_arg.getString() ); setCommandListener(this); d.setCurrent( this ); } } if( c == cancel ) { setCommandListener(this); d.setCurrent( this ); if( arguments != null ) full_flag = true; else { full_flag = false; setStatus( "Connected to peer server." ); } } } private String formString( TextField tf1, TextField tf2 ) { int i = 0; StringBuffer first_str = new StringBuffer(5); StringBuffer second_str = new StringBuffer(5); first_str.append(tf1.getString()); second_str.append(tf2.getString()); int length_first_str = tf1.getString().length(); int length_second_str = tf2.getString().length(); String res; boolean minus1 = false; boolean minus2 = false; if( first_str.charAt(0) == '-' ) { minus1 = true; first_str.setCharAt(0,'0'); } if( second_str.charAt(0) == '-' ) { minus2 = true; second_str.setCharAt(0,'0'); } while( length_first_str++ != 5 ) first_str.insert( i++, '0'); if( minus1 ) first_str.setCharAt(0,'-'); i = 0; while( length_second_str++ != 5 ) second_str.insert( i++, '0'); if( minus2 ) second_str.setCharAt(0,'-'); res = first_str.toString() + second_str.toString(); return res; } public String receiveMessage( InputStream is ) { String msg; StringBuffer sbuf = new StringBuffer(); int c = 0; try { while( (c = is.read())!='\n') sbuf.append( (char) c ); } catch( IOException ioe ) { } msg = new String(sbuf); return msg; } } /* class NCServer */ import javax.microedition.io.*; import java.io.*; public class NCServer implements Runnable { private NCCanvas canvas; private InputStream is; private OutputStream os; private SocketConnection sc; private ServerSocketConnection scn; private boolean closing_rcv, closing_snd; public NCServer( NCCanvas c ) { canvas = c; } public void start() { Thread t = new Thread(this); t.start(); closing_rcv = false; closing_snd = false; } public void stop() { sendMessage( os, "stop" ); closing_snd = true; } public void run() { try { // Connect to the peer client canvas.setStatus("Waiting for peer client..."); scn = ( ServerSocketConnection ) Connector.open("socket://:5000"); // Wait for a connection sc = ( SocketConnection ) scn.acceptAndOpen(); canvas.setStatus( "Connected to peer client." ); is = sc.openInputStream(); os = sc.openOutputStream(); while (true) { String msg = canvas.receiveMessage( is ); if( msg.equals("stop")) { canvas.setStatus( "Connection closed." ); closing_rcv = true; break; } else { sendMessage( os, decodeAndOperation( msg ) ); } } // end while close(); } catch( IOException ioe ) { System.err.println( "The network port is already taken." ); } catch( Exception e ) { } } public void sendMessage( OutputStream os, String msg ) { // Send the message try { // Convert the string message to bytes os.write( msg.getBytes() ); os.write( "\n".getBytes() ); } catch( Exception e ) { } } public String decodeAndOperation( String msg ) { // Parse message String str_first_arg = msg.substring( 0, 5 ); String str_second_arg = msg.substring( 5, 10 ); String str_res; int first_arg = Integer.parseInt( str_first_arg ); int second_arg = Integer.parseInt( str_second_arg ); int res = 0; // Run operation switch( msg.charAt(10) ) { case '+': res = first_arg + second_arg; break; case '-': res = first_arg - second_arg; break; case '*': res = first_arg * second_arg; break; case '/': res = first_arg / second_arg; break; } // end switch str_res = ( new Integer( res ) ).toString(); canvas.setStatus( ( new Integer( first_arg ) ).toString() + " " + msg.charAt(10) + " " + ( new Integer( second_arg ) ).toString() + " = " + str_res ); return str_res; } public void close() { try { // Disconnect to the peer client if( closing_rcv && closing_snd ) { if( is != null ) is.close(); if( os != null ) os.close(); if( sc != null ) sc.close(); if( scn != null ) scn.close(); } } catch( IOException ioe ) { } } } /* class NCClient */ import javax.microedition.io.*; import java.io.*; public class NCClient implements Runnable { private NCCanvas canvas; private InputStream is; private OutputStream os; private SocketConnection sc; private boolean closing_rcv, closing_snd; public NCClient( NCCanvas c ) { canvas = c; } public void start() { Thread t = new Thread(this); t.start(); closing_rcv = false; closing_snd = false; } public void stop() { sendMessage( os, "stop" ); closing_snd = true; } public void run() { try { // Connect to the peer server sc = (SocketConnection) Connector.open( "socket://localhost:5000" ); canvas.setStatus( "Connected to peer server." ); is = sc.openInputStream(); os = sc.openOutputStream(); while(true) { String msg = canvas.receiveMessage( is ); if( msg.equals("stop")) { closing_rcv = true; canvas.removeNumbersCommand(); canvas.setStatus( "Connection closed." ); break; } else canvas.setStatus( "result of " + canvas.getStatus() + " equal " + msg ); } close(); } catch( ConnectionNotFoundException cnfe ) { System.err.println( "The network server is unavailable." ); } catch( IOException ioe ) { } } public void sendMessage1( String msg ) { sendMessage( os, msg ); } public void sendMessage( OutputStream os, String msg ) { // Send the message try { // Convert the string message to bytes os.write( msg.getBytes() ); os.write( "\n".getBytes() ); } catch( Exception e ) { } } public void close() { try { // Disconnect to the peer server if( closing_rcv && closing_snd ) { if( is != null ) is.close(); if( os != null ) os.close(); if( sc != null ) sc.close(); } } catch( IOException ioe ) { } } } Тестирование мобильного приложения 1. Запускаем мобильное устройство У1 в режиме сервера (рис.3) Рис.3 – Экранная форма выбора режима функционирования устройства У1 2. Сервер ожидает запрос на соединение от клиента (рис.4) Рис.4 – Экранная форма устройства У1 в режиме ожидания запроса клиента 3. Запускаем мобильное устройство У2 в режиме клиента (рис.5) Рис.5 – Экранная форма выбора режима функционирования устройства У2 4. Соединение между сервером и клиентом установлено (рис.6) Рис.6 – Экранные формы устройств после установки соединения 5. В меню клиента выбираем команду ”Numbers” для перехода в форму ввода значений операндов (рис.7) Рис.7 – Экранная форма устройства У2 (команда ”Numbers”) 6. Вводим значения операндов и выбираем команду ”Enter” (рис.8) Рис.8 – Экранная форма устройства У2 для ввода значений аргументов 7. Введенные числа отображаются в строке состояния клиента (рис.9) Рис.9 – Экранная форма устройства У2 после ввода значений аргументов 8. Выбираем команду деления. Содержимое дисплеев устройств после выполнения команды деления представлено на рис.10. Рис.10 – Экранные формы устройств после выполнения операции деления 9. Сервер выбирает команду ”Close” и завершает соединение. Информация о завершении соединения отображается в строке состояния клиента (рис.11). Сервер ждет ответное сообщение о завершении соединения от клиента. Рис.11 – Экранная форма устройства У2 после завершения соединения 10. Клиент выполняет команду ”Close” и завершает соединение. Информация отображается в строке состояния сервера (рис.12). Рис.12 – Экранная форма устройства У1 после завершения соединения Дейтаграммное соединение Программа сетевого соединения с использованием протокола UDP приведена в листинге 2. Приложение, как и в случае сокетного соединения, состоит из четырех классов. Класс NetCalcDatagramMIDlet – класс мидлета, класс NCCanvas – класс холста устройств, класс NCClient – класс функционирования устройства дейтаграммного клиента, NCServer – класс устройства дейтаграммного сервера. Классы NetCalcDatagramMIDlet и NCCanvas практически совпадают с подобными классами для сокетного соединения. В классах NCClient и NCServer отличия содержатся в методах run() этих классов в связи с организацией дейтаграммного соединения вместо сокетного. Метод run() класса NCServer дейтаграммного соединения В методе run() выполняются следующие действия: в строку состояния холста выводится сообщение ”Waiting for peer client…” об ожидании подключения клиента; сервер открывает дейтаграммное Connector.open(“datagram://:5555”) DatagramConnection. возвращая соединение, объект вызывая dc метод интерфейса Номер используемого порта – произвольный (в примере, 5555), но важно, чтобы сервер и клиент использовали один и тот же порт для соединения; запускает бесконечный цикл, в котором выполняются попытки принятия дейтаграммных пакетов клиента; создает объект дейтаграммы размером 32 байта (предполагается, что максимальный размер сообщения не превышает 32 байта), принимает дейтаграмму клиента и запоминает адрес, чтобы послать ответную дейтаграмму серверу; получает дейтаграммное сообщение ”Client” от клиента об успешном установлении соединения, выводит в строку состояния сообщение ”Connected to peer client.” и отправляет клиенту ответное сообщение ”Server” об установлении соединения; получает дейтаграммы со значениями операндов и типом операции от клиента, выполняет операцию и посылает дейтаграммы с результатами клиенту, отображая информацию в строке состояния холста мидлета; цикл работает до появления сообщения “stop” от клиента; выводит сообщение "Connection closed." в строку состояния. Метод run() класса NCClient дейтаграммного соединения В методе run() выполняются следующие действия: в строку состояния холста выводится сообщение ”Connecting to peer server…”, что говорит о том, что клиент пытается соединиться с сервером; сервер открывает дейтаграммное соединение, вызывая метод Connector.open( "datagram://localhost:5555" ) возвращая объект dc интерфейса DatagramConnection. Номер используемого порта (в примере, 5555) должен совпадать с портом соединения сервера; запускает бесконечный цикл, в котором выполняются попытки принятия дейтаграммных пакетов сервера; если соединение не установлено отправляет сообщение ”Client” серверу; создает объект дейтаграммы размером 32 байта и принимает дейтаграмму сервера; при получении сообщения ”Server” от сервера в строку состояния холста мидлета выводится сообщение ”Connected to peer server.”; посылает дейтаграммы со значениями операндов и типом операции и получает дейтаграммы с результатами от сервера, отображая информацию в строке состояния холста мидлета; цикл работает до появления сообщения “stop” от сервера; выводит сообщение "Connection closed." в строку состояния. Отметим также, три основных отличия в кодах сервера и клиента. Первое при открытии дейтаграммного соединения клиентом в строке URL используется поле хоста (в примере, localhost). В соединении сервера поле хоста не указывается. Второе отличие в методе sendMessage(String) – клиент не использует адрес при отправлении пакетов серверу, а сервер использует адрес клиента, который извлекает из принятой дейтаграммы клиента. NCClient есть булевская переменная он Третье отличие в коде класса connected, котрая после успешного установления соединения устанавливается в true. Листинг 2. Программа сетевого соединения с использованием протокола UDP /* class NetCalcDatagramMIDlet */ import javax.microedition.midlet.*; import javax.microedition.lcdui.*; import javax.microedition.midlet.*; import javax.microedition.lcdui.*; public class NetCalcDatagramMIDlet extends MIDlet implements CommandListener { private Form form; private Display d; private ChoiceGroup choices; private NCCanvas ncCanvas; private Command exit = new Command( "Exit", Command.EXIT, 0 ); private Command peer = new Command( "Peer Choose", Command.OK, 1 ); public NetCalcDatagramMIDlet() { // Create form form = new Form("Net calc"); // Add the peer choice group String[] peerNames = { "Server", "Client" }; choices = new ChoiceGroup("Please select peer type:", Choice.EXCLUSIVE, peerNames, null); form.append(choices); // Add the exit and peer commands form.addCommand(exit); form.addCommand(peer); form.setCommandListener(this); // Set the form as the current screen d = Display.getDisplay( this ); d.setCurrent( form ); } public void startApp() {} public void pauseApp() {} public void destroyApp(boolean unconditional) { ncCanvas.stop(); } public void commandAction( Command c, Displayable s) { if( c == exit ) { destroyApp(true); notifyDestroyed(); } if( c == peer ) { // Find out which type of peer connection is being used String name = choices.getString(choices.getSelectedIndex()); // Create the game canvas if (ncCanvas == null) { ncCanvas = new NCCanvas( this, d, name ); } // Start up the ncCanvas ncCanvas.start(); } } } /* class NCCanvas */ import javax.microedition.lcdui.*; import javax.microedition.lcdui.game.*; import java.io.*; import javax.microedition.io.*; public class NCCanvas extends GameCanvas implements Runnable, CommandListener { private Display d; private NetCalcMIDlet parent; private NCClient client; private NCServer server; private Form form_num; private TextField first_arg, second_arg; private Image ImageServer, ImageClient; private boolean isClient; private boolean sleeping, full_flag; private String status, arguments; private Command numbers = new Command( "Numbers", Command.SCREEN, 1 ); private Command close = new Command( "Close", Command.SCREEN, 1 ); private Command exit = new Command( "Exit", Command.EXIT, 0 ); private Command enter = new Command( "Enter", Command.OK, 1 ); private Command cancel = new Command( "Cancel", Command.CANCEL, 1 ); public NCCanvas( NetCalcMIDlet m, Display display, String peerType ) { super(true); parent = m; d = display; // Remember the peer type isClient = peerType.equals("Client"); } public void removeNumbersCommand() { removeCommand( numbers ); } public void start() { // Set the canvas as the current screen d.setCurrent(this); // Load background images try { ImageServer = Image.createImage( "/Server.png" ); ImageClient = Image.createImage( "/Client.png" ); } catch( IOException e ) { System.err.println("Loading images error"); } // Start the networking service if( isClient ) { client = new NCClient(this); client.start(); addCommand( numbers ); } else { server = new NCServer(this); server.start(); } addCommand( close ); addCommand( exit ); setCommandListener(this); // Start the thread sleeping = false; Thread t = new Thread(this); t.start(); } public void stop() { // Stop the thread sleeping = true; } public void run() { Graphics g = getGraphics(); // The main game loop while( !sleeping ) { update(); draw(g); try { Thread.sleep( 1500 ); } catch( InterruptedException ie ) {} } } private void update() { // Process user input to issue operation int keyState = getKeyStates(); if( isClient && full_flag ) { if( ( keyState & LEFT_PRESSED ) != 0 ) { if( isClient ) client.sendMessage( arguments.concat("+") ); status = "Addition"; } if( ( keyState & RIGHT_PRESSED ) != 0 ) { if( isClient ) client.sendMessage( arguments.concat("-") ); status = "Substruct"; } if( ( keyState & UP_PRESSED ) != 0 ) { if( isClient ) client.sendMessage( arguments.concat("*") ); status = "Multiplication"; } if( ( keyState & DOWN_PRESSED ) != 0 ) { if( isClient ) client.sendMessage( arguments.concat("/") ); status = "Division"; } } } private void draw(Graphics g) { int x = g.getClipWidth(); int y = g.getClipHeight(); g.setColor( 0xFFFFFF ); g.fillRect( 0, 0, x, y ); g.setColor( 0, 0, 0 ); // black if( isClient ) { g.drawImage( ImageClient, 0, 0, Graphics.TOP | Graphics.LEFT ); g.drawString( "Client", getWidth() / 2, 15, Graphics.TOP | Graphics.HCENTER); } else { g.drawImage( ImageServer, 0, 0, Graphics.TOP | Graphics.LEFT ); g.drawString( "Server", getWidth() / 2, 15, Graphics.TOP | Graphics.HCENTER); } // Draw the status message g.drawString(status, getWidth() / 2, 225, Graphics.TOP | Graphics.HCENTER); // Flush the offscreen graphics buffer flushGraphics(); } public void setStatus( String s ) { // Set the status message status = s; } public String getStatus() { return status; } public void commandAction( Command c, Displayable s) { if( c == exit ) { parent.destroyApp(true); parent.notifyDestroyed(); } if( c == close ) { if( isClient ) { client.stop(); removeCommand( numbers ); } else server.stop(); removeCommand( close ); } if( c == numbers ) { form_num = new Form( "Numbers" ); form_num.addCommand(enter); form_num.addCommand(cancel); form_num.setCommandListener(this); // first argument field first_arg = new TextField( "First argument", "", 5, TextField.NUMERIC) ; form_num.append( first_arg ); // second argument field second_arg = new TextField( "Second argument", "", 5, TextField.NUMERIC) ; form_num.append( second_arg ); d.setCurrent( form_num ); full_flag = false; } if( c == enter ) { if( ( first_arg.size() == 0 ) || ( second_arg.size() == 0 ) ) { Alert al = new Alert( "Warning", "Missing argument", null, AlertType.WARNING ); al.setTimeout( 2000 ); d.setCurrent( al ); } else if( ( Integer.parseInt( first_arg.getString()) > 9999 ) || ( Integer.parseInt( second_arg.getString()) > 9999 ) ) { Alert al = new Alert( "Warning", "Illegal argument", null, AlertType.WARNING ); al.setTimeout( 2000 ); d.setCurrent( al ); } else { arguments = formString( first_arg, second_arg ); full_flag = true; setStatus( "Numbers: " + first_arg.getString() + " , " + second_arg.getString() ); setCommandListener(this); d.setCurrent( this ); } } if( c == cancel ) { setCommandListener(this); d.setCurrent( this ); if( arguments != null ) full_flag = true; else { full_flag = false; setStatus("Connected to peer server. "); } } } private String formString( TextField tf1, TextField tf2 ) { int i = 0; StringBuffer first_str = new StringBuffer(5); StringBuffer second_str = new StringBuffer(5); first_str.append(tf1.getString()); second_str.append(tf2.getString()); int length_first_str = tf1.getString().length(); int length_second_str = tf2.getString().length(); String res; boolean minus1 = false; boolean minus2 = false; if( first_str.charAt(0) == '-' ) { minus1 = true; first_str.setCharAt(0,'0'); } if( second_str.charAt(0) == '-' ) { minus2 = true; second_str.setCharAt(0,'0'); } while( length_first_str++ != 5 ) first_str.insert( i++, '0'); if( minus1 ) first_str.setCharAt(0,'-'); i = 0; while( length_second_str++ != 5 ) second_str.insert( i++, '0'); if( minus2 ) second_str.setCharAt(0,'-'); res = first_str.toString() + second_str.toString(); return res; } } /* class NCServer */ import javax.microedition.io.*; import java.io.*; public class NCServer implements Runnable { private NCCanvas canvas; private DatagramConnection dc; private String address; private boolean closing_rcv, closing_snd; public NCServer( NCCanvas c ) { canvas = c; } public void start() { Thread t = new Thread(this); t.start(); closing_rcv = false; closing_snd = false; } public void stop() { sendMessage( "stop" ); closing_snd = true; } public void run() { try { // Connect to the peer client canvas.setStatus("Waiting for peer client..."); dc = null; while (dc == null) dc = ( DatagramConnection ) Connector.open("datagram://:5555"); while (true) { // Try to receive a datagram packet Datagram dg = dc.newDatagram(32); dc.receive(dg); address = dg.getAddress(); // Make sure the datagram actually contains data if( dg.getLength() > 0 ) { String data = new String( dg.getData(), 0, dg.getLength() ); if( data.equals( "Client" ) ) { // Notify the user of a successful network connection canvas.setStatus( "Connected to peer client." ); // Try to reply with a connection message sendMessage( "Server" ); } else if( data.equals( "stop" ) ) { canvas.setStatus( "Connection closed." ); closing_rcv = true; break; } else { // Send along the network data sendMessage( decodeAndOperation( data ) ); } } } } catch( IOException ioe ) { System.err.println( "The network port is already taken." ); } catch( Exception e ) { } } public void sendMessage( String message ) { // Send the message try { // Convert the string message to bytes byte[] bytes = message.getBytes(); // Send the message Datagram dg = null; dg = dc.newDatagram( bytes, bytes.length, address ); dc.send( dg ); } catch( Exception e ) { } } public String decodeAndOperation( String msg ) { // Parse message String str_first_arg = msg.substring( 0, 5 ); String str_second_arg = msg.substring( 5, 10 ); String str_res; int first_arg = Integer.parseInt( str_first_arg ); int second_arg = Integer.parseInt( str_second_arg ); int res = 0; // Run operation switch( msg.charAt(10) ) { case '+': res = first_arg + second_arg; break; case '-': res = first_arg - second_arg; break; case '*': res = first_arg * second_arg; break; case '/': res = first_arg / second_arg; break; } // end switch str_res = ( new Integer( res ) ).toString(); canvas.setStatus( ( new Integer( first_arg ) ).toString() + " " + msg.charAt(10) + " " + ( new Integer( second_arg ) ).toString() + " = " + str_res ); return str_res; } public void close() { try { // Disconnect to the peer client if( closing_rcv && closing_snd ) { if( dc != null ) dc.close(); } } catch( IOException ioe ) { System.err.println( "The network port doesn't exist." ); } } } /* class NCClient */ import javax.microedition.io.*; import java.io.*; public class NCClient implements Runnable { private NCCanvas canvas; private DatagramConnection dc; private boolean connected; private boolean closing_rcv, closing_snd; public NCClient( NCCanvas c ) { canvas = c; connected = false; } public void start() { Thread t = new Thread(this); t.start(); closing_rcv = false; closing_snd = false; } public void stop() { sendMessage( "stop" ); closing_snd = true; } public void run() { try { // Connect to the peer server canvas.setStatus( "Connecting to peer server..." ); dc = null; while( dc == null ) dc = (DatagramConnection) Connector.open( "datagram://localhost:5555" ); while (true) { // Try to send a connection message if( !connected ) sendMessage( "Client" ); // Try to receive a datagram packet Datagram dg = dc.newDatagram( 32 ); dc.receive(dg); // Make sure the datagram actually contains data if( dg.getLength() > 0 ) { String data = new String( dg.getData(), 0, dg.getLength() ); if( data.equals( "Server" ) ) { // Notify the user of a successful network connection canvas.setStatus("Connected to peer server."); connected = true; } else if( data.equals( "stop" ) ) { canvas.setStatus( "Connection closed." ); canvas.removeNumbersCommand(); closing_rcv = true; break; } else { // Send along the network data receiveMessage( data ); } } } } catch( ConnectionNotFoundException cnfe ) { System.err.println( "The network server is unavailable." ); } catch( IOException ioe ) { } } public void sendMessage( String msg ) { // Send the message try { // Convert the string message to bytes byte[] bytes = msg.getBytes(); // Send the message Datagram dg = null; dg = dc.newDatagram( bytes, bytes.length ); dc.send( dg ); } catch( Exception e ) { } } public void receiveMessage( String msg ) { canvas.setStatus( "result of " + canvas.getStatus() + " equal " + msg ); } public void close() { try { // Disconnect to the peer server if( closing_rcv && closing_snd ) { if( dc != null ) dc.close(); } } catch( IOException ioe ) { System.err.println( "The network port doesn't exist." ); } } } Тестирование мобильного приложения для дейтаграммного соединения аналогично тестированию мобильного прилодения с использованием протокола TCP/IP.