409 lines
19 KiB
TeX
409 lines
19 KiB
TeX
\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}
|