\documentclass{article} \input{../common-preamble} \input{../bmstu-preamble} \input{../fancy-listings-preamble} \begin{document} \pagestyle{empty} \makeBMSTUHeader \makeReportTitle{лабораторной}{3}{Протокол удаленного вызова процедур JSON-RPC}{Распределённые информационные системы}{}{Локтев Д.А.} \newpage \tableofcontents \newpage \lstlistoflistings \newpage \section{Введение} Основной целью лабораторной работы является изучение принципов построения распределённых систем и закрепление полученных теоретических знаний на практике путём разработки проекта с использованием протокола удаленного вызова процедур JSON-RPC. \section{JSONRPC2 Java} Для разработки программ по заданию 1 использовалась IntelliJ IDEA 2021.3 Community Edition (далее IDEA). Для работы с JSONRPC2.0 в секции зависимостей сборщика проектов Gradle были указаны следующие пакеты: \begin{verbatim} dependencies { // ... implementation 'com.thetransactioncompany:jsonrpc2-base:2.0' implementation 'com.thetransactioncompany:jsonrpc2-server:1.11.1' implementation 'com.thetransactioncompany:jsonrpc2-client:1.16.5' } \end{verbatim} Скомпилированный проект сервера в результате компиляции принял вид, представленный на рис \hyperref[pic:srvStruct]{\ref{pic:srvStruct}}. \begin{figure}[H] \centering \includegraphics[width=5cm]{01-dis-rpt-rpc-srv-struct.png} \caption{Структура сервера RPC} \label{pic:srvStruct} \end{figure} \subsection{Сервер} Для программы реализующий сервер был использован код из примера первого задания. Код сервера представлен в листинге \hyperref[lst:server]{\ref{lst:server}}. \begin{lstlisting}[language=Java, caption=Код сервера Java, style=JCodeStyle, label={lst:server}] package ru.iovchinnikov.rpcsample.server; import java.io.*; import java.net.*; import java.text.DateFormat; import java.util.Date; import com.thetransactioncompany.jsonrpc2.*; import com.thetransactioncompany.jsonrpc2.server.*; public class Main { public static class DateTimeHandler implements RequestHandler { public String[] handledRequests() { return new String[]{"getDate", "getTime"}; } public JSONRPC2Response process(JSONRPC2Request req, MessageContext ctx) { if (req.getMethod().equals("getDate")) { DateFormat df = DateFormat.getDateInstance(); String date = df.format(new Date()); return new JSONRPC2Response(date, req.getID()); } else if (req.getMethod().equals("getTime")) { DateFormat df = DateFormat.getTimeInstance(); String time = df.format(new Date()); return new JSONRPC2Response(time, req.getID()); } else { return new JSONRPC2Response(JSONRPC2Error.METHOD_NOT_FOUND, req.getID()); } } } public static void main(String[] arg) { JSONRPC2Parser parse = new JSONRPC2Parser(); Dispatcher dispatcher = new Dispatcher(); String line; JSONRPC2Request req; JSONRPC2Response res; dispatcher.register(new DateTimeHandler()); try { ServerSocket ss = new ServerSocket(3333); System.out.println("Waiting for a client..."); while (true) { Socket socket = ss.accept(); DataInputStream in = new DataInputStream(socket.getInputStream()); DataOutputStream out = new DataOutputStream(socket.getOutputStream()); line = in.readUTF(); System.out.println("Request: " + line); req = parse.parseJSONRPC2Request(line); res = dispatcher.process(req, null); line = res.toJSONObject().toJSONString(); System.out.println("Response: " + line); out.writeUTF(line); out.flush(); } } catch (Exception x) { x.printStackTrace(); } } } \end{lstlisting} \subsection{Клиент} Для программы реализующей клиента был использован код из примера первого задания. Код клиента был переработан в виде тестового стенда для сервера и представлен в листинге \hyperref[lst:client]{\ref{lst:client}} \begin{lstlisting}[language=Java, caption=Код клиента Java, style=JCodeStyle, label={lst:client}] package ru.iovchinnikov.rpcsample.tests; import static org.junit.jupiter.api.Assertions.*; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import com.thetransactioncompany.jsonrpc2.*; import java.net.*; import java.io.*; public class TestServer { private static JSONRPC2Parser parse; private static int serverPort; private static InetAddress ipAddress; @BeforeAll static void setup() throws UnknownHostException { ipAddress = InetAddress.getByName("127.0.0.1"); serverPort = 3333; parse = new JSONRPC2Parser(); } //функция отправки json запроса static private JSONRPC2Response Send(JSONRPC2Request request, InetAddress ip, int port) throws Exception { Socket socket = new Socket(ip, port); String line; DataInputStream in = new DataInputStream(socket.getInputStream()); DataOutputStream out = new DataOutputStream(socket.getOutputStream()); out.writeUTF(request.toJSONObject().toJSONString()); out.flush(); line = in.readUTF(); socket.close(); return parse.parseJSONRPC2Response(line); } @Test void serverReturnsTime() throws Exception { String method = "getTime"; int requestID = 0; JSONRPC2Request request = new JSONRPC2Request(method, requestID); JSONRPC2Response answerJSON = Send(request, ipAddress, serverPort); assertNotNull(answerJSON.getResult()); System.out.println("Time on Server - " + answerJSON.getResult()); } @Test void serverReturnsDate() throws Exception { String method = "getDate"; int requestID = 1; JSONRPC2Request request = new JSONRPC2Request(method, requestID); JSONRPC2Response answerJSON = Send(request, ipAddress, serverPort); assertNotNull(answerJSON.getResult()); System.out.println("Date on Server - " + answerJSON.getResult()); } } \end{lstlisting} \subsection{Результат работы} После запуска приложения сервера оба теста возвращают положительный результат, представленный на рисунках \hyperref[pic:serverTests]{\ref{pic:serverTests}} и \hyperref[pic:serverTestsCl]{\ref{pic:serverTestsCl}} \begin{figure}[H] \centering \includegraphics[height=5cm]{01-dis-rpt-rpc-srv-test1.png} \caption{Проверка работоспособности в терминале сервера} \label{pic:serverTests} \includegraphics[height=5cm]{01-dis-rpt-rpc-srv-test2.png} \caption{Результат проверки работоспособности сервера клиентом} \label{pic:serverTestsCl} \end{figure} \section{JSONRPC2 Python} Для разработки программ по заданию 2 использовалась JetBrains PyCharm 2021.3 Community Edition (далее PyCharm). Для работы с JSONRPC была скачана реализация протокола JSONRPC. Так же была установлена необходимая для работы библиотека SimpleJSON. Библиотека SimpleJSON была подключена к проекту при помощи пакетного менеджера \code{pip}. Библиотека JSONRPC была скачана в корень проектов сервера и клиента. На рисунке \hyperref[pic:contentPy]{\ref{pic:contentPy}} представлен снимок экрана с файлами проекта и загруженными библиотеками \begin{figure}[H] \centering \includegraphics[height=6cm]{01-dis-rpt-rpc-pysrv-struct.png} \caption{Содержимое и настройки проекта} \label{pic:contentPy} \end{figure} \subsection{Сервер} Для программы, реализующей сервер был использован незначительно модифицированный код из примера второго задания. Код сервера представлен в листинге \hyperref[lst:pythonServer]{\ref{lst:pythonServer}} \begin{lstlisting}[language=Python, caption=Сервер на языке Python, style=PyCodeStyle, label={lst:pythonServer}] import jsonrpc def echo(s): return s def multiply(a=None, b=None): if (a or b) is not None: return a * b else: return "error" if __name__ == '__main__': server = jsonrpc.Server(jsonrpc.JsonRpc20(), jsonrpc.TransportTcpIp(addr=("127.0.0.1", 31415), logfunc=jsonrpc.log_file("myrpc.log"))) server.register_function(echo) server.register_function(multiply) print "waiting for client" # start server server.serve() \end{lstlisting} \subsection{Клиент} Для программы, реализующей клиент также был использован незначительно модифицированный код из примера второго задания. Код клиента представлен в листинге \hyperref[lst:pythonClient]{\ref{lst:pythonClient}} \begin{lstlisting}[language=Python, caption=Клиент на языке Python, style=PyCodeStyle, label={lst:pythonClient}] import jsonrpc if __name__ == '__main__': server = jsonrpc.ServerProxy(jsonrpc.JsonRpc20(), jsonrpc.TransportTcpIp(addr=("127.0.0.1", 31415))) result = server.echo("hello world") print(result) result = server.multiply(a=3, b=5) print(result) result = server.multiply() print(result) \end{lstlisting} \subsection{Результат работы} Приложения сервера и клиента были запушены и протестированы. Результат работы сервера, журнал работы, представлен на рисунке \hyperref[pic:pysrvWork]{\ref{pic:pysrvWork}}, а результат работы клиента на рисунке \hyperref[pic:pycliWork]{\ref{pic:pycliWork}}. \begin{figure}[H] \centering \includegraphics[height=5cm]{01-dis-rpt-rpc-pysrv-log.png} \caption{Результат работы (журнал) сервера} \label{pic:pysrvWork} \end{figure} \begin{figure}[H] \centering \includegraphics[height=2cm]{01-dis-rpt-rpc-pyclient.png} \caption{Результат работы клиента} \label{pic:pycliWork} \end{figure} \section{JSONRPC2 Mullti Language} Заданием для практической части является создание клиент-серверного приложения где клиент и сервер разработаны на разных языках. Клиент должен позволять формировать две матрицы заданного размера и посылать эти матрицы на сервер. Сервер в свою очередь должен суммировать полученные матрицы и отсылать результат клиенту. В качестве сервера была использована программы на Python реализованная во второй части задания. В качестве клиента была использована программа на Java реализованная в первой части задания. Код сервера был модифицирован для решения задачи сложения матриц. Код сервера представлен в листинге \hyperref[lst:pythonServerMatrix]{\ref{lst:pythonServerMatrix}} \begin{lstlisting}[language=Python, caption=Сервер складывающий матрицы, style=PyCodeStyle, label={lst:pythonServerMatrix}] import jsonrpc server=jsonrpc.Server(jsonrpc.JsonRpc20(),jsonrpc.TransportTcpIp( addr=("127.0.0.1", 3340), logfunc=jsonrpc.log_file("myrpc.log"))) def matrixSum(a, b): if (a or b) is not None: if(len(a) == len(b)): n = len(a); s = [[0] * n for i in range(n)] for i in range(len(a)): for j in range(len(a)): s[i][j] = a[i][j] + b[i][j] return s else: return "Invalit matrix size" server.register_function( matrixSum ) # start server server.serve() \end{lstlisting} Код клиента был модифицирован для возможности ввода размерности матриц, самих матриц и их вывода в консоль. Код клиента представлен в листинге \hyperref[lst:finalClient]{\ref{lst:finalClient}} \begin{lstlisting}[language=Java, caption=Код со вводом матриц, style=JCodeStyle, label={lst:finalClient}] package ru.iovchinnikov.rpcsample.server; import com.thetransactioncompany.jsonrpc2.*; import java.net.*; import java.io.*; import java.util.*; public class Client { static JSONRPC2Parser parse = new JSONRPC2Parser(); public static void main(String[] ar) { int serverPort = 3340; String address = "localhost"; String method = "matrixSum"; int requestID = 0; try { Scanner in = new Scanner(System.in); System.out.print("Введите размер матриц: "); int size = in.nextInt(); System.out.println("Введите матрицe A: "); int[][] mA = CreateMatrix(size); System.out.println("Введите матрицe B: "); int[][] mB = CreateMatrix(size); InetAddress ipAddress = InetAddress.getByName(address); List param = new LinkedList(); param.add(mA); param.add(mB); JSONRPC2Request request = new JSONRPC2Request(method, param, requestID); JSONRPC2Response answerJSON = Send(request, ipAddress, serverPort); System.out.println("Матрица A - " + prntMatrix(mA)); System.out.println("Матрица B - " + prntMatrix(mB)); System.out.println("Сумма матриц - " + answerJSON.getResult()); } catch (Exception x) { x.printStackTrace(); } } private static int[][] CreateMatrix(int size) { int [][] mtx = new int [size][size]; for(int i = 0; i < mtx.length; i++) { for (int j = 0; j < mtx[i].length; j++) { Scanner in = new Scanner(System.in); System.out.printf("[%d,%d] = ",i,j); int z = in.nextInt(); mtx[i][j] = z; } } return mtx; } //функция отправки json запроса static private JSONRPC2Response Send(JSONRPC2Request request, InetAddress ip, int port) throws Exception { Socket socket = new Socket(ip, port); DataInputStream in = new DataInputStream(socket.getInputStream()); DataOutputStream out = new DataOutputStream(socket.getOutputStream()); out.write(request.toJSONObject().toJSONString().getBytes()); out.flush(); String line = in.readLine(); socket.close(); return parse.parseJSONRPC2Response(line); } private static String prntMatrix(int[][] a) { StringBuilder str = new StringBuilder("["); for (int i = 0; i < a.length; i++) { if (i != 0) str.append(","); str.append("["); for (int j = 0; j < a.length; j++) { if (j != 0) str.append(","); str.append(a[i][j]); } str.append("]"); } str.append("]"); return str.toString(); } } \end{lstlisting} \subsection{Результат работы} Приложения сервера и клиента были запушены и протестированы. Результат работы сервера представлен на рисунке \hyperref[pic:pysrvFin]{\ref{pic:pysrvFin}}, а результат работы клиента на рисунке \hyperref[pic:jcliFin]{\ref{pic:jcliFin}}. \begin{figure}[H] \centering \includegraphics[height=5cm]{01-dis-rpt-rpc-srv-test-final.png} \caption{Результат работы (журнал) сервера} \label{pic:pysrvFin} \end{figure} \begin{figure}[H] \centering \includegraphics[height=2cm]{01-dis-rpt-rpc-srv-test-final-log.png} \caption{Результат работы клиента} \label{pic:jcliFin} \end{figure} \section{Выводы} В ходе выполнения работы была реализованная задача создания распределенной системы, где вся логика обработки данных предоставляется серверу, тогда как работа с данными происходит на клиенте. Для реализации задачи был использован протокол удаленных вызовов процедур JSON-RPC. Протокол использует JSON для кодирования сообщений. Протокол был использован исходя из рекомендаций к заданию и имеет следующий особенности: \begin{itemize} \item небольшой размер; \item простота использования; \item документированность; \item компактность и ёмкость одного выражения; \item независимость от способа передачи данных; \item поддержка именованных параметров; \item поддержка нотификаций; \item поддержка значений null(None); \item встроенный контроль сессий; \end{itemize} Использование протокола позволяет создавать системы где сервер разрабатывается на одном языке программирование, а клиенты на других. \end{document}