Buscar

Sockets em C - Sockets

Esta é uma pré-visualização de arquivo. Entre para ver o arquivo original

Departamento de Engenharia Electrotécnica e de Computadores
Secção de Telecomunicações
Redes de Computadores I
Introdução à Interface "Socket" do UNIX
Licenciatura em Engenharia de
Redes de Comunicação e de Informação
Prof. Paulo Pereira
1
SOCKETS
Domínio INTERNET:
Transporte
Internet
Interface
de Rede
Socket
Camadas
Conceptuais
Interface 1 Interface 2 Interface 3
Módulo IP
Protocolo 1 Protocolo 2 Protocolo 3
Socket
Organização de Software
int socket (int domain, int type, int protocol) ;
Cria um porto para comunicação assíncrona, bidireccional e retorna um descritor
(idêntico aos utilizados nos ficheiros e pipes).
domain - domínio onde o socket é criado, que define os protocolos e o espaço de
nomes.
PF_UNIX - Domínio Unix, local a uma máquina
PF_INET - Domínio Inet, redes Internet
type
SOCK_STREAM - orientado à ligação, comunicação fiável e sequencial.
SOCK_DGRAM - sem ligação, mensagens limitadas no comprimento,
comunicação sem garantia de sequencialidade, fiabilidade ou não
existência de duplicações.
protocol - depende do domínio. Normalmente é colocado a zero, que indica o
protocolo por omissão no domínio respectivo (TCP, UDP).
int bind (int s, struct sockaddr *name, int namelen) ;
Associa um nome a um socket já criado.
s - identificador do socket.
name - o nome depende do domínio onde o socket foi criado. No domínio UNIX
corresponde a um "pathname". No domínio Internet é do tipo struct
sockaddr_in, que é composto pelo endereço da máquina, protocolo e
número de porto .
namelen - inteiro igual a sizeof(*name)
int close (int s) ;
2
Connectionless (SOCK_DGRAM)
Servidor
bloqueia até receber
dados de um cliente
processa pedido
recvfrom ( )
bind ( )
socket ( )
socket ( )
sendto ( )
sendto ( )
recvfrom ( )
Cliente
dados(pedido)
dados(resposta)
int sendto (int s, char *msg, int len, int flags, struct sockaddr *to, 
 int tolen) ;
Envia uma mensagem através do socket s para o socket especificado em to
independentemente de existir ou não ligação estabelecida entre os dois sockets.
msg - mensagem a enviar.
len - dimensão da mensagem a enviar
flags : MSG_OOB - Out of band
to - endereço do socket para onde vai ser enviada a mensagem.
tolen - inteiro igual a sizeof(*to)
int recvfrom (int s, char *buf, int len, int flags, struct sockaddr *from, 
 int * fromlen) ; ou
int recv (int s, char *buf, int len, int flags) ; ou
int read (int s, char *buf, int len) ;
Recebe uma mensagem através do socket s de um socket remoto
independentemente de existir ou não ligação estabelecida entre os dois. Devolve o
tamanho da mensagem lida.
buf - buffer para a mensagem a receber.
len - dimensão do buffer.
flags : MSG_OOB - Out of band.
MSG_PEEK- Ler sem consumir.
from - endereço do socket que enviou a mensagem.
fromlen - ponteiro para inteiro inicializado a sizeof(*from).
3
Connection-oriented (SOCK_STREAM)
Servidor
bloqueia até ter ligação
de um cliente
processa pedido
accept ( )
bind ( )
socket ( )
connect ( )
socket ( )
read ( ) write ( )
read ( )
Cliente
dados(pedido)
dados(resposta)
listen ( )
estabelecimento de ligação
write ( )
int connect (int s, struct sockaddr *name, int namelen) ;
Estabelece uma ligação entre o socket s e o outro socket indicado em name.
int listen (int s, int backlog) ;
Indica que o socket s pode receber ligações.
backlog - comprimento da fila de espera de novos pedidos de ligação.
int accept (int s, struct sockaddr *addr, int * addrlen) ;
Bloqueia o processo até um processo remoto estabelecer uma ligação. Retorna o
identificador de um novo socket para transferência de dados.
int send (int s, char *msg, int len, int flags) ; ou
int write(int s, char *msg, int len) ;
Envia uma mensagem através do socket s para o socket remoto associado.
Retorna o número de bytes efectivamente enviados, ou -1 em caso de erro. No caso de a
ligação ser cortada, o processo pode receber um signal SIGPIPE.
int recv (int s, char *buf, int len, int flags) ; ou
int read (int s, char *buf, int len) ;
Recebe uma mensagem do socket remoto através do socket s. Retorna o número
de bytes lidos, ou 0 se a ligação foi cortada, ou -1 se a operação foi interrompida.
int shutdown (int s, int how) ;
how: 0 - no more reading, 1 - no more writing, 2 - no more reading or writing.
4
Multiplexagem das Entradas/Saídas ( select )
#include <sys/types.h>
#include <sys/time.h>
int select ( int width, fd_set *readfds, fd_set *writefds, 
 fd_set *exceptfds, struct timeval *timeout) ;
Bloqueia o processo até que uma das operações de E/S pendentes se realize.
Entradas:
• máscaras readfds, writefds, exceptfds - padrão de bits
indicando os descritores de E/S sobre os quais se pretende efectuar
operações de Leitura, Escrita ou processar Excepções.
• width - número do maior descritor a considerar na máscara + 1. Este
parâmetro pode ser preenchido com o valor retornado pela função
getdtablesize() que retorna o número máximo de descritores
que pode estar associado a um processo, ou com a macro FD_SETSIZE.
• timeout - tempo de espera (0 = bloqueio)
Saídas :
• máscaras readfds, writefds, exceptfds - padrão de bits
indicando os descritores "ready".
Retorna - número de descritores "ready", 0 se expirou o timeout, -1 se foi
interrompido ou se houve um erro.
Para modificar os bits das máscaras são utilizadas as funções (macros):
FD_ZERO (fd_set *fdset)
Coloca todos os bits da máscara a 0.
FD_SET (int fd, fd_set *fdset)
Liga o bit correspondente ao descritor de ficheiro fd.
FD_CLR (int fd, fd_set *fdset)
Limpa o bit correspondente ao descritor de ficheiro fd.
FD_ISSET (int fd, fd_set *fdset)
Testa se o bit correspondente ao descritor de ficheiro fd está activo.
Em versões antigas do Unix foi utilizado o tipo int em vez de fd_set, mas
nesse caso, o máximo valor de width suportado é 32.
É possível obter o descritor do teclado com: fileno(stdin)
5
Exemplo de utilização ( leitura bloqueante de mensagens de 2 descritores ) :
#define NO_DESC 0
#define BLOCK 0
fd_set rmask; /* máscara de leitura */
int sd1, sd2, /* Descritores de sockets */
 n;
FD_ZERO(&rmask);
FD_SET(sd1, &rmask);
FD_SET(sd2, &rmask);
n= select (getdtablesize(), &rmask, NO_DESC, NO_DESC,
 BLOCK);
if (n <= 0) {
 fprintf(stderr, "Interruption of select\n");
 ...
 }
else {
 if (FD_ISSET(sd1, &rmask)) {
 /* Há dados disponíveis para leitura no socket sd1 */
...
 }
 if (FD_ISSET(sd2, &rmask)) {
 /* Há dados disponíveis para leitura no socket sd2 */
...
 }
 }
Configurar um socket para escrita não bloqueante
Para que uma chamada sistema de escrita num socket s não seja bloqueante deve-
se ligar a flag NDELAY:
#include <sys/fcntl.h>
int flags;
if ((flags= fcntl(s, F_GETFL)) < 0)
 perror("fcntl error getting socket flags");
flags|= O_NDELAY;
if (fcntl(s, F_SETFL, flags) < 0)
 perror("fcntl error setting NO DELAY on socket");
Outro método de obter o mesmo resultado:
#include <sys/filio.h>
int nonblock=1;
if (ioctl(s, FIONBIO, &nonblock) < 0)
 perror("ioctl error setting NO DELAY on socket");
6
As funções apresentadas anteriormente retornam o valor -1 em caso de falha. A
variável errno é afectada com o código da ocorrência. perror escreve a mensagem
de erro do sistema operativo associada a esse código de erro.
Em seguida apresenta-se a definição de alguns dos tipos referidos anteriormente:
<sys/time.h>:
struct timeval {
 long tv_sec; /* seconds */
 long tv_usec; /* and microseconds */
};
<sys/socket.h>:
struct sockaddr {
 u_short sa_family; /* Address family : AF_xxx */
 char sa_data[14]; /* up to 14 bytes of protocol
specific address */
};
<netinet/in.h>:
struct in_addr {
 u_long s_addr; /* 32-bit netid/hostid
 network byte ordered */
};
struct sockaddr_in {
 short sin_family; /* AF_INET */
 u_short sin_port; /* 16-bit port number
 network byte ordered */
 struct in_addr sin_addr; /* 32-bit netid/hostid
 network byte ordered */
 char sin_zero[8]; /* unused */
};
<netdb.h>:
struct hostent {
 char *h_name; /* official name of host */
 char **h_aliases; /* alias list */
 int h_addrtype; /* host address type */
 int h_length; /* length of address */
 char **h_addr_list; /* list of addresses, null
 terminated */
};
#define h_addr h_addr_list[0] /* first address,
 network byte order */
<sys/types.h>:
typedef long fd_mask;
typedef struct fd_set {
 fd_mask fds_bits[ ... ];
} fd_set;
7
Dados fora de banda (OOB) em sockets orientados à conexão
Os sockets orientados à conexão suportam o envio de dados fora de banda
constituídos por um byte de informação. Uma possível utilização é o envio de
CONTROL-C.
O envio de dados fora de banda é feito utilizando a função send() com a flag
MSG_OOB.
A recepção dos dados pode ser síncrona, mantendo-se a ordem em relação aos
dados normais com que foram enviados. Nesse caso, pode-se utilizar qualquer das
primitivas descritas anteriormente para ler dados de sockets. A função ioctl permite
descobrir se os próximos dados lidos do socket s são "fora de banda" ou normais:
int yes;
ioctl (s, SIOCATMARK, &yes);
if (yes) {
/* dados fora de banda */
} else {
/* dados normais */
}
Outra hipótese será fazer leitura assíncrona, associando o signal SIGURG à
recepção de dados fora de banda. Essa associação é feita utilizando a função ioctl
sobre o socket s, fornecendo como parâmetro o pid do processo vezes -1, como está
representado:
#ifdef sun
#include <sys/sockio.h>
#else
#include <sys/ioctl.h>
#endif
int pid= -getpid();
if (ioctl(s, SIOCSPGRP, (char *)&pid) < 0) {
perror ("ioctl: SIOCSPGRP,");
}
Neste caso, os dados fora de banda são lidos na rotina de tratamento do signal,
utilizando a função recv() ou recvfrom() com a flag MSG_OOB.
8
Funções auxiliares
Existem definidas um conjunto de funções de biblioteca.
struct hostent *gethostbyname(char *hostname);
Devolve o identificador de host/rede associado ao hostname.
unsigned long inet_addr(char *cp);
Converte uma string cp na notação standard da Internet “d.d.d.d” no endereço IP
em formato rede, devolvendo -1 em caso de erro.
char *inet_ntoa(struct in_addr in);
Converte o endereço IP in, no formato rede para uma string com o endereço
“d.d.d.d” associado.
No quadro seguinte apresenta-se um excerto de um programa com o
preenchimento de uma estrutura sockaddr_in com um endereço dado pelo número
de porto e nome do host (máquina).
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
char *host_name= "mega.ist.utl.pt";
struct sockaddr_in addr;
struct hostent *hp;
...
hp= gethostbyname(host_name);
if (hp == NULL) {
fprintf (stderr, "%s : unknown host\n", host_name);
...
}
bzero((char *)&addr, sizeof addr);
bcopy (hp->h_addr, (char *) &addr.sin_addr, hp->h_length);
addr.sin_family= AF_INET;
addr.sin_port= htons(port_number);
printf("Built address %s:%d\n", inet_ntoa(addr.sin_addr),
 (int)ntohs(addr.sin_port));
9
Quando se está a preencher a estrutura para uma chamada à função bind,
pode-se inicializar o campo sin_addr.s_addr com INADDR_ANY, que fica
automaticamente associado a todos os endereços IP do host local. Se o porto for
inicializado a 0, então é atribuído um valor durante a chamada a bind.
int getsockname ( int s, struct sockaddr *addr, int *addrlen );
Utilizada para obter a estrutura com o endereço associado ao socket s.
Em seguida apresenta-se um excerto de um programa, onde se obtém o número de
porto associado a um socket s.
struct sockaddr_in addr;
int len= sizeof(addr);
...
if (getsockname(s, &addr, &len)) {
fprintf(stderr, "Error getting socket name\n");
exit(1);
}
if (addr.sin_family != AF_INET) {
fprintf(stderr, "Nao e' socket INET\n");
...
}
printf("Socket has port #%d\n", ntohs(addr.sin_port));
Outras funções
C Run-time Routines :
Call Synopsis
bcmp (s1, s2, n)
bcopy (s1, s2, n)
bzero (base, n)
htonl (val)
htons (val)
ntohl (val)
ntohs (val)
Compare byte-strings; 0 if same, not 0 otherwise
Copy n bytes from s1 to s2
Zero-fill n bytes starting at base
32-bit quantity (long) from host into network byte order
16-bit quantity (short) from host into network byte order
32-bit quantity (long) from network into host byte order
16-bit quantity (short) from network into host byte order
10
Para permitir a portabilidade do código para máquinas que utilizem uma ordem
dos bytes diferentes da ordem utilizada na rede, devem ser utilizadas as rotinas ?to??
(em <netinet/in.h>) para manipular as componentes dos endereços. Estas funções
também podem ser utilizadas para possibilitar a transmissão de shorts e ints entre
máquinas que usam uma ordem diferente dos bytes.
Note-se que a função memcpy tem os argumentos pela ordem inversa de bcopy,
e o seu correcto funcionamento não é garantido quando as zonas de memória se
sobrepõem. A função bcopy trata correctamente de sobreposições. Em alguns sistemas
operativos tem o nome memmove.
A função bzero pode ser substituída por memset com os parâmetros adequados.
Por razões de eficiência, deve-se evitar cópias e limpezas de memória
desnecessárias.
11
Exemplo de envio de dados datagram no domínio Internet.
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#define DATA "The sea in calm tonight, the tide is full..."
/*
 * Here I send a datagram to a receiver whose name I get
 * from the command line arguments.
 */
main (int argc, char *argv[])
{
int sock;
struct sockaddr_in name;
struct hostent *hp;
 /* Create socket on which to send. */
 sock = socket(PF_INET, SOCK_DGRAM, 0);
 if (sock < 0) {
perror("opening datagram socket");
exit(1);
 }
/*
 * Construct name, with no wildcards, of the socket to send
 * to. Gethostbyname() returns a structure including the
 * network address of the specified host. The port number
 * is taken from the command line.
 */
 hp = gethostbyname(argv[1]);
 if (hp == 0) {
fprintf(stderr, "%s: unknown host\n", argv[1]);
exit(2);
 }
 bcopy(hp->h_addr, &name.sin_addr, hp->h_length);
 name.sin_family = AF_INET;
 name.sin_port = htons(atoi(argv[2]));
 /* Send message. */
 if (sendto(sock, DATA, strlen(DATA)+1, 0, &name,
 sizeof(name)) < 0)
perror("sending datagram message");
 close(sock);
}
12
Exemplo de recepção de dados datagram no domínio Internet.
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <stdio.h>
#include <unistd.h>
/*
 * This program creates a datagram socket, binds a name
 * to it, then reads from the socket.
 */
#define BUFSIZE 1024
main ()
{
int sock, length;
struct sockaddr_in name, from;
int fromlen= sizeof(from);
char buf[BUFSIZE];
 /* Create socket on which to read. */
 sock = socket(PF_INET, SOCK_DGRAM, 0);
 if (sock < 0) {
perror("opening datagram socket");
exit(1);
 }
 /* Create name with wildcards. */
 name.sin_family = AF_INET;
 name.sin_addr.s_addr = INADDR_ANY;
 name.sin_port = 0;
 if (bind(sock, &name, sizeof(name))) {
perror("binding datagram socket");
exit(1);
 }
 /*
Find assigned port value and print it out. */
 length = sizeof(name);
 if (getsockname(sock, &name, &length)) {
perror("getting socket name");
exit(1);
 }
 printf("Socket has port #%d\n", ntohs(name.sin_port));
 /* Read from the socket */
 if (recvfrom(sock, buf, BUFSIZE, 0, &from, &fromlen) < 0)
perror("receiving datagram packet");
 printf("-->%s\n", buf);
 close(sock);
}
13
Exemplo de envio de dados stream no domínio Internet.
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#define DATA "Half a league, half a league ..."
/*
 * This program creates a socket and initiates a connection
 * with the socket given in the command line. One message
 * is sent over the connection and then the socket is
 * closed, ending the connection.
 */
main (int argc, char *argv[])
{
int sock;
struct sockaddr_in server;
struct hostent *hp;
 /* Create socket */
 sock = socket(PF_INET, SOCK_STREAM, 0);
 if (sock < 0) {
perror("opening stream socket");
exit(1);
 }
 /* Connect socket using name specified by command line. */
 hp = gethostbyname(argv[1]);
 if (hp == 0) {
fprintf(stderr, "%s: unknown host\n", argv[1]);
exit(1);
 }
 bcopy(hp->h_addr, &server.sin_addr, hp->h_length);
 server.sin_family = AF_INET;
 server.sin_port = htons(atoi(argv[2]));
 if (connect(sock, &server, sizeof(server)) < 0) {
perror("connecting stream socket");
exit(1);
 }
 if (write(sock, DATA, strlen(DATA)+1) < 0)
perror("writing on stream socket");
 close(sock);
}
14
Exemplo de recepção de dados stream no domínio Internet.
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
#include <stdio.h>
#include <unistd.h>
#define BUFSIZE 1024
main ()
{
int sock, msgsock, length, n;
struct sockaddr_in server;
char buf[BUFSIZE+1];
 /* Create socket on which to read. */
 sock = socket(PF_INET, SOCK_STREAM, 0);
 if (sock < 0) {
perror("opening stream socket");
exit(1);
 }
 /* Create name with wildcards. */
 server.sin_family = AF_INET;
 server.sin_addr.s_addr = INADDR_ANY;
 server.sin_port = 0;
 if (bind(sock, &server, sizeof(server))) {
perror("binding stream socket");
exit(1);
 }
 /* Find assigned port value and print it out. */
 length = sizeof(server);
 if (getsockname(sock, &server, &length)) {
perror("getting socket name");
exit(1);
 }
 printf("Socket has port #%d\n", ntohs(server.sin_port));
 /* Start accepting connections */
 listen (sock, 5);
 while (1) {
msgsock = accept(sock, 0, 0);
if (msgsock == -1)
perror("accept");
else {
if ((n=read(msgsock, buf, BUFSIZE)) < 0)
perror("receiving stream data");
else if (n==0)
fprintf(stderr, "EOF\n");
else {
buf[n]='\0';
printf("-->%s\n", buf);
}
close(msgsock);
}
 }
}
15
Exemplo de utilização da instrução select()com sockets orientados à
conexão no domínio Internet.
/* Processo Servidor */
#include <sys/types.h>
#include <sys/socket.h>
#define BUFSIZE 1024
#define BLOCK 0
#define NO_DESC 0
int which (fd_set *mask)
{ ... procurar primeiro bit ligado ... }
main () {
int sd, ns, n, fromlen, active;
fd_set rmask, Rmask;
char buf[BUFSIZE];
struct sockaddr_in NameSocket, OtherSocket;
 sd = socket (PF_INET, SOCK_STREAM, 0);
 ... Preenchimento do endereço em NameSocket ...
 bind (sd, &NameSocket, sizeof(struct sockaddr_in));
 listen (sd, 1);
 FD_ZERO(&rmask);
 FD_SET(sd, &rmask);
 fromlen= sizeof(struct sockaddr_in);
 for ( ; ; ) {
 memcpy(&Rmask, &rmask, sizeof(rmask));
 select (FD_SETSIZE, &Rmask, NO_DESC, NO_DESC, BLOCK);
 active= which (&Rmask);
 if (active == sd) {
 /* Recepção no socket de Ligações */
 ns= accept (sd, &OtherSocket, &fromlen);
 FD_SET (ns, &rmask);
 } else {
 /* Recepção num socket ordinário */
 n= read (active, buf, BUFSIZE);
 if (n>0)
 printf("O serviço recebeu %.*s\n", n, buf);
 else { /* == 0: fim de ligação; == -1: erro */
 ... Tratamento do fim de ligação, ou erro ...
 FD_CLR(active, &rmask);
 close(active);
 }
 }
 }
}
16
Exemplo de utilização de processos filhos com sockets orientados à
conexão no domínio Internet.
int main()
{
 int sock, msgsock, n, pid;
 struct sockaddr_in server;
 char buf[BUFSIZE+1];
 /* Criar socket... */
 /* ignorar o signal SIG_PIPE para evitar core dumps
 quando escrever para um socket com ligação fechada */
 signal(SIGPIPE, SIG_IGN);
 /* ignorar o signal SIG_CHILD para evitar processos
 zombies */
 signal(SIGCHLD, SIG_IGN);
 /* começar a aceitar ligações */
 listen(sock, 5);
 while (1) {
 msgsock= accept(sock, NULL, NULL);
 if (msgsock == -1)
 perror("accept");
 else {
 printf("Father process accepted new connection.
 Starting a child\n");
 if ( (pid=fork()) == -1)
 perror("fork");
 else if (pid==0) {
 pid= getpid();
 printf("Son %d running\n", pid);
 /* fechar o socket para aceitar ligações:
 fica aberto apenas no processo pai */
 close(sock);
 if ((n=read(msgsock, buf, BUFSIZE)) < 0)
 perror("receiving stream data");
 else if (n==0)
 fprintf(stderr, "EOF\n");
 else {
 buf[n]= '\0';
 printf("-->%s\n", buf);
 }
 printf("Son %d exiting\n", pid);
 exit(0);
 }
 printf("Father started child %d\n", pid);
 /* Pai: fecha o socket utilizado pelo filho */
 close(msgsock);
 }
 }
}

Teste o Premium para desbloquear

Aproveite todos os benefícios por 3 dias sem pagar! 😉
Já tem cadastro?

Outros materiais