Сокет - коммуникационный канал между процессами .
При создании сокета нужно определить 2 вещи :
1. communication style
2. тип протокола
communication style определяет структуру пакетов , возможность потери данных ,
число соединений , namespace . Например , TCP-protocol имеет byte-тип коммуникации
и Internet namespace .
Communication Styles бывают следующих типов :
1. SOCK_STREAM
2. SOCK_DGRAM
3. SOCK_RAW
Сокет создается с помощью функции socket и при этом не имеет адреса , который позже
можно присвоить с помощью функции bind . Для назначения адреса используется структура struct sockaddr * .
В ней есть следующие члены :
short int sa_family
char sa_data[14]
Формат адреса может быть следующих типов :
AF_LOCAL
AF_UNIX
AF_FILE
AF_INET
AF_INET6
AF_UNSPEC
Прочитать арес из этой структуры можно с помощью функции
int getsockname (int socket, struct sockaddr *addr, socklen_t *length-ptr)
Для local namespace адресом сокета служит имя файла . К такому сокету нельзя законнектиться
с другой машины .
Internet Namespace имеет имя PF_INET . При этом сокет включает в себя адрес машины коннекта
и порт машины коннекта .
Базовая структура - struct sockaddr_in
Она включает члены :
sa_family_t sin_family
struct in_addr sin_addr
unsigned short int sin_port
Каждый компьютер имееи host address вида `128.52.46.32' и
host name вида `mescaline.gnu.org'
База данных сервисов хранится обычно в файле /etc/services и имеет вид :
systat 11/udp users
daytime 13/tcp
qotd 17/tcp quote
Для получения информации можно использовать getservbyname или getservbypor .
Для потоковых сокетов протоколом по умолчанию является TCP .
Для датаграмм - UDP .
База данных протоколов обычно находится в /etc/protocols .
Ниже привелен пример создания tcp-протокола :
#include < stdio.h >
#include < stdlib.h >
#include < sys/socket.h >
#include < netinet/in.h >
int make_socket (uint16_t port)
{
int sock;
struct sockaddr_in name;
/* Create the socket. */
sock = socket (PF_INET, SOCK_STREAM, 0);
if (sock < 0)
{
perror ("socket");
exit (EXIT_FAILURE);
}
/* Give the socket a name. */
name.sin_family = AF_INET;
name.sin_port = htons (port);
name.sin_addr.s_addr = htonl (INADDR_ANY);
if (bind (sock, (struct sockaddr *) &name, sizeof (name)) < 0)
{
perror ("bind");
exit (EXIT_FAILURE);
}
return sock;
}
В следующем примере показано , как заполнить структуру sockaddr_in
#include < stdio.h >
#include < stdlib.h >
#include < sys/socket.h >
#include < netinet/in.h >
#include < netdb.h >
void init_sockaddr (struct sockaddr_in *name,const char *hostname,uint16_t port)
{
struct hostent *hostinfo;
name->sin_family = AF_INET;
name->sin_port = htons (port);
hostinfo = gethostbyname (hostname);
if (hostinfo == NULL)
{
fprintf (stderr, "Unknown host %s.\n", hostname);
exit (EXIT_FAILURE);
}
name->sin_addr = *(struct in_addr *) hostinfo->h_addr;
}
Следующий пример создает клиентский сокет и посылает серверу строку данных :
#include < stdio.h >
#include < errno.h >
#include < stdlib.h >
#include < unistd.h >
#include < sys/types.h >
#include < sys/socket.h >
#include < netinet/in.h >
#include < netdb.h >
#define PORT 5555
#define MESSAGE "Yow!!! Are we having fun yet?!?"
#define SERVERHOST "mescaline.gnu.org"
void write_to_server (int filedes)
{
int nbytes;
nbytes = write (filedes, MESSAGE, strlen (MESSAGE) + 1);
if (nbytes < 0)
{
perror ("write");
exit (EXIT_FAILURE);
}
}
int main (void)
{
extern void init_sockaddr (struct sockaddr_in *name,
const char *hostname, uint16_t port);
int sock;
struct sockaddr_in servername;
/* Create the socket. */
sock = socket (PF_INET, SOCK_STREAM, 0);
if (sock < 0)
{
perror ("socket (client)");
exit (EXIT_FAILURE);
}
/* Connect to the server. */
init_sockaddr (&servername, SERVERHOST, PORT);
if (0 > connect (sock,(struct sockaddr *) &servername,sizeof (servername)))
{
perror ("connect (client)");
exit (EXIT_FAILURE);
}
/* Send data to the server. */
write_to_server (sock);
close (sock);
exit (EXIT_SUCCESS);
}
Следующий пример создает сервер для предыдущего клиента . В отличие от клтента ,
он рассчитан для работы на множество потоков , для чего используется функция select .
#include < stdio.h >
#include < errno.h >
#include < stdlib.h >
#include < unistd.h >
#include < sys/types.h >
#include < sys/socket.h >
#include < netinet/in.h >
#include < netdb.h >
#define PORT 5555
#define MAXMSG 512
int read_from_client (int filedes)
{
char buffer[MAXMSG];
int nbytes;
nbytes = read (filedes, buffer, MAXMSG);
if (nbytes < 0)
{
/* Read error. */
perror ("read");
exit (EXIT_FAILURE);
}
else if (nbytes == 0)
/* End-of-file. */
return -1;
else
{
/* Data read. */
fprintf (stderr, "Server: got message: `%s'\n", buffer);
return 0;
}
}
int main (void)
{
extern int make_socket (uint16_t port);
int sock;
fd_set active_fd_set, read_fd_set;
int i;
struct sockaddr_in clientname;
size_t size;
/* Create the socket and set it up to accept connections. */
sock = make_socket (PORT);
if (listen (sock, 1) < 0)
{
perror ("listen");
exit (EXIT_FAILURE);
}
/* Initialize the set of active sockets. */
FD_ZERO (&active_fd_set);
FD_SET (sock, &active_fd_set);
while (1)
{
/* Block until input arrives on one or more active sockets. */
read_fd_set = active_fd_set;
if (select (FD_SETSIZE, &read_fd_set, NULL, NULL, NULL) < 0)
{
perror ("select");
exit (EXIT_FAILURE);
}
/* Service all the sockets with input pending. */
for (i = 0; i < FD_SETSIZE; ++i)
if (FD_ISSET (i, &read_fd_set))
{
if (i == sock)
{
/* Connection request on original socket. */
int new;
size = sizeof (clientname);
new = accept (sock,
(struct sockaddr *) &clientname, &size);
if (new < 0)
{
perror ("accept");
exit (EXIT_FAILURE);
}
fprintf (stderr,"Server: connect from host %s, port %hd.\n",
inet_ntoa (clientname.sin_addr), ntohs (clientname.sin_port));
FD_SET (new, &active_fd_set);
}
else
{
/* Data arriving on an already-connected socket. */
if (read_from_client (i) < 0)
{
close (i);
FD_CLR (i, &active_fd_set);
}
}
}
}
}
Для того , чтобы в очередь потоков вставить поток с более высоким приоритетом ,
ему присваивают статус out-of-band . Функция для подобной маркировки :
intdiscard_until_mark (int socket)
{
while (1)
{
/* This is not an arbitrary limit; any size will do. */
char buffer[1024];
int atmark, success;
/* If we have reached the mark, return. */
success = ioctl (socket, SIOCATMARK, &atmark);
if (success < 0)
perror ("ioctl");
if (result)
return;
/* Otherwise, read a bunch of ordinary data and discard it.
This is guaranteed not to read past the mark
if it starts before the mark. */
success = read (socket, buffer, sizeof buffer);
if (success < 0)
perror ("read");
}
}
Чтение out-of-band :
struct buffer
{
char *buf;
int size;
struct buffer *next;
};
/* Read the out-of-band data from SOCKET and return it
as a `struct buffer', which records the address of the data
and its size.
It may be necessary to read some ordinary data
in order to make room for the out-of-band data.
If so, the ordinary data are saved as a chain of buffers
found in the `next' field of the value. */
struct buffer *
read_oob (int socket)
{
struct buffer *tail = 0;
struct buffer *list = 0;
while (1)
{
/* This is an arbitrary limit.
Does anyone know how to do this without a limit? */
#define BUF_SZ 1024
char *buf = (char *) xmalloc (BUF_SZ);
int success;
int atmark;
/* Try again to read the out-of-band data. */
success = recv (socket, buf, BUF_SZ, MSG_OOB);
if (success >= 0)
{
/* We got it, so return it. */
struct buffer *link = (struct buffer *) xmalloc (sizeof (struct buffer));
link->buf = buf;
link->size = success;
link->next = list;
return link;
}
/* If we fail, see if we are at the mark. */
success = ioctl (socket, SIOCATMARK, &atmark);
if (success < 0)
perror ("ioctl");
if (atmark)
{
/* At the mark; skipping past more ordinary data cannot help.
So just wait a while. */
sleep (1);
continue;
}
/* Otherwise, read a bunch of ordinary data and save it.
This is guaranteed not to read past the mark
if it starts before the mark. */
success = read (socket, buf, BUF_SZ);
if (success < 0)
perror ("read");
/* Save this data in the buffer list. */
{
struct buffer *link = (struct buffer *) xmalloc (sizeof (struct buffer));
link->buf = buf;
link->size = success;
/* Add the new link to the end of the list. */
if (tail)
tail->next = link;
else
list = link;
tail = link;
}
}
}
Пример серверной UDP-датаграммы :
#include < stdio.h >
#include < errno.h >
#include < stdlib.h >
#include < sys/socket.h >
#include < sys/un.h >
#define SERVER "/tmp/serversocket"
#define MAXMSG 512
int main (void)
{
int sock;
char message[MAXMSG];
struct sockaddr_un name;
size_t size;
int nbytes;
/* Remove the filename first, it's ok if the call fails */
unlink (SERVER);
/* Make the socket, then loop endlessly. */
sock = make_named_socket (SERVER);
while (1)
{
/* Wait for a datagram. */
size = sizeof (name);
nbytes = recvfrom (sock, message, MAXMSG, 0,(struct sockaddr *) & name, &size);
if (nbytes < 0)
{
perror ("recfrom (server)");
exit (EXIT_FAILURE);
}
/* Give a diagnostic message. */
fprintf (stderr, "Server: got message: %s\n", message);
/* Bounce the message back to the sender. */
nbytes = sendto (sock, message, nbytes, 0,(struct sockaddr *) & name, size);
if (nbytes < 0)
{
perror ("sendto (server)");
exit (EXIT_FAILURE);
}
}
}
Вместо написания серверной части можно использовать для прослушивания демона inetd .
Для ее настройки существует конфиг-файл /etc/inetd.conf .
|