Antichat снова доступен.
Форум Antichat (Античат) возвращается и снова открыт для пользователей.
Здесь обсуждаются безопасность, программирование, технологии и многое другое.
Сообщество снова собирается вместе.
Новый адрес: forum.antichat.xyz
 |
|
[для новичков]сетевое программирование на си под unix |

21.02.2008, 22:49
|
|
Moderator - Level 7
Регистрация: 16.02.2008
Сообщений: 580
Провел на форуме: 1595333
Репутация:
291
|
|
[для новичков]сетевое программирование на си под unix
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 <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);
}
}
Последний раз редактировалось -=lebed=-; 24.02.2008 в 01:58..
|
|
|

22.02.2008, 16:01
|
|
Moderator - Level 7
Регистрация: 16.02.2008
Сообщений: 580
Провел на форуме: 1595333
Репутация:
291
|
|
клиент
клиент
теперь напишем консольную версию нашего клиента к нашему серваку.
итак. определим что будет делать наш клиент.
на стороне клиента все организовать очень очень легко. нужно сделать вот что.
1. задать параметры подключения
2. подключится к серверу
3. получить сообщение
4. закрыть соединение с сервером
как задать параметры подключения?
помните когда мы объявляли структуру типа sockaddr_in для сервера? там мы задавали параметры сервера. тобишь на каком айпишнике он будет, на каком порте будет работать... для клиента мы тоже должны объявить структуру. и там будут параметры подключения. на какой порт подключаться (поле sin_port), ip адрес сервера (поле sin_addr.s_addr), а поле sin_family остается тоже (AF_INET). думаю нет надобности объяснять почему.
как подключится к серверу?
функция connect(2).
Код:
int connect(int s, const struct sockaddr *name, socklen_t namelen);
что за аргументы функции объяснять не буду ибо объяснил в статье посвященной серверу.
как получить сообщения?
функции read(2) и write(2). их я также описал в предыдущей статье.
как закрыть соединение с сервером?
a
функция close(2). см. статью "сервер"
<---------------------cut here------------------------>
ну вот собственно и все. клиента мы описали. ниже приведу листинг клиента.
Код:
#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. вот листинг этого участка кода:
Код:
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);
пусть это будет вашим домашним заданием ((*
Последний раз редактировалось zythar; 24.02.2008 в 08:46..
|
|
|

22.02.2008, 16:07
|
|
Moderator - Level 7
Регистрация: 16.02.2008
Сообщений: 580
Провел на форуме: 1595333
Репутация:
291
|
|
теперь немножко о копирах.
статью писал сам. примеры привел те на которых сам учился.
когда было совсем туго (не мог нормально сформулировать мыслю) использовал "FreeBSD Developers' Handbook". брал и нагло переводил ((*
графический клиент напишем через 1-2 дня (тема на самом деле очень большая)
|
|
|

23.02.2008, 16:23
|
|
Moderator - Level 7
Регистрация: 16.02.2008
Сообщений: 580
Провел на форуме: 1595333
Репутация:
291
|
|
несколько раз пробовал написать статью. ничего не получалось... ну не могу я статьи писать ((*
поэтому я решил делать так. я приведу листинг кода с подробными коментами. если че нибудь будет не понятно стучитесь в асю попробую объяснить.
листинг:
Код:
#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`
Последний раз редактировалось zythar; 23.02.2008 в 16:30..
|
|
|

23.02.2008, 19:45
|
|
Участник форума
Регистрация: 02.09.2006
Сообщений: 176
Провел на форуме: 645316
Репутация:
327
|
|
неплохая статья , сделай ещё цикл видео по теме будет большим плюсом
|
|
|

23.02.2008, 19:55
|
|
Moderator - Level 7
Регистрация: 16.02.2008
Сообщений: 580
Провел на форуме: 1595333
Репутация:
291
|
|
а можно по подробнее? мне все равно делать нечего, чем нить хоть бы займусь ((*
|
|
|

23.02.2008, 21:15
|
|
Постоянный
Регистрация: 03.02.2007
Сообщений: 520
Провел на форуме: 1777536
Репутация:
932
|
|
За старание + , но не ново - rsdn.ru/article/unix/sockets.xml
А ещё лучше - forum.antichat.ru/thread59197.html - Сетевой боевой софт
|
|
|

23.02.2008, 23:58
|
|
Banned
Регистрация: 22.08.2006
Сообщений: 608
Провел на форуме: 6144796
Репутация:
1095
|
|
не вижу никакого смысла в подобных статьях.. за знаниями такого рода лучше обращаться к основательно и толково написанным книгам..
|
|
|

27.02.2008, 16:24
|
|
Познавший АНТИЧАТ
Регистрация: 27.04.2007
Сообщений: 1,044
Провел на форуме: 3660186
Репутация:
905
|
|
Не вижу ничего плохого, что чел сел и разобрался с сетевым кодингом в никсах. Тока вот заметка по статейке - fork(2) не делает приложение демоном (как написано в каментах к коду), fork(2) порождает дочерний процесс
fork порождает подпроцесс (дочерний процесс), отличающийся от родительского процесса
значениями PID (идентификатора процесса), PPID (идентификатора родительского процесса) и значением счетчиков использования ресурсов, равного 0. Блокировки файлов и сигналы, ожидающие обработки, не наследуются.
...
Последний раз редактировалось krypt3r; 27.02.2008 в 16:29..
|
|
|

05.03.2008, 09:49
|
|
Новичок
Регистрация: 02.03.2008
Сообщений: 5
Провел на форуме: 25747
Репутация:
5
|
|
А на чём писать этот код? точнее на каком Компиляторе? имеется ввиду если писать под Linux
|
|
|
|
 |
|
Похожие темы
|
| Тема |
Автор |
Раздел |
Ответов |
Последнее сообщение |
|
Linux books
|
nerezus |
*nix |
68 |
23.01.2010 00:31 |
|
Программирование боевого софта под Linux
|
xcedz |
С/С++, C#, Delphi, .NET, Asm |
9 |
16.11.2008 19:03 |
|
программирование под *nix ?
|
St__one |
С/С++, C#, Delphi, .NET, Asm |
6 |
24.01.2008 02:33 |
|
Программирование под windows mobile 6.0
|
MegaDeth |
С/С++, C#, Delphi, .NET, Asm |
2 |
28.12.2007 08:40 |
|
Краткая история хакерства
|
foreva |
Чужие Статьи |
3 |
06.02.2005 19:29 |
|
Здесь присутствуют: 1 (пользователей: 0 , гостей: 1)
|
|
|
|