OCaml Sockets

OCaml Sockets

No artigo anterior, exploramos os file handles e 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.

namedescrição
PF_UNIXcomunicação local (Unix)
PF_INETcomunicação via internet (IPv4)
PF_INET6comunicaçã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 tipo de comunicaçã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 (fluxo de bytes ou sequência de pacotes). O Communication type restringe o protocolo utilizado para a transmissão de dados.

TypeConfiavelRepresentacao dos dados
SOCK_STREAMsimStream de bytes
SOCK_DGRAMnãoPacotes
SOCK_RAWnãoPacotes
SOCK_SEQPACKETsimSequencia de pacotes

A função socket, localizada no módulo Unix, tem a seguinte assinatura:

OCAML
1type socket_domain =
2 PF_UNIX (** Unix domain *)
3 | PF_INET (** Internet domain (IPv4) *)
4 | PF_INET6 (** Internet domain (IPv6) *)
5(** The type of socket domains. Not all platforms support
6 IPv6 sockets (type [PF_INET6]).
7
8 On Windows: [PF_UNIX] not implemented. *)
9
10type socket_type =
11 SOCK_STREAM (** Stream socket *)
12 | SOCK_DGRAM (** Datagram socket *)
13 | SOCK_RAW (** Raw socket *)
14 | SOCK_SEQPACKET (** Sequenced packets socket *)
15(** The type of socket kinds, specifying the semantics of
16 communications. [SOCK_SEQPACKET] is included for completeness,
17 but is rarely supported by the OS, and needs system calls that
18 are not available in this library. *)
19
20val 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).

TXT
1# Internet (IP) protocols
2#
3# Updated from http://www.iana.org/assignments/protocol-numbers and other
4# sources.
5# New protocols will be added on request if they have been officially
6# assigned by IANA and are not historical.
7# If you need a huge list of used numbers please install the nmap package.
8
9ip 0 IP # internet protocol, pseudo protocol number
10hopopt 0 HOPOPT # IPv6 Hop-by-Hop Option [RFC1883]
11icmp 1 ICMP # internet control message protocol
12igmp 2 IGMP # Internet Group Management
13ggp 3 GGP # gateway-gateway protocol
14ipencap 4 IP-ENCAP # IP encapsulated in IP (officially ``IP'')
15st 5 ST # ST datagram mode
16tcp 6 TCP # transmission control protocol
17egp 8 EGP # exterior gateway protocol
18igp 9 IGP # any private interior gateway (Cisco)
19pup 12 PUP # PARC universal packet protocol
20udp 17 UDP # user datagram protocol
21hmp 20 HMP # host monitoring protocol
22xns-idp 22 XNS-IDP # Xerox NS IDP
23rdp 27 RDP # "reliable datagram" protocol
24iso-tp4 29 ISO-TP4 # ISO Transport Protocol class 4 [RFC905]
25dccp 33 DCCP # Datagram Congestion Control Prot. [RFC4340]
26xtp 36 XTP # Xpress Transfer Protocol
27ddp 37 DDP # Datagram Delivery Protocol
28idpr-cmtp 38 IDPR-CMTP # IDPR Control Message Transport
29ipv6 41 IPv6 # Internet Protocol, version 6
30ipv6-route 43 IPv6-Route # Routing Header for IPv6
31ipv6-frag 44 IPv6-Frag # Fragment Header for IPv6
32idrp 45 IDRP # Inter-Domain Routing Protocol
33rsvp 46 RSVP # Reservation Protocol
34gre 47 GRE # General Routing Encapsulation
35esp 50 IPSEC-ESP # Encap Security Payload [RFC2406]
36ah 51 IPSEC-AH # Authentication Header [RFC2402]
37skip 57 SKIP # SKIP
38ipv6-icmp 58 IPv6-ICMP # ICMP for IPv6
39ipv6-nonxt 59 IPv6-NoNxt # No Next Header for IPv6
40ipv6-opts 60 IPv6-Opts # Destination Options for IPv6
41rspf 73 RSPF CPHB # Radio Shortest Path First (officially CPHB)
42vmtp 81 VMTP # Versatile Message Transport
43eigrp 88 EIGRP # Enhanced Interior Routing Protocol (Cisco)
44ospf 89 OSPFIGP # Open Shortest Path First IGP
45ax.25 93 AX.25 # AX.25 frames
46ipip 94 IPIP # IP-within-IP Encapsulation Protocol
47etherip 97 ETHERIP # Ethernet-within-IP Encapsulation [RFC3378]
48encap 98 ENCAP # Yet Another IP encapsulation [RFC1241]
49# 99 # any private encryption scheme
50pim 103 PIM # Protocol Independent Multicast
51ipcomp 108 IPCOMP # IP Payload Compression Protocol
52vrrp 112 VRRP # Virtual Router Redundancy Protocol [RFC5798]
53l2tp 115 L2TP # Layer Two Tunneling Protocol [RFC2661]
54isis 124 ISIS # IS-IS over IPv4
55sctp 132 SCTP # Stream Control Transmission Protocol
56fc 133 FC # Fibre Channel
57mobility-header 135 Mobility-Header # Mobility Support for IPv6 [RFC3775]
58udplite 136 UDPLite # UDP-Lite [RFC3828]
59mpls-in-ip 137 MPLS-in-IP # MPLS-in-IP [RFC4023]
60manet 138 # MANET Protocols [RFC5498]
61hip 139 HIP # Host Identity Protocol
62shim6 140 Shim6 # Shim6 Protocol [RFC5533]
63wesp 141 WESP # Wrapped Encapsulating Security Payload
64rohc 142 ROHC # Robust Header Compression

Com isso, podemos criar um socket para comunicação via internet (IPv4) utilizando o protocolo TCP da seguinte maneira:

OCAML
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:

OCAML
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.

OCAML
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.

Bash
$ 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:

OCAML
# 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:

OCAML
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:

OCAML
1let print_server_response fdin =
2 let buffer_size = 4096 in
3 let buffer = BytesLabels.create buffer_size in
4 let rec copy () = match read fdin buffer 0 buffer_size with
5 | 0 -> ()
6 | n ->
7 let response = Bytes.sub_string buffer 0 n in
8 print_string response;
9 copy ()
10 in
11 copy ();;
12
13let make_friend address =
14 let s = socket Unix.PF_INET Unix.SOCK_STREAM 0 in
15 try
16 connect s address;
17 let message = "Olá Mundo! \n" in
18 send_substring s message 0 (String.length message) [] |> ignore;
19 print_server_response s stdout;
20 shutdown s Unix.SHUTDOWN_ALL;
21 with exn ->
22 close s;
23 raise exn;;
24;;
25
26let ocaml_org_address =
27 let ocaml_org_host_entry = Unix.gethostbyname "ocaml.org" in
28 Unix.ADDR_INET (ocaml_org_host_entry.h_addr_list.(0), 80)
29;;

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:

OCAML
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:

OCAML
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.