Конечно же в JavaScript`е нету сокетов. Но зато есть такая чудесная вещь как AJAX, а также есть еще более привлекательная вещь как Comet. И с помощью этих двух технологий можно добиться от веб-страницы поразительного сходства с прикладными программами по части socket-соединений.
Хочу немного расказать о сокетах своими словами. Сокет — это что-то вроде радио-приемника, который появляется на сервере после соединения с ним клиента, для обратной связи с клиентским приложением. Как только в этот приемник попадают (записываются) данные, он сразу отправляет их своему приложению-слушателю. Приложение, услышав (прочитав) данные, выполняет какие-либо действия на их основе. Это может быть простое сообщение, которое следует напечатать в чате, или команда которую нужно выполнить итд. Если не углубляться в подробности работы TCP/IP, то примерно так все и происходит.
Ну а теперь давайте вернемся к нашим бара… сокетам. Итак, что нам нужно? Нам нужно сделать так, чтобы как только один соединенный с сервером клиент что-то отправит на сервер, это сразу же было отправлено всем другим соединенным в данный момент с сервером клиентам. Или другая ситуация. Допустим при определенных обстоятельствах послать с сервера сообщение всем соединенным с ним клиентам, и чтобы они его мгновенно (!) получили. Как этого добиться? Я раскажу ниже.
Итак, определим что нам нужно для этого:
Для начала я приведу листинги клиента и сервера, а потом раскажу как воспользоваться готовым примером.
Клиент (index.html):
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"«http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd»>
<html xmlns=«http://www.w3.org/1999/xhtml»>
<head>
<title>Socket AppClient</title>
<meta http-equiv=«content-type»content=«text/html; charset=utf-8» />
<meta name=«author» content=«Melnaron»/>
<link rel=«stylesheet» type=«text/css»href=«css/style.css» />
<style type=«text/css»>
#log {
border: 1px solid #999999;
height: 250px;
overflow: auto;
margin: 10px 0;
padding: 10px;
}
</style>
<script type=«text/javascript»src=«lib/jquery.js»></script>
<script type=«text/javascript»>
$(document).ready(function() {
/*
* Настройка AJAX
*/
$.ajaxSetup({url: 'server.php', type: 'post', dataType: 'json'});
/*
* События кнопок и поля ввода
*/
$('#btnConnect').click(user.Connect);
$('#btnDisconnect').click(user.Disconnect);
$('#btnSend').click(user.Send);
$('#input')
.keydown(function(e){ if (e.keyCode == 13) { user.Send();return false; } })
.keypress(function(e){ if (e.keyCode == 13) { return false; }})
.keyup(function(e){ if (e.keyCode == 13) { return false; } })
;
});
</script>
<script type=«text/javascript»>
/*
* Лог
*/
var log = {
print: function(s) {
$('#log').append('<div>'+s+'</div>').get(0).scrollTop += 100;
}
};
/*
* Действияприсылаемые с сервера
*/
var actions = {
Connect: function(params) {
log.print('Connected.');
log.print('Sock: '+params.sock);
user.sock = params.sock;
user.conn = true;
user.Read();
},
Disconnect: function(params) {
log.print('Disconnected.');
},
Print: function(params) {
log.print(params.message);
}
};
/*
* Пользователь(клиент)
*/
var user = {
sock: null,
conn: false,
busy: false,
read: null,
/*
* Эта функция обрабатывает приходящие с сервера действия и выполняет их.
*/
onSuccess: function(data) {
if (typeof data.actions == 'object') {
for (var i = 0; i <data.actions.length; i++) {
if (typeofactions[data.actions[i].action] == 'function') {
actions[data.actions[i].action](data.actions[i].params);
}
}
}
},
/*
* Эта функция выполняется по завершении ajax-запроса.
*/
onComplete: function(xhr) {
if (xhr.status == 404) {
actions.Disconnect();
}
user.busy = false;
},
/*
* Эта функция выполняется по завершении запроса-слушания.
* При удачном завершении запроса (==200) моментальное возобновлениепрослушивания соккета.
* При неудачном (!=200) возобновление через 5 секунд.
*/
onCompleteRead: function(xhr) {
if (xhr.status == 200) {
user.Read();
} else {
setTimeout(user.Read, 5000);
}
},
/*
* Действие.
* Соединение с сервером.
*/
Connect: function() {
if (user.conn == false && user.busy == false) {
log.print('Connecting...');
user.busy = true;
$.ajax({
data: 'action=Connect',
success:user.onSuccess,
complete:user.onComplete
});
}
},
/*
* Действие.
* Отсоединение от сервера.
*/
Disconnect: function() {
if (user.conn && user.busy == false &&user.read) {
log.print('Disconnecting...');
user.busy = true;
$.ajax({
data:'action=Disconnect&sock='+user.sock,
success:user.onSuccess,
complete:user.onComplete
});
user.sock = null;
user.conn = false;
user.read.abort();
}
},
/*
* Действие.
* Отправка данных на сервер.
*/
Send: function() {
if (user.conn) {
var data = $.trim($('#input').val());
if (!data) {
return;
}
$.ajax({
data:'action=Send&sock='+user.sock+'&data='+data,
success:user.onSuccess,
complete:user.onComplete
});
$('#input').val('');
} else {
log.print('Please connect.');
}
},
/*
* Действие.
* Прослушивание соккета.
*/
Read: function() {
if (user.conn) {
user.read = $.ajax({
data:'action=Read&sock='+user.sock,
success:user.onSuccess,
complete:user.onCompleteRead
});
}
}
};
</script>
</head>
<body>
<input id=«btnConnect» type=«button»value=«Connect» />
<input id=«btnDisconnect» type=«button»value=«Disconnect» />
<div id=«log»></div>
<input id=«input» type=«text» />
<input id=«btnSend» type=«button»value=«Send» />
</body>
</html>
Сервер (server.php):
<?php
class Server {
/*
* Стек действий для отправки клиенту в текущем запросе.
*/
static private $actions;
/*
* Основная функция сервера.
* Запускает на выполнение переданное клиентом действие.
*/
static function Run() {
foreach (glob('sockets/*') as $sock) {
if (filesize($sock)> 2048) {
unlink($sock);
}
}
$action = 'action'.$_POST['action'];
if (is_callable('self::'.$action)) {
self::$action();
self::Send();
}
}
/*
* Эта функция пишет действие в сокеты.
* Если передан параметр $self, то исключает указанный в этом параметре сокет.
*/
static function AddToSock($action, $params = '', $self =null) {
foreach (glob('sockets/*') as $sock) {
if ($self &&strpos($sock, $self) !== false) {
continue;
}
$f = fopen($sock,'a+b') or die('socket not found');
flock($f, LOCK_EX);
fwrite($f, '{action:"'.$action.'", params: {'.$params.'}}'."\r\n");
fclose($f);
}
}
/*
* Эта функция добавляет действие в стек для отправки в текущем запросе.
*/
static function AddToSend($action, $params = '') {
self::$actions[] = '{action:"'.$action.'", params: {'.$params.'}}';
}
/*
* Отправка стека действий на выполнение клиенту.
*/
static function Send() {
if (self::$actions) {
exit('{actions:['.implode(', ', self::$actions).']}');
}
}
/*
* Действие.
* Соединение с сервером.
* Создает сокет и отправляет его идентификатор клиенту.
*/
static function actionConnect() {
$sock = md5(microtime().rand(1, 1000));
fclose(fopen('sockets/'.$sock, 'a+b'));
self::AddToSock('Print', 'message: «Clientconnected.»', $sock);
self::AddToSend('Connect', 'sock:"'.$sock.'"');
}
/*
* Действие.
* Отсоединение от сервера.
* Удаляет сокет.
*/
static function actionDisconnect() {
$sock = $_POST['sock'];
unlink('sockets/'.$sock);
self::AddToSock('Print', 'message: «Client disconnected.»');
self::AddToSend('Disconnect');
}
/*
* Действие.
* Отправляет введенные данные всем клиентам.
*/
static function actionSend() {
$sock = $_POST['sock'];
$data =htmlspecialchars(trim($_POST['data']), ENT_QUOTES);
if (strlen($data)) {
self::AddToSock('Print', 'message: "'.$data.'"', $sock);
self::AddToSend('Print', 'message: "'.$data.'"');
}
}
/*
* Действие.
* Слушает сокет до момента когда в нем появятся данные или же до истечения таймаута.
*/
static function actionRead() {
$sock = $_POST['sock'];
$time = time();
while ((time() — $time) < 30) {
if ($data =file_get_contents('sockets/'.$sock)) {
$f =fopen('sockets/'.$sock, 'r+b') or die('socket not found');
flock($f, LOCK_EX);
ftruncate($f, 0);
fwrite($f, '');
fclose($f);
$data = trim($data, "\r\n");
foreach (explode("\r\n", $data) as $action) {
self::$actions[] = $action;
}
self::Send();
}
usleep(250);
}
}
}
if (@$_SERVER['HTTP_X_REQUESTED_WITH'] == 'XMLHttpRequest') {
header('content-type: text/plain; charset=utf-8');
Server::Run();
}
?>
Для того чтобы увидеть это чудо в действии вам следует открыть приведенную ниже ссылку в 2х или более окнах браузера (или вообще в разных браузерах).
Итак, работающий пример находится тут.
Открываем, распологаем окна так чтобы работая в одном окне видить что происходит в другом. Соединяемся с сервером.
Соединив с сервером первое окно-приложение вы увидите идентификатор своего сокета отправленный с сервера. Соединив второе окно, в первом вы тут же увидите сообщение о соединении клиента. А теперь попробуйте наблюдая за первым окном что-то отправить из второго. Я надеюсь интернет у вас не очень медленный/загруженный, и вы увидите как быстро сообщение отобразится в обоих окнах — почти мгновенно.
Итак, это простой пример. В коде конечно можно еще покопаться. Для старта я думаю хватит.
Исходники полностью работающего примера можно скачать тут.
http://habrahabr.ru/post/41223/
Хочу немного расказать о сокетах своими словами. Сокет — это что-то вроде радио-приемника, который появляется на сервере после соединения с ним клиента, для обратной связи с клиентским приложением. Как только в этот приемник попадают (записываются) данные, он сразу отправляет их своему приложению-слушателю. Приложение, услышав (прочитав) данные, выполняет какие-либо действия на их основе. Это может быть простое сообщение, которое следует напечатать в чате, или команда которую нужно выполнить итд. Если не углубляться в подробности работы TCP/IP, то примерно так все и происходит.
Ну а теперь давайте вернемся к нашим бара… сокетам. Итак, что нам нужно? Нам нужно сделать так, чтобы как только один соединенный с сервером клиент что-то отправит на сервер, это сразу же было отправлено всем другим соединенным в данный момент с сервером клиентам. Или другая ситуация. Допустим при определенных обстоятельствах послать с сервера сообщение всем соединенным с ним клиентам, и чтобы они его мгновенно (!) получили. Как этого добиться? Я раскажу ниже.
Итак, определим что нам нужно для этого:
- Один php-файл для сервера.
- Один html-файл для клиента.
- Библиотека jQuery.
- И папка sockets для хранения сокетов.
Для начала я приведу листинги клиента и сервера, а потом раскажу как воспользоваться готовым примером.
Клиент (index.html):
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"«http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd»>
<html xmlns=«http://www.w3.org/1999/xhtml»>
<head>
<title>Socket AppClient</title>
<meta http-equiv=«content-type»content=«text/html; charset=utf-8» />
<meta name=«author» content=«Melnaron»/>
<link rel=«stylesheet» type=«text/css»href=«css/style.css» />
<style type=«text/css»>
#log {
border: 1px solid #999999;
height: 250px;
overflow: auto;
margin: 10px 0;
padding: 10px;
}
</style>
<script type=«text/javascript»src=«lib/jquery.js»></script>
<script type=«text/javascript»>
$(document).ready(function() {
/*
* Настройка AJAX
*/
$.ajaxSetup({url: 'server.php', type: 'post', dataType: 'json'});
/*
* События кнопок и поля ввода
*/
$('#btnConnect').click(user.Connect);
$('#btnDisconnect').click(user.Disconnect);
$('#btnSend').click(user.Send);
$('#input')
.keydown(function(e){ if (e.keyCode == 13) { user.Send();return false; } })
.keypress(function(e){ if (e.keyCode == 13) { return false; }})
.keyup(function(e){ if (e.keyCode == 13) { return false; } })
;
});
</script>
<script type=«text/javascript»>
/*
* Лог
*/
var log = {
print: function(s) {
$('#log').append('<div>'+s+'</div>').get(0).scrollTop += 100;
}
};
/*
* Действияприсылаемые с сервера
*/
var actions = {
Connect: function(params) {
log.print('Connected.');
log.print('Sock: '+params.sock);
user.sock = params.sock;
user.conn = true;
user.Read();
},
Disconnect: function(params) {
log.print('Disconnected.');
},
Print: function(params) {
log.print(params.message);
}
};
/*
* Пользователь(клиент)
*/
var user = {
sock: null,
conn: false,
busy: false,
read: null,
/*
* Эта функция обрабатывает приходящие с сервера действия и выполняет их.
*/
onSuccess: function(data) {
if (typeof data.actions == 'object') {
for (var i = 0; i <data.actions.length; i++) {
if (typeofactions[data.actions[i].action] == 'function') {
actions[data.actions[i].action](data.actions[i].params);
}
}
}
},
/*
* Эта функция выполняется по завершении ajax-запроса.
*/
onComplete: function(xhr) {
if (xhr.status == 404) {
actions.Disconnect();
}
user.busy = false;
},
/*
* Эта функция выполняется по завершении запроса-слушания.
* При удачном завершении запроса (==200) моментальное возобновлениепрослушивания соккета.
* При неудачном (!=200) возобновление через 5 секунд.
*/
onCompleteRead: function(xhr) {
if (xhr.status == 200) {
user.Read();
} else {
setTimeout(user.Read, 5000);
}
},
/*
* Действие.
* Соединение с сервером.
*/
Connect: function() {
if (user.conn == false && user.busy == false) {
log.print('Connecting...');
user.busy = true;
$.ajax({
data: 'action=Connect',
success:user.onSuccess,
complete:user.onComplete
});
}
},
/*
* Действие.
* Отсоединение от сервера.
*/
Disconnect: function() {
if (user.conn && user.busy == false &&user.read) {
log.print('Disconnecting...');
user.busy = true;
$.ajax({
data:'action=Disconnect&sock='+user.sock,
success:user.onSuccess,
complete:user.onComplete
});
user.sock = null;
user.conn = false;
user.read.abort();
}
},
/*
* Действие.
* Отправка данных на сервер.
*/
Send: function() {
if (user.conn) {
var data = $.trim($('#input').val());
if (!data) {
return;
}
$.ajax({
data:'action=Send&sock='+user.sock+'&data='+data,
success:user.onSuccess,
complete:user.onComplete
});
$('#input').val('');
} else {
log.print('Please connect.');
}
},
/*
* Действие.
* Прослушивание соккета.
*/
Read: function() {
if (user.conn) {
user.read = $.ajax({
data:'action=Read&sock='+user.sock,
success:user.onSuccess,
complete:user.onCompleteRead
});
}
}
};
</script>
</head>
<body>
<input id=«btnConnect» type=«button»value=«Connect» />
<input id=«btnDisconnect» type=«button»value=«Disconnect» />
<div id=«log»></div>
<input id=«input» type=«text» />
<input id=«btnSend» type=«button»value=«Send» />
</body>
</html>
Сервер (server.php):
<?php
class Server {
/*
* Стек действий для отправки клиенту в текущем запросе.
*/
static private $actions;
/*
* Основная функция сервера.
* Запускает на выполнение переданное клиентом действие.
*/
static function Run() {
foreach (glob('sockets/*') as $sock) {
if (filesize($sock)> 2048) {
unlink($sock);
}
}
$action = 'action'.$_POST['action'];
if (is_callable('self::'.$action)) {
self::$action();
self::Send();
}
}
/*
* Эта функция пишет действие в сокеты.
* Если передан параметр $self, то исключает указанный в этом параметре сокет.
*/
static function AddToSock($action, $params = '', $self =null) {
foreach (glob('sockets/*') as $sock) {
if ($self &&strpos($sock, $self) !== false) {
continue;
}
$f = fopen($sock,'a+b') or die('socket not found');
flock($f, LOCK_EX);
fwrite($f, '{action:"'.$action.'", params: {'.$params.'}}'."\r\n");
fclose($f);
}
}
/*
* Эта функция добавляет действие в стек для отправки в текущем запросе.
*/
static function AddToSend($action, $params = '') {
self::$actions[] = '{action:"'.$action.'", params: {'.$params.'}}';
}
/*
* Отправка стека действий на выполнение клиенту.
*/
static function Send() {
if (self::$actions) {
exit('{actions:['.implode(', ', self::$actions).']}');
}
}
/*
* Действие.
* Соединение с сервером.
* Создает сокет и отправляет его идентификатор клиенту.
*/
static function actionConnect() {
$sock = md5(microtime().rand(1, 1000));
fclose(fopen('sockets/'.$sock, 'a+b'));
self::AddToSock('Print', 'message: «Clientconnected.»', $sock);
self::AddToSend('Connect', 'sock:"'.$sock.'"');
}
/*
* Действие.
* Отсоединение от сервера.
* Удаляет сокет.
*/
static function actionDisconnect() {
$sock = $_POST['sock'];
unlink('sockets/'.$sock);
self::AddToSock('Print', 'message: «Client disconnected.»');
self::AddToSend('Disconnect');
}
/*
* Действие.
* Отправляет введенные данные всем клиентам.
*/
static function actionSend() {
$sock = $_POST['sock'];
$data =htmlspecialchars(trim($_POST['data']), ENT_QUOTES);
if (strlen($data)) {
self::AddToSock('Print', 'message: "'.$data.'"', $sock);
self::AddToSend('Print', 'message: "'.$data.'"');
}
}
/*
* Действие.
* Слушает сокет до момента когда в нем появятся данные или же до истечения таймаута.
*/
static function actionRead() {
$sock = $_POST['sock'];
$time = time();
while ((time() — $time) < 30) {
if ($data =file_get_contents('sockets/'.$sock)) {
$f =fopen('sockets/'.$sock, 'r+b') or die('socket not found');
flock($f, LOCK_EX);
ftruncate($f, 0);
fwrite($f, '');
fclose($f);
$data = trim($data, "\r\n");
foreach (explode("\r\n", $data) as $action) {
self::$actions[] = $action;
}
self::Send();
}
usleep(250);
}
}
}
if (@$_SERVER['HTTP_X_REQUESTED_WITH'] == 'XMLHttpRequest') {
header('content-type: text/plain; charset=utf-8');
Server::Run();
}
?>
Для того чтобы увидеть это чудо в действии вам следует открыть приведенную ниже ссылку в 2х или более окнах браузера (или вообще в разных браузерах).
Итак, работающий пример находится тут.
Открываем, распологаем окна так чтобы работая в одном окне видить что происходит в другом. Соединяемся с сервером.
Соединив с сервером первое окно-приложение вы увидите идентификатор своего сокета отправленный с сервера. Соединив второе окно, в первом вы тут же увидите сообщение о соединении клиента. А теперь попробуйте наблюдая за первым окном что-то отправить из второго. Я надеюсь интернет у вас не очень медленный/загруженный, и вы увидите как быстро сообщение отобразится в обоих окнах — почти мгновенно.
Итак, это простой пример. В коде конечно можно еще покопаться. Для старта я думаю хватит.
Исходники полностью работающего примера можно скачать тут.
http://habrahabr.ru/post/41223/
Комментариев нет:
Отправить комментарий