
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.
| 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 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.
| 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, tem a seguinte assinatura:
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 support6 IPv6 sockets (type [PF_INET6]).78 On Windows: [PF_UNIX] not implemented. *)910type 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 of16 communications. [SOCK_SEQPACKET] is included for completeness,17 but is rarely supported by the OS, and needs system calls that18 are not available in this library. *)1920val socket : socket_domain -> socket_type -> int -> file_descrEntã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).
1# Internet (IP) protocols2#3# Updated from http://www.iana.org/assignments/protocol-numbers and other4# sources.5# New protocols will be added on request if they have been officially6# assigned by IANA and are not historical.7# If you need a huge list of used numbers please install the nmap package.89ip 0 IP # internet protocol, pseudo protocol number10hopopt 0 HOPOPT # IPv6 Hop-by-Hop Option [RFC1883]11icmp 1 ICMP # internet control message protocol12igmp 2 IGMP # Internet Group Management13ggp 3 GGP # gateway-gateway protocol14ipencap 4 IP-ENCAP # IP encapsulated in IP (officially ``IP'')15st 5 ST # ST datagram mode16tcp 6 TCP # transmission control protocol17egp 8 EGP # exterior gateway protocol18igp 9 IGP # any private interior gateway (Cisco)19pup 12 PUP # PARC universal packet protocol20udp 17 UDP # user datagram protocol21hmp 20 HMP # host monitoring protocol22xns-idp 22 XNS-IDP # Xerox NS IDP23rdp 27 RDP # "reliable datagram" protocol24iso-tp4 29 ISO-TP4 # ISO Transport Protocol class 4 [RFC905]25dccp 33 DCCP # Datagram Congestion Control Prot. [RFC4340]26xtp 36 XTP # Xpress Transfer Protocol27ddp 37 DDP # Datagram Delivery Protocol28idpr-cmtp 38 IDPR-CMTP # IDPR Control Message Transport29ipv6 41 IPv6 # Internet Protocol, version 630ipv6-route 43 IPv6-Route # Routing Header for IPv631ipv6-frag 44 IPv6-Frag # Fragment Header for IPv632idrp 45 IDRP # Inter-Domain Routing Protocol33rsvp 46 RSVP # Reservation Protocol34gre 47 GRE # General Routing Encapsulation35esp 50 IPSEC-ESP # Encap Security Payload [RFC2406]36ah 51 IPSEC-AH # Authentication Header [RFC2402]37skip 57 SKIP # SKIP38ipv6-icmp 58 IPv6-ICMP # ICMP for IPv639ipv6-nonxt 59 IPv6-NoNxt # No Next Header for IPv640ipv6-opts 60 IPv6-Opts # Destination Options for IPv641rspf 73 RSPF CPHB # Radio Shortest Path First (officially CPHB)42vmtp 81 VMTP # Versatile Message Transport43eigrp 88 EIGRP # Enhanced Interior Routing Protocol (Cisco)44ospf 89 OSPFIGP # Open Shortest Path First IGP45ax.25 93 AX.25 # AX.25 frames46ipip 94 IPIP # IP-within-IP Encapsulation Protocol47etherip 97 ETHERIP # Ethernet-within-IP Encapsulation [RFC3378]48encap 98 ENCAP # Yet Another IP encapsulation [RFC1241]49# 99 # any private encryption scheme50pim 103 PIM # Protocol Independent Multicast51ipcomp 108 IPCOMP # IP Payload Compression Protocol52vrrp 112 VRRP # Virtual Router Redundancy Protocol [RFC5798]53l2tp 115 L2TP # Layer Two Tunneling Protocol [RFC2661]54isis 124 ISIS # IS-IS over IPv455sctp 132 SCTP # Stream Control Transmission Protocol56fc 133 FC # Fibre Channel57mobility-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 Protocol62shim6 140 Shim6 # Shim6 Protocol [RFC5533]63wesp 141 WESP # Wrapped Encapsulating Security Payload64rohc 142 ROHC # Robust Header CompressionCom 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 0O 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 * intADDR_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.orgocaml.org has address 51.159.83.169ocaml.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:
1let print_server_response fdin =2 let buffer_size = 4096 in3 let buffer = BytesLabels.create buffer_size in4 let rec copy () = match read fdin buffer 0 buffer_size with5 | 0 -> ()6 | n ->7 let response = Bytes.sub_string buffer 0 n in8 print_string response;9 copy ()10 in11 copy ();;1213let make_friend address =14 let s = socket Unix.PF_INET Unix.SOCK_STREAM 0 in15 try16 connect s address;17 let message = "Olá Mundo! \n" in18 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;;2526let ocaml_org_address =27 let ocaml_org_host_entry = Unix.gethostbyname "ocaml.org" in28 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:
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.