Для ясности изложения рассмотрим простейший вариант клиента и сервера (ниже обсуждается, что в этой реализации плохо). Задача сервера — принимать соединения на порт 1234. От каждого клиента принимается ровно одно сообщение и соединение с клиентом закрывается.
Начнем с сервера.
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
#include <stdio.h>
#include <stdlib.h>
#include <strings.h>
int main(int argc, char *argv[])
{
int as, ms;
struct sockaddr_in server;
char buf[1024]; /* буфер для приема сообщений от клиентов */
as = socket(AF_INET, SOCK_STREAM, 0 ); /* Создаем сокет для работы по TCP/IP */
/* Заполняем структуру адреса, на котором будет работать сервер */
server.sin_family = AF_INET; /* IP */
server.sin_addr.s_addr = INADDR_ANY; /* любой сетевой интерфейс */
server.sin_port = htons(1234); /* порт */
/* сопоставляем адрес с сокетом */
bind(as, (struct sockaddr *) &server, sizeof(server));
listen(as, 5); /* сокет as используется для приема соединений; 5 - длина очереди */
/* цикл обработки клиентов */
while( 1 ) {
ms = accept( as, 0, 0 ); /* выбираем первое соединение из очереди */
bzero( buf, sizeof(buf)); /* обнуляем буфер сообщения */
read(ms, buf, sizeof(buf)); /* читаем сообщение от клиента */
close( ms ); /* закрываем соединение с клиентом */
printf("message is = %s\n", buf );
if ( strcmp(buf, "quit") == 0 ) break;
}
close( as ); /* закрываем порт 1234; клиенты больше не могут подключаться */
return 0;
}
Компилировать надо командой gcc -o server server.c
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(int argc, char *argv[])
{
int s;
struct sockaddr_in server;
struct hostent *hp;
if( argc<4 ) { return 1; } /* должно быть 3 аргумента: адрес, порт и сообщение */
s = socket(AF_INET, SOCK_STREAM, 0 ); /* Создаем сокет для работы по TCP/IP */
/* Заполняем струтуру с адресом сервера */
server.sin_family = AF_INET; /* протокол IP */
hp = gethostbyname(argv[1]); /* Обращаемся к DNS и узнаем адрес по символьному имени*/
bcopy( hp->h_addr, &server.sin_addr, hp->h_length); /* копируем из hp->h_addr в &server.sin_addr hp->h_length байт */
server.sin_port = htons(atoi(argv[2])); /* номер порта, на котором запущен сервер */
connect(s, (struct sockaddr *)&server, sizeof(server)); /* устанавливаем соединение с сервером */
write(s, argv[3], strlen(argv[3]) ); /* посылаем строчку */
close( s );
return 0;
}
./client localhost 1234 "Unbelievable! It works!"
При выполнении этой команды сервер должен напечатать текст сообщения (Unbelievable! It works!). Обратите внимание, что сообщение заключено в кавычки.
В этом случае вся строка, которая содержит пробелы, считается одним аргументом командной строки. Если клиент посылает
сообщение quit, то, как это видно из текста программы, сервер останавливается.
if( (s = socket(AF_INET, SOCK_STREAM, 0 )) == -1) {
perror("Ошибка при вызове socket"); /* perror — стандартная функция печати значения errno */
exit(1);
}
В случае ошибки вы можете увидеть сообщение вида/* строка передается в виде <длина> <байты> */
int len;
recv(s, &len, sizeof(int), MSG_WAITALL); /* читаем длину сообщения */
recv(s, buf, len, MSG_WAITALL); /* читаем len байт */
При таком вызове функции recv гарантируется, что из сокета s будет прочитано в точности указанное количество байт. Если
сообщение передается в нескольких сетевых пакетах, то recv будет ждать их получения.
В случае ошибки (например, если передающая сторона закрыла соединение) функция recv возвращает значение -1.
При успешном выполнении возвращается количество прочитанных байт.
int on=1;
if(setsockopt(sa, SOL_SOCKET, SO_REUSEADDR, (char *)&on, sizeof(on)) == -1) {
perror("Ошибка при вызове setsockopt");
}
то сокет будет использоваться без ожидания таймаута и команда bind выполнится успешно.
Теоретически, при использовании данной опции сервер может получить пакет "из прошлой жизни", то есть пакет,
отправленный клиентом "предыдущего сервера", но такая ситуация маловероятна.