Каж­дую секун­ду во все­мир­ной сети появ­ляет­ся око­ло вось­ми новых поль­зовате­лей. Изме­няет­ся воз­раст, род занятий, увле­чения сред­неста­тис­тичес­кого поль­зовате­ля. Изме­няют­ся и его пот­ребнос­ти. Воз­раста­ет необ­ходимость в мгно­вен­ном дос­тупе к изме­няющей­ся информа­ции: прог­нозам погоды, колеба­ниям кур­са валют, резуль­татам голосо­вания. Все боль­ше дан­ных перено­сит­ся в обла­ка. Для быс­тро­го и лег­кого дос­тупа к ним из любой точ­ки мира и с любого устрой­ства исконно дес­ктоп­ные при­ложе­ния перек­валифи­циру­ются в онлай­новые: редак­торы тек­ста, гра­фики, прос­мот­рщи­ки фотог­рафий, ауди­опле­еры. Прос­мотр и редак­тирова­ние информа­ции ста­новит­ся кол­лектив­ным. И вот уже рас­пре­делен­ная по все­му миру коман­да обсужда­ет детали про­екта, рисуя сов­мес­тную диаг­рамму онлайн, добав­ляя прав­ки и ком­мента­рии, которые сра­зу же отоб­ража­ются у всех учас­тни­ков обсужде­ния...

Ре­али­зация таких воз­можнос­тей пот­ребова­ла перес­мотра архи­тек­туры веб‑при­ложе­ний. Зап­росы дол­жны выпол­нять­ся прак­тичес­ки в реаль­ном вре­мени, при­ложе­ние — лег­ко рас­ширять­ся и мас­шта­биро­вать­ся. Сущес­тву­ющая модель, в которой запус­кает­ся один тяжело­вес­ный сер­вер при­ложе­ний, рас­пре­деля­ющий зап­росы по раз­ным потокам, не справ­ляет­ся с эти­ми задача­ми эффектив­но. Соз­дание потоков тре­бует ресур­сов памяти, перек­лючение меж­ду потока­ми — про­цес­сорных ресур­сов, под­дер­жка син­хро­низа­ции — вре­мени и нер­вов прог­раммис­та, мас­шта­биро­вание — исполь­зования сто­рон­них биб­лиотек и прог­рамм.

По­это­му была пред­ложена новая модель: для обслу­жива­ния зап­росов запус­кает­ся мно­го мел­ких одно­поточ­ных сер­веров, которые обща­ются меж­ду собой сооб­щени­ями. Став­ку на такой под­ход сде­лали раз­работ­чики недав­но появив­шегося фрей­мвор­ка Vert.x. О том, чем он может прив­лечь прог­раммис­та и осчас­тли­вить конеч­ного поль­зовате­ля, и пой­дет речь даль­ше.

 

Основные черты Vert.x

Фичи Вертекса по версии Хабра
Фи­чи Вер­текса по вер­сии Хаб­ра

Пер­вая вер­сия Vert.x была выпуще­на в 2012 году. Как утвер­жда­ет его соз­датель, Тим Фокс, его вдох­новила прос­тота и лег­кость Node.js, и он решил сде­лать что‑то похожее, но луч­ше и на JVM. В отли­чие от Node.js Тим решил не огра­ничи­вать прог­раммис­тов в выборе язы­ка прог­рамми­рова­ния и сра­зу внес в про­ект под­дер­жку муль­тиязыч­ности. Про­ект изна­чаль­но даже называл­ся Node.x, где x намекал на его полиг­лотную натуру.

На дан­ный момент Vert.x под­держи­вает Java, JavaScript, Groovy, Ruby и Python. Активно идет раз­работ­ка под­дер­жки Scala, Clojure и PHP. Под­дер­жка язы­ков реали­зова­на в виде модулей, которые лег­ко под­клю­чают­ся и отклю­чают­ся. Исполь­зование модуль­ной архи­тек­туры так­же поз­воля­ет писать раз­ные ком­понен­ты про­екта на раз­ных язы­ках или под­клю­чать к сво­ему Java-про­екту биб­лиотеч­ный модуль, написан­ный, к при­меру, на руби.

Раз­работ­чики Vert.x при­няли во вни­мание и то, что логика сов­ремен­ных веб‑при­ложе­ний все боль­ше реали­зует­ся на JavaScript и выпол­няет­ся в бра­узе­ре. Так что фрей­мворк не ста­вит сво­ей задачей отоб­ражение HTML-стра­ниц или обра­бот­ку дан­ных из фор­мы. Его задача — прос­то вывес­ти ста­тич­ную стра­ницу с джа­вас­крип­том и работать уже с соеди­нени­ем от JS-кли­ента через веб‑сокеты, пре­дос­тавляя необ­ходимые дан­ные. Помимо WebSockets, Vert.x под­держи­вает так­же SockJS, ана­логич­ную JS-биб­лиоте­ку. Оба этих про­токо­ла тре­буют от сер­вера пос­тоян­но дер­жать откры­тым соеди­нение с кли­ентом. В обыч­ном веб‑при­ложе­нии для каж­дого такого соеди­нения выделя­ется отдель­ный поток. С каж­дым новым кли­ентом количес­тво потоков в сис­теме уве­личи­вает­ся, мно­гие из них прос­таивают без дела, рас­тра­чивая нап­расно ресур­сы сис­темы на их содер­жание и перек­лючение меж­ду ними. Поэто­му в Vert.x исполь­зует­ся дру­гой под­ход. Все соеди­нения веша­ются на один поток, который занима­ется толь­ко тем, что при­нима­ет и отправ­ляет дан­ные. Обра­бот­ка при­нятых дан­ных в этом потоке не про­исхо­дит.

По­это­му соеди­нения не бло­киру­ют друг дру­га.

Vert.x явля­ется пол­ностью асин­хрон­ным. Все дей­ствия пред­став­ляют­ся в фор­ме событий и по оче­реди обра­баты­вают­ся в потоке цик­ла событий (event loop). Таких потоков может быть нес­коль­ко, но их количес­тво не пре­выша­ет количес­тва про­цес­сорных ядер, потому что они активны поч­ти все вре­мя и могут исполь­зовать вычис­литель­ные мощ­ности ядра на 100%.

Ар­хитек­турно Vert.x мож­но раз­делить на две час­ти: сер­висы ядра и модули. Сер­висы ядра вклю­чают в себя реали­зацию кли­ентов и сер­веров TCP/SSL, HTTP, SockJS, веб‑сокеты. Так­же есть сер­висы для дос­тупа к шине событий, к фай­ловой сис­теме, тай­меры, буферы, сер­висы нас­трой­ки прав дос­тупа, раз­верты­вания и дру­гие. Пред­полага­ется, что сер­висы ядра будут ста­тич­ны, а вся фун­кци­ональ­ность, которая изме­няет­ся, вынесе­на в модули. Модуль — это zip-файл, в котором содер­жатся исполня­емые фай­лы, ресур­сы, биб­лиоте­ки, исполь­зуемые в при­ложе­нии, и файл опи­сания модуля mod.json. Модули мож­но заг­ружать в репози­торий Мавена или Bintray. Так­же модули мож­но помещать в реестр Vert.x-модулей, что­бы дру­гие раз­работ­чики мог­ли им поль­зовать­ся.

 

Архитектура Vert.x

Раз­работ­чики Vert.x вве­ли нес­коль­ко новых понятий, которые понадо­бят­ся при раз­работ­ке при­ложе­ний. Еди­ница раз­верты­вания в Vert.x называ­ется вер­тикл (verticle). Каж­дый вер­тикл содер­жит метод main, который выпол­няет­ся при запус­ке. При­ложе­ние может сос­тоять толь­ко из одно­го вер­тикла или из нес­коль­ких, обща­ющих­ся меж­ду собой через шину событий. Каж­дый вер­тикл запус­кает­ся в сво­ем заг­рузчи­ке клас­сов (classloader), что­бы изо­лиро­вать вер­тиклы друг от дру­га и избе­жать ситу­ации, ког­да один вер­тикл меня­ет ста­тич­ные перемен­ные дру­гого. В одной вир­туаль­ной машине может быть запущен толь­ко один экзем­пляр Vert.x. Но на одной машине мож­но запус­тить нес­коль­ко JVM с Vert.x внут­ри.

Архитектура Vert.x
Ар­хитек­тура Vert.x

Эк­зем­пляр Vert.x гаран­тиру­ет, что каж­дый экзем­пляр вер­тикла всег­да будет выпол­нять­ся в одном и том же потоке. Так что прог­раммис­ту не при­ходит­ся пережи­вать о син­хро­низа­ции, дед­локах и про­чих проб­лемах мно­гопо­точ­ности.

Ко­неч­но, есть задачи, вре­мя выпол­нения которых слож­но рас­счи­тать, нап­ример обра­щение к базе дан­ных или слож­ные вычис­ления. Выпол­нение такой задачи в потоке цик­ла событий при­вело бы к проб­лемам про­изво­дитель­нос­ти. Поэто­му ее мож­но пометить как worker verticle, что­бы она выпол­нялась в отдель­ном потоке из спе­циаль­но выделен­ного пула потоков и не пре­пятс­тво­вала работе основно­го потока цик­ла событий. Нуж­но пом­нить, что все задачи, помечен­ные как «рабочий вер­тикл», выпол­няют­ся пос­ледова­тель­но, так что луч­ше умень­шать их количес­тво до миниму­ма и исполь­зовать, толь­ко ког­да они дей­стви­тель­но необ­ходимы.

Вер­тиклы обща­ются меж­ду собой, переда­вая сооб­щения в ши­ну событий (event bus). Экзем­пляр Vert.x дер­жит спе­циаль­ный поток для обра­бот­ки таких сооб­щений (event loop). Ког­да пос­тупа­ет новое сооб­щение, поток про­сыпа­ется, выпол­няет его, переда­ет резуль­тат слу­шате­лю сооб­щения и сно­ва засыпа­ет. Так­же Vert.x пре­дос­тавля­ет об­щую кар­ту (map) и на­бор (set) для переда­чи дан­ных меж­ду вер­тикла­ми, запущен­ными в рам­ках одно­го Vert.x-экзем­пля­ра. Что­бы избе­жать проб­лем с син­хро­низа­цией дан­ных, переда­вать мож­но толь­ко неиз­меня­емые дан­ные (immutable).

Пе­редать сооб­щение через шину событий мож­но дву­мя спо­соба­ми: исполь­зуя метод publish или метод send. Ме­тод publish переда­ет сооб­щение по ука­зан­ному адре­су. Каж­дый под­писчик с таким адре­сом получит сооб­щение. Адрес — это прос­то стро­ка. Адрес может быть каким угод­но, но по пра­вилам хороше­го тона при­нято ука­зывать в качес­тве пре­фик­са пол­ное имя клас­са, который опуб­ликовал сооб­щение.

Ме­тод send отправ­ляет сооб­щение одно­му кон­крет­ному адре­сату. Если на дан­ный адрес под­писано нес­коль­ко вер­тиклов, то получа­тель опре­деля­ется по алго­рит­му цик­личес­кого рас­пре­деле­ния наг­рузки (round-robin). Пре­иму­щес­тва исполь­зования это­го алго­рит­ма в лег­ком мас­шта­биро­вании. Если для выпол­нения какого‑то дей­ствия не хва­тает ресур­сов, то мож­но прос­то запус­тить еще нес­коль­ко экзем­пля­ров одно­го вер­тикла, и наг­рузка будет рав­номер­но рас­пре­деле­на меж­ду ними.

 

Пример приложения

В пред­две­рии 8 мар­та напишем прос­тень­кий сер­вис вир­туаль­ных поз­драв­лений (сох­раняй­те спо­кой­ствие, ведь автор — жен­щина! — Прим. ред.). Поль­зователь заходит на сайт и оставля­ет коор­динаты челове­ка, которо­го он хотел бы поз­дра­вить, и свои пожела­ния к поз­драв­лению: шуточ­ное, роман­тичес­кое, офи­циаль­ное... Заказ на поз­драв­ление попада­ет к одно­му из аген­тов, тот его выпол­няет и ста­вит соот­ветс­тву­ющий ста­тус, который сра­зу переда­ется поль­зовате­лю.

Что­бы добить­ся инте­рак­тивнос­ти, вос­поль­зуем­ся веб‑сокета­ми. Для начала зай­мем­ся сер­вером. Нам понадо­бит­ся пос­ледний релиз Vert.x (берем с сай­та) и обыч­ный Java-про­ект в тво­ей любимой сре­де прог­рамми­рова­ния. К про­екту нуж­но добавить биб­лиоте­ки vertx-core, vertx-platform, netty-all для работы сер­вера и кли­ента и биб­лиоте­ки jackson-annotation, jackson-core, jackson-databind для работы с дан­ными в фор­мате JSON.

Соз­даем класс сер­вера, нас­ледован­ный от Verticle. Метод start явля­ется вход­ной точ­кой при­ложе­ния. Выпол­няем ини­циали­зацию HTTP-сер­вера, он будет отда­вать стра­ницу client.html по дефол­тно­му пути и фай­лы ресур­сов, которые соот­ветс­тву­ют мас­ке. Осталь­ные зап­росы будут игно­риро­вать­ся. Запус­каем сер­вер на 8080-м пор­ту. Handler — это базовый класс для всех обра­бот­чиков, исполь­зует­ся вез­де, где надо что‑то обра­ботать асин­хрон­но. HttpRequestServer — это класс с информа­цией о HTTP-зап­росе от кли­ента.

public class Server extends Verticle {
public void start() {
RouteMatcher httpRouteMatcher = new RouteMatcher()
.get("/", new Handler<HttpServerRequest>() {
@Override
public void handle(HttpServerRequest request) {
request.response().sendFile("webroot/client.html");
}
})
.get(".*\\.(css|js)$", new Handler<HttpServerRequest>() {
@Override
public void handle(HttpServerRequest request) {
request.response().sendFile("webroot/" + new File(request.path()));
}
});
vertx.createHttpServer().requestHandler(httpRouteMatcher).listen(8080);
}
}

Все ресур­сы при­ложе­ния хра­нят­ся в пап­ке webroot, которую сле­дует помес­тить в пап­ке, отку­да запус­кает­ся при­ложе­ние. Если положить туда прос­той HTML-файл с наз­вани­ем client.html, то уже на этом эта­пе мож­но убе­дить­ся, что сер­вер работа­ет. Запус­тить сер­вер мож­но из IntelliJ IDEA (или дру­гой сре­ды прог­рамми­рова­ния) либо пря­мо из коман­дной стро­ки даже без пред­варитель­ной ком­пиляции.

Настройки для IntelliJ IDEA
Нас­трой­ки для IntelliJ IDEA
vertx run Server.java

На 8090-м пор­ту будем при­нимать соеди­нения от веб‑сокетов. Сер­вер будет при­нимать толь­ко два адре­са: "/client" для соеди­нений от кли­ента, "/agent" для соеди­нений от аген­тов, осталь­ные соеди­нения откло­няют­ся.

final String clientUrl = "/client";
final String agentUrl ="/agent";
vertx.createHttpServer().websocketHandler(new Handler<ServerWebSocket>() {
@Override
public void handle(ServerWebSocket socket) {
logger.info("WebSocket " + socket.path());
if (socket.path().startsWith(agentUrl)) {
processAgent(socket);
} else if (socket.path().startsWith(clientUrl)) {
// Заберем имя пользователя
processClient(socket, socket.query().split("=")[1]);
} else {
socket.reject();
}
}
}).listen(8090);

Фрон­тенд для кли­ента реали­зуем с помощью HTTP и JavaScript. Поль­зователь ука­зыва­ет свое имя‑фамилию и логинит­ся в сис­тему.

Вход в систему
Вход в сис­тему

В это вре­мя соз­даем веб‑сокет­ное соеди­нение к сер­веру и регис­три­руем обра­бот­чик вхо­дящих сооб­щений.

<script>
var serviceLocation = "ws://localhost:8090/client/";
var wsocket;
function connectToServer() {
fullName = $userName.val() + ' ' + $userSurname.val();
wsocket = new WebSocket(serviceLocation + '?username=' + fullName);
wsocket.onmessage = onMessageReceived;
}
</script>

Пред­лага­ем поль­зовате­лю ввес­ти имя и телефон «жер­твы» и свои осо­бые пожела­ния к поз­драв­лению. Всю эту информа­цию скла­дыва­ем в JSON и отправ­ляем на сер­вер.

function sendMessage() {
var msg = '{"recipientName":"' + $recipientName.val() +
'", "recipientPhone":"' + $recipientPhone.val() +
'", "message":"' + $message.val() +
'", "sender":"' + $userName.val() +
'"}';
wsocket.send(msg);
}

На сер­вере регис­три­руем обра­бот­чик дан­ных для кли­ента. При получе­нии нового сооб­щения прос­тавля­ем ему ста­тус «При­нято» и отправ­ляем заказ аген­ту и обратно кли­енту, что­бы показать, что заказ в обра­бот­ке. Для хра­нения иден­тифика­тора сокета кли­ента вос­поль­зуем­ся шарен­ным сетом, где клю­чом будет имя поль­зовате­ля. Иден­тифика­тор понадо­бит­ся нам, что­бы перенап­равлять сооб­щения от аген­та нуж­ному поль­зовате­лю.

// Получаем объект шины событий, чтобы обмениваться сообщениями между клиентом и агентом
final EventBus eventBus = vertx.eventBus();
void processClient(ServerWebSocket socket, final String userName) {
final String id = socket.textHandlerID();
// Сохраняем идентификатор сокета, чтобы в дальнейшем пересылать сообщения от агента
getVertx().sharedData().getSet(userName).add(id);
socket.dataHandler(new Handler<Buffer>() {
@Override
public void handle(Buffer buffer) {
JsonObject root = new JsonObject(buffer.toString());
root.putString("status", "Принято");
// Шлем ответ клиенту
eventBus.send(id, root.toString());
//send to the agent
eventBus.send(agentUrl, root.toString());
}
});
socket.closeHandler(new Handler<Void>() {
@Override
public void handle(final Void event) {
// Когда пользователь отсоединяется, удаляем идентификатор сокета, чтобы не слать сообщения в никуда
vertx.sharedData().getSet(userName).remove(id);
}
});
}

Ког­да кли­ент получа­ет сооб­щение от сер­вера с новым ста­тусом, отоб­ража­ем его в таб­лице.

function onMessageReceived(evt) {
var msg = JSON.parse(evt.data); // native API
var $messageLine = $('<tr><td class="status">' + msg.status
+ '</td><td>' + msg.recipientName
+ '</td><td>' + msg.recipientPhone
+ '</td><td>' + msg.message
+ '</td></tr>');
$chatWindow.append($messageLine);
}

Для аген­тов напишем при­ложе­ние на JavaFX, что­бы сра­зу было вид­но, что они работа­ют, а не в ине­те шарят­ся. Для это­го добавим новый модуль в про­ект и соз­дадим класс FelicitationAgent, нас­ледован­ный от Application. Наб­роса­ем лег­кий интерфейс: таб­личку, в которую будут при­ходить новые заказы, и фор­му для редак­тирова­ния ста­туса заказа.

Приложение для обработки заказов
При­ложе­ние для обра­бот­ки заказов

Что­бы заг­ружать дан­ные с сер­вера, откры­ваем веб‑сокет и наз­нача­ем обра­бот­чик на вхо­дящие сооб­щения. Сооб­щения из JSON-фор­мата пре­обра­зуем в класс и добав­ляем в таб­лицу.

private void loadData() {
Vertx vertx = VertxFactory.newVertx();
vertx.createHttpClient().setHost("localhost").setPort(8090).connectWebsocket("/agent", new Handler<WebSocket>() {
@Override
public void handle(WebSocket websocket) {
websocket.dataHandler(new Handler<Buffer>() {
@Override
public void handle(Buffer data) {
JsonObject object = new JsonObject(data.toString());
FelicitationRequest request = new FelicitationRequest(
object.getString("recipientName"),
object.getString("recipientPhone"),
object.getString("message"),
object.getString("status"),
object.getString("sender")
);
// Добавляем новый запрос в таблицу
updateData(request);
}
});
}
});
}

Ос­талось дописать обра­бот­ку соеди­нения от аген­та на сер­вере. Регис­три­руем обра­бот­чик для вхо­дящих сооб­щений от кли­ента, который прос­то перенап­равля­ет их через веб‑сокет в JavaFX-при­ложе­ние для аген­та. Вто­рой обра­бот­чик при­нима­ет сооб­щения от аген­та и отправ­ляет их нуж­ному кли­енту, отыс­кав иден­тифика­тор сокета по име­ни кли­ента из сооб­щения.

void processAgent(final ServerWebSocket socket) {
eventBus.registerHandler(agentUrl, new Handler<Message>() {
@Override
public void handle(Message message) {
Buffer buffer = new Buffer(message.body().toString());
socket.write(buffer);
}
});
socket.dataHandler(new Handler<Buffer>() {
@Override
public void handle(Buffer buffer) {
JsonObject root = new JsonObject(buffer.toString());
Set<Object> set = getVertx().sharedData().getSet(root.getString("sender"));
for (Object client : set) {
eventBus.send((String) client, root.toString());
}
}
});
}

Вот и все. Поль­зователь регис­три­рует­ся на сай­те и оставля­ет заяв­ку. Сер­вер нап­равля­ет ее пер­вому сво­бод­ному аген­ту. Тот зво­нит с поз­драв­лени­ем и меня­ет ста­тус заяв­ки. Пос­ле чего она отправ­ляет­ся обратно кли­енту и отоб­ража­ется в таб­лице заявок с новым ста­тусом.

Таблица заявок клиента
Таб­лица заявок кли­ента

К сожале­нию, в дан­ной реали­зации есть один сущес­твен­ный недос­таток. Если один из аген­тов отсо­еди­нит­ся от сер­вера, не отме­нив под­писку на события, Vert.x будет счи­тать, что он в онлай­не, и про­дол­жать отправ­лять ему сооб­щения. Что­бы избе­жать такой ситу­ации, мож­но либо исполь­зовать метод sendWithTimeout, либо соз­дать отдель­ный тай­мер, который будет про­верять, обра­баты­вает ли кто‑то из аген­тов сооб­щение, или сто­ит его передать дру­гому аген­ту.

 

Выводы

В целом Vert.x оставля­ет при­ятное впе­чат­ление. Асин­хрон­ная модель поз­воля­ет не думать о син­хро­низа­ции и бло­киро­вании дос­тупа. Исполь­зование модулей со сла­бой свя­зан­ностью дела­ет при­ложе­ние гиб­че и умень­шает зат­раты вре­мени на раз­работ­ку и под­дер­жку. Умень­шение исполь­зования потоков зна­читель­но эко­номит ресур­сы сис­темы. При­ложе­ние на Vert.x лег­ко рас­ширя­ется и мас­шта­биру­ется. Что­бы запус­тить, ска­жем, десять вер­тиклов для рас­пре­деле­ния наг­рузки, дос­таточ­но про­писать в коман­дной стро­ке -instance 10. Все это дела­ет исполь­зование Vert.x прив­лекатель­ным для раз­работ­ки тра­дици­онных веб‑при­ложе­ний, популяр­ных в пос­леднее вре­мя при­ложе­ний реаль­ного вре­мени, игр и даже для back end’a к мобиль­ным при­ложе­ниям.

 

Основные методы получения событий

 

Polling

Са­мый прос­той, но самый неэф­фектив­ный метод. Кли­ент раз в нес­коль­ко секунд опра­шива­ет сер­вер на наличие событий.

Плю­сы:

  • прос­тота.

Ми­нусы:

  • очень мно­го лиш­них зап­росов;
  • со­бытия всег­да при­ходят с опоз­дани­ем;
  • сер­веру при­ходит­ся хра­нить события, пока кли­ент не заберет их или пока они не уста­реют.
 

Long Polling

Улуч­шенный вари­ант пре­дыду­щего метода. Кли­ент отправ­ляет зап­рос на сер­вер, сер­вер дер­жит откры­тым соеди­нение, пока не при­дут какие‑нибудь дан­ные или кли­ент не отклю­чит­ся самос­тоятель­но. Как толь­ко дан­ные приш­ли, отправ­ляет­ся ответ, соеди­нение зак­рыва­ется и откры­вает­ся сле­дующее и так далее.

Плю­сы по срав­нению с Polling:

  • ми­нималь­ное количес­тво зап­росов;
  • вы­сокая вре­мен­ная точ­ность событий;
  • сер­вер хра­нит события толь­ко на вре­мя рекон­некта.

Ми­нусы по срав­нению с Polling:

  • бо­лее слож­ная схе­ма.
 

WebSockets

Плю­сы по срав­нению с Long Polling:

  • под­нима­ется одно соеди­нение;

  • пре­дель­но высокая вре­мен­ная точ­ность событий;

  • уп­равле­ние сетевы­ми сбо­ями кон­тро­лиру­ет бра­узер.

Ми­нусы по срав­нению с Long Polling:

  • HTTP не сов­мести­мый про­токол, нужен свой сер­вер, усложня­ется отладка.

Это бинар­ный дуп­лек­сный про­токол, поз­воля­ющий кли­енту и сер­веру общать­ся на рав­ных. Этот про­токол мож­но при­менять для игр, чатов и всех тех при­ложе­ний, где нуж­ны пре­дель­но точ­ные события, близ­кие к реаль­ному вре­мени.

  • Подпишись на наc в Telegram!

    Только важные новости и лучшие статьи

    Подписаться

  • Подписаться
    Уведомить о
    0 комментариев
    Межтекстовые Отзывы
    Посмотреть все комментарии