Ir para conteúdo

Arquivado

Este tópico foi arquivado e está fechado para novas respostas.

Micilini Roll

[Resolvido] [Puzzle] Chat Privado (WebSockets + PHP Sockets)

Recommended Posts

Fala galera, estou aqui para pedir a ajuda de todos voces. Tudo que iremos tentar fazer é criar um chat privado utilizando websockets junto ao php sockets, para adiantar algumas coisas eu ja fiz um chat e disponibilizei no github, voces podem acessar atraves do link abaixo:

 

 

Esse chat funciona perfeitamente, mas no entanto ele é um chat global, ou seja, uma mensagem enviada por um usuario X, vai para todos os usuarios que estão na sala do chat. Como dito anteriormente a nossa ideia é criar um chat privado, então vamos a algumas considerações:

 

  • No meu caso estou utilizando os arquivos dentro da pasta easychat (que esta no link enviado anteriormente)
  • Podemos utilizar node.js, socket.io e afins? Não, estou querendo resolver somente com php e javascript puro.

 

Agora chegou a hora de dar uma pincelada no meu codigo:

 

Aplicacao.js

 

Existem duas funções responsaveis por enviar e receber a mensagem do servidor e abaixo estão elas:

form.onsubmit = function(e) { e.preventDefault(); // Recuperando a mensagem do textarea.
var mensagem = mensagemTexto.value; // Enviando a mensagem através do WebSocket.
socket.send(mensagem); // Adicionando a mensagem numa lista de mensagens enviadas.
listaMensagem.innerHTML += '<li class="envia"><span>Enviado:</span>' + mensagem + '</li>'; // Limpando o campo da mensagem após o envio.
mensagemTexto.value = ''; return false;
};


socket.onmessage = function(event) {
var mensagem = event.data; listaMensagem.innerHTML += '<li class="recebida"><span>Recebida:</span>' + mensagem + '</li>';
};

Quando enviamos uma mensagem ele executa o comando socket.send(mensagem) ali eu pensei em enviar mais alguns parametros como:

 

  • IP do usuario (Pode precisar)
  • Chat de onde ele esta acessando (Chat1, Chat2, Chat3)
  • Mensagem (é claro rs)

 

Já no lado do PHP, o comando abaixo é responsavel por pegar a resposta do usuario e retorna-la de volta:

 

Server.php

 }else{
$bytes = socket_recv($sock, $data, 2048, null);
$d = unmask($data);
foreach ($cls as $socket) {
if($socket != $m_sock && $val > 0){
try{
socket_write($socket,(encode($d)));
}catch(Exception $e){
unset($cls[$socket]);
socket_close($cls[$socket]);
}
}
}
}
} 

Com o comando socket_recv ele consegue captar a mensagem que esta sendo enviada e realiza a função unmask nela, um pouco mais adiante ele faz o encode desta mensagem que recebeu e envia de volta para aquele socket (e todos que estão conectados naquele socket recebem a mensagem).

 

Nesse caso, como nosso chat é privada devemos pensar tambem em fazer o insert e em seguida um select no banco de dados, entao melhor maneira de fazer isso é inserindo esses comandos entre os codigos try e socket_write, então vamos aos testes:

......

try{

//Aqui estabilizamos uma conexão com o banco de dados  e inserimos na tabela a mensagem + ip + data + Chat1 (ou 2 ou 3).

socket_write($socket,(encode($d)));
}

.....

Acho que tambem seria melhor criar um campo no banco de dados para verificar se a mensagem foi visualizada ou não?

 

Depois que inserimos no banco de dados chegou a hora do puzzle:

 

Como iremos fazer para retornar a mensagem para aquele determinado usuario?

 

Antes de respondermos a esta pergunta vamos criar um cenario novo e de simples visualização para todos nos:

 

  • Vamos criar um arquivo php com 2 links: Ir para o Chat 1 & Ir para o Chat 2
  • Vamos reaproveitar aqueles arquivos dentro da pasta EasyChat
  • Vamos duplicar o arquivo index.php, criando indexChat1.php e IndexChat2.php

 

Ambos os indexs irão utilizar a porta 8080 (assim como esta no github), Não! nos não iremos separar os chats em portas diferentes (tenha em mente que isso é so um teste para realizar algo mais completo lá na frente, IMAGINE 10000 chats privados acontecendo simultaneamente e abrimos uma porta para cada um deles......).

 

Entao apartir deste momento temos 2 indexes diferentes utilizando os mesmos arquivos: aplicacao.js e server.php, no caso do arquivo aplicacao.js ele envia valores diferentes para o socket php como ip, mensagem e o tipo de chat que ele esta inserido, como chat1 ou chat 2.

 

Executandos os Testes

 

OK, vamos abrir o arquivo indexChat1.php em uma aba, e indexChat2.php em outra aba do nosso navegador, vamos inicialmente escrever uma mensagem no chat1, quando fizermos isso (e do jeito que nosso codigo atualmente esta), a mensagem será recebida tambem na aba do chat2, e a mesma coisa aconteceria caso digitarmos algo no chat2.

 

Mas pelo menos no banco de dados esta dá seguinte forma:

 

ID = 0

IP = 32.22.221.22

Local = Chat1

Mensagem = Olá mundo

Data = 01/01/2016

Visualizado = false

 

Agora voltamos a seguinte pergunta: Como iremos fazer para os usuarios que estão no chat1, nao receber e nem enviar mensagens para os usuarios que estão no chat2 e virce-versa?

 

Eu acredito que a resposta esta na criação de um codigo que ainda nao sabemos mas que deve ser colocado dentro do arquivo server.php mais especificamente entre os comandos try e socket_write.

 

Alguem tem mais alguma ideia para contribuir, assim que vou avançando postarei algumas soluções neste post ;)

 

 

Compartilhar este post


Link para o post
Compartilhar em outros sites

SOLUÇÃO 1

 

Estava pensando em uma solução para esse determinado problema, atualmente não tenho absoluta certeza se o arquivo php junto ao socket se comportam como se fosse um SINGLETON (o que pode ser um problema para esta solução) mas a solução pensada foi:

 

Quando inserirmos no banco de dados, automaticamente nos iremos puxar as conversas que estão como visualizadas = false, isso vai fazer com que se retorne a mensagem que o proprio usuario enviou (e que apareça no chat) e tambem poderá puxar as mensagens que outras pessoas enviaram. Puxaremos tambem no banco de dados as mensagens que estão como marcadas como Local = chat1, isso vai fazer com que se retorne somente as mensagens daquela pessoa.

 

Ate ai tudo bem, e parece ser uma otima ideia, mas como todos nos estamos na mesma porta, acredito que ainda continuaremos a receber as mensagens advindas do chat2. Então como proceder?

 

Se lembra que no arquivo aplicacao.js as mensagens são recebidas desta forma: var mensagem = event.data;

 

Neste caso será que nao poderiamos fazer com que o socket_Write nos retorne 2 parametros como a mensagem e o local (chat1), isso faria com que no javascript ele fizesse uma checagem e filtrasse as mensagens e pegasse somente aquela referente ao chat1? Bem tenho algumas considerações a fazer:

 

  • Problema na segurança, alguem pode ir lá no codigo e tirar essa filtragem e começar a receber a mensagem de todos.
  • Considerando que temos 1000 chats ativos simultaneamente, uma pessoa irá receber todas as mensagens que não são referentes a ela. (E não acho que esses chats privados funcionam desta forma)

 

Atualização: Este metodo não é muito confiavel e dependendo da velocidade de conexão do usuario isso pode se tornar um grande problema.

Compartilhar este post


Link para o post
Compartilhar em outros sites

Solução 2

 

Fazendo uma breve busca na documentação do php foi encontrado um comando que acredito que possa substituir o socket_write, o socket_sendTo, de acordo com o que foi visto parece que ele envia uma resposta para o socket conectado, se estiver conectado ou não, abaixo se encontra um simples exemplo:


<?php
    $sh = socket_create(AF_INET,SOCK_STREAM,SOL_TCP);
    if (socket_bind($sh, '127.0.0.1', 4242)) {
        echo "Socket bound correctly";
    }
    $buf = 'Test Message';
    $len = strlen($buf);
    if (socket_sendto($sh, $buf, $len, 0x100, '192.168.0.2', 4242) !== FALSE) {
        echo "Message sent correctly";
    }
    socket_close($sh);
?>

Parece que este comando ele envia a resposta para o socket conectado naquela porta, mais especificamente para um determinado IP que esta conectado, isso evita que diferente da primeira solução, agora as respostas são enviadas para determinados ips conectados na rede.

 

Com isso nos iremos pegar a resposta do usuario, registrar no banco de dados, e criar um loop deste comando socket_sendTo e enviando o ip do pessoal que esta no chat1, acredito que isso evitaria o chat2 receber mensagens paralelas.

 

Esta solução ainda não foi testada, caso funciona estarei voltando aqui para trazer os resultados ;)

 

Atualização: Este metodo não funcionou, como o esperado.

Compartilhar este post


Link para o post
Compartilhar em outros sites

Solução 3 - Definitiva

 

 

Analisando mais um pouco o segundo chat (mediumChat) localizado naquele link do github, e analisando tambem alguns chats na internet foi analisado que toda nova conexão de um socket é armazenado dentro de um array.

 

Isso significa que quando voce entra em uma sala de chat global, uma instancia do seu socket é criada, e enumerada como:

Resource id #3
Resource id #4
Resource id #5
.......

Utilizando o comando socket_getpeername é possivel selecionar o ip do socket da pessoa junto a uma porta de acesso (esta porta de acessa nao é a mesma porta de escuta do servidor do socket) diferente para cada usuario que entra (e muda constantemente -> se eu entrar na sala a porta é uma, se eu sair e entrar novamente a porta será outra).

 

Sabendo disso, tudo que devemos fazer é criar um array como por exemplo:

$socketsLista = array();

E quando um novo socket entrar nós iremos adicionar a instancia do socket dentro desse array, no meu caso eu fiz da seguinte forma (ESTE É UM EXEMPLO DO EASYCHAT)

 if($sock === $m_sock){
......
socket_write($msgsock, $headers);
echo "handshak done...\n";

//Insira a variavel $m_sock  dentro do array

$socketLista[] = $_sock;

}

A variavel $m_sock é a responsável por criar uma nova conexão com o socket sempre quando que um novo usuario entra no chat o codigo responsavel por isso pode se encontrar abaixo:

$m_sock = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);

Para realizar um simples teste inicial, nos iremos substituir este comando:

}else{
$bytes = socket_recv($sock, $data, 2048, null);
$d = unmask($data);
foreach ($cls as $socket) {
if($socket != $m_sock && $val > 0){
try{
socket_write($socket,(encode($d)));
}catch(Exception $e){
unset($cls[$socket]);
socket_close($cls[$socket]);
}
}
}
}
} 

Por este:

}else{
$bytes = socket_recv($sock, $data, 2048, null);
$d = unmask($data);
try{
socket_write($socketLista[0],(encode($d)));
}catch(Exception $e){
unset($cls[$socket]);
socket_close($cls[$socket]);
}
}

} 

Isso vai fazer com que todas as respostas sejam enviadas somente para a instancia do socket que esta no index 0 do nosso array (que no caso seria o nosso primeiro usuario que abriu o chat), agora vamos aos testes:

 

  • Abra no navegador o arquivo server.php.
  • Em seguida abra o index.php em uma aba.
  • Agora abra mais 2 novas tabs no seu navegador e abra o index.php em cada uma delas.

 

Agora experimente digitar algum texto dentro dessas duas novas abas que voce abriu, voce verá que nem a 2º aba nem a 3º aba receberam as mensagens enviadas, mas ambas enviaram para o socket, por fim abra a primeira aba (aquela que contem o chat) e veja que voce recebeu 2 mensagens (tais mensagens enviadas das 2º e 3º aba), é claro que se enviarmos uma mensagem pela 1º aba nada ocorre (porque não configuramos isso).

 

Mas a ideia é essa mesmo, agora basta criarmos um banco de dados que armazene qual index do socket está se comunicando com qual index do socket e no socket write colocarmos algo como:

socket_write($socketLista[0],(encode($d)));

socket_write($socketLista[1],(encode($d)));

Que no caso ja estabelecemos uma conexão privada entre o chat 1 e o chat 2, no caso, se colocarmos assim, o chat 3 nao irá enviar nem receber nada, somente o chat 1 e 2.

 

Assim que eu terminar de fazer um teste final (chat privado de uma forma mais automatizada e com banco de dados), estarei postando no github e aqui tambem o projeto final e open source :D

 

Resolvido

Compartilhar este post


Link para o post
Compartilhar em outros sites

  • Conteúdo Similar

    • Por landerbadi
      Olá pessoal, boa tarde
       
      Tenho uma tabela chamada "produtos" com os seguintes campos (id, produto) e outra tabela chamada "itens" com os seguintes campos (id, prod_01, prod_02, prod_03, prod_04).
       
      Na tabela produtos eu tenho cadastrado os seguintes produtos: laranja, maçã, uva, goiaba, arroz, feijão, macarrão, etc.
       
      Na tabela itens eu tenho cadastrado os itens da seguinte maneira:
       
      1, laranja, uva, arroz, feijão;
      2, maçã, macarrão, goiaba, uva;
      3, arroz, feijão, maçã, azeite
       
      Meu problema é o seguinte: 
      Eu escolho um produto da tabela "produtos", por exemplo "uva".  Preciso fazer uma consulta na tabela "itens" para ser listado todos os registros que contenham o produto "uva" e que todos os demais produtos estejam cadastrados na tabela "produtos".
       
      No exemplo acima seria listado apenas dois registros, pois o terceiro registro não contém o produto "uva". 
       
      Alguém pode me ajudar? Pois estou quebrando a cabeça a vários dias e não consigo achar uma solução.
    • Por landerbadi
      Boa tarde pessoal. Estou tentado fazer uma consulta no banco de dados porém estou tendo dificuldades. Tenho uma tabela chamada "itens" com os seguintes campos: id, item, plural, ativo. Nela tem cadastrado vários itens e seu respectivo plural. No campo ativo eu coloco a letra "S" para informar que esta palavra está ativa no sistema. Por exemplo: 1, casa, casas, S 2, mesa, mesas, S 3, cama, camas, S 4, moto, motos, S 5, rádio, rádios O quinto registro "radio" não está ativo no sistema pois não tem um "S" no campo ativo. E outra tabela chamada "variações" com os seguintes campos (id, item1, item2, item3) com os seguintes registros: 1, casa, camas, moto 2, mesas, casas, radio 3, rádio, cama, mesa Eu preciso fazer uma busca na tabela variações da seguinte maneira: Eu escolho um registro na tabela "itens", por exemplo "casa". Preciso fazer com que o php me liste todos os registros da tabela "variações" que contenham a palavra "casa". Porém se tiver algum registro com a palavra "casas" também tem que ser listado. Neste caso ele irá encontrar dois registros. Agora eu preciso que o php verifique os demais itens e faça a listagem apenas dos item que estão ativos (que contenham um "S" no campo ativo. Neste caso ele irá encontrar apenas um registro, pois o segundo registro contém a palavra "rádio". E "rádio" não está ativo na tabela itens. Como faço isso?
    • Por First
      Olá a todos!
       
      Quando eu tento fazer o login me mostra esse erro "Could not log you in."; Alguém sabe me ajudar a resolver esse problema no meu código?
      <?php require_once("core/init.php"); if (Input::exists()) { if (Token::check(Input::get("token"))) { $validate = new Validate(); $validation = $validate->check($_POST, array( "username" => array("required" => true), "password" => array("required" => true) )); if ($validation->passed()) { $user = new User(); $remember = (Input::get("remember")) === "on" ? true : false; $login = $user->login(Input::get("username"), Input::get("password"), $remember); if ($login) { Session::flash("home", "Welcome back!"); Redirect::to("index.php"); } else { echo "Could not log you in."; } } else { foreach ($validation->errors() as $error) { echo $error."<BR>"; } } } } ?> <form action="" method="POST"> <div class="field"> <label for="username">Username</label> <input type="text" name="username" id="username"> </div> <div class="field"> <label for="password">Password</label> <input type="password" name="password" id="password"> </div> <div class="field"> <label for="remember"> <input type="checkbox" name="remember" id="remember"> Remember me </label> </div> <input type="hidden" name="token" value="<?php echo Token::generate(); ?>"> <input type="submit" value="Log in"> </form>  
       
      Desde já obrigado.
    • Por ckcesar
      Eu tenho uma aplicação no zend com a versão 5.6 e com o postgresql 9.6. Agora eu precisei mudar a versão do meu postgresql para o 16.1, a parte de conexão do bd e consultas sqls funciona perfeitamente, o meu problema está para acessar os meus controllers. Eles não são encontrados em nenhuma rota, quero ver se alguém já passou por esse problema para me ajudar. Obrigado.
    • Por ILR master
      Fala galera, tudo bem?
       
      Tenho o seguinte codigo:
       
       class Data {
      public static function ExibirTempoDecorrido($date)
      {
          if(empty($date))
          {
              return "Informe a data";
          }
          $periodos = array("segundo", "minuto", "hora", "dia", "semana", "mês", "ano", "década");
          $duracao = array("60","60","24","7","4.35","12","10");
          $agora = time();
          $unix_data = strtotime($date);
          // check validity of date
          if(empty($unix_data))
          {  
              return "Bad date";
          }
          // is it future date or past date
          if($agora > $unix_data) 
          {  
              $diferenca     = $agora - $unix_data;
              $tempo         = "atrás";
          } 
          else 
          {
              $diferenca     = $unix_data - $agora;
              $tempo         = "agora";
          }
          for($j = 0; $diferenca >= $duracao[$j] && $j < count($duracao)-1; $j++) 
          {
              $diferenca /= $duracao[$j];
          }
          $diferenca = round($diferenca);
          if($diferenca != 1) 
          {
              $periodos[$j].= "s";
          }
          return "$diferenca $periodos[$j] {$tempo}";
      }
      }
       
      Funciona redondinho se o valor retornado for de algumas horas, mas...
      Quando passa de dois meses, ele retorna a palavra mess. Deve ser por conta dessa linha
      if($diferenca != 1) 
          {
              $periodos[$j].= "s";
          }
       
      Quero que modre:
       
      2 meses atrás
      e não
      2 mess atrás.
       
      Espero que tenham entendido.
       
      Valeu
×

Informação importante

Ao usar o fórum, você concorda com nossos Termos e condições.