Nota: Os exemplos de códigos foram feitos utilizando a versão 2.7.12 do interpretador Python, com o Sistema Operacional Linux Lite 3.4, baseado no Ubuntu 16.04. Este material foi criado para a disciplina de Redes de Computadores da Universidade Federal Fluminense, pelos alunos Robson Araújo Lima e Reza Amirahmadi
O protocolo UDP (User Datagram Protocol), é um protocolo orientado a mensagens e, não precisa do estabelecimento de uma conexão. Por outro lado, ele não garante a entrega confiável de pacotes.
Como o servidor não precisa de uma conexão, então apenas precisamos criar um
socket e vincula-lo à uma porta usando a função bind()
e, aguardar
por pacotes individuais.
import socket import sys #deixar vazio para receber conexões fora da rede local host = '' #porta onde o servidor irá escutar port = 1234 #cria um UDP/IP socket s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) #garante que o socket será destruído (pode ser reusado) após uma interrupção da execução s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) #associa o socket à uma porta s.bind((host, port)) while True: print('waiting to receive message') data, address = s.recvfrom(1024) print 'received: ' + data + '\nfrom: ' + address[0] + '\nlistening on port: ' + str(address[1])
A função recvfrom()
recebe dados de um socket e, retorna a
tupla (string, address)
, onde string é uma string representando
os dados e address é o endereço do socket que está transmitindo os dados.
O valor 1024 na função recvfrom()
indica a quantidade máxima de bytes recebidos por
pacotes.
Salve o código em um arquivo de texto com a extensão .py, por exemplo
server.py.
Agora, o cliente UDP apenas tem que se preocupar em enviar as mensagens para o servidor.
Para isso, é necessário cria um socket e utilizar a função sendto()
para enviar os dados.
import socket import sys #endereço para o qual os dados vão ser enviados host = 'localhost' #número da porta que o servidor que vai receber os dados está escutando port = 1234 #cria um UDP/IP socket s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) print 'Para sair use CTRL+X e pressione enter\n' msg = raw_input() while msg <> '\x18': #envia os dados s.sendto(msg, (host, port)) msg = raw_input() print('closing socket') s.close()
A função sendto()
, recebe como parâmetros a mensagem a ser enviada e,
o endereço do destino. O endereço é composto por um endereço IP e um número de porta.
Lembre-se que o nosso servidor server.py
, está escutando na porta
1234. A função cliente portanto, vai enviar dados para o endereço 'localhost'
(pois estamos executando cliente e servidor na mesma máquina) e para a porta 1234.
Perceba que todo socket deve ser destruído após seu uso, isso é feito
com a função close()
. Salve a função acima em um arquivo de texto com o nome client.py
Agora, abra um terminal no mesmo diretório onde se encontram os arquivos
server.py
e client.py
. Execute primeiro o arquivo
server.py
com o comando:
Agora, abra uma nova janela do terminal e, execute o arquivo client.py
com o
comando:
No terminal onde está sendo executado o cliente a saída deve ser algo como:
robson@robson:~$ python client.pyDigite algumas palavras e pressione enter.
robson@robson:~$ python client.pyNo terminal onde está sendo executado o servidor a saída deve ser semelhante a esta:
robson@robson:~$ python server.pyImagine agora, uma situação onde precismos mandar a mesma mensagem para vários computadores. Para tal tarefa, utilizamos sockets broadcast.
Para criar um cliente com um socket broadcast, reaproveitaremos os códigos
do arquivo client.py
alterando apenas algumas linhas.
import socket import sys #endereço broadcast para o qual os dados vão ser enviados host = '192.168.255.255' #número da porta que o servidor que vai receber os dados está escutando port = 1234 #cria um UDP/IP socket s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) #Envie para todo mundo s.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1) print 'Para sair use CTRL+X e pressione enter\n' msg = raw_input() while msg <> '\x18': #envia os dados s.sendto(msg, (host, port)) msg = raw_input() print('closing socket') s.close()Repare que a única mudança feita no código foi a inserção da linha
s.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)
e, o
endereço de host que mudou para o endereço broadcast da rede local.
Para testar, é preciso executar o arquivo server.py
em máquinas de sua rede local, depois executar o cliente broadcast
em apenas uma máquina. Todos as máquinas que estão com o servidor rodando
vão receber a mensagem enviada pelo cliente.
Servidores DNS (Domain Name System, ou sistema de nomes de domínios) são os responsáveis por localizar e traduzir para números IP os endereços dos sites que digitamos nos navegadores. Com o conhecimentos que obtivemos até agora, já somos capazes de implementar um simples servidor que recebe uma string e retorna um endereço IP correspondente.
O servidor DNS terá que saber previamente todos os nomes e endereços IP correspondentes, para isso, utilizaremos uma matriz, onde cada linha contém uma string e um endereço IP.
import socket import sys host = '' port = 1234 matriz = [['robson','192.168.1.14'], ['arthur','192.168.1.44'], ['reza','192.168.3.221']] #create a UDP/IP socket s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) #Bind the socket to the port s.bind((host, port)) while True: print('waiting to receive message') data, address = s.recvfrom(1024) #procura na matriz a string (data) recebida, quando encontra, sai do for #com o índice corresponde da linha. for i in xrange(3): if data == matriz[i][0]: break data = matriz[i][1] if data: s.sendto(data, address) print('sent %s back to %s'%(data, address))
Perceba que na coluna 1 da matriz temos as strings e, na segunda coluna são os
IP correspondentes. Salve esse código com o nome dns.py
.
Agora, para o cliente usar nosso servidor DNS, ele precisa primeiro, mandar o nome de quem (destinatário) ele quer mandar mensagens. Então, o servidor responde ao cliente com o IP, só assim, o cliente consegue mandar mensagens para o destino. Abaixo codificamos um possível cliente para ser usado com o servidor.
import socket import sys port = 1234 #Create a udp socket s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) print 'Com quem você quer conversar?\n' msg = raw_input() #Endereço do servidor dns s.sendto(msg, ('192.168.4.13',1234)) #Servidor DNS responde com o IP na variável host host, addr = s.recvfrom(1024) print 'Para sair use CTRL+X e pressione enter\n' msg = raw_input() while msg <> '\x18': # send the data s.sendto(msg, (host, port)) msg = raw_input() print('closing socket') s.close()
Salve o código com o nome dns_client.py
. Para testar os códigos,
é preciso preencher a tabela com alguns nomes fictícios e os endereços IP de
algumas máquina em um rede local. No exemplo, o servidor DNS está rodando na
máquina com IP 192.168.4.13 e escutando na porta 1234. Se executarmos o dns_client.py
em uma máquina diferente do servidor DNS, podemos por exemplo, falar com o robson, apenas
digitando o nome robson no terminal do cliente DNS.
Diferentemente dos sockets UDP, o protocolo TCP estabelece uma conexão entre o socket cliente e socket servidor, antes do envio de qualquer mensagem de dados.
Um servidor TCP/IP, precisa, como no servidor UDP, associar um socket à alguma porta. Quando o servidor recebe uma solicitação de conexão, ele primeiramente precisa aceitá-la, para depois receber dados.
O nosso servidor TCP/IP vai receber uma conexão por vez e, responder ao cliente uma confirmação do recebimento da mensagem.
import socket import sys # Significa que é possível receber conexões fora da rede local host = '' port = 1234 # Cria um socket TCP/IP s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) #garante que o socket será destruído (pode ser reusado) após uma interrupção da execução s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) # associa o socket a porta s.bind((host, port)) print('Server %s on port %s' % (socket.gethostname(), port))
A função listen()
coloca o socket no modo servidor, e define
a quantidade de solicitações de novas conexões que podem ser enfileiradas.
A função accept()
espera por novas solicitações de conexões.
# Enfileira uma nova conexão, (define o modo servidor) s.listen(1) while True: # Espera por novas conexões print('waiting for a connection') # Aceita conexões conn, address = s.accept();
A função accept()
retorna um novo socket para ser usado com aquela
conexão e, o endereço do cliente. O novo socket utiliza uma nova porta
atribuída automaticamente pelo kernel. Os dados são recebidos com a função
recv()
e, enviados com a função sendall()
.
try: print('Connected to %s on port %s ' % (client_address)) while True: data = conn.recv(1024) reply = 'OK...' + data if not data: break conn.sendall(reply) finally: # Clean up the connection conn.close()
Quando a comunicação com o cliente for encerrada, o socket retornado pela
função accept()
precisa ser destruído usando a função close().
Um bloco try:finally
é utilizado para garantir que mesmo na ocorrências
de erros o socket seja encerrado.
Salve as linhas de códigos em um arquivo de texto com o nome server_tcp.py
connect()
,
que recebe como parâmetro o endereço do servidor.
import socket import sys host = 'localhost' port = 1234 # Cria um socket TCP/IP s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.connect((host, port))Os dados são enviados com a função
sendall()
e, recebidos com a
função recv()
.
print 'Para sair use CTRL+X e pressione enter\n' msg = raw_input() try: while msg <> '\x18': # Envia os dados s.sendall(msg) #recebe a resposta do servidor data = s.recv(1024) print data msg = raw_input() finally: print('closing connection bye...') s.close()
Note que nesta função cliente, além de enviarmos dados, estamos recebendo uma confirmação do servidor que nossos dados foram recebidos com sucesso.
Salve em um documente de texto com o nome client_tcp.py
.
Abra um terminal e execute primeiro o arquivo server_tcp.py
.
Note que, precisamos necessariamente executar primeiro o arquivo do servidor,
pois se executássemos o cliente primeiro, não teria nenhum servidor escutando e,
consequentemente geraria um erro.
Após executar o servidor a saída será:
robson@robson:~$ python server_tcp.pyAgora execute o client_tcp.py
, digite algum texto e pressione
enter, a saída será:
Temos então um cliente conversando com um servidor. Mas o que acontece se mais de um cliente tentar se conectar ao servidor? Neste caso, o servidor irá enfileirar a conexão até que a conexão atual seja encerrada. Tente você mesmo abrir uma nova janela do terminal e, executar mais um cliente. Você verá que apenas o primeiro cliente que conectou que receberá uma resposta. O segundo receberá uma resposta logo após o encerramento da conexão do primeiro.
Para termos um servidor que responda a mais de um cliente por vez, devemos criar uma thread para cada nova conexão ao servidor.
Este servidor responde da mesma forma que o último servidor TCP/IP apresentado.
A função clientthread()
recebe como parâmetro o socket retornado
pela função accept()
, na qual, é este socket que responde ao
respectivo cliente que solicitou a conexão.
Quando um cliente encerra a conexão, o socket no servidor que estava respondendo este cliente é destruído.
import socket import sys from thread import * host = '' port = 1234 s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) #garante que o socket será destruído (pode ser reusado) após uma interrupção da execução s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) #Bind socket to local host and port try: s.bind((host, port)) except socket.error as msg: print 'Bind failed. Error Code : ' + str(msg[0]) + ' Message ' + msg[1] sys.exit() s.listen(1) #Função para lidar com cada conexão (cliente) def clientthread(conn): #Sai do loop quando o cliente desconecta, pois a variável data não conterá #nenhum conteúdo while True: #Receiving from client data = conn.recv(1024) reply = 'OK...' + data if not data: break conn.sendall(reply) #destroi o socket e encerra thread ao sair do loop conn.close() #Continua recebendo conexões while True: #Aceita conexões conn, addr = s.accept() print 'Connected with ' + addr[0] + ':' + str(addr[1]) #Cria nova thread para uma nova conexão. O primeiro #argumento é o nome da função, e o segundo é uma tupla #com os parâmetros da função. start_new_thread(clientthread ,(conn,))
Salve o código em um arquivo com o nome server_thread.py
e depois
o execute com o comando python
no terminal.
Agora abra pelo menos duas novas janelas do terminal e execute o cliente
TCP/IP client_tcp.py
nos dois terminais. Em um dos terminais, digite uma frase e
pressione enter. Você terá a saída:
No terminal onde está rodando o servidor server_thread.py
você terá
a saída:
Repare agora que, o servidor está respondendo os dois terminais ao mesmo tempo.
Podemos usar o programa telnet
como um cliente TCP/IP do nosso servidor.
Com algum servidor TCP/IP rodando, abra uma nova janela do terminal e digite os seguintes comandos:
Após digitar os comandos e pressionar enter, escreva alguma frase e pressiona enter novamente. Você obterá a saída:
robson@robson:~$ telnet localhost 1234O Protocolo de Transferência de Hipertextos (HTTP em inglês), trabalha como um protocolo de requisição-resposta entre um cliente e um servidor. Um navegador web pode ser o cliente e uma aplicação em um computador que hospeda um site pode ser o servidor.
O funcionamento básico do protocolo é feito da seguinte forma: Um cliente (navegador) envia
uma requisição HTTP para um servidor, solicitando algum arquivo desse servidor, como por exemplo
o arquivo index.html
. O servidor retorna uma resposta para o cliente, contendo informações
sobre o status da requisição e, o arquivo solicitado.
Podemos, portanto, implementar um simples servidor, que recebe requisições
de um navegador e, retorna um Hello Word
como resposta.
Para este servidor, usaremos o mesmo código do arquivo server_thread.py
, com uma pequena
modificação na função clientthread()
. O código ficou assim:
import socket import sys from thread import * host = '' port = 1234 s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) #garante que o socket será destruído (pode ser reusado) após uma interrupção da execução s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) #Bind socket to local host and port try: s.bind((host, port)) except socket.error as msg: print 'Bind failed. Error Code : ' + str(msg[0]) + ' Message ' + msg[1] sys.exit() s.listen(1) #Função para lidar com cada conexão (cliente) def clientthread(conn): #Sai do loop quando o cliente desconecta, pois a variável data não conterá #mais nenhum conteúdo. while True: #Receiving from client data = conn.recv(1024) print data reply = 'HTTP/1.1 200 OK\n\nHello Word' conn.sendall(reply) #destroi o socket e encerra thread ao sair do loop conn.close() break #Continua recebendo conexões while True: #Aceita conexões conn, addr = s.accept() print 'Connected with ' + addr[0] + ':' + str(addr[1]) #Cria nova thread para uma nova conexão. O primeiro #argumento é o nome da função, e o segundo é uma tupla #com os parâmetros da função. start_new_thread(clientthread ,(conn,))
Quando alguma mensagem é recebida através da função recv()
,
apenas exibimos ela. Logo após encaminhamos a resposta para o cliente (HTTP/1.1 200 OK
), assim
como o conteúdo (Hello Word). O navegador interpretará
a resposta da seguinte forma:
O servidor confirmou que vai manter a comunicação na versão do protocolo
HTTP/1.1
e o
número 200 significa que a requisição foi processada com sucesso, por isso
o OK. Depois do cabeçalho, é importante notar que, deve-se sempre ter
uma linha em branco, entre o cabeçalho e o coteúdo, por isso colocamos dois
\n\n
. Isso também facilita o navegador identificar o que é cabeçalho e o
que é o corpo da mensagem. Depois, vem a parte do conteúdo (Hello Word), simples assim!
Salve este arquivo com o nome http_server.py
. Abra o terminal e
o execute.
Não se preocupe, é normal não ser exibido nenhuma mensagem até agora, aliás, não colocamos nada no código que exiba algo até um cliente se conectar. Agora abra um navegador, e digite:
http://localhost:1234/helloPressione enter. Neste momento, deve aparecer um Hello Word no seu navegador. No terminal onde está rodando o servidor, deve aparecer algo como:
GET /hello HTTP/1.1Estas são as informações que o navegador manda numa requisição.
Repare na primeira linha, GET /hello HTTP/1.1
, aqui o navegador
diz, preciso (GET) do arquivo (hello) e quero me comunicar utilizando a versão
HTTP/1.1. As outras informações são sobre codificação, língua suportada
e informações específicas do navegador, neste exemplo usei o Opera 47.0.