|
| 1 | +Title: Upload de Arquivos com Socket e Struct |
| 2 | +Slug: upload-de-arquivos-com-socket-e-struct |
| 3 | +Date: 2018-05-17 19:24:00 |
| 4 | +Category: Network |
| 5 | +Tags: python,tutorial,network,struct,socket |
| 6 | +Author: Silvio Ap Silva |
| 7 | +Email: contato@kanazuchi.com |
| 8 | +Github: kanazux |
| 9 | +Linkedin: SilvioApSilva |
| 10 | +Twitter: @kanazux |
| 11 | +Site: http://kanazuchi.com |
| 12 | + |
| 13 | +Apesar de termos muitas formas de enviarmos arquivos para servidores hoje em dia, como por exemplo o *scp* e *rsync*, podemos usar o python com seus modulos *built-in* para enviar arquivos a servidores usando struct para serializar os dados e socket para criar uma conexão cliente/servidor. |
| 14 | + |
| 15 | +### *Struct* |
| 16 | + |
| 17 | +O modulo [struct](https://docs.python.org/3/library/struct.html) é usado para converter bytes no python em formatos do struct em C. |
| 18 | +Com ele podemos enviar num unico conjunto de dados o nome de um arquivo e os bytes referentes ao seus dados. |
| 19 | + |
| 20 | +Struct também é utilizado para serializar diversos tipos de dados diferentes, como bytes, inteiros, floats além de outros, no nosso caso usaremos apenas bytes. |
| 21 | + |
| 22 | +Vamos criar um arquivo para serializar. |
| 23 | + |
| 24 | +```python |
| 25 | +!echo "Upload de arquivos com sockets e struct\nCriando um arquivo para serializar." > arquivo_para_upload |
| 26 | +``` |
| 27 | + |
| 28 | +Agora em posse de um arquivo vamos criar nossa estrutura de bytes para enviar. |
| 29 | + |
| 30 | +```python |
| 31 | +arquivo = "arquivo_para_upload" |
| 32 | +``` |
| 33 | + |
| 34 | +```python |
| 35 | +with open(arquivo, 'rb') as arq: |
| 36 | + dados_arquivo = arq.read() |
| 37 | + serializar = struct.Struct("{}s {}s".format(len(arquivo), len(dados_arquivo))) |
| 38 | + dados_upload = serializar.pack(*[arquivo.encode(), dados_arquivo]) |
| 39 | +``` |
| 40 | + |
| 41 | +Por padrão, struct usa caracteres no inicio da sequencia dos dados para definir a ordem dos bytes, tamanho e alinhamento dos bytes nos dados empacotados. |
| 42 | +Esses caracteres podem ser vistos na [seção 7.1.2.1](https://docs.python.org/3/library/struct.html#byte-order-size-and-alignment) da documentação. |
| 43 | +Como não definimos, será usado o **@** que é o padrão. |
| 44 | + |
| 45 | +Nessa linha: |
| 46 | +```python |
| 47 | +serializar = struct.Struct("{}s {}s".format(len(arquivo), len(dados_arquivo))) |
| 48 | +``` |
| 49 | +definimos que nossa estrutura serializada seria de dois conjuntos de caracteres, a primeira com o tamanho do nome do arquivo, e a segunda com o tamanho total dos dados lidos em |
| 50 | +```python |
| 51 | +dados_arquivo = arq.read() |
| 52 | +``` |
| 53 | + |
| 54 | +Se desempacotarmos os dados, teremos uma lista com o nome do arquivo e os dados lidos anteriormente. |
| 55 | + |
| 56 | +```python |
| 57 | +serializar.unpack(dados_upload)[0] |
| 58 | +``` |
| 59 | + |
| 60 | + b'arquivo_para_upload' |
| 61 | + |
| 62 | + |
| 63 | +```python |
| 64 | +serializar.unpack(dados_upload)[1] |
| 65 | +``` |
| 66 | + |
| 67 | + b'Upload de arquivos com sockets e struct\\nCriando um arquivo para serializar.\n' |
| 68 | + |
| 69 | + |
| 70 | +Agora de posse dos nossos dados já serializados, vamos criar um cliente e um servidor com socket para transferirmos nosso arquivo. |
| 71 | + |
| 72 | +### *Socket* |
| 73 | + |
| 74 | +O modulo [socket](https://docs.python.org/3/library/socket.html) prove interfaces de socket BSD, disponiveis em praticamente todos os sistemas operacionais. |
| 75 | + |
| 76 | +#### Familias de sockets |
| 77 | + |
| 78 | +Diversas familias de sockets podem ser usadas para termos acessos a objetos que nos permitam fazer chamadas de sistema. |
| 79 | +Mais informações sobre as familias podem ser encontradas na [seção 18.1.1](https://docs.python.org/3/library/socket.html#socket-families) da documentação. No nosso exemplo usaremos a AF_INET. |
| 80 | + |
| 81 | +#### AF_INET |
| 82 | + |
| 83 | +**AF_INET** precisa basicamente de um par de dados, contendo um endereço IPv4 e uma porta para ser instanciada. |
| 84 | +Para endereços IPv6 o modulo disponibiliza o **AF_INET6** |
| 85 | + |
| 86 | +#### Constante [SOCK_STREAM] |
| 87 | + |
| 88 | +As constantes representam as familias de sockets, como a constante AF_INET e os protocolos usados como parametros para o modulo socket. |
| 89 | +Um dos protocolos mais usados encontrados na maioria dos sistemas é o SOCK_STREAM. |
| 90 | + |
| 91 | +Ele é um protocolo baseado em comunicação que permite que duas partes estabeleçam uma conexão e conversem entre si. |
| 92 | + |
| 93 | +### *Servidor e cliente socket* |
| 94 | + |
| 95 | +Como vamos usar um protocolo baseado em comunicação, iremos construir o servidor e cliente paralelamente para um melhor entendimento. |
| 96 | + |
| 97 | +> Servidor |
| 98 | +
|
| 99 | +Para esse exemplo eu vou usar a porta 6124 para o servidor, ela esta fora da range reservada pela IANA para sistemas conhecidos, que vai de 0-1023. |
| 100 | + |
| 101 | +Vamos importar a bilioteca socket e definir um host e porta para passarmos como parametro para a constante AF_INET. |
| 102 | + |
| 103 | +```python |
| 104 | +import socket |
| 105 | +host = "127.0.0.1" |
| 106 | +porta = 6124 |
| 107 | +sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) |
| 108 | +``` |
| 109 | + |
| 110 | +Agora usaremos o metodo bind para criarmos um ponto de conexão para nosso cliente. Esse metodo espera por uma tupla contento o host e porta como parametros. |
| 111 | + |
| 112 | +```python |
| 113 | +sock.bind((host, porta)) |
| 114 | +``` |
| 115 | + |
| 116 | +Agora vamos colocar nosso servidor socket em modo escuta com o metodo listen. Esse metodo recebe como parametro um numero inteiro (**backlog**) definindo qual o tamanho da fila que será usada para receber pacotes SYN até dropar a conexão. Usaremos um valor baixo o que evita SYN flood na rede. Mais informações sobre *backlog* podem ser encontradas na [RFC 7413](https://tools.ietf.org/html/rfc7413). |
| 117 | + |
| 118 | +```python |
| 119 | +sock.listen(5) |
| 120 | +``` |
| 121 | + |
| 122 | +Agora vamos colocar o nosso socket em um loop esperando por uma conexão e um inicio de conversa. Pra isso vamos usar o metodo *accept* que nos devolve uma tupla, onde o primeiro elemento é um novo objeto socket para enviarmos e recebermos informações, e o segundo contendo informações sobre o endereço de origem e porta usada pelo cliente. |
| 123 | + |
| 124 | +**Vamos criar um diretório para salvar nosso novo arquivo.** |
| 125 | + |
| 126 | +```python |
| 127 | +!mkdir arquivos_recebidos |
| 128 | +``` |
| 129 | + |
| 130 | +*Os dados são enviados sempre em bytes*. **Leia os comentários** |
| 131 | + |
| 132 | +```python |
| 133 | +while True: |
| 134 | + novo_sock, cliente = sock.accept() |
| 135 | + with novo_sock: # Caso haja uma nova conexão |
| 136 | + ouvir = novo_sock.recv(1024) # Colocamos nosso novo objeto socket para ouvir |
| 137 | + if ouvir != b"": # Se houver uma mensagem... |
| 138 | + """ |
| 139 | + Aqui usaremos os dados enviados na mensagem para criar nosso serielizador. |
| 140 | +
|
| 141 | + Com ele criado poderemos desempacotar os dados assim que recebermos. |
| 142 | + Veja no cliente mais abaixo qual a primeira mensagem enviada. |
| 143 | + """ |
| 144 | + mensagem, nome, dados = ouvir.decode().split(":") |
| 145 | + serializar = struct.Struct("{}s {}s".format(len(nome.split()[0]), int(dados.split()[0]))) |
| 146 | + novo_sock.send("Pode enviar!".encode()) # Enviaremos uma mensagem para o cliente enviar os dados. |
| 147 | + dados = novo_sock.recv(1024) # Agora iremos esperar por eles. |
| 148 | + nome, arquivo = serializar.unpack(dados) # Vamos desempacotar os dados |
| 149 | + """ |
| 150 | + Agora enviamos uma mensagem dizendo que o arquivo foi recebido. |
| 151 | +
|
| 152 | + E iremos salva-lo no novo diretório criado. |
| 153 | + """ |
| 154 | + novo_sock.send("Os dados do arquivo {} foram enviados.".format(nome.decode()).encode()) |
| 155 | + with open("arquivos_recebidos/{}".format(nome.decode()), 'wb') as novo_arquivo: |
| 156 | + novo_arquivo.write(arquivo) |
| 157 | + print("Arquivo {} salvo em arquivos_recebidos.".format(nome.decode())) |
| 158 | + |
| 159 | + Arquivo arquivo_para_upload salvo em arquivos_recebidos. |
| 160 | + |
| 161 | +``` |
| 162 | + |
| 163 | +> Cliente |
| 164 | +
|
| 165 | +Nosso cliente irá usar o metodo *connect* para se connectar no servidor e a partir dai começar enviar e receber mensagens. Ele também recebe como parametros uma tupla com o host e porta de conexão do servidor. |
| 166 | + |
| 167 | +```python |
| 168 | +host = '127.0.0.1' |
| 169 | +porta = 6124 |
| 170 | +sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # Cria nosso objeto socket |
| 171 | +sock.connect((host, porta)) |
| 172 | +sock.send("Enviarei um arquivo chamado: {} contendo: {} bytes".format( |
| 173 | + arquivo, len(dados_arquivo)).encode()) # Enviamos a mensagem com o nome e tamanho do arquivo. |
| 174 | +ouvir = sock.recv(1024) # Aguardamos uma mensagem de confirmação do servidor. |
| 175 | +if ouvir.decode() == "Pode enviar!": |
| 176 | + sock.send(dados_upload) # Enviamos os dados empacotados. |
| 177 | + resposta = sock.recv(1024) # Aguardamos a confirmação de que os dados foram enviados. |
| 178 | + print(resposta.decode()) |
| 179 | + |
| 180 | + Os dados do arquivo arquivo_para_upload foram enviados. |
| 181 | + |
| 182 | +``` |
| 183 | + |
| 184 | +Agora podemos checar nossos arquivos e ver se eles foram salvos corretamente. |
| 185 | + |
| 186 | +```python |
| 187 | +!md5sum arquivo_para_upload; md5sum arquivos_recebidos/arquivos_para_upload |
| 188 | + |
| 189 | + 605e99b3d873df0b91d8834ff292d320 arquivo_para_upload |
| 190 | + 605e99b3d873df0b91d8834ff292d320 arquivos_recebidos/arquivo_para_upload |
| 191 | + |
| 192 | +``` |
| 193 | + |
| 194 | +Com base nesse exemplo, podem ser enviados diversos arquivos, sendo eles texto, arquivos compactados ou binários. |
| 195 | + |
| 196 | +Sem mais delongas, fiquem com Cher e até a próxima! |
| 197 | + |
| 198 | +Abraços. |
0 commit comments