Reverse Ajax: Часть 2. WebSockets

Мощное решение (несмотря на некоторые ограничения для сервера)

В этом цикле статей говорится о том, как разработать событийно-управляемое Web-приложение с использованием методов Reverse Ajax. В первой части приведены различные способы реализации связи Reverse Ajax: опрос, составные вызовы и Comet, использующие ждущие опросы и потоки. Эта статья рассказывает о новом способе реализации Reverse Ajax с использованием WebSockets, нового API из HTML5. WebSockets может быть реализован внутренне поставщиками браузеров или с помощью моста, который передает вызовы в скрытый Flash- компонент FlashSockets. В этой статье обсуждаются также некоторые ограничения методов Reverse Ajax на стороне сервера.

Матье Карбу, Java Web-архитектор, Ovea

Фото Матье КарбуМатье Карбу (Mathieu Carbou), Java Web-архитектор и консультант компании Ovea, предоставляет услуги и разрабатывает решения. Он коммиттер и руководитель нескольких проектов по разработке ПО с открытым исходным кодом, докладчик и глава монреальской группы пользователей Java. Матье обладает богатым практическим опытом программирования и специализируется на разработке событийно-управляемых Web-программ для клиентских и серверных систем, а также на решениях для высокомасштабируемых Web-приложений, управляемых событиями и сообщениями. Познакомьтесь с его блогом.



02.05.2012

Введение

Сегодня пользователи рассчитывают на быстрые, динамичные приложения, доступные через Интернет. В этом цикле статей показано, как разработать событийно-управляемое Web-приложение с использованием методов Reverse Ajax. Часть 1 знакомит читателя с методами Reverse Ajax - опросами, потоками, Comet и ждущим опросом. Из нее видно, что Comet с использованием долгоживущих HTTP-запросов ― это лучший способ надежной реализации Reverse Ajax, поскольку он поддерживается во всех современных браузерах.

Из этой статьи вы узнаете, как реализовать Reverse Ajax с использованием WebSockets. Примеры кода помогают проиллюстрировать WebSockets, FlashSockets, ограничения на стороне сервера, сервисы, ограниченные одним запросом (request-scoped), и приостановку долгоживущих запросов. Загрузите исходный код, используемый в этой статье.

Предварительные требования

В идеале, чтобы извлечь максимальную пользу из этой статьи, нужно знать JavaScript и Java. Пример, создаваемый в этой статье, построен с помощью Google Guice, среды внедрения зависимостей, написанной на Java. Для работы с этой статьей требуется знакомство с идеями сред внедрения зависимостей, таких как Guice, Spring или Pico.

Для запуска примера из этой статьи также понадобится последняя версия Maven и JDK (см. раздел Ресурсы).


WebSockets

Метод WebSockets, который появился в HTML5 - это гораздо более молодой метод Reverse Ajax, чем Comet. WebSockets создает двунаправленные, дуплексные каналы связи, и многие браузеры (Firefox, Google Chrome и Safari) уже поддерживают его. Соединение открывается посредством HTTP-запроса со специальными заголовками, который называется рукопожатием WebSockets. Это соединение сохраняется постоянно, и через него можно записывать и получать данные посредством JavaScript, как при использовании стандартного сокета TCP.

URL WebSocket начинается с ws:// или wss:// (по SSL).

Временная диаграмма на рисунке 1 иллюстрирует связь с использованием WebSockets. На сервер посылается HTTP-рукопожатие с определенными заголовками. Затем создается нечто вроде сокета на JavaScript на стороне сервера или клиента. Этот сокет можно использовать для асинхронного приема данных через обработчик событий.

Рисунок 1. Reverse Ajax с WebSockets
Reverse Ajax с WebSockets

Пример WebSockets содержится в файле загрузки исходного кода для этой статьи. При его запуске вы увидите результат, похожий на Листинг 1. Он показывает, что события, происходящие на стороне сервера, сразу же появляются и на стороне клиента. Когда клиент посылает данные, сервер отражает их обратно клиенту.

Листинг 1. Пример WebSocket на JavaScript
[client] WebSocket connection opened 
[server] 1 events 
[event] ClientID = 0 
[server] 1 events 
[event] At Fri Jun 17 21:12:01 EDT 2011 
[server] 1 events 
[event] From 0 : qqq 
[server] 1 events 
[event] At Fri Jun 17 21:12:05 EDT 2011 
[server] 1 events 
[event] From 0 : vv

Как правило, JavaScript позволяет использовать WebSockets, как показано в листинге 2, если эту возможность поддерживает браузер.

Листинг 2. JavaScript-код клиента
var ws = new WebSocket('ws://127.0.0.1:8080/async'); 
ws.onopen = function() { 
    // вызывается при открытии соединения 
}; 
ws.onerror = function(e) { 
    // вызывается в случае ошибки, например, при обрыве связи 
}; 
ws.onclose = function() { 
    // вызывается при закрытии соединения 
}; 
ws.onmessage = function(msg) { 
    // вызывается, когда сервер посылает сообщение клиенту.
    // сообщение содержится в msg.data. 
}; 
// Вот как послать некоторые данные на сервер:
ws.send('some data'); 
// Как закрыть сокет:
ws.close();

Отправляемые и получаемые данные могут быть любого типа. WebSockets можно рассматривать как TCP сокеты, так что решение о том, какой тип данных использовать, остается за клиентом и сервером. В приведенном примере отправляются строки JSON.

При создании объекта JavaScript WebSocket в HTTP-запросах в консоли браузера (или в Firebug) видны заголовки рукопожатия WebSocket. См. пример в 3.

Листинг 3. Пример заголовков HTTP-запроса и ответа
Request URL:ws://127.0.0.1:8080/async 
Request Method:GET 
Status Code:101 WebSocket Protocol Handshake 

Request Headers 
Connection:Upgrade 
Host:127.0.0.1:8080 
Origin:http://localhost:8080 
Sec-WebSocket-Key1:1 &1~ 33188Yd]r8dp W75q 
Sec-WebSocket-Key2:1   7;    229 *043M 8 
Upgrade:WebSocket 
(Key3):B4:BB:20:37:45:3F:BC:C7 

Response Headers 
Connection:Upgrade 
Sec-WebSocket-Location:ws://127.0.0.1:8080/async 
Sec-WebSocket-Origin:http://localhost:8080 
Upgrade:WebSocket 
(Challenge Response):AC:23:A5:7E:5D:E5:04:6A:B5:F8:CC:E7:AB:6D:1A:39

Все заголовки используются процессом рукопожатия WebSocket для установки и настройки долгоживущего соединения. Объект WebSocket JavaScript также содержит два полезных свойства:

ws.url
Возвращает URL сервера WebSocket.
ws.readyState
Возвращает значение текущего состояния соединения:
  • CONNECTING = 0
  • OPEN = 1
  • CLOSED = 2

На стороне сервера обработка WebSockets будет немного сложнее. Спецификации Java для стандартной поддержки WebSockets (пока) не существует. Для использования WebSockets-функций Web-контейнера (например, Tomcat или Jetty), нужно тесно связать код приложения с зависящей от контейнера библиотекой, которая позволяет получить доступ к функции WebSockets.

В примере, приведенном в папке websocket кода примеров, используется API WebSocket Jetty, так как мы применяем контейнер Jetty. В листинге 4 показан обработчик WebSocket. (В части 3 этого цикла будут использоваться другие серверные API WebSocket.)

Листинг 4. Обработчик WebSocket для контейнера Jetty
public final class ReverseAjaxServlet extends WebSocketServlet { 
    @Override 
    protected WebSocket doWebSocketConnect(HttpServletRequest request,
                                           String protocol) { 
        return [...] 
    } 
}

В Jetty есть несколько способов обработки рукопожатия WebSocket. Наиболее простой заключается в создании подкласса Jetty WebSocketServlet и реализации метода doWebSocketConnect. Этот метод требует возврата экземпляра интерфейса WebSocket Jetty. Нужно реализовать интерфейс и вернуть своего рода конечную точку, соответствующую соединению WebSocket. См. листинг 5.

Листинг 5. Пример реализации WebSocket
class Endpoint implements WebSocket { 

    Outbound outbound; 

    @Override 
    public void onConnect(Outbound outbound) { 
        this.outbound = outbound;    
    } 

    @Override 
    public void onMessage(byte opcode, String data) { 
        // вызывается при получении сообщения 
        // обычно используется этот метод 
    } 

    @Override 
    public void onFragment(boolean more, byte opcode, 
                           byte[] data, int offset, int length) { 
        // когда фрагмент завершен, вызывается OnMessage. 
        // Этот  метод  обычно оставляют пустым. 
    } 

    @Override 
    public void onMessage(byte opcode, byte[] data, 
                          int offset, int length) { 
        onMessage(opcode, new String(data, offset, length)); 
    } 

    @Override 
    public void onDisconnect() { 
        outbound = null; 
    } 
}

Для отправки сообщения клиенту производится запись в outbound, как показано в листинге 6.

Листинг 6. Отправка сообщения клиенту
if (outbound != null && outbound.isOpen()) {
    outbound.sendMessage('Hello World !');
}

Чтобы отключить клиент и закрыть соединение WebSocket, используется метод outbound.disconnect();.

WebSockets - это очень мощный способ реализации двунаправленной связи без задержки. Он поддерживается Firefox, Google Chrome, Opera и другими современными браузерами. По данным Web-сайта jWebSocket:

  • Chrome содержит встроенную поддержку WebSockets начиная с версии 4.0.249;
  • Safari 5.x содержит встроенную поддержку WebSockets;
  • Firefox 3.7a6 и 4.0b1+ содержат встроенную поддержку WebSockets.
  • Chrome содержит встроенную поддержку WebSockets, начиная с версии 10.7.9067.

Подробнее о jWebSocket см. в разделе Ресурсы.

Преимущества

WebSockets предоставляет мощный способ организации двусторонней связи с короткой задержкой и простой обработкой ошибок. При этом не создается большого числа соединений, как при ждущем опросе Comet, и исключаются недостатки потоковой связи Comet. API очень прост в использовании и не требует никаких дополнительных уровней, как Comet, которому нужна хорошая библиотека для обработки повторного соединения, тайм-аута, Ajax-запросов, рукопожатий, и, возможно, различных способов передачи (ждущий опрос Ajax и опрос jsonp).

Недостатки

К числу недостатков WebSockets относятся:

  • • поддержка не во всех браузерах, так как это новая спецификация HTML5;
  • отсутствие ограничения срока жизни запроса. Так как WebSockets – это TCP-сокет, а не HTTP-запрос, нельзя легко использовать сервисы, ограниченные одним запросом (request-scoped services), такие как SessionInViewFilter из Hibernate. Hibernate ― это среда персистентности, которая предоставляет фильтр для среды HTTP-запроса. В начале запроса объект context (содержащий транзакции и JDBC-соединение) bound настраивается на поток запроса. В конце запроса фильтр уничтожает этот context.

FlashSockets

Для браузеров, не поддерживающих WebSockets, некоторые библиотеки позволяют прибегнуть к FlashSockets (сокеты через Flash). Эти библиотеки обычно предоставляют тот же официальный API WebSocket, но реализуют его путем делегирования вызовов скрытому Flash-компоненту, включенному в Web-сайт.

Преимущества

FlashSockets прозрачно предоставляют возможности WebSockets, даже в браузерах, не поддерживающих WebSockets из HTML5.

Недостатки

Метод FlashSockets имеет следующие недостатки:

  • он требует установки Flash-плагина (как правило, он есть во всех браузерах);
  • он требует, чтобы порт 843 брандмауэра был открыт, так чтобы Flash-компонент мог делать HTTP-запросы для получения файла политики с авторизацией домена.

    Если порт 843 недоступен, библиотека откажется работать или выдаст сообщение об ошибке. Все это требует времени (до 3 секунд в зависимости от библиотеки), что замедляет работу Web-сайта;

  • если клиент находится за прокси-сервером, в подключении к порту 843 может быть отказано.

Соответствующее решение предоставляет проект WebSocketJS. Он требует наличия версии Flash не ниже 10 и обеспечивает поддержку WebSockets в Firefox 3, Internet Explorer 8 и Internet Explorer 9.

Рекомендации

WebSockets имеет много преимуществ по сравнению с Comet. Клиенты, поддерживающие WebSockets, обычно работают быстрее и создают меньше запросов (и, следовательно, потребляют меньше трафика). Но, поскольку не все браузеры поддерживают WebSockets, лучшим выбором библиотеки Reverse Ajax будет та, которая способна обнаруживать поддержку WebSockets и возвращаться к методу Comet (ждущий опрос), если WebSockets не поддерживаются.

Эти два метода необходимы, чтобы добиться максимальной отдачи от любого браузера и сохранить совместимость, поэтому рекомендуется использовать клиентскую библиотеку JavaScript, которая создает уровень абстракции поверх этих методов. Некоторые библиотеки будут рассмотрены в частях 3 и 4 этого цикла, а их применение ― в части 5. На стороне сервера дело обстоит немного сложнее, как будет видно из следующего раздела.


Ограничения Reverse Ajax на стороне сервера

После обзора решений Reverse Ajax на клиентской стороне рассмотрим решения Reverse Ajax на сервере. До сих пор в примерах использовался главным образом клиентский код JavaScript. На стороне сервера, чтобы принимать соединения Reverse Ajax, некоторые методы требуют специальных функций для обработки долгоживущих соединений вместо привычных коротких HTTP-запросов. Для лучшего масштабирования следует использовать новую потоковую модель, которая требует наличия в Java специального API, способного приостанавливать запросы. Кроме того, для WebSockets нужно правильно управлять ограничением срока действия сервисов, используемых в приложении.

Потоки и неблокирующий ввод/вывод

Как правило, Web-сервер связывает с каждым входящим HTTP-соединением один поток, или один процесс. Это соединение может быть постоянным (keep-alive), так что через него может поступать множество запросов. В примере для этой статьи это поведение можно изменить, настроив Web-сервер Apache с помощью моделей mpm_fork или mpm_worker. Java-Web-серверы (включая серверы приложений, что то же самое), как правило, используют по одному потоку для каждого входящего соединения.

Размножение потоков приводит к разбазариванию памяти и ресурсов, так как нельзя гарантировать, что все порожденные потоки будут использоваться. Может оказаться, что через установленное соединение не передается никаких данных ни от клиента, ни от сервера. Используется ли этот поток или нет, он потребляет память и ресурсы процессора для планирования и переключателей контекста. При этом при настройке сервера с использованием потоковой модели, как правило, нужно настраивать пул потоков, задавая максимальное количество потоков для обработки входящих соединений. Если это значение указано неправильно или выбрано слишком малым, вы столкнетесь с проблемой дефицита потоков; запросы будут ожидать освобождения потока для своей обработки. После достижения максимального числа одновременных соединений отклик становится замедленным. С другой стороны, установка слишком большого значения может привести к исчерпанию памяти. Слишком большое число потоков займет всю кучу JVM и приведет к сбою сервера.

Недавно в Java появился новый API ввода/вывода, называемый неблокирующим вводом/выводом (non-blocking I/O). Этот API использует селектор, позволяющий избежать привязки нового потока при создании каждого нового HTTP-соединения с сервером. При поступлении данных происходит прием события и для обработки запроса выделяется поток. Это так называемая модель "поток на запрос" (thread-per-request). Она позволяет Web-серверам, таким как WebSphere и Jetty, масштабироваться и обрабатывать большее число подключений пользователей при фиксированном количестве потоков. При одной и той же конфигурации оборудования Web-серверы, работающие в этом режиме, масштабируются гораздо лучше, чем в режиме "поток на соединение" (thread-per-connection).

В блоге Филиппа Маккарти (автора Comet and Reverse Ajax) есть интересное сравнение масштабируемости двух потоковых моделей (см. раздел Ресурсы). Рисунок 2 отражает ту же картину: когда соединений слишком много, потоковая модель перестает работать.

Рисунок 2. Сравнение потоковых моделей
Сравнение потоковых моделей

Модель "поток на соединение" ("Потоки" на рисунке 2) обычно имеет меньшее время отклика, так как все потоки готовы и ждут, но она перестает работать, когда число соединений слишком велико. В модели "поток на запрос" ("Продолжения" на рисунке 2) поток используется для обслуживания поступившего запроса, и связь осуществляется через селектор NIO. Время отклика может быть немного больше, зато потоки используются повторно, и в результате это решение лучше масштабируется при большом количестве соединений.

Чтобы понять, как работают потоки, представьте себе, что селектор – это блок LEGO™. Каждое входящее соединение поступает в этот блок LEGO и определяется по штырьку. У блока LEGO/селектора будет столько штырьков (ключей), сколько соединений. Для перебора штырьков достаточно одного потока, ожидающего новых событий. Когда что-то случается, поток селектора извлекает ключи для произошедших событий и выделяет поток для обслуживания поступившего запроса.

Хороший пример использования NIO в Java приведен в руководстве "Rox Java NIO Tutorial" (см. раздел Ресурсы ).


Сервисы, ограниченные запросом (request-scoped)

Многие среды предоставляют сервисы, или фильтры, которые управляют Web-запросами, поступающими в сервлет. Например, фильтр может выполнять:

  • привязку JDBC-соединения к потоку запроса, чтобы для всего запроса использовалось только одно соединение;
  • фиксацию изменений в конце запроса.

Другой пример ― расширение Guice Servlet службы Google Guice (библиотека внедрения зависимостей). Как и Spring, Guice может связывать сервисы в пределах запроса. Для каждого нового запроса экземпляр создается не более одного раза (подробности см. в разделе Ресурсы).

К типичным применениям такого подхода относится кэширование в запросе пользовательского объекта, извлекаемого из хранилища (например, базы данных), с помощью идентификатора пользователя, взятого из кластерного HTTP-сеанса. В Google Guice можно написать код, аналогичный коду листинга 7.

Листинг 7. Связывание с областью видимости в пределах запроса
@Provides 
@RequestScoped 
Member member(AuthManager authManager, 
              MemberRepository memberRepository) { 
    return memberRepository.findById(authManager.getCurrentUserId());
}

Когда элемент внедряется в класс, Guice пытаться извлечь его из запроса. Если тот не найден, он обращается к хранилищу и помещает результат в запрос.

Сервисы с областью видимости в пределах запроса можно использовать с любым решением Reverse Ajax, кроме WebSockets. Любое другое решение основывается на HTTP-запросах, коротких или долгоживущих, так что каждый запрос проходит через системы диспетчеризации сервлетов, и фильтры выполняются. В последующих частях этого цикла вы увидите, что при выполнении приостановленного (долгоживущего) HTTP-запроса есть также возможность еще раз пропустить запрос через цепочку фильтров.

В WebSockets данные поступают непосредственно по обратному вызову onMessage, как в TCP-сокете. В этом случае никакого HTTP-запроса нет, поэтому нет контекста запроса, из которого можно брать и сохранять объекты с областью видимости в пределах запроса. Таким образом, использовать сервис, требующий от обратного вызова OnMessage request-scoped объект, не удастся.

Пример guice-и-websocket в загрузке исходного кода показывает, как обойти это ограничение и использовать request-scoped объекты для обратного вызова onMessage. Если запустить пример и нажать каждую кнопку на Web-странице для проверки вызова Ajax (request-scoped), WebSocket и WebSocket с имитацией request-scoped объекта, получится результат, показанный на рисунке 3.

Рисунок 3. Результат обработчика WebSocket с использованием request-scoped сервисов
Результат обработчика WebSocket с использованием request-scoped служб

С такими проблемами можно столкнуться, используя:

  • Spring;
  • Hibernate;
  • любую другую среду, которая требует request-scoped или per-request модели, например, OpenSessionInViewFilter;
  • любую систему, использующую средства ThreadLocal для привязки переменных к потоку запроса внутри фильтра и последующего обращения к нему.

В Guice есть элегантное решение, которое показано в листинге 8.

Листинг 8. Имитация request-scoped модели из обратного вызова onMessage
// Ссылка на запрос удерживается при вызове 
// doWebSocketMethod
HttpServletRequest request = [...] 
Map<Key<?>, Object> bindings = new HashMap<Key<?>, Object>(); 
// У меня есть сервис, которому требуется запрос для получения доступа к сеансу, 
// и я подаю такой запрос, но вы можете предоставить любое другое 
// связывание, которое может вам понадобиться
bindings.put(Key.get(HttpServletRequest.class), request); 
ServletScopes.scopeRequest(new Callable<Object>() {
 	@Override 
	public Object call() throws Exception { 
  		// обращение к хранилищу или любому сервису с использованием объектов, 
  		// ограниченных одним запросом. 
		outbound.sendMessage([...]); 
		return null; 
	} 
}, bindings).call();

Приостановка долгоживущих запросов

С Comet возникает еще одна проблема. Как серверу приостановить долгоживущий запрос без снижения производительности, а затем восстановить и выполнить его, как только на сервере произойдет событие?

Очевидно, нельзя просто задерживать запрос и ответ – это может привести к дефициту потоков и высокому потреблению памяти. Для приостановки запроса при ждущем опросе в среде неблокирующего ввода/вывода требуется специальный API. В Java такой API обеспечивает спецификация Servlet 3.0 (см. часть 1 этого цикла). Пример приведен в листинге 9.

Листинг 9. Определение асинхронного сервлета с помощью Servlet 3.0
<?xml version="1.0" encoding="UTF-8"?> 

<web-app version="3.0" xmlns="http://java.sun.com/xml/ns/javaee" 
         xmlns:j2ee="http://java.sun.com/xml/ns/javaee" 
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
         xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml
/ns/j2ee/web-app_3.0.xsd"> 

    <servlet> 
        <servlet-name>events</servlet-name> 
        <servlet-class>ReverseAjaxServlet</servlet-class> 
        <async-supported>true</async-supported> 
    </servlet> 

    <servlet-mapping> 
        <servlet-name>events</servlet-name> 
        <url-pattern>/ajax</url-pattern> 
    </servlet-mapping> 

</web-app>

Определив асинхронный сервлет, можно использовать API Servlet 3.0 для приостановки и возобновления запроса, как показано в листинге 10.

Листинг 10. Приостановка и возобновление действия запроса
AsyncContext asyncContext = req.startAsync(); 
// Ссылка asyncContext где-то запоминается,

// а затем, при необходимости, ее можно продолжить или завершить в другом потоке 
HttpServletResponse req = 
    (HttpServletResponse) asyncContext.getResponse(); 
req.getWriter().write("data"); 
req.setContentType([...]); 
asyncContext.complete();

До появления Servlet 3.0 каждый контейнер должен был иметь (и до сих пор имеет) свой собственный механизм. Хорошо известным примером является Jetty Continuations; на Jetty Continuations опираются многие библиотеки Reverse Ajax в Java. При этом не обязательно запускать приложение в контейнере Jetty. API достаточно "умен", чтобы определить контейнер, с которым вы работаете, и вернуться к Servlet 3.0 API, если он есть, при запуске в другом контейнере, таком как Tomcat или Grizzly. Это справедливо для Comet, но если вы хотите воспользоваться преимуществами WebSockets, другого выбора, кроме использования функций, зависящих от контейнера, пока нет.

Спецификация Servlet 3.0 еще не вышла, но многие контейнеры уже реализуют этот API, так как это стандартный способ работы с Reverse Ajax.


Заключение

Несмотря на некоторые недостатки, WebSockets – это чрезвычайно мощное решение Reverse Ajax. Оно пока реализовано не во всех браузерах, и его непросто использовать на стороне сервера в Java без помощи библиотеки Reverse Ajax. Так как стандартный стиль запрос-ответ не используется, нельзя полагаться на выполнение цепочки фильтров для реализации request-scoped сервисов. Для Comet и WebSockets требуются специальные функции контейнеров, зависящие от реализации на стороне сервера, и об этом нужно помнить при использовании нового контейнера, иначе он не будет масштабироваться.

В части 3 этого цикла мы рассмотрим различные API для Comet и WebSockets на стороне сервера, а также расскажем об Atmosphere - среде Reverse Ajax.


Загрузка

ОписаниеИмяРазмер
Исходный код примера для статьиreverse_ajaxpt2_source.zip14 КБ

Ресурсы

Научиться

Получить продукты и технологии

  • WebSocketJS (WebSocket Flash Bridge): реализация HTML5 WebSocket на базе Flash.
  • Google Guice: легкая среда внедрения зависимостей для Java 5 и выше.
  • Jetty: Jetty, Web-сервер и контейнер javax.servlet с поддержкой WebSockets.
  • Apache Maven: инструмент для управления проектами разработки программного обеспечения.

Обсудить

Комментарии

developerWorks: Войти

Обязательные поля отмечены звездочкой (*).


Нужен IBM ID?
Забыли Ваш IBM ID?


Забыли Ваш пароль?
Изменить пароль

Нажимая Отправить, Вы принимаете Условия использования developerWorks.

 


Профиль создается, когда вы первый раз заходите в developerWorks. Информация в вашем профиле (имя, страна / регион, название компании) отображается для всех пользователей и будет сопровождать любой опубликованный вами контент пока вы специально не укажите скрыть название вашей компании. Вы можете обновить ваш IBM аккаунт в любое время.

Вся введенная информация защищена.

Выберите имя, которое будет отображаться на экране



При первом входе в developerWorks для Вас будет создан профиль и Вам нужно будет выбрать Отображаемое имя. Оно будет выводиться рядом с контентом, опубликованным Вами в developerWorks.

Отображаемое имя должно иметь длину от 3 символов до 31 символа. Ваше Имя в системе должно быть уникальным. В качестве имени по соображениям приватности нельзя использовать контактный e-mail.

Обязательные поля отмечены звездочкой (*).

(Отображаемое имя должно иметь длину от 3 символов до 31 символа.)

Нажимая Отправить, Вы принимаете Условия использования developerWorks.

 


Вся введенная информация защищена.


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=40
Zone=Web-архитектура, SOA и web-сервисы
ArticleID=812773
ArticleTitle=Reverse Ajax: Часть 2. WebSockets
publish-date=05022012