
No artigo anterior, exploramos os file handles ee como eles são empregados na manipulação de arquivos. Neste artigo, continuaremos a discussão sobre handles, focando agora nos socket handles. Contudo, o conceito fundamental permanece o mesmo: um Socket Handle é um identificador utilizado pelo sistema operacional para distinguir uma conexão específica. Um canal de comunicação gerenciado pelo sistema operacional é denominado Socket; já o identificador que um processo emprega para referenciar esse canal é conhecido como socket handle. Usar um socket handle é similar a usar um file handle. Podemos solicitar ao SO a abertura de um socket, e ele nos retornará um socket handle que podemos usar para enviar e receber dados, e devemos fechá-lo uma vez que tenhamos terminado de usá-lo. Agora, ao invés de interagir com um arquivo, estaremos interagindo com processos que podem estar localizados tanto localmente quanto remotamente, de maneira transparente.
Criando Socket
O socket possui um communication domain que limita o tipo de comunicação que pode ser realizada e também determina o formato do endereço do socket.
name | descrição |
---|---|
PF_UNIX | comunicação local (Unix) |
PF_INET | comunicação via internet (IPv4) |
PF_INET6 | comunicação via internet (IPv6) |
PF_UNIX utiliza como endereço o nome do file system da máquina, limitando a comunicação a processos executados na mesma máquina. Por outro lado PF_INET e PF_INET6 são usados para comunicação via internet, onde o endereço é um par de host e port. Isso possibilita a comunicação entre processos executados em quaisquer duas máquinas conectadas à internet.
O Communication type do socket, por sua vez, indica se a comunicação é confiável (sem perda de pacotes ou duplicação de dados) e também determina como os dados são enviados e recebidos (stream de bytes ou sequência de pacotes). O Communication type restringe o protocolo utilizado para a transmissão de dados.
Type | Confiavel | Representacao dos dados |
---|---|---|
SOCK_STREAM | sim | Stream de bytes |
SOCK_DGRAM | não | Pacotes |
SOCK_RAW | não | Pacotes |
SOCK_SEQPACKET | sim | Sequencia de pacotes |
A função socket, localizada no módulo Unix, possui a seguinte assinatura:
type socket_domain =
PF_UNIX (** Unix domain *)
| PF_INET (** Internet domain (IPv4) *)
| PF_INET6 (** Internet domain (IPv6) *)
(** The type of socket domains. Not all platforms support
IPv6 sockets (type [PF_INET6]).
On Windows: [PF_UNIX] not implemented. *)
type socket_type =
SOCK_STREAM (** Stream socket *)
| SOCK_DGRAM (** Datagram socket *)
| SOCK_RAW (** Raw socket *)
| SOCK_SEQPACKET (** Sequenced packets socket *)
(** The type of socket kinds, specifying the semantics of
communications. [SOCK_SEQPACKET] is included for completeness,
but is rarely supported by the OS, and needs system calls that
are not available in this library. *)
val socket : socket_domain -> socket_type -> int -> file_descr
Então, com base no que vimos até agora, precisamos apenas entender o terceiro parâmetro da função socket
. Esse
parâmetro é o protocolo que será utilizado para a comunicação, representado por um inteiro. O valor 0 indica a seleção
do protocolo padrão para um dado communication domain e type (ex: UDP para SOCK_DGRAM). Os números desses protocolos
podem ser encontrados no arquivo /etc/protocols
ou na tabela protocols do NIS (Network Information Service).
# Internet (IP) protocols
#
# Updated from http://www.iana.org/assignments/protocol-numbers and other
# sources.
# New protocols will be added on request if they have been officially
# assigned by IANA and are not historical.
# If you need a huge list of used numbers please install the nmap package.
ip 0 IP # internet protocol, pseudo protocol number
hopopt 0 HOPOPT # IPv6 Hop-by-Hop Option [RFC1883]
icmp 1 ICMP # internet control message protocol
igmp 2 IGMP # Internet Group Management
ggp 3 GGP # gateway-gateway protocol
ipencap 4 IP-ENCAP # IP encapsulated in IP (officially ``IP'')
st 5 ST # ST datagram mode
tcp 6 TCP # transmission control protocol
egp 8 EGP # exterior gateway protocol
igp 9 IGP # any private interior gateway (Cisco)
pup 12 PUP # PARC universal packet protocol
udp 17 UDP # user datagram protocol
hmp 20 HMP # host monitoring protocol
xns-idp 22 XNS-IDP # Xerox NS IDP
rdp 27 RDP # "reliable datagram" protocol
iso-tp4 29 ISO-TP4 # ISO Transport Protocol class 4 [RFC905]
dccp 33 DCCP # Datagram Congestion Control Prot. [RFC4340]
xtp 36 XTP # Xpress Transfer Protocol
ddp 37 DDP # Datagram Delivery Protocol
idpr-cmtp 38 IDPR-CMTP # IDPR Control Message Transport
ipv6 41 IPv6 # Internet Protocol, version 6
ipv6-route 43 IPv6-Route # Routing Header for IPv6
ipv6-frag 44 IPv6-Frag # Fragment Header for IPv6
idrp 45 IDRP # Inter-Domain Routing Protocol
rsvp 46 RSVP # Reservation Protocol
gre 47 GRE # General Routing Encapsulation
esp 50 IPSEC-ESP # Encap Security Payload [RFC2406]
ah 51 IPSEC-AH # Authentication Header [RFC2402]
skip 57 SKIP # SKIP
ipv6-icmp 58 IPv6-ICMP # ICMP for IPv6
ipv6-nonxt 59 IPv6-NoNxt # No Next Header for IPv6
ipv6-opts 60 IPv6-Opts # Destination Options for IPv6
rspf 73 RSPF CPHB # Radio Shortest Path First (officially CPHB)
vmtp 81 VMTP # Versatile Message Transport
eigrp 88 EIGRP # Enhanced Interior Routing Protocol (Cisco)
ospf 89 OSPFIGP # Open Shortest Path First IGP
ax.25 93 AX.25 # AX.25 frames
ipip 94 IPIP # IP-within-IP Encapsulation Protocol
etherip 97 ETHERIP # Ethernet-within-IP Encapsulation [RFC3378]
encap 98 ENCAP # Yet Another IP encapsulation [RFC1241]
# 99 # any private encryption scheme
pim 103 PIM # Protocol Independent Multicast
ipcomp 108 IPCOMP # IP Payload Compression Protocol
vrrp 112 VRRP # Virtual Router Redundancy Protocol [RFC5798]
l2tp 115 L2TP # Layer Two Tunneling Protocol [RFC2661]
isis 124 ISIS # IS-IS over IPv4
sctp 132 SCTP # Stream Control Transmission Protocol
fc 133 FC # Fibre Channel
mobility-header 135 Mobility-Header # Mobility Support for IPv6 [RFC3775]
udplite 136 UDPLite # UDP-Lite [RFC3828]
mpls-in-ip 137 MPLS-in-IP # MPLS-in-IP [RFC4023]
manet 138 # MANET Protocols [RFC5498]
hip 139 HIP # Host Identity Protocol
shim6 140 Shim6 # Shim6 Protocol [RFC5533]
wesp 141 WESP # Wrapped Encapsulating Security Payload
rohc 142 ROHC # Robust Header Compression
Com isso, podemos criar um socket para comunicação via internet (IPv4) utilizando o protocolo TCP da seguinte maneira:
let socket = Unix.socket Unix.PF_INET Unix.SOCK_STREAM 0
O próximo passo é conectar o socket a um endereço. Para isso, utilizamos a função connect
:
val connect : file_descr -> sockaddr -> unit
(** Connect a socket to an address. *)
Conectando a um endereço
Agora, fomos apresentados a um novo tipo, sockaddr. Esse tipo é utilizado para representar um endereço de socket.
type sockaddr =
| ADDR_UNIX of string
| ADDR_INET of inet_addr * int
ADDR_UNIX f é um endereço de socket local, onde f é o nome do arquivo no sistema de arquivos. ADDR_INET (a,p) é um endereço de socket de internet, onde a é o endereço IP e p é a porta.
A função host realiza um DNS lookup no nome passado como parâmetro e retorna o endereço IP associado ao domínio.
$ host ocaml.org
ocaml.org has address 51.159.83.169
ocaml.org mail is handled by 50 fb.mail.gandi.net.
ocaml.org mail is handled by 10 spool.mail.gandi.net.
Recebemos 1 endereço IP associado ao domínio ocaml.org
. É sabido que por convenção a porta 80 é utilizada para o HTTP.
Uma forma equivalente de realizar a mesma ação em OCaml seria:
# string_of_inet_addr (Unix.gethostbyname "ocaml.org").h_addr_list.(0);;
- : string = "51.159.83.169"
Logo, podemos criar nosso sockaddr da seguinte maneira:
let ocaml_org_address =
let ocaml_org_host_entry = Unix.gethostbyname "ocaml.org" in
Unix.ADDR_INET (ocaml_org_host_entry.h_addr_list.(0), 80)
;;
Agora, usando tudo o que vimos e adicionando algumas funções novas, que vou explicar logo em seguida, temos:
let print_server_response fdin =
let buffer_size = 4096 in
let buffer = BytesLabels.create buffer_size in
let rec copy () = match read fdin buffer 0 buffer_size with
| 0 -> ()
| n ->
let response = Bytes.sub_string buffer 0 n in
print_string response;
copy ()
in
copy ();;
let make_friend address =
let s = socket Unix.PF_INET Unix.SOCK_STREAM 0 in
try
connect s address;
let message = "Olá Mundo! \n" in
send_substring s message 0 (String.length message) [] |> ignore;
print_server_response s stdout;
shutdown s Unix.SHUTDOWN_ALL;
with exn ->
close s;
raise exn;;
;;
let ocaml_org_address =
let ocaml_org_host_entry = Unix.gethostbyname "ocaml.org" in
Unix.ADDR_INET (ocaml_org_host_entry.h_addr_list.(0), 80)
;;
A função print_server_response recebe um file_descriptor e imprime o conteúdo recebido do servidor. Para isso, ela cria um buffer de 4096 bytes e lê o conteúdo do file_descriptor para o buffer, imprimindo o conteúdo do buffer até que não haja mais nada para ler.
A função make_friend cria um socket e tenta conectar a um endereço passado como parâmetro. Caso a conexão seja bem-sucedida, ela envia a mensagem "Olá Mundo! \n" para o servidor e imprime a resposta recebida. Por fim, ela fecha o socket.
Gostaria de pontuar o uso da função read:
val read : file_descr -> bytes -> int -> int -> int
(** [read fd buf pos len] reads [len] bytes from descriptor [fd],
storing them in byte sequence [buf], starting at position [pos] in
[buf]. Return the number of bytes actually read. *)
A função read lê até len bytes do file_descr fd e armazena no buffer buf a partir da posição pos. Ela retorna o número de bytes lidos. Caso a função retorne 0, significa que não há mais dados para serem lidos. Essa é exatamente a lógica que aplicamos na função print_server_response.
Para realizar uma chamada à função make_friend, basta passar o endereço do servidor como parâmetro:
make_friend ocaml_org_address
- HTTP/1.1 400 Bad Request
- Content-Type: text/plain; charset=utf-8
- Connection: close
- 400 Bad Request- : unit = ()
Bem, ao menos fizemos a conexão com o servidor. Encontramos alguém lá fora e enviamos uma mensagem; eles nos responderam. O que a resposta significa é que eles não entenderam nossa mensagem. A resposta começa com "HTTP/1.1", e esse é o tópico do nosso próximo artigo.
Conclusão
Neste artigo, exploramos a criação de sockets e a conexão a um endereço. Vimos como criar um socket para comunicação via internet (IPv4) utilizando o protocolo TCP e como conectar a um endereço. Além disso, vimos como criar um sockaddr e como realizar um DNS lookup para obter o endereço IP associado a um domínio.