\documentclass{article}

\input{settings/common-preamble}
\input{settings/bmstu-preamble}
\input{settings/fancy-listings-preamble}

\def\makeyear{2021}
\def\grpNum{11М}

\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}
