
Eu continuo aprendendo ferrugem. Ainda não sei muito, então cometo muitos erros. A última vez que tentei fazer um jogo de
cobra . Tentei ciclos, coleções, trabalhei com o 3D
Three.rs . Aprendeu sobre
ggez e
ametista . Desta vez, tentei criar um cliente e um servidor para conversar. Para GUI usado
Azul . Também assistiram
Conrod ,
Yew e
Orbtk . Tentei multithreading, canais e redes. Levei em consideração os erros do artigo anterior e tentei tornar isso mais detalhado. Para detalhes, bem-vindo ao gato.
→
Fontes, funciona no Windows 10 x64Para redes, usei o UDP porque quero fazer meu próximo projeto usando esse protocolo e queria treiná-lo aqui. Para a GUI, rapidamente acessei os projetos do Google no Rust, observei os exemplos básicos para eles, e o Azul me fisgou porque ele usa um Modelo de Objeto de Documento e um mecanismo de estilo semelhante ao CSS, e eu estava envolvido no desenvolvimento da Web por um longo tempo. Em geral, eu escolhi o Framework subjetivamente. Até o momento, está em alfa profundo: a rolagem não funciona, o foco de entrada não funciona, não há cursor. Para inserir dados em um campo de texto, você precisa passar o mouse sobre ele e mantê-lo diretamente acima dele enquanto digita.
Mais detalhes ...Na verdade, a maior parte do artigo são comentários de código.
Azul
Estrutura da GUI usando estilo funcional, DOM, CSS. Sua interface consiste em um elemento raiz, que possui muitos descendentes, que podem ter seus próprios descendentes, como em HTML e XML. Toda a interface é criada com base nos dados de um único DataModel. Nele, todos os dados são transferidos para a apresentação em geral. Se alguém está familiarizado com o ASP.NET, o Azul e seu DataModel são como o Razor e seu ViewModel. Como no HTML, você pode vincular funções a eventos de elementos DOM. Você pode estilizar elementos usando a estrutura CSS. Este não é o mesmo CSS que o HTML, mas é muito semelhante a ele. Também há ligação bidirecional como no Angular ou MVVM no WPF, UWP. Mais informações
no site .
Uma breve visão geral do restante das estruturas
- Orbtk - quase o mesmo que Azul e também em alfa profundo
- Conrod - Vídeo Você pode criar aplicativos de desktop multiplataforma.
- Yew é WebAssembly e semelhante ao React. Para desenvolvimento web.
Cliente
A estrutura na qual as funções auxiliares de leitura e gravação em um soquete são agrupadas
struct ChatService {} impl ChatService {
- Ler dados do soquete
- Buffer para dados a serem lidos do soquete.
- Bloqueio de chamada. Aqui, o encadeamento de execução para até que os dados sejam lidos ou ocorra um tempo limite.
- Obtemos uma string de uma matriz de bytes na codificação UTF8.
- Chegamos aqui se a conexão foi interrompida por um tempo limite ou outro erro ocorreu.
- Envia uma string para um soquete.
- Converta a string em bytes na codificação UTF8 e envie os dados para o soquete. A gravação de dados em um soquete não está bloqueando, ou seja, o segmento de execução continuará seu trabalho. Se os dados não puderam ser enviados, interrompemos o programa com a mensagem "não é possível enviar".
Uma estrutura que agrupa funções para manipular eventos do usuário e modificar nosso DataModel
struct Controller {}
- O tempo limite em milissegundos após o qual a operação de bloqueio da leitura do soquete será interrompida.
- A função é concluída quando o usuário deseja enviar uma nova mensagem para o servidor.
- Tomamos posse do mutex com nosso modelo de dados. Isso bloqueia o encadeamento de redesenho de interface até que o mutex seja liberado.
- Fazemos uma cópia do texto inserido pelo usuário para transferi-lo ainda mais e limpar o campo de entrada de texto.
- Estamos enviando uma mensagem.
- Informamos ao Framework que, após o processamento deste evento, precisamos redesenhar a interface.
- A função funciona quando o usuário deseja se conectar ao servidor.
- Conectamos a estrutura para representar o período de tempo da biblioteca padrão.
- Se já estamos conectados ao servidor, interrompemos a execução da função e informamos ao Framework que não há necessidade de redesenhar a interface.
- Adicione uma tarefa que será executada de forma assíncrona no thread do pool de threads do Azul Framework. O acesso ao mutex com o modelo de dados bloqueia a atualização da interface do usuário até que o mutex seja liberado.
- Adicione uma tarefa recorrente que é executada no thread principal. Quaisquer cálculos longos neste daemon são bloqueados pela atualização da interface.
- Nós entramos na posse do mutex.
- Lemos a porta inserida pelo usuário e criamos um endereço local com base nela, vamos ouvir.
- Crie um soquete UDP que leia pacotes que chegam ao endereço local.
- Lemos o endereço do servidor digitado pelo usuário.
- Dizemos ao nosso soquete UDP para ler pacotes somente deste servidor.
- Defina o tempo limite para a operação de leitura do soquete. A gravação no soquete ocorre sem esperar, ou seja, apenas gravamos dados e não esperamos nada, e a operação de leitura do soquete bloqueia o fluxo e aguarda até que os dados que podem ser lidos cheguem. Se você não definir um tempo limite, a operação de leitura do soquete aguardará indefinidamente.
- Defina um sinalizador indicando que o usuário já está conectado ao servidor.
- Passamos o soquete criado para o modelo de dados.
- Informamos ao Framework que, após o processamento deste evento, precisamos redesenhar a interface.
- Uma operação assíncrona que é executada no pool de threads do Azul Framework.
- Obtenha uma cópia do soquete em nosso modelo de dados.
- Tentando ler dados de um soquete. Se você não fizer uma cópia do soquete e aguardar diretamente aqui até que uma mensagem chegue do soquete que está no mutex em nosso modelo de dados, toda a interface deixará de ser atualizada até que nós liberemos o mutex.
- Se recebermos algum tipo de mensagem, alterando nosso modelo de dados, modify fará o mesmo que lock (). Desembrulhe () passando o resultado para o lambda e liberando o mutex após o término do código lambda.
- Defina um sinalizador para indicar que temos uma nova mensagem.
- Adicione uma mensagem à matriz de todas as mensagens de bate-papo.
- Uma operação síncrona repetida em execução no encadeamento principal.
- Se tivermos uma nova mensagem, informamos ao Framework que precisamos redesenhar a interface do zero e continuar trabalhando com esse daemon; caso contrário, não desenharemos a interface desde o início, mas ainda chamaremos essa função no próximo ciclo.
- Cria uma cópia do nosso soquete para não manter o mutex bloqueado com nosso modelo de dados.
- Nós obtemos o mutex e obtemos um link para o soquete.
- Crie uma cópia do soquete. O Mutex será liberado automaticamente ao sair de uma função.
Processamento de dados assíncrono e daemons em Azul
O daemon é sempre executado no encadeamento principal, portanto, o bloqueio é inevitável lá. Com uma tarefa assíncrona, se você fizer, por exemplo, assim, não haverá bloqueio por 10 segundos.
fn start_async_task(app_data: Arc<Mutex<MyDataModel>>, _: Arc<()>) {
A função de modificação chama lock () e o mutex com o modelo de dados, portanto, bloqueia a atualização da interface durante a execução.
Nossos estilos
const CUSTOM_CSS: &str = " .row { height: 50px; } .orange { background: linear-gradient(to bottom, #f69135, #f37335); font-color: white; border-bottom: 1px solid #8d8d8d; }";
Na verdade, funções para criar nosso DOM para exibir ao usuário
impl azul::prelude::Layout for ChatDataModel {
- A função que cria o DOM final e é chamada toda vez que você precisa redesenhar a interface.
- Se já estamos conectados ao servidor, mostramos o formulário para enviar e ler mensagens, caso contrário, exibimos o formulário para conectar-se ao servidor.
- Cria um formulário para inserir os dados necessários para conectar-se ao servidor.
- Crie um botão com a inscrição de texto Login.
- Converta-o em um objeto DOM.
- Adicione a classe de linha a ela.
- Adicione a laranja da classe css.
- Adicione um manipulador de eventos para clicar no botão.
- Crie um rótulo de texto com o texto a ser exibido na linha do usuário e da classe css.
- Criamos uma caixa de texto para inserir texto com o texto da propriedade do nosso modelo e linha da classe css.
- Vincule o campo de texto à propriedade do nosso DataModel. Esta é uma ligação bidirecional. Agora, editar o TextInput altera automaticamente o texto na propriedade do nosso modelo e o oposto também é verdadeiro. Se alterarmos o texto em nosso modelo, o texto em TextInput será alterado.
- Criamos um elemento DOM raiz no qual colocamos nossos elementos de interface do usuário.
- Cria um formulário para enviar e ler mensagens.
- Crie um botão com o texto "Enviar" e css com as classes "linha", "laranja" e um manipulador de eventos quando ele for clicado.
- Crie um campo de entrada de texto bidirecional com a propriedade de modelo self.messaging_model.text_input_state e css com a classe "row".
- Adicione rótulos de texto que exibam mensagens escritas no bate-papo.
Nosso modelo que armazena o estado da nossa interface
A documentação da Azul diz que ele deve armazenar todos os dados do aplicativo, incluindo a conexão com o banco de dados, então coloquei um soquete UDP nele.
- Isso nos permitirá exibir nossa estrutura como uma string em um modelo no formato {:?}
- Nosso modelo de dados. Para que possa ser usado na Azul. Ela deve implementar a característica de layout.
- Um sinalizador para verificar se o usuário está conectado ao servidor ou não.
- Um modelo para exibir um formulário para enviar mensagens para o servidor e salvar mensagens recebidas do servidor.
- Modelo para exibir o formulário para conectar-se ao servidor.
- A porta que o usuário digitou. Vamos ouvi-lo com o nosso soquete.
- O endereço do servidor que o usuário digitou. Vamos nos conectar a ele.
- Mensagem do usuário. Nós o enviaremos para o servidor.
- Uma matriz de mensagens que vieram do servidor.
- O soquete através do qual nos comunicamos com o servidor.
- Um sinalizador para verificar se uma nova mensagem chegou do servidor.
E, finalmente, o principal ponto de entrada para o aplicativo. Inicia um ciclo de desenho da GUI e processamento de entrada do usuário
pub fn run() {
- Criamos um aplicativo com dados iniciais.
- Os estilos usados pelo aplicativo por padrão.
- Adicione nossos próprios estilos a eles.
- Criamos uma janela na qual nosso aplicativo será exibido.
- Inicie o aplicativo nesta janela.
Servidor
O principal ponto de entrada do aplicativo
Aqui geralmente temos um aplicativo de console.
pub fn run() {
- Crie um soquete.
- Criamos um canal unidirecional com um remetente de mensagem sx e muitos destinatários rx.
- Começamos a enviar mensagens para todos os destinatários em um fluxo separado.
- Lemos os dados do soquete e enviamos para o fluxo, que envia mensagens aos clientes conectados ao servidor.
Função para criar um fluxo para enviar mensagens aos clientes
fn start_sender_thread(rx: mpsc::Receiver<(Vec<u8>, SocketAddr)>, socket: UdpSocket) {
- Inicie um novo thread. move significa que as variáveis assumem o lambda e o fluxo, respectivamente. Mais especificamente, nosso novo thread "absorverá" as variáveis rx e socket.
- Uma coleção de endereços conectados a nós pelos clientes. Enviaremos a todos eles nossas mensagens. Em geral, em um projeto real, seria necessário desconectar um cliente de nós e remover seu endereço dessa matriz.
- Começamos um loop infinito.
- Lemos os dados do canal. Aqui o fluxo será bloqueado até que novos dados cheguem.
- Se não houver esse endereço em nossa matriz, adicione-o lá.
- Decodifique uma sequência UTF8 de uma matriz de bytes.
- Criamos uma matriz de bytes que vamos enviar a todos os nossos clientes.
- Examinamos a coleta de endereços e enviamos dados para todos.
- A operação de gravação no soquete UDP é sem bloqueio; portanto, aqui a Função não espera até que a mensagem chegue ao destinatário e seja executada quase instantaneamente.
- esperar em caso de erro fará uma saída de emergência do programa com a mensagem fornecida.
A função cria um soquete com base na entrada do usuário
const TIMEOUT_IN_MILLIS: u64 = 2000; fn create_socket() -> UdpSocket { println!("Enter port to listen");
- Lemos a porta que nosso servidor escutará e criamos um endereço de servidor local com base nela.
- Crie um soquete UDP escutando neste endereço.
- Defina o tempo limite para a operação de leitura. A operação de leitura está bloqueando e bloqueará o fluxo até que novos dados cheguem ou ocorra um tempo limite.
- Retornamos o soquete criado da Função.
- A função lê os dados do soquete e os retorna juntamente com o endereço do remetente.
Função para ler dados de um soquete
fn read_data(socket: &UdpSocket) -> (Vec<u8>, SocketAddr) {
- O buffer é o local onde vamos ler os dados.
- Inicia um loop que será executado até que dados válidos sejam lidos.
- Obtemos o número de bytes lidos e o endereço do remetente.
- Cortamos a matriz desde o início até o número de bytes lidos e a convertemos em um vetor de bytes.
- Se ocorrer um tempo limite ou outro erro, prossiga para a próxima iteração do loop.
Sobre camadas no aplicativo
Offtopic: Um pequeno programa educacional para dois de junho no trabalho. Eu decidi colocá-lo aqui, talvez alguém venha a calhar. Os poetas afiados de junho são exemplos em C # e estamos falando sobre o ASP.NETEntão, não havia nada a fazer, era à noite e decidi escrever um pequeno programa educacional sobre arquitetura para Artem e Victor. Bem, vamos lá.
Na verdade, eu adicionei aqui porque o modo é de reconhecimento e só posso escrever artigos uma vez por semana, e o material já está lá e na próxima semana eu queria enviar outra coisa para Habr.
Normalmente, um aplicativo é em camadas. Em cada camada, há objetos que implementam a característica de comportamento da camada em que estão localizados. E assim Essas são as camadas.
- Camada de apresentação.
- Camada lógica de negócios.
- Camada de acesso a dados.
- Entidades (usuário, animal etc.)
Cada camada pode conter seu próprio DTO e classes completamente arbitrárias com métodos arbitrários. O principal é que eles executem a funcionalidade associada à camada em que estão localizados. Em aplicativos simples, algumas das camadas podem estar ausentes. Por exemplo, uma visualização de camada pode ser implementada através do padrão MVC, MVP, MVVM. O que é completamente opcional. O principal é que as classes que estão nessa camada implementam a funcionalidade atribuída à camada. Lembre-se, padrões e arquitetura são apenas recomendações, não direções. Padrão e arquitetura não é uma lei, este é um conselho.
E assim, consideraremos cada camada no exemplo de um aplicativo ASP.NET padrão usando o Entity Framework padrão.
Camada de apresentação
Temos MVC aqui. Essa é a camada que fornece a interação do usuário. Os comandos vêm aqui e os usuários obtêm dados daqui. Não necessariamente pessoas, se tivermos uma API, nosso usuário será um programa diferente. Carros se comunicam com carros.
Camada lógica de negócios
Aqui, geralmente, as classes são chamadas de Serviço, por exemplo, UserService, embora possa ser qualquer coisa. Apenas um conjunto de classes com métodos. O principal é que os cálculos e cálculos de nossa aplicação ocorram aqui. Essa é a camada mais grossa e mais volumosa. Existe a maior parte do código e várias classes. Esta é, de fato, a nossa aplicação.
Camada de acesso a dados
Geralmente aqui a EF implementa padrões de Unidade de trabalho e repositório. Então, sim, o DbContext é, você pode dizer, Unidade de Trabalho, e o DB define que é Repositório. Este é, de fato, o local onde colocamos os dados e de onde obtemos. Independentemente de a fonte de dados ser um banco de dados, API de outro aplicativo, cache na memória ou apenas algum tipo de gerador de números aleatórios. Qualquer fonte de dados.
Entidades
Sim, apenas todos os tipos de usuário, animal e muito mais. Um ponto importante - eles podem ter algum tipo de comportamento característico apenas deles. Por exemplo:
class User { public string FirstName { get; set; } public string LastName { get; set; } public string FullName { get { return FirstName + " " + LastName; } } public bool Equal(User user) { return this.FullName == user.FullName; } }
Bem, e um exemplo muito simples. Shoba era
using System; using System.Collections.Generic; using System.Text;
PS
Bem, quero agradecer ao meu Nastya por corrigir erros gramaticais no artigo. Então sim, Nastya, você não é em vão com um diploma vermelho e geralmente é legal. Eu te amo <3.