Авторские статьи [для новичков]сетевое программирование на си под unix

Discussion in 'Статьи' started by zythar, 21 Feb 2008.

  1. zythar

    zythar Elder - Старейшина

    Joined:
    16 Feb 2008
    Messages:
    517
    Likes Received:
    109
    Reputations:
    5
    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 zythar, 21 Feb 2008
    Last edited by a moderator: 24 Feb 2008
    17 people like this.
  2. zythar

    zythar Elder - Старейшина

    Joined:
    16 Feb 2008
    Messages:
    517
    Likes Received:
    109
    Reputations:
    5
    клиент

    клиент
    теперь напишем консольную версию нашего клиента к нашему серваку.
    итак. определим что будет делать наш клиент.
    на стороне клиента все организовать очень очень легко. нужно сделать вот что.
    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);
    
    пусть это будет вашим домашним заданием ((*
     
    #2 zythar, 22 Feb 2008
    Last edited: 24 Feb 2008
  3. zythar

    zythar Elder - Старейшина

    Joined:
    16 Feb 2008
    Messages:
    517
    Likes Received:
    109
    Reputations:
    5
    теперь немножко о копирах.
    статью писал сам. примеры привел те на которых сам учился.
    когда было совсем туго (не мог нормально сформулировать мыслю) использовал "FreeBSD Developers' Handbook". брал и нагло переводил ((*

    графический клиент напишем через 1-2 дня (тема на самом деле очень большая)
     
  4. zythar

    zythar Elder - Старейшина

    Joined:
    16 Feb 2008
    Messages:
    517
    Likes Received:
    109
    Reputations:
    5
    несколько раз пробовал написать статью. ничего не получалось... ну не могу я статьи писать ((*

    поэтому я решил делать так. я приведу листинг кода с подробными коментами. если че нибудь будет не понятно стучитесь в асю попробую объяснить.

    листинг:
    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`
     
    #4 zythar, 23 Feb 2008
    Last edited: 23 Feb 2008
    4 people like this.
  5. ReVOLVeR

    ReVOLVeR Banned

    Joined:
    2 Sep 2006
    Messages:
    170
    Likes Received:
    100
    Reputations:
    32
    неплохая статья , сделай ещё цикл видео по теме будет большим плюсом
     
  6. zythar

    zythar Elder - Старейшина

    Joined:
    16 Feb 2008
    Messages:
    517
    Likes Received:
    109
    Reputations:
    5
    а можно по подробнее? мне все равно делать нечего, чем нить хоть бы займусь ((*
     
  7. Ky3bMu4

    Ky3bMu4 Elder - Старейшина

    Joined:
    3 Feb 2007
    Messages:
    487
    Likes Received:
    284
    Reputations:
    42
    За старание + , но не ново - rsdn.ru/article/unix/sockets.xml
    А ещё лучше - forum.antichat.ru/thread59197.html - Сетевой боевой софт
     
  8. Digimortal

    Digimortal Banned

    Joined:
    22 Aug 2006
    Messages:
    471
    Likes Received:
    248
    Reputations:
    189
    не вижу никакого смысла в подобных статьях.. за знаниями такого рода лучше обращаться к основательно и толково написанным книгам..
     
    4 people like this.
  9. krypt3r

    krypt3r Elder - Старейшина

    Joined:
    27 Apr 2007
    Messages:
    1,507
    Likes Received:
    389
    Reputations:
    101
    Не вижу ничего плохого, что чел сел и разобрался с сетевым кодингом в никсах. Тока вот заметка по статейке - fork(2) не делает приложение демоном (как написано в каментах к коду), fork(2) порождает дочерний процесс
     
    #9 krypt3r, 27 Feb 2008
    Last edited: 27 Feb 2008
  10. Fenich

    Fenich New Member

    Joined:
    2 Mar 2008
    Messages:
    3
    Likes Received:
    1
    Reputations:
    0
    А на чём писать этот код? точнее на каком Компиляторе? имеется ввиду если писать под Linux
     
    1 person likes this.
  11. bul.666

    bul.666 булка

    Joined:
    6 Jun 2006
    Messages:
    719
    Likes Received:
    425
    Reputations:
    140
    gcc =\
     
  12. ezotrank

    ezotrank New Member

    Joined:
    27 Jan 2008
    Messages:
    20
    Likes Received:
    4
    Reputations:
    0
    Статья понравилась, давно хотел чем то подобным заняться, но что то лень было.
     
  13. KEZ

    KEZ Ненасытный школьник

    Joined:
    18 May 2005
    Messages:
    1,604
    Likes Received:
    754
    Reputations:
    397
    > #include <sys/types.h> //хз просто нужно

    угу.. просто так отпизды..
     
    3 people like this.
  14. 47726573684e

    47726573684e New Member

    Joined:
    26 Apr 2008
    Messages:
    8
    Likes Received:
    0
    Reputations:
    0
    Наверное в никсах компилятор gcc
     
  15. mr.celt

    mr.celt Elder - Старейшина

    Joined:
    6 Feb 2008
    Messages:
    133
    Likes Received:
    16
    Reputations:
    12
    Респект zythar, статья делает то что нужно - дает пищу для размыгления, а остальное полет фантазии. :)