TCP/IP сервер теория и практика
От: PSD  
Дата: 27.03.06 04:58
Оценка:
В связи с тем что не нашел студенческой литературы по вопросу прошу разъяснить технологию(разговор не за конкретную реализацию а о технологии вообще)

Прочитал статью на этом сайте посвященную серверам и основным их способом реализации, много плохо понятного кода и не очень много теории.

Все приведенные в статье сервера работали по алгоритму,
Слушающий порт, принимает коннект, выдает аксепт, принимает данные, ретранслирует их, обрывает соединение и заново открывает порт на прослушку.
То есть фактически соединение создается только для передачи одного пакета.

Мне необходимо создать несколько постоянных подключений, по которым на сервер будут слаться запросы и понимже возвращаться данные. Я уже решал подобную задачу, но не имея достаточных знаний по технологии программирования сетей использовал алгоритм:
Основной порт на прослушке, коннект, аксепт, отсылка клиенту номера свободного динамически выделяемого порта сервера, клиент соединяется устанавливае соединение с выделенным портом и работает по нему.

Возможно ли в принципе с точки зрения технологии WinSock поддержание нескольких постоянных соединений через один открытый порт на сервере?
Как это реализовывается (интересует не столько код сколько сам алгоритм.) ?
Re: TCP/IP сервер теория и практика
От: butcher Россия http://bu7cher.blogspot.com
Дата: 27.03.06 06:12
Оценка:
Здравствуйте, PSD, Вы писали:

PSD>Возможно ли в принципе с точки зрения технологии WinSock поддержание нескольких постоянных соединений через один открытый порт на сервере?


Вообще говоря, ИМХО, это обычно так и реализуется. Один сокет слушается постоянно, а закрываются только те сокеты, которые были возвращены accept'ом при принятии соединений.

PSD> Как это реализовывается (интересует не столько код сколько сам алгоритм.) ?

В общем случае, можно придумать множество реализаций и алгоритмов. Всё зависит от вашей задачи. Например:
1. Создаётся слушающий сокет.
2. Начинаем прослушивать его.
3. Цикл обработки событий на сокетах: принимаем соединения; принимаем данные, обрабатываем их, посылаем; закрываем соединения.

Для вас как я понял вызывает сложности часть "принимаем данные, обрабатываем их, посылаем"? Если обработка данных не ресурсоёмкая процедура, то можно все соединения обрабатывать в одной нити. Если нет, то тут уже надо придумывать другие алгоритмы. По этой причине и сложно найти какие-то описания "как делать что-то стандартно?". Для одних и тех же задач при разных условиях алгоритмы могут быть разными, что уж говорить про разные задачи и то, как они могут быть реализованы на разных ОС..

Нет ничего невозможного..
Re: TCP/IP сервер теория и практика
От: ansi  
Дата: 27.03.06 06:48
Оценка:
Здравствуйте, PSD, Вы писали:

PSD>Мне необходимо создать несколько постоянных подключений, по которым на сервер будут слаться запросы и понимже возвращаться данные. Я уже решал подобную задачу, но не имея достаточных знаний по технологии программирования сетей использовал алгоритм:

PSD>Основной порт на прослушке, коннект, аксепт, отсылка клиенту номера свободного динамически выделяемого порта сервера, клиент соединяется устанавливае соединение с выделенным портом и работает по нему.

Ну вообще-то, accept возвращает хэндл сокета для установленного соединения, которое и является "постоянным подключением" (пока одна из сторон не закроет сокет).

Т.е. тебе надо делать так:

while(true)
{
listen(desired_port);
socket = accept();
start_new_thread(socket); // thread reads from and writes to socket
}

Т.е. у тебя много коннектов на одном и том же порту, это стандартная процедура.
Re: TCP/IP сервер теория и практика
От: Michael Chelnokov Украина  
Дата: 27.03.06 09:12
Оценка:
Здравствуйте, PSD, Вы писали:

PSD>Мне необходимо создать несколько постоянных подключений, по которым на сервер будут слаться запросы и понимже возвращаться данные.


Несколько постоянных? Напрашивается по потоку на клиента.

PSD>Основной порт на прослушке, коннект, аксепт, отсылка клиенту номера свободного динамически выделяемого порта сервера, клиент соединяется устанавливае соединение с выделенным портом и работает по нему.


Жуть. После accept просто создавайте поток, чтобы работало "одновременно". А то что вернул accept — это и есть необходимое Вам соединение.

PSD>Возможно ли в принципе с точки зрения технологии WinSock поддержание нескольких постоянных соединений через один открытый порт на сервере?


Безусловно. Всякое соединение определяется четырмя параметрами — IP-адресами сервера и клиента и номерами их портов. Для двух разных соединений эти четыре параметра не будут одинаковыми, даже если адрес:порт сервера будут одни и те же.
Re: В MSDN есть хороший примерчик
От: R.O. Prokopiev Россия http://127.0.0.1/
Дата: 04.04.06 08:45
Оценка: :)
Называется simples.c
Всё работает в одном потоке.

/******************************************************************************\
* simples.c - Simple TCP/UDP server using Winsock 1.1
*       This is a part of the Microsoft Source Code Samples.
*       Copyright 1996-1997 Microsoft Corporation.
*       All rights reserved.
*       This source code is only intended as a supplement to
*       Microsoft Development Tools and/or WinHelp documentation.
*       See these sources for detailed information regarding the
*       Microsoft samples programs.
\******************************************************************************/

#define WIN32_LEAN_AND_MEAN
#include <winsock2.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>

#define DEFAULT_PORT 5001
#define DEFAULT_PROTO SOCK_STREAM // TCP

void Usage(char *progname) {
    fprintf(stderr,"Usage\n%s -p [protocol] -e [endpoint] -i [interface]\n",
        progname);
    fprintf(stderr,"Where:\n\tprotocol is one of TCP or UDP\n");
    fprintf(stderr,"\tendpoint is the port to listen on\n");
    fprintf(stderr,"\tinterface is the ipaddr (in dotted decimal notation)");
    fprintf(stderr," to bind to\n");
    fprintf(stderr,"Defaults are TCP,5001 and INADDR_ANY\n");
    WSACleanup();
    exit(1);
}
int main(int argc, char **argv) {

    char Buffer[128];
    char *interface= NULL;
    unsigned short port=DEFAULT_PORT;
    int retval;
    int fromlen;
    int i;
    int socket_type = DEFAULT_PROTO;
    struct sockaddr_in local, from;
    WSADATA wsaData;
    SOCKET listen_socket, msgsock;

    /* Parse arguments */
    if (argc >1) {
        for(i=1;i <argc;i++) {
            if ( (argv[i][0] == '-') || (argv[i][0] == '/') ) {
                switch(tolower(argv[i][1])) {
                    case 'p':
                        if (!stricmp(argv[i+1], "TCP") )
                            socket_type = SOCK_STREAM;
                        else if (!stricmp(argv[i+1], "UDP") )
                            socket_type = SOCK_DGRAM;
                        else
                            Usage(argv[0]);
                        i++;
                        break;

                    case 'i':
                        interface = argv[++i];
                        break;
                    case 'e':
                        port = atoi(argv[++i]);
                        break;
                    default:
                        Usage(argv[0]);
                        break;
                }
            }
            else
                Usage(argv[0]);
        }
    }
    
    if (WSAStartup(0x202,&wsaData) == SOCKET_ERROR) {
        fprintf(stderr,"WSAStartup failed with error %d\n",WSAGetLastError());
        WSACleanup();
        return -1;
    }
    
    if (port == 0){
        Usage(argv[0]);
    }

    local.sin_family = AF_INET;
    local.sin_addr.s_addr = (!interface)?INADDR_ANY:inet_addr(interface); 

    /* 
     * Port MUST be in Network Byte Order
     */
    local.sin_port = htons(port);

    listen_socket = socket(AF_INET, socket_type,0); // TCP socket
    
    if (listen_socket == INVALID_SOCKET){
        fprintf(stderr,"socket() failed with error %d\n",WSAGetLastError());
        WSACleanup();
        return -1;
    }
    //
    // bind() associates a local address and port combination with the
    // socket just created. This is most useful when the application is a 
    // server that has a well-known port that clients know about in advance.
    //

    if (bind(listen_socket,(struct sockaddr*)&local,sizeof(local) ) 
        == SOCKET_ERROR) {
        fprintf(stderr,"bind() failed with error %d\n",WSAGetLastError());
        WSACleanup();
        return -1;
    }

    //
    // So far, everything we did was applicable to TCP as well as UDP.
    // However, there are certain steps that do not work when the server is
    // using UDP.
    //

    // We cannot listen() on a UDP socket.

    if (socket_type != SOCK_DGRAM) {
        if (listen(listen_socket,5) == SOCKET_ERROR) {
            fprintf(stderr,"listen() failed with error %d\n",WSAGetLastError());
            WSACleanup();
            return -1;
        }
    }
    printf("%s: 'Listening' on port %d, protocol %s\n",argv[0],port,
        (socket_type == SOCK_STREAM)?"TCP":"UDP");
    while(1) {
        fromlen =sizeof(from);
        //
        // accept() doesn't make sense on UDP, since we do not listen()
        //
        if (socket_type != SOCK_DGRAM) {
            msgsock = accept(listen_socket,(struct sockaddr*)&from, &fromlen);
            if (msgsock == INVALID_SOCKET) {
                fprintf(stderr,"accept() error %d\n",WSAGetLastError());
                WSACleanup();
                return -1;
            }
            printf("accepted connection from %s, port %d\n", 
                        inet_ntoa(from.sin_addr),
                        htons(from.sin_port)) ;
            
        }
        else
            msgsock = listen_socket;

        //
        // In the case of SOCK_STREAM, the server can do recv() and 
        // send() on the accepted socket and then close it.

        // However, for SOCK_DGRAM (UDP), the server will do
        // recvfrom() and sendto()  in a loop.

        if (socket_type != SOCK_DGRAM)
            retval = recv(msgsock,Buffer,sizeof (Buffer),0 );
        else {
            retval = recvfrom(msgsock,Buffer,sizeof (Buffer),0,
                (struct sockaddr *)&from,&fromlen);
            printf("Received datagram from %s\n",inet_ntoa(from.sin_addr));
        }
            
        if (retval == SOCKET_ERROR) {
            fprintf(stderr,"recv() failed: error %d\n",WSAGetLastError());
            closesocket(msgsock);
            continue;
        }
        if (retval == 0) {
            printf("Client closed connection\n");
            closesocket(msgsock);
            continue;
        }
        printf("Received %d bytes, data [%s] from client\n",retval,Buffer);

        printf("Echoing same data back to client\n");
        if (socket_type != SOCK_DGRAM)
            retval = send(msgsock,Buffer,sizeof(Buffer),0);
        else
            retval = sendto(msgsock,Buffer,sizeof (Buffer),0,
                (struct sockaddr *)&from,fromlen);
        if (retval == SOCKET_ERROR) {
            fprintf(stderr,"send() failed: error %d\n",WSAGetLastError());
        }
        if (socket_type != SOCK_DGRAM){
            printf("Terminating connection\n");
            closesocket(msgsock);
        }
        else 
            printf("UDP server looping back for more requests\n");
        continue;
    }
}
Re[2]: TCP/IP сервер теория и практика
От: R.O. Prokopiev Россия http://127.0.0.1/
Дата: 04.04.06 08:50
Оценка:
Здравствуйте, ansi, Вы писали:

A>Здравствуйте, PSD, Вы писали:


PSD>>Мне необходимо создать несколько постоянных подключений, по которым на сервер будут слаться запросы и понимже возвращаться данные. Я уже решал подобную задачу, но не имея достаточных знаний по технологии программирования сетей использовал алгоритм:

PSD>>Основной порт на прослушке, коннект, аксепт, отсылка клиенту номера свободного динамически выделяемого порта сервера, клиент соединяется устанавливае соединение с выделенным портом и работает по нему.

A>Ну вообще-то, accept возвращает хэндл сокета для установленного соединения, которое и является "постоянным подключением" (пока одна из сторон не закроет сокет).


A>Т.е. тебе надо делать так:


A>while(true)

A>{
A> listen(desired_port);
A> socket = accept();
A> start_new_thread(socket); // thread reads from and writes to socket
A>}

Зачем listen вызывать в цикле?
вызываешь listen один раз — а в цикле акцепть и создавай потоки.
Re[2]: В MSDN есть хороший примерчик
От: butcher Россия http://bu7cher.blogspot.com
Дата: 04.04.06 10:54
Оценка:
Здравствуйте, R.O. Prokopiev, Вы писали:

Хех.. хорош пример:

ROP>
ROP>/******************************************************************************\
ROP>* simples.c - Simple TCP/UDP server using Winsock 1.1

ROP>    if (WSAStartup(0x202,&wsaData) == SOCKET_ERROR) {

WSAStartup никогда не возвращяет SOCKET_ERROR:

The WSAStartup function returns zero if successful. Otherwise, it returns one of the error codes listed in the following.

ROP>        fprintf(stderr,"WSAStartup failed with error %d\n",WSAGetLastError());
ROP>        WSACleanup();

Вообще говоря вызывать WSAGetLastError когда инициализация не удалась.. Ну и WSACleanup туда же..

/*
    Не плохо бы было ещё local нулями забить, перед тем как заполнять и передавать в bind.
*/
ROP>    if (bind(listen_socket,(struct sockaddr*)&local,sizeof(local) ) 
ROP>


Ну и вообще, где тут отдновременная работа с несколькими соединениями?

Нет ничего невозможного..
Re[3]: В MSDN есть хороший примерчик
От: R.O. Prokopiev Россия http://127.0.0.1/
Дата: 04.04.06 12:06
Оценка:
Здравствуйте, butcher, Вы писали:

B>Здравствуйте, R.O. Prokopiev, Вы писали:


B>Хех.. хорош пример:


ROP>>
ROP>>/******************************************************************************\
ROP>>* simples.c - Simple TCP/UDP server using Winsock 1.1

ROP>>    if (WSAStartup(0x202,&wsaData) == SOCKET_ERROR) {
B>

B>WSAStartup никогда не возвращяет SOCKET_ERROR:
B>

The WSAStartup function returns zero if successful. Otherwise, it returns one of the error codes listed in the following.

B>
ROP>>        fprintf(stderr,"WSAStartup failed with error %d\n",WSAGetLastError());
ROP>>        WSACleanup();
B>

B>Вообще говоря вызывать WSAGetLastError когда инициализация не удалась.. Ну и WSACleanup туда же..

Дык ить это не ко мне, а к Биллу Гейтсу.
Баг мелочевый, все работает.

Пример хорош именно для того чтобы с нула понять как работает TCP-сервер.
А для усовершенствований тут конечно большое поле.

B>
B>/*
B>    Не плохо бы было ещё local нулями забить, перед тем как заполнять и передавать в bind.
B>*/
ROP>>    if (bind(listen_socket,(struct sockaddr*)&local,sizeof(local) ) 
ROP>>


B>Ну и вообще, где тут отдновременная работа с несколькими соединениями?


Ну строго одновременной нет.
Сервер в цикле акцептит соединение, принимает данные, отправляет ответ,
закрывает кошелку, тьфу соединение

Если клиент задумается, сервер задумается тоже,
это конечно плохо, но в освоения винсока этот пример самое то.

Дальше можно уже наворачивать с select'ом или неблокирующими сокетами.

Кстати, в MSDN'е есть версия этого сервера с использованием select...
Re[2]: блин, а за что смайл??? crashed, объяснись (-)
От: R.O. Prokopiev Россия http://127.0.0.1/
Дата: 04.04.06 12:16
Оценка:
 
Re[3]: блин, а за что смайл??? crashed, объяснись (-)
От: crashed США  
Дата: 04.04.06 12:29
Оценка:
Здравствуйте, R.O. Prokopiev, Вы писали:

Пожалуйста!
Читаем исходную задачу:

...
Все приведенные в статье сервера работали по алгоритму,
Слушающий порт, принимает коннект, выдает аксепт, принимает данные, ретранслирует их, обрывает соединение и заново открывает порт на прослушку.
То есть фактически соединение создается только для передачи одного пакета.

Мне необходимо создать несколько постоянных подключений, по которым на сервер будут слаться запросы и понимже возвращаться данные. Я уже решал подобную задачу, но не имея достаточных знаний по технологии программирования сетей использовал алгоритм:
Основной порт на прослушке, коннект, аксепт, отсылка клиенту номера свободного динамически выделяемого порта сервера, клиент соединяется устанавливае соединение с выделенным портом и работает по нему.
...


Читаем ваш пост и видим все то, о чем сказал butcher здесь
Автор: butcher
Дата: 04.04.06
. Т.е. вы привели "примерчик", показывающий все то, что PSD уже видел и ничего из того, что он хотел бы.
Так что первым делом хотел поставить минус, потом решил не делать этого.
А смайл — за название вашего поста, а именно за слово "хороший"
... << RSDN@Home 1.2.0 alpha rev. 648>>
Re[4]: мда, невнимательность.
От: R.O. Prokopiev Россия http://127.0.0.1/
Дата: 04.04.06 12:40
Оценка: +1
Здравствуйте, crashed, Вы писали:


C>Читаем ваш пост и видим все то, о чем сказал butcher здесь
Автор: butcher
Дата: 04.04.06
. Т.е. вы привели "примерчик", показывающий все то, что PSD уже видел и ничего из того, что он хотел бы.

C>Так что первым делом хотел поставить минус, потом решил не делать этого.
C>А смайл — за название вашего поста, а именно за слово "хороший"
C>



пример на самом деле хороший (сам по нему учился),
но к задаче к сож. не подходит.

Если приделать сюда селект, то будет то что хотел PSD.

Главное, ни в коем случае не вызывать listen в цикле,
как советуют некоторые товарищи
Re[5]: мда, невнимательность.
От: crashed США  
Дата: 04.04.06 12:57
Оценка:
Здравствуйте, R.O. Prokopiev, Вы писали:

ROP>Главное, ни в коем случае не вызывать listen в цикле,

ROP>как советуют некоторые товарищи
Кстати, вот что говорит MSDN по поводу повторного вызова listen:

An application can call listen more than once on the same socket. This has the effect of updating the current backlog for the listening socket. Should there be more pending connections than the new backlog value, the excess pending connections will be reset and dropped.

Так что все-таки можно вызывать listen в цикле.
Но не нужно
... << RSDN@Home 1.2.0 alpha rev. 648>>
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.