Skip to content

Commit 38d8c2c

Browse files
authored
Merge pull request #300 from kanazux/add_socket_struct
artigo "Upload de Arquivos com Socket e Struct" por @kanazux
2 parents c89ace9 + 0c5de03 commit 38d8c2c

File tree

1 file changed

+198
-0
lines changed

1 file changed

+198
-0
lines changed
Lines changed: 198 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,198 @@
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

Comments
 (0)