Сопрягали мы недавно в кораблике одну РЛС со своей системой управления по TCP/IP через сокеты, система управления — клиент, РЛС тоже клиент, а сопряжение 2 сервера. Сопрячь надо было блок обработки сигналов и устройство где оператор тырцает кнопки и смотрит кто нас хочет завалить, нас это кораблик то есть, но да не об этом сейчас, а о сервере и клиенте…
Любые наши программы проходят проверку, и чтобы проверить наш сервер необходимо было написать 2 клиентов для двух серверов, клиенты забирали и передавали на сервера информацию, при этом в любой момент времени сервера или клиенты могли перезагружаться и должны были подконнектиться снова и продолжить работать, с этим и могут возникнуть проблемы.
В интернете немало примеров серверов и клиентов для socket протоколов, но полностью и правильно работающих да и еще и под QNX 6.3.2 мы не видели. Приведем пример для следующего поколения программистов этого кода (сервера практически одинаковые приведем один), будем использовать стандартные BSD sockets:
/*
******************************************************************
* file RMO1US.c
* brief Главный файл проекта связи с неведомой супер штукой
*
* В данном файле содержатся функции для работы
* с протоколом
*
* Copyright @ 2008-2010 NTC "RIF", jsc
******************************************************************
*/
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "BOSMessages.h"
int sFd[2];// 2 сокета
int newFd;
pthread_t readFromUsThr_id;
void * readFromUSTHr(void *);
int main(int argc, char *argv[]) {
struct sockaddr_in serverAddr;
int sockAddrSize;
unsigned char buffUSask[1024]; // типа временный буфер
sockAddrSize = sizeof (struct sockaddr_in);
bzero ((char *) &serverAddr, sockAddrSize);
serverAddr.sin_family = AF_INET;
serverAddr.sin_len = (u_char) sockAddrSize;
serverAddr.sin_port = htons (SERVER_BOS_PORT_NUM);
serverAddr.sin_addr.s_addr = htonl (INADDR_ANY);// вообще с любого адреса канектим в этот порт
if ((sFd[0] = socket (AF_INET, SOCK_STREAM, 0)) == -1)
{
perror ("socket");
return (-1);
}
setsockopt(sFd[0], SOL_SOCKET,SO_KEEPALIVE, NULL, 0);// супер настройка аж всего через 2 часа сервер увидит что соединение погибло))
if (bind (sFd[0], (struct sockaddr *) &serverAddr, sockAddrSize) == -1)
{
perror ("bind");
close (sFd[0]);
return (-1);
}
if (listen (sFd[0], SERVER_MAX_CONNECTIONS) == -1)
{
perror ("listen");
close (sFd[0]);
return (-1);
}
while (1)
{
if ((newFd = accept (sFd[0], (struct sockaddr *) &clientAddr,&sockAddrSize)) == -1)
// пока хоть какой нибудь клиент не приконнектится код висит на этой строчке
{
perror ("accept");
close (sFd[0]);
return (-1);
}
// завалить старый поток и создать его заново, допишем потом
// создадим поток чтения всякого
if (pthread_create(&readFromUsThr_id, NULL,&readFromUSTHr, NULL) == -1) {
perror ("pthread_create()");
return (-1);
}
while (1)
{
// будем посылать ответы
buffUSask[0]=CODOGRAMM_ZAPROS_MODE;
if (send(newFd,buffUSask,SIZEOF_CODOGRAMM_ZAPROS_MODE,0==0)
break;
// ... всякие другие секретные кодограммы
// ... проверка других условий выхода из отправки
}
}
return 0;
}
/**********************************************
* Функция вызываемая для чтения того что накидал клиент
*/
void * readFromUSTHr(void * arg)
{
unsigned char buffUSask[1024];
unsigned char buffSend[1024];
while (1)
if (recv(newFd,&buffUSask[0],1,0);0)
{
switch (buffUSask[0])
{
case OPERATOR_DIRECTIVE:
recv(newFd,&buffSend,SIZEOF_OPERATOR_DIRECTIVE-1,0);
default:
break;
}
}
}
Это был упрощенный, но работающий код сервера, без проверок на разрыв соединения, их вы можете добавить сами или спросить у нас как.
А вот клиент к нему, тоже простенький:
/*
******************************************************************
* file RMO1ImitatorClient.c
* brief Главный файл проекта связи с
*
* В данном файле содержатся функции для работы
* с протоколом
*
* Copyright @ 2008-2010 NTC "RIF", jsc
******************************************************************
*/
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "BOSMessages.h"
int sFd[2];// 2 сокета
pthread_t readFromUsThr_id;
void * readFromUSTHr(void *);
int main(int argc, char *argv[]) {
unsigned char buffSend[1024];
struct sockaddr_in serverAddr;
int sockAddrSize;
sockAddrSize = sizeof (struct sockaddr_in);
bzero ((char *) &serverAddr, sockAddrSize);
serverAddr.sin_family = AF_INET;
serverAddr.sin_len = (u_char) sockAddrSize;
serverAddr.sin_port = htons (SERVER_BOS_PORT_NUM);// порт сервера
serverAddr.sin_addr.s_addr = inet_addr(RMO1USADDR);
// айпи адрес сервера "192.168.четатам.четатам"
while (1)
{
if ((sFd[0] = socket (AF_INET, SOCK_STREAM, 0)) == -1)
{
perror ("socket");
return (-1);
}
setsockopt(sFd[0], SOL_SOCKET,SO_KEEPALIVE, NULL, 0);
if (connect(sFd[0], (struct sockaddr *)&serverAddr, sockAddrSize) != -1)
{
break; // не удался коннект
}
else
{
close(sFd[0]);
}
delay(2000);
// если сервер вдруг не загружен, то без этой задержки клиент
// не подключится к серверу, даже, если тот загрузится вскоре
}
// поток чтение из сервера
if (pthread_create(&readFromUsThr_id, NULL,&readFromUSTHr, NULL) == -1) {
perror ("pthread_create()");
return (-1);
}
// что нибудь шлем серверу
while (1)
{
buffSend[0]=OPERATOR_DIRECTIVE;
send(sFd[0],&buffSend,SIZEOF_OPERATOR_DIRECTIVE,0);
// другие мегакоманды
}
return (0);
}
/***********************************************************
*
* Функция поток для чтения того чего отвечает сервер
*/
void * readFromUSTHr(void * arg)
{
unsigned char buffUSask[1024];
while (1)
if (recv(sFd[0],&buffUSask[0],1,0);0)
{
switch (buffUSask[0])
{
case CODOGRAMM_TESTFAZAVR:
recv(sFd[0],buffUSask+1,SIZEOF_CODOGRAMM_TESTFAZAVR-1,0);
break;
// другие мегакоманды
default:
break;
}
}
}
Это были отлаженые куски сервера и клиента на 2 компьютерах, которые вы можете использовать в своих приложениях, измените команды на свои и все заработает, как надо вам.
Для использования этоко кода в Windowse или других ОС необходимо использовать библиотеку sockets2, если это не POSIX система(только виндовс не поддерживает сокеты BSD), и изменить вызовы создания потоков (они разные во всех ОС).
Будем рады, если кому-нибудь поможет.
Инет местами штука богопротивная…
Под нейтрину аналогичное списывал из книжек по сетевому обуздательству для Linux.
Какой ужасный код.
Accept может возвращать -1 по нормальным причинам , например израсходованы все файловый дескрипторы, пришел сигнал. Нельзя после этого падать.
Неплохо бы для listen сокета установить SO_REUSEADDR.
Дальше вообще мрак.
Зачем вам здесь многопоточность да еще криво написанная, если сервер поддерживает только один коннект. Зачем создавать себе проблемы на пустом месте.
Кроме того TCP/IP — это потоковый протокол, данные могут рваться. Нужно всегда анализировать, то что вернул send и recv.
Может это и будет работать, но очень нестабильно, с одним клиентом, с малым кол-вом данных и с хорошей связью.
без EPIPE обработчика тут не добится «полностью и правильно работающих» результатов
Что же делать если дескрипторы кончились, как не упасть? Можно конечно лампочками поморгать, попищать)) На кораблике то?
Про send recv я написал добавьте сами, кому надо.
Даже эта не боевая программа будет работать вполне стабильно))
По поводу многопоточности надо нам и в чем кривота ее? Обоснуйте.
Кому ужасный напишите краше и покажите свй мега код.
Да нету тут кривости…
ведь асинхронно же пахать нада ?
Работа асинхронная поэтому пример сервера и клиента разнесен по потокам, про SIGPIPE мы, конечно, слышали, но видеть чтобы приложение по нему вылетало, не видели.В рабочем коде анализ EPIPE стоит, но при любых извращениях с двумя нашими отладочными бордами нам его словить не удалось. Кстати на клиенте под WinXP а сервере на VxWorks тоже не прилетало…
Про SO_REUSEADDR юные и не очень втыкатели могут почитать хелп нужен он им или нет.
Вероятно у вас трафик не очень плотный, если клиент закроет соединение в момент передачи сервером, то сервер получит SIGPIPE. Как и любой сигнал без назначенного обработчика этот приложение положит. А если сработает обработчик, то send вернёт -1 с EPIPE в errno. Ну а дальше только адекватно обработать.
Сам тоже во всю магию епайпа не вникал, но виндовый .NET клиент у нас при закрытии почти всегда таковой вызывает
По специфике приложения закрытия соединения, как такового, с помошью спец функций неожидается, а сама система долго думает прежде чем понять, что связь оборвалась, за это время мы сами все прибиваем, поэтому, наверное, этих SIGPIPE и не приходит. В любом случае замечание ценное, для тех кто захочет скопипастеть этот кусок.
О как ! Век живи век учись ! Не знал, что так можно с потоковыми сокетами — один и тоже дескриптор в разных потоках читать/писать. Пасибки вам ! (осталось проверить эту идею в четыре-двадцатьпять, где всё не совсем так, как казалось должно быть)