Практическая организация 3

advertisement
Механизм сокетов
Средства межпроцессного взаимодействия ОС Unix,
представленные в системе IPС, решают проблему
взаимодействия процессов, выполняющихся в рамках одной
операционной системы.
Но необходимы:
• Унифицированный механизм, позволяющий использовать
одни и те же подходы для локального и нелокального
взаимодействия.
• Общий интерфейс, позволяющий пользоваться услугами
различных протоколов по выбору пользователя.
Эти проблемы решает механизм сокетов (sockets)
Сокеты
• Сокеты представляют собой в определенном смысле
обобщение механизма каналов, но с учетом возможных
особенностей, возникающих при работе в сети
• Сокеты предоставляют по сравнению с каналами больше
возможностей по передаче сообщений
• Общая схема работы с сокетами:
Каждый из взаимодействующих процессов должен на
своей стороне создать и отконфигурировать сокет
Процессы должны осуществить соединение с
использованием этой пары сокетов
По окончании взаимодействия сокеты уничтожаются
Средства взаимодействия
процессов в сети
Типы сокетов.
• Соединение с использованием виртуального канала
Последовательный поток байтов, гарантирующий надежную
доставку сообщений с сохранением порядка их следования.
Данные начинают передаваться только после того, как
виртуальный канал установлен, и канал не разрывается, пока
все данные не будут переданы.
• Датаграммное соединение
Типы сокетов.
• Соединение с использованием виртуального канала
• Датаграммное соединение
Используется для передачи отдельных
содержащих порции данных – датаграмм.
пакетов,
Надежность соединения в этом случае ниже, чем при
установлении виртуального канала (для датаграмм не
гарантируется доставка в том же порядке, в каком они
были посланы, и доставка вообще)
Скорость выше, чем у соединений с использованием
виртуального канала.
Коммуникационный домен.
Поскольку сокеты используются как для локального, так и для
удаленного взаимодействия, встает вопрос о пространстве
адресов сокетов.
Коммуникационный домен сокета определяет форматы
адресов и правила их интерпретации.
Мы будем рассматривать:
- домен AF_UNIX (для локального взаимодействия)
- домен AF_INET (взаимодействия в рамках сети)
Современные системы поддерживают и другие домены,
например:
- домен AF_NS (использует протоколы удаленного
взаимодействия Xerox NS).
Создание Сокета
#include <sys/types.h>
#include <sys/socket.h>
int socket (int domain, int type, int protocol);
domain – коммуникационный домен, к которому должен
принадлежать создаваемый сокет
AF_UNIX
AF_INET
type – тип соединения, которым будет пользоваться сокет
(тип сокета)
SOCK_STREAM виртуальный канал
SOCK_DGRAM датаграммы
Создание Сокета
#include <sys/types.h>
#include <sys/socket.h>
int socket (int domain, int type, int protocol);
protocol –протокол, который будет использоваться в рамках
данного коммуникационного домена для создания соединения.
Если установить значение данного аргумента в 0, система
автоматически выберет подходящий протокол. Константы для
протокола AF_INET:
IPPROTO_TCP – обозначает протокол TCP
(корректно при создании сокета типа SOCK_STREAM)
IPPROTO_UDP – обозначает протокол UDP
(корректно при создании сокета типа SOCK_DGRAM)
Создание Сокета
#include <sys/types.h>
#include <sys/socket.h>
int socket (int domain, int type, int protocol);
В случае успеха функция возвращает положительное целое
число – дескриптор сокета (аналог файлового дескриптора).
В случае неудачи (например, при некорректном сочетании
коммуникационного домена, типа сокета и протокола),
функция возвращает –1.
#include <sys/types.h>
Связывание
#include <sys/socket.h>
int bind (int sockfd, struct sockaddr *myaddr, int addrlen);
sockfd – дескриптор сокета
myaddr – указатель на структуру, содержащую адрес сокета
#include <sys/un.h>
Для домена
AF_UNIX
struct sockaddr_un {
short sun_family; /* == AF_UNIX */
char sun_path[108];
};
#include <sys/types.h>
Связывание
#include <sys/socket.h>
int bind (int sockfd, struct sockaddr *myaddr, int addrlen);
sockfd – дескриптор сокета
myaddr – указатель на структуру, содержащую адрес сокета
#include <netinet/in.h>
struct sockaddr_in {
Для домена
AF_INET
short sin_family;
/* == AF_INET */
u_short sin_port;
/* port number */
struct in_addr sin_addr; /* host IP address */
char sin_zero[8];
};
/* not used */
#include <sys/types.h>
Связывание
#include <sys/socket.h>
int bind (int sockfd, struct sockaddr *myaddr, int addrlen);
addrlen – последний аргумент функции задает реальный
размер структуры, на которую указывает myaddr.
В случае успешного связывания bind возвращает 0, в
случае ошибки – -1.
Сокеты с предварительным
установлением соединения. Запрос на
соединение
#include <sys/types.h>
Запрос на
#include <sys/socket.h>
int connect (int sockfd,
struct sockaddr *serv_addr,
int addrlen);
соединение
sockfd – дескриптор сокета
serv_addr – указатель на структуру, содержащую адрес сокета,
с которым производится соединение, в формате, который мы
обсуждали выше
addrlen – реальная длина структуры
В случае успешного связывания функция возвращает 0,
в случае ошибки – -1. Код ошибки заносится в errno.
Прослушивание
#include <sys/types.h>
#include <sys/socket.h>
сокета
int listen (int sockfd, int backlog);
sockfd – дескриптор сокета
backlog – максимальный размер очереди запросов на
соединение.
В большинстве современных систем равен 5
В случае успешного связывания функция возвращает 0,
в случае ошибки – -1. Код ошибки заносится в errno.
Подтверждение
#include <sys/types.h>
#include <sys/socket.h>
соединения
int accept (int sockfd,
struct sockaddr *addr,
int *addrlen);
sockfd – дескриптор сокета
addr – указатель на структуру, в которой возвращается адрес
клиентского сокета, с которым установлено соединение (если
адрес клиента не интересует, передается NULL).
addrlen – возвращается реальная длина этой структуры.
максимальный размер очереди запросов на соединение.
Возвращает дескриптор нового сокета, соединенного с
сокетом клиентского процесса.
#include <sys/types.h>
#include <sys/socket.h>
int send(int sockfd,
const void *msg,
int len,
unsigned int flags);
Прием и передача
данных
int recv(int sockfd,
void *buf,
int len,
unsigned int flags);
sockfd - дескриптор сокета, через который передаются данные
msg – сообщение
buf – указатель на буфер для
приема данных
len – длина сообщения
len – первоначальная длина
буфера.
#include <sys/types.h>
#include <sys/socket.h>
int send(int sockfd,
const void *msg,
int len,
unsigned int flags);
Прием и передача
данных
int recv(int sockfd,
void *buf,
int len,
unsigned int flags);
flags - может содержать комбинацию специальных опций.
MSG_OOB - флаг сообщает ОС, что процесс хочет
осуществить прием/передачу экстренных сообщений
MSG_PEEK – При вызове recv( ) процесс может прочитать
порцию данных, не удаляя ее из сокета. Последующий вызов
recv вновь вернет те же самые данные.
#include <sys/types.h>
#include <sys/socket.h>
Прием и передача
данных
int send(int sockfd,
const void *msg,
int len,
unsigned int flags);
int recv(int sockfd,
void *buf,
int len,
unsigned int flags);
Функция возвращает
количество переданных
байт в случае успеха и -1 в
случае неудачи. Код
ошибки при этом
устанавливается в errno.
В случае успеха функция
возвращает количество
считанных байт, в случае
неудачи -1
Прием и передача данных
• Read()
• Write()
В качестве параметра этим функциям передается
дескриптор сокета
#include <sys/types.h>
#include <sys/socket.h>
Прием и передача данных
int sendto(int sockfd,
const void *msg,
int len,
unsigned int flags,
const struct sockaddr *to,
int tolen);
int recvfrom(int sockfd,
void *buf,
int len,
unsigned int flags,
struct sockaddr *from,
int *fromlen);
Такие же, как и у
рассмотренных раньше
указатель на структуру,
содержащую адрес
получателя
размер структуры to
Такие же, как и у
рассмотренных раньше
указатель на структуру
с адресом отправителя
размер структуры from
Закрытие
#include <sys/types.h>
#include <sys/socket.h>
int shutdown (int sockfd, int mode);
сокета
sockfd – дескриптор сокета
mode – режим закрытия соединения
= 0, сокет закрывается для чтения
= 1, сокет закрывается для записи
= 2, сокет закрывается и для чтения, и для записи
В случае успеха функция возвращает 0, в случае неудачи -1
Закрытие сокета
Аналогично файловому дескриптору, дескриптор сокета
освобождается системным вызовом close( ).
Можно не вызывать shutdown( ), соединение будет прервано.
Но если используемый для соединения протокол гарантирует
доставку данных (тип сокета – виртуальный канал), то вызов
close() будет блокирован до тех пор, пока система будет
пытаться доставить все данные, находящиеся «в пути» (если
таковые имеются). Shutdown( ) извещает систему о том, что
данные не нужны.
Схема работы с сокетами с
установлением соединения
Серверный сокет
Клиентский сокет
socket
socket
bind
bind
listen
connect
accept
recv
send
recv
send
shutdown
shutdown
close
close
Схема работы с сокетами без
установления соединения
socket
bind
recvfrom
sendto
close
Пример. Работа с локальными
сокетами
AF_UNIX
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <stdio.h>
#include <string.h>
#define SADDRESS "mysocket"
#define CADDRESS "clientsocket"
#define BUFLEN 40
int main(int argc, char **argv)
{
struct sockaddr_un party_addr,
own_addr;
int sockfd;
int is_server;
char buf[BUFLEN];
int party_len;
int quitting;
if (argc != 2) {
printf("Usage: %s
client|server.\n", argv[0]);
return 0;
}
…
…
quitting = 1;
is_server = !strcmp(argv[1],"server");
memset(&own_addr, 0,
sizeof(own_addr));
own_addr.sun_family = AF_UNIX;
strcpy(own_addr.sun_path,
is_server?SADDRESS:CADDRESS);
if ((sockfd = socket(AF_UNIX,
SOCK_DGRAM, 0))<0){
printf("can't create socket\n");
return 0;
}
…
…
unlink(own_addr.sun_path); /* связываем сокет */
if (bind(sockfd, (struct sockaddr *) &own_addr,
sizeof(own_addr.sun_family)+
strlen(own_addr.sun_path)) < 0)
{
printf("can't bind socket!");
return 0;
}
if (!is_server) { /* это – клиент */
memset(&party_addr, 0, sizeof(party_addr));
party_addr.sun_family = AF_UNIX;
strcpy(party_addr.sun_path, SADDRESS);
printf("type the string: ");
…
...
while (gets(buf)) {/* не пора ли выходить? */
quitting = (!strcmp(buf, "quit"));
/* считали строку и передаем ее серверу */
if (sendto(sockfd, buf, strlen(buf) + 1, 0,
(struct sockaddr *) &party_addr,
sizeof(party_addr.sun_family) +
strlen(SADDRESS)) != strlen(buf) + 1)
{
printf("client: error writing socket!\n");
return 0;
}
if (recvfrom(sockfd, buf, BUFLEN, 0, NULL, 0)<0)
{
printf("client: error reading socket!\n");
return 0;
} ...
...
printf("client: server answered: %s\n", buf);
if (quitting) break;
printf("type the string: ");
}
// while
close(sockfd);
return 0;
}
// if (!is_server), клиент
…
...
while (1) { /* получаем строку от клиента и выводим на печать */
party_len = sizeof(party_addr);
if (recvfrom(sockfd, buf, BUFLEN, 0,
(struct sockaddr *) &party_addr, &party_len) < 0)
{
printf("server: error reading socket!");
return 0;
}
printf("server: received from client: %s \n", buf);
/* не пора ли выходить? */
quitting = (!strcmp(buf, "quit"));
if (quitting) strcpy(buf, "quitting now!");
else
if (!strcmp(buf, "ping!")) strcpy(buf, "pong!");
else strcpy(buf, "wrong string!");
…
...
/* посылаем ответ */
if (sendto(sockfd, buf, strlen(buf) + 1, 0,
(struct sockaddr *) & party_addr,
party_len) != strlen(buf)+1)
{
printf("server: error writing socket!\n");
return 0;
}
if (quitting) break;
} // while (1)
close(sockfd);
return 0;
}
Пример. Работа с локальными
сокетами
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <netinet/in.h>
AF_INET
GET /<имя файла>
#include <stdio.h>
#include <string.h>
#include <fcntl.h>
#define PORTNUM 8080
#include <unistd.h>
#define BACKLOG 5
#define BUFLEN 80
#define FNFSTR "404 Error File Not Found "
#define BRSTR "Bad Request "
int main(int argc, char **argv)
{
struct sockaddr_in own_addr, party_addr;
int sockfd, newsockfd, filefd;
int party_len;
char buf[BUFLEN];
int len;
int i;
/* создаем сокет */
if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
printf("can't create socket\n");
return 0;
}
…
…
/* связываем сокет */
memset(&own_addr, 0, sizeof(own_addr));
own_addr.sin_family = AF_INET;
own_addr.sin_addr.s_addr = INADDR_ANY;
own_addr.sin_port = htons(PORTNUM);
if (bind(sockfd, (struct sockaddr *) &own_addr,
sizeof(own_addr)) < 0)
{
printf("can't bind socket!");
return 0;
}
/* начинаем обработку запросов на соединение */
if (listen(sockfd, BACKLOG) < 0) {
printf("can't listen socket!");
return 0;
}
…
…
while (1) {
memset(&party_addr, 0, sizeof(party_addr));
party_len = sizeof(party_addr);
/* создаем соединение */
if ((newsockfd = accept(sockfd, (struct sockaddr *)&party_addr,
&party_len)) < 0) {
printf("error accepting connection!");
return 0;
}
if (!fork()) {/*это – сын, он обрабатывает запрос и посылает ответ*/
close(sockfd); /* этот сокет сыну не нужен */
if ((len = recv(newsockfd,&buf,BUFLEN, 0)) < 0) {
printf("error reading socket!");
return 0;
}
…
…
/* разбираем текст запроса */
printf("received: %s \n", buf);
if (strncmp(buf, "GET /", 5)) {/*плохой запрос!*/
if (send(newsockfd, BRSTR,
strlen(BRSTR)+ 1, 0) != strlen(BRSTR) + 1)
{
printf("error writing socket!");
return 0;
}
shutdown(newsockfd, 1);
close(newsockfd);
return 0;
}
…
…
for (i=5; buf[i] && (buf[i] > ' '); i++);
buf[i] = 0;
/* открываем файл */
if ((filefd = open(buf+5, O_RDONLY)) < 0) {/* нет файла! */
if (send(newsockfd, FNFSTR,
strlen(FNFSTR) + 1, 0) != strlen(FNFSTR) + 1) {
printf("error writing socket!");
return 0;
}
shutdown(newsockfd, 1);
close(newsockfd);
return 0;
}
…
…
/* читаем из файла порции данных и посылаем их клиенту */
while (len = read(filefd, &buf, BUFLEN))
if (send(newsockfd, buf, len, 0) < 0) {
printf("error writing socket!");
return 0;
}
close(filefd);
shutdown(newsockfd, 1);
close(newsockfd);
return 0;
} /* процесс – отец. Он закрывает новый сокет и продолжает
прослушивать старый */
close(newsockfd);
} // while (1)
}
Download