intro
Я не писатель а кодер. так что я в принципе знаю что с точки зрения
творчества моя статья не шедевр. там могут быть разные неточности,
ошибки итд итп. ну и поскольку я не русски то на русском я не умею
очень четко излагать свои мысли.
если будут хоть какие то предложения по поводу улучшения статьи я с радостью их приму и постараюсь сделать статью читабельной.
сейчас я напишу про сервер. про клиент напишу несколько позднее (скорее всего через 1-2 дня).
в разделе клиента напишу не только как создать простой консольный
клиент, но и познакомлю читателя с библиотекой gtk+. потом мы напишем
графический клиент к нашему серверу и все будет хорошо ((*
<--------------------cut here-------------------->
сервер
желательно иметь некоторые знания в области программирования под си ибо
самые самые основы (дескриптор файла, функции ввода вывода итп) я тут
описивать не буду.
ну из названия статьи само собой понятно что нужно иметь опыт общение с ОС UNIX
для начала давайте решим что будет делать наш сервер. пусть когда к
нему подключаются он выводит какой нить текст. "My daemonic greetings"
например.
ну и наш сервер не был бы настоящим если бы не был демоном.
ну отсюда приблизительно понятно алгоритм действий нашего сервера:
1. становится демоном
2. открывает порт
3. слушает порт
4. принимает соединение
5. обрабатываем соединение
6. закрывает соединение с клиентом
7. возвращяется к пункту 4
начнем с пункта 1.
как стать демоном?
очень просто. при помощи системного вызова fork(2).
вызов fork(2) создает дочерний и возвращяет идентификатор дочернего
процесса. дочерний процесс полностью идентичный родительскому с
несколькимим различиями :
дочерний процесс имеет свой pid.
дочернему процессу передаются все файловые дескрипторы родительского процесса.
подробнее читать на man страницах.
как открыть порт?
вот тут уже не все так просто.
для начала нужно создать сокет.
сокет это специальный тип файла в который записивают свои сообщения
клиент и сервер. тоесть когда наш сервер напишет "My daemonic
greetings" то он напишет это не на компютер клиента а в сокет, потом
клиент прочитает наше сообщение из сокета и выведет его на свой
стандтартный вывод.
сокет создается при помощи системного вызова socket:
Код:
int socket(int domain, int type, int protocol)
domain - указывает на семейство протоколов с которыми мы будем иметь дело. для протоколов семейства tcp/ip это AF_INET.
type - тип сокета. на даный момент поддерживаются SOCK_STREAM,
SOCK_DGRAM, SOCK_RAW, SOCK_RDM, SOCK_SEQPACKET. SOCK_STREAM
используется для протокола tcp, SOCK_DGRAM для протокола udp. с другими
сокетами честно говоря не общялся и не знаю для чего они. кому
интересно может погуглить.
подробнее о типе сокета SOCK_STREAM. этот тип обеспечивает
последовательный, надежный, ориентированный на установление
двусторонней связи поток байтов.
функция возвращяет дескриптор сокета.
мы создали сокет. но он не привязан ни к ip адресу компютера ни к
какому либо порту. для того чтобы привязать сокет мы воспользуемся
системным вызовом bind
Код:
int bind(int s, const struct sockaddr *addr, socklen_t addrlen)
где
s - это дескриптор сокета который был создан вызовом socket
addr - структура типа sockaddr (об этом ниже)
addrlen - размер структуры
ну с дескриптором сокета все понятно.
а вот структура...
различные функции для работы с сокетами предполагают использование
адреса (указатель, в терминологии языка С) маленького участка памяти.
различные объявления из файла sys/socket.h в качестве этого указателя
используют адрес struct sockaddr.
когда адресным семейством является AF_INET мы можем использовать struct
sockaddr_in, определённую в netinet/in.h вместо sockaddr.
структура sockaddr_in имеет поля:
Код:
struct sockaddr_in {
u_char sin_len;
u_char sin_family;
u_short sin_port;
struct in_addr sin_addr;
char sin_zero[8];
};
sin_family - семейство протоколов (в нашем случае AF_INET)
sin_port - порт который будет прослушиваться
sin_addr - наш ip адрес
чтобы привязать сокет к порту и адресу мы сначала должны заполнить структуру а затем дать ее вызову bind.
как прослушивать порт?
при помощи функции listen
Код:
int listen(int s, int backlog)
s - дескриптор сокета
backlog - максимальное количество запросов на установление связи, которые могут стоять в очереди, ожидая обработки сервером.
как принимать соединение?
при помощи функции accept
Код:
int accept(int s, struct sockaddr * restrict addr, socklen_t * restrict addrlen)
s - дескриптор сокета
addr - структура которая будет заполнена данными клиента (ip адрес клиента, порт с которого установлен соединение итп)
addrlen - размер addr
как обрабатывать соединение?
при помощи функций read и write (можно также и recv и send но я этим не
пользуюсь считая их ересью, я предпочитаю пользоватся функциями ((*)
Код:
ssize_t read(int d, void *buf, size_t nbytes)
ssize_t write(int d, const void *buf, size_t nbytes)
d - дескриптор файла в который будем писать (в нашем случае дескриптор сокета)
buf - то что мы будем читать/записивать из/в файл (сокет)
nbytes - сколько будем читать/записивать из/в файл(сокет)
они (функции) возвращяют количество прочитаных/записаных байтов
как закрывать соединение?
функция close
d - дескриптор файла (в нашем случае сокета) который мы хотим закрыть.
сейчас я приведу листинг сервера с коментариями. если будет что то не ясное спрашиваете я объясню.
листинг сервера
Код:
#include //printf()
#include //read(); write(); close()
#include //хз просто нужно
#include //socket() и другое
#include //sockaddr_in
int main(int argc, char *argv[]) {
int sock,cl,sz; //дескрипторы сокета сервера (sock) и клиента (cl), размеры (sz)
struct sockaddr_in sa,ca; //структуры сервера (sa) и клиента (ca)
char buffer[32]; // буффер
sock = socket(PF_INET, SOCK_STREAM, 0);
sa.sin_family = AF_INET;
sa.sin_port = htons(666);//если просто присвоим порту значение 666 то будет любой порт кроме того который нам нужен (с) Журнал ][aKeR
sa.sin_addr.s_addr = htonl(INADDR_ANY);//см выше
bind(sock, (struct sockaddr *)&sa, sizeof(sa));
switch (fork()) { //проццес становится демоном
case -1:
printf("fork");
return 3;
break;
default:
close(sock);
return 0;
break;
case 0:
break;
}
listen(sock, 5);
for (;;) {
sz=sizeof(ca);
cl = accept(sock, (struct sockaddr *)&ca, &sz);
switch (fork()) { //тут тоже. опять демонами становимся по той причине ибо хотим одновременно обслуживать несколько клиентов. теперь для каждого клиента будет создан процесс
case -1:
printf("fork");
return 3;
break;
default:
close(cl);
return 0;
break;
case 0:
break;
}
write(cl, "My daemonic greetings", 21);
close(cl);
}
}