Git Product home page Git Product logo

websocket's People

Contributors

morozovsk avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

websocket's Issues

Локальный сокет

Здравствуйте!
Не нашел, как можно лично задать вам вопрос.
Попытался реализовать у себя сокет на примере первого чата. Не получается установить с ним подключение по сокету unix:///tmp/websocket.sock, про который вы говорите в статье http://habrahabr.ru/company/ifree/blog/210228/.
Например, пишу в index.php 'localsocket' => 'unix:///var/www/websocket.sock' , затем пытаюсь передать данные:
$fp = stream_socket_client("unix:///var/www/websocket.sock", $errno, $errstr, 30);
if (!$fp) {
echo "$errstr ($errno)
\n";
} else {
fwrite($fp, "vsem privet"."\r\n");
while (!feof($fp)) {
echo fgets($fp, 1024);
}
fclose($fp);
}
Пишет, доступ запрещен, меняю права на 777, скрипт долго выполняется, но ничего не передается. Подскажите, пожалуйста, что я не так делаю?

Еще подскажите, пожалуйста, правильно ли я понимаю, уязвимости, про которые вам писали в комментариях, есть в первом чате, но исправлены во втором?

Падение мастера и несуществующий connectionId в buffer'ах

Проблема 1:
в Generic.php (для любых модулей) есть функции _read, _write, close которые могут заэррорить и сказать Call to undefined function read/write/disable on null, решается добавлением
if(!isset($this->buffers[$connectionId])) return;
в каждую из функций

Проблема 2:
Event, связанная с этой библиотекой, через время (прим. 15мин) $this->getIdByConnection($this->_master) отдает 0 (GenericEvent.php), думая что это не мастер, передача данных в функцию onMasterMessage не происходит, решил для себя добавлением возле
$this->onServiceMessage($connectionId, $data);
этого
$this->onMasterMessage($data);

Но лучше ждать оф. фикс от автора

список онлайн юзеров

Есть вопрос, как при подключении/отключении новых юзеров отображать изменения с списке онлайн юзеров? ведь сокет напрямую не связан с javascript на фронте? или можно как-то отправлять сообщения в json формате и обрабатывать отдельно текстовую часть, отдельно данные?

Работа в Safari

При работе в сафари js выдает ошибкуIndexSizeError (DOM Exception 1): The index is not in the allowed range.
У вас было такое? Можно включить чат сервер на демке, а то не получается протестить...
Кстати заметил что периодически чат сервер самопроизвольно отваливается, с чем это может быть связано?

Не сбрасываются коннекты

Здравствуйте.

Используем примеры Chat3 и eventDriver=>event.
Со вчерашнего утра и по сегодняшнее коннекты только растут.
На текущий момент 3840.
То есть за ночь не сбросились.
Есть такая проблема и как бороть если есть?
Или это реальные коннекты и радоваться такому обстоятельству дел?

PS: Вопрос бонусом, если нужно создам Issue
В итоге в конфиге осталась одна закомментированная строка:
//'master' => 'tcp://127.0.0.1:8020',
Для чего она?

Вопрос о наследовании и Demon

стал изучать Ваш demon и заметил что он наследуется от класса Generic но самого класса не нашел не нашел так же и следующих свойств

  1. $this->clients
  2. $this->_write
  3. $this->_read
  4. $this->_read

и метода

  • $this->_read();

я так понимаю они должны быть в классе Generic и как сейчас без них работает Demon ?

Running on windows

Hi! Is it possible to get workaround?
PHP Fatal Error 'yii\base\ErrorException' with message 'Call to undefined function morozovsk\websocket\posix_getpid()'

in C:\wamp\www\flex\vendor\morozovsk\websocket\Server.php:61

json-post-request, связка Service+Websocket сервер, exceptions

привет! из коробки не работают POST-запросы с JSON-payload, корректно(а может и нет) работает вот такой апдейт GenericSelect (с 99 строки): http://pastebin.com/2M6U3C9v

Так же не очень понятно зачем вызывается колбэк onServiceMessage через каждый перенос строки... Использую, например, service как микро-веб-сервер для обработки входящих запросов с другого сервера и связи их(запросов) с вебсокетами. Так коряво - потому, что, опять же из коробки нельзя получить доступ к подключенным вебсокет-клиентам НЕ из текущего экземпляра компонента, т.е. приходится этих клиентов где-то хранить, апдейтить их статус...

Проблема 3: если пропадает соединение с вебсокет-клиентом, то при попытке ему что-то отослать (sendToClient), сервер падает целиком, не выбрасывая никакого исключения, с точки зрения парадигмы Yii - это не правильно... Обернув stream_select в проверку и try...catch в принципе это решается, но иногда дропает и из других неопределенных мест

Можно ли создавать комнаты?

Здравствуйте.

Хочу интегрировать ваше решение к себе на сайт.
Подскажите можно ли как то отправлять сообщение конкретно одному пользователю?
В примерах Чат3 - улучшенная вариация Чат2 ?

Спасибо.

Управление сервером

Было б хорошо сделать управление сервером, остановку, статус и т.п.
А то чтобы остановить надо убивать процес, а после этого сервер не запускаеться, пишет что порт занат

EventListener callback

В этом методе https://github.com/morozovsk/websocket/blob/master/WebsocketGenericEvent.php#L51 , вы параметр $connection обрабатываете с помощью $this->getIdByConnection($connection);
Вопрос : В каких случаях $connection не int ?

Мой дамп при конекте:

var_dump([$listener, $connection, $address, $id]);

array(4) {
  [0]=>
  object(EventListener)#5 (1) {
    ["fd"]=> int(3)
  }
  [1]=> int(7)
  [2]=>
  array(2) {
    [0]=> string(9) "127.0.0.1"
    [1]=> int(60314)
  }
  [3]=>
  object(EventBase)#4 (0) {
  }
}

PHP7

Добрый день!
Не подскажите, нет ли в планах адаптации библиотеки под PHP7? Или библиотека в текущем состоянии полностью совместима?
К тому же, буквально вчера (29.02.2016) вышла стабильная версия Event с поддержкой PHP7 - https://pecl.php.net/package/event
Во второй версии, судя по ченжлогу поправили утечки памяти (не знаю, может только у меня так, но на php 5.6.x с event после 14-15к коннектов, последующие коннекты начинают приниматься 1 через 5, перезапускаешь демона - все как по маслу, возвращаешься на socket_select - проблем нет.

Как подружить с pg_notify

Имеется скрипт следующего содержания:
pg_query($this->connection, 'LISTEN user_notify;');
while (1) {
$notify = pg_get_notify($this->connection);
if ($notify) {
var_dump(json_decode($notify['payload'], true));
}
}

Как появляется уведомление в канале user_notify - оно распечатывается. От библиотеки нужно что б она слушала этот канал и как там появлялись данные отсылала по клиентам. Вопрос: как подружить это с библиотекой?

Реализация на Event + Pthreads

Спасибо, за вашу работу.
Очень хотелось бы увидеть качественный пример исполнения данных примеров на в немного другой реализации, вместо модуля libevent использовать более качественный и удобный модуль Event (http://ua2.php.net/manual/ru/book.event.php)

вместо pcntl_fork, использовать Pthreads (http://docs.php.net/manual/ru/book.pthreads.php)

Или услышать ваше мнения про использования данных модулей, для этих задач?
Просто не хочется использовать тяжелые PHPDaemon и/или Ratchet.
Хочется самому реализовать, но сокеты и pcntl_fork довольно низко уровневое программирования и много кода, хочется использовать библиотеки более высокого уровня и на ООП.

how to start 2 websocket servers on 1 VDS

Hello.

Is it possible to run 2 websocket servers on 1 VDS?

I have 2 web applications on 1 VDS. Both of them use your yii2 websocket extension.

To start the 1st websocket server I run:
cd /var/www/app1
./yii websocket/start notificationServer1

But when I run the 2nd websocket server:
cd /var/www/app2
./yii websocket/start notificationServer2

then I get "already started" in the console output. I want the 2nd server working as well.

How can I make it work?

config1:
'websocket' => [ 'class' => 'morozovsk\yii2websocket\Connection', 'servers' => [ 'notificationServer1' => [ 'class' => 'app\modules\v1\modules\notification\components\WebsocketDaemonHandler', 'pid' => '/tmp/websocket_chat.pid', 'websocket' => 'tcp://127.0.0.1:8004', 'localsocket' => 'tcp://127.0.0.1:8010' ] ], ],

config2:
'websocket' => [ 'class' => 'morozovsk\yii2websocket\Connection', 'servers' => [ 'notificationServer1' => [ 'class' => 'app\modules\v1\modules\notification\components\WebsocketDaemonHandler', 'pid' => '/tmp/websocket_chat.pid', 'websocket' => 'tcp://127.0.0.1:8014', 'localsocket' => 'tcp://127.0.0.1:8024' ] ], ],

Hope you got what I mean. Thanks in advance.

Как сделать многопоточность?

У меня при соединении по некоторым клиентам будет осуществляться рассылка больших объемов данных, как сделать чтоб они не блокировали работу остальных клиентов?
Прикручивать pthreads?

Possible logic error & websocket standards

Possible logic error
I did not actually run your code but from looking at it..
And I might be wrong because i really do not understand the Russian language, in that case i apologize..
Here goes..
Daemon.php

protected function _onOpen($connectionId) {
  $this->_handshakes[$connectionId] = '';//отмечаем, что нужно сделать рукопожатие
}
protected function _onMessage($connectionId) {
  if (isset($this->_handshakes[$connectionId])) {
    if ($this->_handshakes[$connectionId]) {//если уже было получено рукопожатие от клиента
      return;//то до отправки ответа от сервера читать здесь пока ничего не надо
    }
    if (!$this->_handshake($connectionId)) {
      $this->close($connectionId);
    }
  } else {
    while (($data = $this->_decode($connectionId)) && mb_check_encoding($data['payload'], 'utf-8')) {//декодируем буфер (в нём может быть несколько сообщений)
      $this->onMessage($connectionId, $data['payload'], $data['type']);//вызываем пользовательский сценарий
    }
  }
}

The above 2 methods are called from the while loop GenericSelect->start() in GenericSelect.php.

...
if ($this->_handshakes[$connectionId]) {//если уже было получено рукопожатие от клиента
  return;//то до отправки ответа от сервера читать здесь пока ничего не надо
}
...

The $this->_handshakes[$connectionId] contains an empty string ('') which evaluates to true.
This causes the function to return and actual handshake $this->_handshake($connectionId) to never happen..
You probably meant to check for:

...
if ($this->_handshake($connectionId)) {//если уже было получено рукопожатие от клиента
  return;//то до отправки ответа от сервера читать здесь пока ничего не надо
}
...

That would evaluate to true on an incomplete handshake and return to continue reading until a complete handshake has been received..

Websocket standards
Daemon.php

protected function _onMessage($connectionId) {
  ...
    if (!$this->_handshake($connectionId)) {
      $this->close($connectionId);
    }
  ...
}

The connection simply gets closed while the websocket standard states that a proper response should be written to the client.
For example when the handshake does not contain Sec-WebSocket-Key the proper response to write to the client would be "HTTP/1.1 400 Bad Request\r\n\r\n"
In fact the handshake should be checked for many more errors for example when Sec-WebSocket-Version is incompatible the proper response to write to the client would be "HTTP/1.1 426 Upgrade Required\r\nSec-WebSocketVersion: 13\r\n\r\n"

A possible solution would be to:
Daemon.php

protected function _onMessage($connectionId) {
  if (isset($this->_handshakes[$connectionId])) {
    if ($this->_handshake($connectionId)) {//если уже было получено рукопожатие от клиента
      return;//то до отправки ответа от сервера читать здесь пока ничего не надо
    }
    if ($this->_handshakes[$connectionId] != false && !$this->_handshake($connectionId)) {
     $this->_handshakes[$connectionId] = false;
    }
  } else {
    while (($data = $this->_decode($connectionId)) && mb_check_encoding($data['payload'], 'utf-8')) {//декодируем буфер (в нём может быть несколько сообщений)
      $this->onMessage($connectionId, $data['payload'], $data['type']);//вызываем пользовательский сценарий
    }
  }
}

Do not call $this->close($connectionId) strait away but set $this->_handshakes[$connectionId] to false.

GenericSelect.php

...
  if ($write) {
    foreach ($write as $client) {
      if (is_resource($client)) {//проверяем, что мы его ещё не закрыли во время чтения
        $this->_sendBuffer($client);
        if ($this->_handshakes[$client] == false) { $this->close($client); }
      }
    }
  }
...

On the next while loop stream_select($read, $write, $except, null); will pick up the _write buffer containing the error and once it has written the proper response check $this->_handshakes[$client] for false and close the connection there..

That's what i've found so far..
I like your coding style, keep up the good work!

how to set WSS

Hello. Thank you for this great library

I have a website using HTTPS. And I can not connect to websocket server via WS protocol. I need WSS instead. How can I do it with your library?

Потраченное время на разработку.

Здравствуйте.

Такой вопрос не тривиальный.
Сколько у вас ушло времени на создание рабочего прототипа и примеров с третьим чатом?
Спасибо.

Yii components using

  1. WebsocketGeneric.php class hasn't method onTimer which used in
if ( $this->timer && in_array( $timer, $read ) ) {
                unset( $read[ array_search( $timer, $read ) ] );
                fread( $timer, self::SOCKET_BUFFER_SIZE );
                $this->onTimer();
            }
  1. When I run the command in console: console.php Websocket start I see nothing,
    but when I run the command (in another console): console.php Websocket stop

I see the error:
PHP Error[8]: fwrite(): send of 1 bytes failed with errno=32 Broken pipe

It is in the code:

else { //дочерний процесс
            fclose( $pair[ 1 ] );
            $parent = $pair[ 0 ]; //второй в дочернем процессе

            while ( true ) {

                fwrite( $parent, '1' );
                usleep( $this->timer * 1000000 );
            }
        }

Also parent process wasn't closed. In process manager I see that child process has closed, but parent process is not. And I see two PHP processes per worker. It is ok?

posix functions not working for Windows

First of all thanks for the amazing code. Especially the websocket chat for private messaging.

So this issue was already posted at #9 but since there is still no answer, I will post this issue again.

I really like to use and study the script but I am using windows 7 64 bit with PHP 5.6, and since the "posix" functions of PHP like "posix_getpid" doesn't work on windows, I can't use it.

Is there any alternative PHP functions for posix or could you update the chat3 demo so I can use it under Windows?

Один конект по сокету на несколько вкладок

Здравствуйте.

Использую примеры Chat3.
Только userId формирую из своих даных.
Случается так, что нужно продублировать вкладку, хотя бы просто что бы запомнить выдачу, а другую обновить и сравнить результат.
Итого одна и та же страница , одни и те же данные для формирования userId и два коннекта по сокету.
Подскажите пожалуйста, можно ли не создавать новые соединение, а вернуть имеющееся, если совпадает userId?

Возможно ответ и есть в коде, но вы свой код знаете лучше, не поймите не правильно.

Спасибо.

websocket lunch on remote host

hi, I'm sorry to open ticket for my question , but actually I'm not pro in php and with your instruction not figure out how to install it in my remote directory host , and really appreciate for couple of hints , in fact I've tried to hook url on my host to the Telegram webhoock call back bot, so at first step new update data inserted to MySQL database then websoket server give the update data to designated live client .
can you tell me which files should be copy in designated host URL and what files should be modified?
Thanks

Ошибка при остановке

Здравствуйте.
Большое спасибо за проект, разбираюсь и интегрирую.

Передо мной встала задача ловить pg_get_notify(), поразбиравшись в примерах я решил использовать метод onTimer(). Насколько я понял, принцип работы заключается в запуске дочернего процесса, который через заданный интервал выполняет заданные действия. Судя по всему это то, что надо, однако есть небольшая проблема.

Дело в том, что при выполнении команды ./yii websocket/stop сервер корректно завершает работу, а вот дочерний процесс похоже остается, и поэтому в консоль выводится ошибка:

PHP Notice 'yii\base\ErrorException' with message 'fwrite(): send of 1 bytes failed with errno=32 Broken pipe'
in /var/www/project.zz/vendor/morozovsk/websocket/GenericSelect.php:188

Или я ошибаюсь? Это, в принципе, не критично, но есть желание сделать все корректно.
Спасибо.

Тестирование нагрузки

Добрый день! В первую очередь хотел выразить слова благодарности Вам за вашу библиотеку, используем в боевых условиях, очень круто!

По мере роста проекта уперлись конечно же в ограничение 1024 коннекта. Переписываем на вариант с воркерами, и тут возникает такой вопрос: а чем/как можно все это дело протестировать? В самой первой вашей статье на хабре Вы писали, что локально подавали одновременно 10к соединений, хотелось бы протестировать также работу с воркерами.

fwrite() failed errno=32

Добрый день!
Спасибо за вашу библиотеку.
Подскажите пожалуйста, при использовании библиотеки, довольно часто возникает ошибка:
Notice: fwrite(): send of 1024 bytes failed with errno=32 Broken pipe
Может ли это связано с тем, что я увеличил MAX_SOCKET_BUFFER_SIZE до 262144 ?
И еще, простите за столь нубский вопрос, для чего нужны 'localsocket' => и 'master' =>, в чем отличие от 'websocket' => ?
Спасибо.

Отправка изображения через вебсокет

Здравствуйте
Можете подсказать, как можно передать изображение из браузера через вебсокет именно как файл (бинарник) и как принять правильно это файл на стороне сервера?

request response order

Hello.
My OS is windows 7 -> php 7.1
i have one issue with request/response. I use GenericSelect class.
Example:
send requests to server
1 - task:'login' ... (take 10 seconds) sent time - 00:00:00
2 - task:'event'.... (take 1 second) sent time - 00:00:01
This is response
1 - task:'login' ... time - 00:00:10
2 - task:'event'.... time - 00:00:10

but i would have this respone
1 - task:'event' ... time - 00:00:01
2 - task:'login'.... time - 00:00:10

Could anybody help me solving this issue? I tried to use timer but unfortunately o had an error inside _createTimer function
Thanks

"Залипает" при слишком длинном handshake-сообщение (режим event)

Проблема в том, что читается один раз до SOCKET_BUFFER_SIZE байт. Если в буфере байт больше, повторно onRead не вызовется и соединение "зависнет".

Лечится заменой в GenericEvent функции _read на такую:
protected function _read($connectionId)
{
while (true)
{
$data = $this->buffers[$connectionId]->read(self::SOCKET_BUFFER_SIZE);

		if (!strlen($data)) break;

		@$this->_read[$connectionId] .= $data; //add the data into the read buffer
	}
	return strlen($this->_read[$connectionId]) < self::MAX_SOCKET_BUFFER_SIZE;
}

GenericEvent считывает не весь буфер

Если пакет превышает значение константы SOCKET_BUFFER_SIZE, то он не считывает следующую часть, а ждет повторного срабатывания события onRead.

wss timeot

При переезде на https получаю timeout, клиент соединяется через wss://

Отваливается соединение при отправке сообщения

Добрый день. Не поможете советом?

Есть небольшая проблема. Может быть, Вы с подобным уже сталкивались, и мне не придётся заново изобретать велосипед.

Всё установил, запустил, даже подключился. Но есть одно но, сообщения на клиент приходят через раз и какие-то обрезанные. Причём после отдачи этого обрезанного сообщения падает соединение с сервером. Клиент есть здесь: http://boroviha.dev.ooosis.com/example/index.html

В Вашем коде практически ничего не менял, только в методе ChatWebsocketDaemonHandler::onOpen убрал второй параметр.

Не закрывает соединение при окончании на сайте

Возможно это так и задумано, но если на сайте делать ws.close то происходит событие onMessage с $type=="close", а событие onClose происходит только после того как сайт прерывает соединение (наверно по тайм ауту).

PHP Client

Здравствуйте.

Использую https://github.com/morozovsk/websocket-examples/tree/master/chat3

Нужно получить список пользователей через PHP.

Взял текущий файл: https://github.com/morozovsk/websocket-examples/blob/master/chat3/server/send.php

И постарался самостоятельно состряпать, что то вроде:

#!/usr/bin/env php
<?php
$socketUrl = 'tcp://127.0.0.1:8010';

$message = "message";
$userId = "k4krmk4k3";

$context = stream_context_create();
$instance = stream_socket_client($socketUrl, $errno, $errstr, 10, STREAM_CLIENT_CONNECT, $context);

if( !$instance ) echo "$errstr ($errno)<br />\n";
else
{
    $sent = fwrite( $instance, json_encode( ['message' => $message, 'userId' => $userId] )."\n");
    if( $sent > 0 )
    {
        $server_response = fread($instance, 4096);
        echo $server_response;
    }
}

В данном файле https://github.com/morozovsk/websocket-examples/blob/master/chat3/server/Chat3WebsocketDaemonHandler.php в методе onOpen я добавил условие, и получил такое:

protected function onOpen($connectionId, $info) /// Вызывается при соединении с новым клиентом
{
        ///$message = 'пользователь #' . $connectionId . ' : ' . var_export($info, true) . ' ' . stream_socket_get_name($this->clients[$connectionId], true);
        ///foreach( $this->clients as $clientId => $client ) $this->sendToClient($clientId, $message);

        $info['GET']; /// or use $info['Cookie'] for use PHPSESSID or $info['X-Real-IP'] if you use proxy-server like nginx
        parse_str(substr($info['GET'], 1), $_GET);//parse get-query
        ///var_export($_GET['id']);
        $userId = @$_GET['userId'];
        $this->userIds[$connectionId] = $userId;
        if( 'k4krmk4k3'==$userId )
        {
            $message = json_encode(array(
                'clients'=>$this->userIds,
                'info'=>$info,
            ));
            $this->sendToClient($connectionId, $message);
        }
}

Через вашего клиента на JS, все гуд, но нужно через PHP.

Конечно на это все не знание и не понимание, но в первую очередь решил обратиться к вам.
Надеюсь поможете с этим вопросом.

Спасибо.

stream_select(): supplied argument is not a valid stream resource

Периодически возникает ошибка PHP Error[2]: stream_select(): supplied argument is not a valid stream resource
in file /var/www/ph2/protected/extensions/websocket/GenericSelect.php at line 63
#0 /var/www/ph2/protected/extensions/websocket/GenericSelect.php(63): stream_select()
#1 /var/www/ph2/protected/extensions/websocket/Server.php(84): morozovsk\websocket\Chat3WebsocketDaemonHandler->start()

С чем может быть связано?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.