intro Я не писатель а кодер. так что я в принципе знаю что с точки зрения творчества моя статья не шедевр. там могут быть разные неточности, ошибки итд итп. ну и поскольку я не русски то на русском я не умею очень четко излагать свои мысли. если будут хоть какие то предложения по поводу улучшения статьи я с радостью их приму и постараюсь сделать статью читабельной. сейчас я напишу про сервер. про клиент напишу несколько позднее (скорее всего через 1-2 дня). в разделе клиента напишу не только как создать простой консольный клиент, но и познакомлю читателя с библиотекой gtk+. потом мы напишем графический клиент к нашему серверу и все будет хорошо ((* <--------------------cut here--------------------> сервер желательно иметь некоторые знания в области программирования под си ибо самые самые основы (дескриптор файла, функции ввода вывода итп) я тут описивать не буду. ну из названия статьи само собой понятно что нужно иметь опыт общение с ОС UNIX для начала давайте решим что будет делать наш сервер. пусть когда к нему подключаются он выводит какой нить текст. "My daemonic greetings" например. ну и наш сервер не был бы настоящим если бы не был демоном. ну отсюда приблизительно понятно алгоритм действий нашего сервера: 1. становится демоном 2. открывает порт 3. слушает порт 4. принимает соединение 5. обрабатываем соединение 6. закрывает соединение с клиентом 7. возвращяется к пункту 4 начнем с пункта 1. как стать демоном? очень просто. при помощи системного вызова fork(2). Code: pid_t fork(void); вызов fork(2) создает дочерний и возвращяет идентификатор дочернего процесса. дочерний процесс полностью идентичный родительскому с несколькимим различиями : дочерний процесс имеет свой pid. дочернему процессу передаются все файловые дескрипторы родительского процесса. подробнее читать на man страницах. как открыть порт? вот тут уже не все так просто. для начала нужно создать сокет. сокет это специальный тип файла в который записивают свои сообщения клиент и сервер. тоесть когда наш сервер напишет "My daemonic greetings" то он напишет это не на компютер клиента а в сокет, потом клиент прочитает наше сообщение из сокета и выведет его на свой стандтартный вывод. сокет создается при помощи системного вызова socket: Code: 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 Code: 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 имеет поля: Code: 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 Code: int listen(int s, int backlog) s - дескриптор сокета backlog - максимальное количество запросов на установление связи, которые могут стоять в очереди, ожидая обработки сервером. как принимать соединение? при помощи функции accept Code: int accept(int s, struct sockaddr * restrict addr, socklen_t * restrict addrlen) s - дескриптор сокета addr - структура которая будет заполнена данными клиента (ip адрес клиента, порт с которого установлен соединение итп) addrlen - размер addr как обрабатывать соединение? при помощи функций read и write (можно также и recv и send но я этим не пользуюсь считая их ересью, я предпочитаю пользоватся функциями ((*) Code: 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 Code: int close(int d) d - дескриптор файла (в нашем случае сокета) который мы хотим закрыть. сейчас я приведу листинг сервера с коментариями. если будет что то не ясное спрашиваете я объясню. листинг сервера Code: #include <stdio.h> //printf() #include <unistd.h> //read(); write(); close() #include <sys/types.h> //хз просто нужно #include <sys/socket.h> //socket() и другое #include <netinet/in.h> //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); } }
клиент клиент теперь напишем консольную версию нашего клиента к нашему серваку. итак. определим что будет делать наш клиент. на стороне клиента все организовать очень очень легко. нужно сделать вот что. 1. задать параметры подключения 2. подключится к серверу 3. получить сообщение 4. закрыть соединение с сервером как задать параметры подключения? помните когда мы объявляли структуру типа sockaddr_in для сервера? там мы задавали параметры сервера. тобишь на каком айпишнике он будет, на каком порте будет работать... для клиента мы тоже должны объявить структуру. и там будут параметры подключения. на какой порт подключаться (поле sin_port), ip адрес сервера (поле sin_addr.s_addr), а поле sin_family остается тоже (AF_INET). думаю нет надобности объяснять почему. как подключится к серверу? функция connect(2). Code: int connect(int s, const struct sockaddr *name, socklen_t namelen); что за аргументы функции объяснять не буду ибо объяснил в статье посвященной серверу. как получить сообщения? функции read(2) и write(2). их я также описал в предыдущей статье. как закрыть соединение с сервером? a функция close(2). см. статью "сервер" <---------------------cut here------------------------> ну вот собственно и все. клиента мы описали. ниже приведу листинг клиента. Code: #include <stdio.h> //printf() #include <unistd.h> //read(); write(); close() #include <sys/types.h> //хз просто нужно #include <sys/socket.h> //socket() и другое #include <netinet/in.h> //sockaddr_in #include <string.h> //strlen() int main(int argc, char *argv[]) { int s; int bytes; struct sockaddr_in ca; char buffer[32]; bzero(&buffer, 32); //обнуляем массив s=socket(AF_INET, SOCK_STREAM, 0); ca.sin_family=AF_INET; ca.sin_port=htons(666); //порт на котором сервак ca.sin_addr.s_addr=inet_addr("127.0.0.1"); //если дадим просто 127.0.0.1 то он не подключится к данному айпишнику. айпишник нуно переделать в сетевой формат connect(s, (struct sockaddr *)&ca, sizeof ca); bytes=read(s, buffer, 32); close(s); /*вывод можно организовать двумя способами: либо при помощи printf: printf("%s\n", buffer), либо функцией write: write(1, buffer, strlen(buffer)). я предпочитаю выводить строки write-ом*/ write(1, buffer, strlen(buffer)); return 0; } p.s. при подключении к серверу можно написать так чтобы он подключался к доменному имени. тобишь в поле sin_addr.s_addr написать не 194.67.57.26 а mail.ru к примеру. для этого нужно заранее объявить указатель на структуру типа struct hostent *he; потом юзать функцию gethostbyname(3). а потом скопировать содержимое поля h_addr_list[0] структуры he в поле sin_addr структуры типа sockaddr_in. вот листинг этого участка кода: Code: struct hostent *he; sa.sin_family=AF_INET; sa.sin_port=htons(666); he=gethostbyname("mail.ru"); bcopy(he->h_addr_list[0], &sa->sin_addr, he->h_length); пусть это будет вашим домашним заданием ((*
теперь немножко о копирах. статью писал сам. примеры привел те на которых сам учился. когда было совсем туго (не мог нормально сформулировать мыслю) использовал "FreeBSD Developers' Handbook". брал и нагло переводил ((* графический клиент напишем через 1-2 дня (тема на самом деле очень большая)
несколько раз пробовал написать статью. ничего не получалось... ну не могу я статьи писать ((* поэтому я решил делать так. я приведу листинг кода с подробными коментами. если че нибудь будет не понятно стучитесь в асю попробую объяснить. листинг: Code: #include <sys/socket.h> #include <sys/types.h> #include <netinet/in.h> #include <unistd.h> #include <string.h> #include <gtk/gtk.h> GtkWidget *text; int main(int argc, char *argv[]) { GtkWidget *window; //окно GtkWidget *button; //кнонпки GtkWidget *table; //таблица GtkWidget *scroll; // полоса прокрутки gtk_init (&argc, &argv); /*создаем окно*/ window = gtk_window_new (GTK_WINDOW_TOPLEVEL);//создаем окно gtk_widget_set_usize(window, 500, 600);//задаем высоту и ширину gtk_signal_connect(GTK_OBJECT(window), "delete_event", (GtkSignalFunc)destroy, NULL);//когда окно получит сигнал "закрыть" то выполнит функцию destroy gtk_container_set_border_width(GTK_CONTAINER(window), 10); /*создали*/ /*создаеи таблицу*/ table=gtk_table_new(100,100,TRUE);//количество столбцов, количсество колонок, все элементы таблицы равны друг другу /*таблица создана*/ text=gtk_text_new(NULL, NULL);//создаем поле текста gtk_table_attach_defaults(GTK_TABLE(table), text, 0,97,0,95);//прикрепляем поле текста к таблице gtk_widget_show(text);//показываем поле текста scroll=gtk_vscrollbar_new(GTK_TEXT (text)->vadj);//создаем полосу прокрутки и связываем ее с полем текста gtk_table_attach_defaults(GTK_TABLE(table), scroll, 97,100,0,95); gtk_widget_show(scroll); button=gtk_button_new_with_label("connect");//создаем кнопку gtk_signal_connect(GTK_OBJECT(button), "clicked", (GtkSignalFunc)client, NULL);//когда кнопка будет нажата вызовится функция client gtk_table_attach_defaults(GTK_TABLE(table), button, 0,50,95,100); gtk_widget_show(button); button=gtk_button_new_with_label("close"); gtk_signal_connect(GTK_OBJECT(button), "clicked", (GtkSignalFunc)destroy, NULL); gtk_table_attach_defaults(GTK_TABLE(table), button, 51,100,95,100); gtk_widget_show(button); gtk_container_add(GTK_CONTAINER(window), table);//добавляем таблицу в окно gtk_widget_show_all(window);//показываем все что есть в окне gtk_main ();//цикл return 0; } void destroy(GtkWidget *widget, gpointer data) { gtk_main_quit(); //выход из цикла } void client() { int s; int bytes=0; struct sockaddr_in sa; char buffer[32],res[32]; extern GtkWidget *text; //поле текста bzero(&res, 32); bzero(&buffer, 32); s=socket(PF_INET, SOCK_STREAM, 0); sa.sin_family = AF_INET; sa.sin_port = htons(666); sa.sin_addr.s_addr=inet_addr("127.0.0.1"); connect(s, (struct sockaddr *)&sa, sizeof sa); bytes=read(s, buffer, 32); close(s); gtk_text_insert(GTK_TEXT(text), NULL, NULL, NULL, buffer, strlen(buffer)); //вывводим все что получили их сокета в текстовой виджет } компилица все это дело так: gcc -o выходной_файл входной_файл `gtk-config --cflags --libs`
За старание + , но не ново - rsdn.ru/article/unix/sockets.xml А ещё лучше - forum.antichat.ru/thread59197.html - Сетевой боевой софт
не вижу никакого смысла в подобных статьях.. за знаниями такого рода лучше обращаться к основательно и толково написанным книгам..
Не вижу ничего плохого, что чел сел и разобрался с сетевым кодингом в никсах. Тока вот заметка по статейке - fork(2) не делает приложение демоном (как написано в каментах к коду), fork(2) порождает дочерний процесс