Ir para conteúdo

POWERED BY:

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 luiz monteiro
      Olá, tudo bem?
       
      Estou melhorando meu conhecimento em php e mysql e, me deparei com o seguinte. A tabela da base de dados tem um campo do tipo varchar(8) o qual armazena números. Eu não posso alterar o tipo desse campo. O que preciso é fazer um select para retornar o números que contenham zeros a direita ou a esquerda.
      O que tentei até agora
       
      Ex1
      $busca = $conexao->prepare("select campo form tabela where (campo = :campo) ");
      $busca->bindParam('campo', $_REQUEST['campo_form']);
       
      Se a direita da string $_REQUEST['campo_form'] termina ou inicia com zero ou zeros, a busca retorna vazio.
      Inseri dados numéricos, da seguinte maneira para testar: 01234567;  12345670: 12345678: 12340000... entre outros nessa coluna. Todos os valores que não terminam ou não iniciam com zero ou zeros, o select funciona.
       
       
      Ex2
      $busca = $conexao->prepare("select campo form tabela where (campo = 0340000) ");
      Esse número está cadastrado, mas não retorna.
       
      Ex3
      $busca = $conexao->prepare("select campo form tabela where (campo = '02340001' ) ");
      Esse número está cadastrado, mas não retorna.
       
       
      Ex4
      $busca = $conexao->prepare("select campo form tabela where (campo like 2340000) ");
      Esse número está cadastrado, mas não retorna.
       
      Ex5
      $busca = $conexao->prepare("select campo form tabela where (campo like '12340000') ");
      Esse número está cadastrado, mas não retorna.
       
      Ex6
      $busca = $conexao->prepare("select campo form tabela where (campo like '"12340000"' ) ");
      Esse número está cadastrado, mas não retorna.
       
       
      Ex7
      $busca = $conexao->prepare("select campo form tabela where (campo like :campo) ");
      $busca->bindParam('campo', $_REQUEST['campo_form'])
      Não retorna dados.
       
      O  $_REQUEST['campo_form'] é envio via AJAX de um formulário. 
      Usei o gettype para verificar o post, e ele retorna string.
      Fiz uma busca com número 12345678 para verificar o que o select retorna, e também retrona como string.
       
      Esse tipo de varchar foi usado porque os números que serão gravados nesse campo,  terão zeros a direita ou na esquerda. Os tipos number do mysql não gravam zeros, então estou usando esse. O problema é a busca.
      Agradeço desde já.
       
       
    • Por daemon
      Boa tarde,
       
      Eu tenho uma rotina que faz uma leitura do arquivo .xml de vários sites.

      Eu consigo pegar o tópico e a descrição, e mostrar a imagem que esta na pagina do link.
      Para isso utilizo esta função:
      function getPreviewImage($url) { // Obter o conteúdo da página $html = file_get_contents($url); // Criar um novo objeto DOMDocument $doc = new DOMDocument(); @$doc->loadHTML($html); // Procurar pela tag meta og:image $tags = $doc->getElementsByTagName('meta'); foreach ($tags as $tag) { if ($tag->getAttribute('property') == 'og:image') { return $tag->getAttribute('content'); } } // Se não encontrar og:image, procurar pela primeira imagem na página $tags = $doc->getElementsByTagName('img'); if ($tags->length > 0) { return $tags->item(0)->getAttribute('src'); } // Se não encontrar nenhuma imagem, retornar null return null; } // Uso: $url = "https://example.com/article"; $imageUrl = getPreviewImage($url); if ($imageUrl) { echo "<img src='$imageUrl' alt='Preview'>"; } else { echo "Nenhuma imagem encontrada"; }  
      Mas estou com um problema, esta funcão funciona quando coloco em uma pagina de teste.php. Preciso mostrar em uma página inicial diversas fotos de todos os links. (No caso acima só funciona 1).
    • Por violin101
      Caros amigos, saudações.
       
      Por favor, me permita tirar uma dúvida com os amigos.

      Tenho um Formulário onde o Usuário digita todos os Dados necessários.

      Minha dúvida:
      --> como faço após o usuário digitar os dados e salvar, o Sistema chamar uma Modal ou mensagem perguntando se deseja imprimir agora ?

      Grato,
       
      Cesar
    • Por Carcleo
      Tenho uma abela de usuarios e uma tabela de administradores e clientes.
      Gostaria de uma ajuda para implementar um cadastro
       
      users -> name, login, passord (pronta) admins -> user_id, registratiom, etc.. client -> user_id, registratiom, etc...
      Queria ajuda para extender de user as classes Admin e Client
      Olhem como estáAdmin
      <?php namespace App\Models; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; class Admin extends User {     use HasFactory;            protected $fillable = [         'name',         'email',         'password',         'registration'     ];      private string $registration;     public function create(         string $name,          string $email,          string $password,         string $registration     )     {         //parent::create(['name'=>$name, 'email'=>$email, 'password'=>$password]);         parent::$name = $name;         parent::$email = $email;         parent::$password = $password;         $this->registration = $registration;     } } User
      <?php namespace App\Models; // use Illuminate\Contracts\Auth\MustVerifyEmail; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Foundation\Auth\User as Authenticatable; use Illuminate\Notifications\Notifiable; use Illuminate\Database\Eloquent\Relations\BelongsToMany; class User extends Authenticatable {     /** @use HasFactory<\Database\Factories\UserFactory> */     use HasFactory, Notifiable;     static string $name;     static string $email;     static string $password;     /**      * The attributes that are mass assignable.      *      * @var list<string>      */     protected $fillable = [         'name',         'email',         'password',     ];          /**      * The attributes that should be hidden for serialization.      *      * @var list<string>      */     protected $hidden = [         'remember_token',     ];     /**      * Get the attributes that should be cast.      *      * @return array<string, string>      */     protected function casts(): array     {         return [             'email_verified_at' => 'datetime',             'password' => 'hashed',         ];     }          public function roles() : BelongsToMany {         return $this->belongsToMany(Role::class);     }       public function hasHole(Array $roleName): bool     {                 foreach ($this->roles as $role) {             if ($role->name === $roleName) {                 return true;             }         }         return false;     }         public function hasHoles(Array $rolesName): bool     {                 foreach ($this->roles as $role) {             foreach ($rolesName as $rolee) {             if ($role->name === $rolee) {                 return true;             }          }         }         return false;     }         public function hasAbility(string $ability): bool     {         foreach ($this->roles as $role) {             if ($role->abilities->contains('name', $ability)) {                 return true;             }         }         return false;     }     } Como gravar um Admin na tabela admins sendo que ele é um User por extensão?
      Tentei assim mas é claro que está errado...
      public function store(Request $request, Admin $adminModel) {         $dados = $request->validate([             "name" => "required",             "email" => "required|email",             "password" => "required",             "registration" => "required"         ]);         $dados["password"] =  Hash::make($dados["password"]);                  $admin = Admin::where("registration",  $dados["registration"])->first();                  if ($admin)              return                    redirect()->route("admin.new")                             ->withErrors([                                 'fail' => 'Administrador já cadastrados<br>, favor verificar!'                   ]);                            $newAdmin = $adminModel->create(                                    $dados['name'],                                    $dados['email'],                                    $dados['password'],                                    $dados['registration']                                 );         dd($newAdmin);         $adminModel->save();         //$adminModel::create($admin);                  return redirect()->route("admin.new")->with("success",'Cadastrado com sucesso');     }  
    • Por violin101
      Caros amigos, saudações.
       
      Gostaria de tirar uma dúvida com os amigos, referente a PDV.
       
      Estou escrevendo um Sistema com Ponto de Vendas, a minha dúvida é o seguinte, referente ao procedimento mais correto.

      Conforme o caixa vai efetuando a venda, o Sistema de PDV já realiza:
      a baixa direto dos produtos no estoque
      ou
      somente após concretizar a venda o sistema baixa os produtos do estoque ?
       
      Grato,
       
      Cesar
       
×

Informação importante

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