GeorgeZv Ответов: 1

Мультиплексирование ввода-вывода клиент-сервер (язык C)


Я пытаюсь написать клиент-серверное приложение с мультиплексированием inp/out. Вот мой код, и после того, как я объясню, в чем моя проблема.

Клиент:

#pragma comment(lib, "ws2_32.lib")
#define _WINSOCK_DEPRECATED_NO_WARNINGS
#include <winsock2.h>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h> 
#include <sys/types.h>

#define SERVERADDR argv[1]
#define PORT 3765
#define MAXDATASIZE 1024

int main(int argc, char *argv[])
{
    struct sockaddr_in destAddr = {0};
    SOCKET socketDescriptor;
    WSADATA wsaData;
    fd_set readSet;
    fd_set writeSet;
    timeval tv;
    fd_set readSocketDescriptor;
    fd_set writeSocketDescriptor;

    char bufferData[MAXDATASIZE];
    int numBytes;
    unsigned long int nb = 1;
    int retVal;

    if (argc != 2)
    {
        printf("Uses: <client> <hostname>\nExample: \"client 127.0.0.1\"\n");
        return -1;
    }
    do
    {
        if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)
        {
            printf("WSAStartup error "); 
            break;
        }
        if (gethostbyname(SERVERADDR) == NULL)
        {
            printf("Gethostbyname error ");
            break;
        }
        if ((socketDescriptor = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) == INVALID_SOCKET)
        {
            printf("Socket error ");
            break; 
        }
        if (ioctlsocket(socketDescriptor, FIONBIO, (unsigned long *)&nb) != 0)
        {
            printf("ioctlsocket error ");
            break;
        }

        destAddr.sin_family = AF_INET;
        destAddr.sin_port = htons(PORT);
        destAddr.sin_addr.s_addr = inet_addr(SERVERADDR);

        printf("Attempt to connect %s:%d\n", inet_ntoa(destAddr.sin_addr), htons(destAddr.sin_port));
        tv.tv_sec = 20;

        if (connect(socketDescriptor, (sockaddr *)&destAddr, sizeof(destAddr)))
        {
            printf("Connect error ");
            FD_ZERO(&writeSet);
            FD_SET(socketDescriptor, &writeSet);
            retVal = select(socketDescriptor + 1, NULL, &writeSet, NULL, &tv);
            if (retVal == SOCKET_ERROR)
            {
                printf("Send non-blocking error ");
                break;
            }
            else if (retVal == 0)
            {
                printf("Non-blocking connect time limit is expired");
                break;
;           }
        }
        printf("Connection with %s\n", SERVERADDR);

        while (1)
        {
            FD_ZERO(&readSet);
            FD_ZERO(&writeSet);
            FD_SET(socketDescriptor, &readSet);
            FD_SET(socketDescriptor, &writeSet);

            retVal = select(1, &readSet, &writeSet, NULL, &tv);

            if (retVal == SOCKET_ERROR)
            {
                printf("Select error ");
                break;
            }
            else if (retVal == 0)
            {
                printf("Time limit is expired ");
                continue;
            }

            char *testmsg = "Hello, world!\n";
            if (FD_ISSET(socketDescriptor, &writeSet) != 0)
            {
                if (send(socketDescriptor, testmsg, strlen(testmsg), 0) == SOCKET_ERROR)
                {
                    printf("Send error ");
                    break;
                }
                printf("Send function - OK\n");
            }
            if (FD_ISSET(socketDescriptor, &readSet) != 0)
            {
                if ((numBytes = recv(socketDescriptor, bufferData, MAXDATASIZE, 0)) == SOCKET_ERROR)
                {
                    printf("Recv error ");
                    break;
                }
                printf("Recv function - OK\n");
            }
            bufferData[numBytes] = '\0';
            printf("Received text: %s\n", bufferData);
        }
    } while (FALSE);

    printf("- Error code: %d\n", WSAGetLastError());
    closesocket(socketDescriptor);
    WSACleanup();
    return 0;
}


Сервер:

<pre>Sorry for a long pause, I was away during long weekends.
First of all, thank you for all your reponses.
When I tried to close SocketDescriptor my client stopped working completely. I rewrote a little bit my server, could you check it, please? I still have problems with repeated message and I have error code in client-output (10054, can't fix it).

And I also wrote a structure client and pushBack function to add my new connected clients to the list but not sure if it works fine. It seems I add smth wrong there in while(1) cycle.

<pre>#pragma comment(lib, "ws2_32.lib")
#define _WINSOCK_DEPRECATED_NO_WARNINGS
#define _CRT_SECURE_NO_WARNINGS
#include <winsock2.h>
#include <stdio.h>
#include <stdlib.h>
#include <conio.h>
//#include "list.h"

#define PRINTUSERS if (nclients)\
	printf("We have %d user online\n", nclients);\
	else printf("No users online\n***************\n");

#define PORT 3765
#define BACKLOG 10
#define MAXDATASIZE 1024
#define MAXUSERSONLINE 64

struct client
{
	int numClient;
	struct client* next;
};

void print(struct client* head);
int pushBack(struct client** head, int numClient);
int checkSockOpt(int socket);

int main(int argc, char* argv[])
{
	struct sockaddr_in serverAddr;
	struct sockaddr_in clientAddr;

	SOCKET socketDescriptor;
	fd_set readSocketDescriptor;
	fd_set writeSocketDescriptor;
	int newSocketDescriptor;

	WSADATA wsaData;
	WORD wsaStart;
	timeval tv;

	// LIST TEST #1
	struct client* head = 0;
	pushBack(&head, 111);
	pushBack(&head, 222);
	pushBack(&head, 999);
	print(head); //

	int retVal;
	int clientAddrSize = sizeof(clientAddr);
	char bufferData[MAXDATASIZE] = { 0 };
	int nclients = 0;
	int numBytes = 0;

	if (argc > 1)
	{
		printf("Uses: <chatserver>\n");
		return -1;
	}
	do
	{
		if ((wsaStart = WSAStartup(MAKEWORD(2, 2), &wsaData)) != 0)
		{
			printf("WSAStartup error ");
			break;
		}
		if ((socketDescriptor = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) == INVALID_SOCKET)
		{
			printf("Socket error ");
			closesocket(socketDescriptor);
			break;
		}

		serverAddr.sin_family = AF_INET;
		serverAddr.sin_port = htons(PORT);
		serverAddr.sin_addr.s_addr = INADDR_ANY;

		if (bind(socketDescriptor, (struct sockaddr *)&serverAddr, sizeof(serverAddr)) == SOCKET_ERROR)
		{
			printf("Bind error ");
			closesocket(socketDescriptor);

			break;
		}
		if (listen(socketDescriptor, BACKLOG) == SOCKET_ERROR)
		{
			printf("Listening error ");
			closesocket(socketDescriptor);
			break;
		}

		while (1)
		{

			FD_ZERO(&readSocketDescriptor);
			FD_ZERO(&writeSocketDescriptor);
			FD_SET(socketDescriptor, &readSocketDescriptor);
			FD_SET(socketDescriptor, &writeSocketDescriptor);

			printf("Setup SERVER socket descriptor number: %d\n", socketDescriptor);
			tv.tv_sec = 5;

			// Do not forget to put after WHILE(1)
			if ((retVal = select(socketDescriptor + 1, &readSocketDescriptor, &writeSocketDescriptor, NULL, &tv)) == SOCKET_ERROR)
			{
				printf("Select error ");
				break;
			}
			if ((FD_ISSET(socketDescriptor, &readSocketDescriptor)) != 0)
			{
				printf("CHECK FD_ISSET value #1: %d\n", FD_ISSET(socketDescriptor, &readSocketDescriptor));
				//int clientList[MAXUSERSONLINE] = { 0 };
				//for (int i = 0; i <= MAXUSERSONLINE; i++)
				//{

				if ((newSocketDescriptor = accept(socketDescriptor, (struct sockaddr *)&clientAddr, &clientAddrSize)) == SOCKET_ERROR)
				{
					printf("Accept error ");
					break;
				}
				//	newSocketDescriptor = clientList[i];
				struct client* head = 0;
				pushBack(&head, newSocketDescriptor);
				print(head); //
				nclients++; PRINTUSERS

					printf("Server got connection from %s\n", inet_ntoa(clientAddr.sin_addr));
				FD_SET(newSocketDescriptor, &readSocketDescriptor);
				//FD_CLR(socketDescriptor, &readSocketDescriptor);

				printf("CHECK FD_ISSET value #2: %d\n", (FD_ISSET(socketDescriptor, &readSocketDescriptor)));
				printf("newSocketDescriptor = %d, READ_FD status #1 after accept = %d\n", newSocketDescriptor, FD_ISSET(socketDescriptor, &readSocketDescriptor));
				checkSockOpt(newSocketDescriptor);

				if (FD_ISSET(newSocketDescriptor, &readSocketDescriptor) != 0)
				{

					pushBack(&head, newSocketDescriptor);
					print(head);
					//RECV function
					if ((numBytes = recv(newSocketDescriptor, bufferData, MAXDATASIZE, 0)) == SOCKET_ERROR)
					{
						printf("Recv error ");
						break;
					}

					bufferData[numBytes] = '\0';
					printf("Recv: retVal = %d, bufferData = %s\n", retVal, bufferData);
					printf("newSocketDescriptor = %d, READ_FD status #2 after recv = %d\n", newSocketDescriptor, FD_ISSET(socketDescriptor, &readSocketDescriptor));
					if (numBytes == -1)
					{
						printf("One user has disconnected");
						nclients--;
						PRINTUSERS
					}
					else
					{
						// SEND message to CLIENT
						char *testmsg = "Hello, world!\n";
						if (send(newSocketDescriptor, testmsg, strlen(testmsg), 0) == SOCKET_ERROR)
						{
							printf("Send error ");
							break;
						}
						FD_CLR(newSocketDescriptor, &readSocketDescriptor);
					}
					printf("newSocketDescriptor = %d, READ_FD status #3 after send = %d\n", newSocketDescriptor, FD_ISSET(socketDescriptor, &readSocketDescriptor));
					FD_SET(newSocketDescriptor, &readSocketDescriptor);
					closesocket(newSocketDescriptor);
				}
				//closesocket(newSocketDescriptor);
				//}
			}
			else
			{
				printf("Sorry, time limit is expired, there is no active sockets\n");
			}
		}

	} while (FALSE);
	printf("- Error code: %d\n", WSAGetLastError());
	closesocket(socketDescriptor);
	WSACleanup();
	return 0;
}

// Check Socket Option
int checkSockOpt(int socket)
{
	int optVal = 0;
	int optLen = sizeof(int);
	if (getsockopt(socket, SOL_SOCKET, SO_ACCEPTCONN, (char*)&optVal, &optLen) != SOCKET_ERROR)
	{
		printf("SockOpt Value: %ld. Error code: %d\n", optVal, WSAGetLastError());
	}
	return 0;
}

// Print Client List
void print(struct client* head)
{
	struct client* list = head;
	while (list != 0)
	{
		printf("Client socket %d successfully added to the list\n", list->numClient);
		list = list->next;
	}
}

void printAllFD(struct client* head)
{
	struct client* list = head;
	while (list != 0)
	{
		printf("Client socket %d successfully added to the list\n", list->numClient);
		list = list->next;
	}
}

// pushBack for Client 
int pushBack(struct client** head, int numClient)
{
	//struct client* fd = 0;
	struct client* fd = (struct client*)malloc(sizeof(struct client));
	struct client* list = *head;
	fd->numClient = numClient;
	fd->next = 0;

	if (list == 0)
	{
		*head = fd;
		return 0;
	}
	while (list->next != 0)
	{
		list = list->next;
	}
	list->next = fd;
	return 0;
}

Это вывод сервера:
SockOpt Value: 1. Error code: 0
Setup Socket descriptor number: 256
Socket value after "Select": 256
CHECK FD_ISSET value #1: 1
We have 1 user online
Server got connection from 127.0.0.1
CHECK FD_ISSET value #2: 1
Checking now newSocketDescriptor = 256, readfs(j) status = 1
SockOpt Value: 1. Error code: 0
Recv error - Error code: 10057 

И вывод клиента:
Attempt to connect 127.0.0.1:3765
Connect error Connection with 127.0.0.1
Send function - OK
Received text: ä
Send function - OK
Received text: ä
Send function - OK
Received text: ä
Send function - OK
Received text: ä
Send error - Error code: 10054


Что я уже пробовал:

Как я понимаю, я получаю несколько "полученных текстов", так как моя функция send и recv (клиентская часть) находится в цикле while(1). Что я должен делать с этим? Чтобы вывести их из цикла, чтобы сообщение прошло только один раз или что? Не могу себе представить, как это было в моем случае.

У меня есть мысль, что сервер пытается отправить одно сообщение, но так как клиент пытается отправить несколько (из-за while(1)), соединение прерывается, и я получаю ошибку 10054.Как это можно исправить?

Я также попытался проверить опцию сокета с помощью "getsockopt", и она должна вернуть 0, если все в порядке, я получил 1. - Что случилось?

И я также не могу понять, как исправить мою "ошибку Recv" (код 10057) в клиентской части.

P.S. прежде чем я пришел к этому решению, я исправил около 7 ошибок и некоторые ошибки, но полностью застрял в этой части. Я буду признателен за любую помощь и советы. Спасибо

Ошибка 10054: сброс подключения к равному.
Существующее соединение было принудительно закрыто удаленным хостом. Обычно это происходит, если одноранговое приложение на удаленном хосте внезапно останавливается, хост перезагружается, хост или удаленный сетевой интерфейс отключаются или удаленный хост использует жесткое закрытие (см. setsockopt для получения дополнительной информации о опции SO_LINGER на удаленном сокете). Эта ошибка также может возникнуть, если соединение было разорвано из-за активности keep-alive, обнаруживающей сбой во время выполнения одной или нескольких операций. Операции, которые осуществляются не с WSAENETRESET. Последующие операции завершаются неудачей с WSAECONNRESET.

Ошибка 10057: розетка не подключена.
Запрос на отправку или получение данных был запрещен, поскольку сокет не подключен и (при отправке на сокет дейтаграммы с помощью sendto) адрес не был указан. Любой другой тип операции также может вернуть эту ошибку—например, setsockopt setting SO_KEEPALIVE, если соединение было сброшено.

Patrice T

а "ошибка 10054" должна что-то значить для нас ?

GeorgeZv

Ошибка 10054: сброс подключения к равному.
Существующее соединение было принудительно закрыто удаленным хостом. Обычно это происходит, если одноранговое приложение на удаленном хосте внезапно останавливается, хост перезагружается, хост или удаленный сетевой интерфейс отключаются или удаленный хост использует жесткое закрытие (см. setsockopt для получения дополнительной информации о опции SO_LINGER на удаленном сокете). Эта ошибка также может возникнуть, если соединение было разорвано из-за активности keep-alive, обнаруживающей сбой во время выполнения одной или нескольких операций. Операции, которые осуществляются не с WSAENETRESET. Последующие операции завершаются неудачей с WSAECONNRESET.

Ошибка 10057: розетка не подключена.
Запрос на отправку или получение данных был запрещен, поскольку сокет не подключен и (при отправке на сокет дейтаграммы с помощью sendto) адрес не был указан. Любой другой тип операции также может вернуть эту ошибку—например, setsockopt setting SO_KEEPALIVE, если соединение было сброшено.

Patrice T

Воспользуйся Улучшить вопрос чтобы обновить ваш вопрос.
Чтобы каждый мог обратить внимание на эту информацию.

GeorgeZv

Ладно, спасибо. Я все исправлю.

1 Ответов

Рейтинг:
1

Jochen Arndt

Ваш серверный код прерывается здесь с ошибкой 10057 (как указано в выходных данных):

if ((numBytes = recv(socketDescriptor, bufferData, MAXDATASIZE, 0)) == SOCKET_ERROR)
{
    printf("Recv error ");
    break;
}
Причина в том, что socketDescriptor это не подключенная розетка. Подключенный сокет-это тот, который возвращается accept(): newSocketDescriptor.

Решение:
Заменять socketDescriptor около newSocketDescriptor во всех вызовах, связанных с соединением (как правило, все функции сокета после accept() вызов). Проверьте также FD_SET() и и FD_CLR() звонки, потому что они, похоже, также используют перепутанные сокеты.


GeorgeZv

Спасибо, это сработало, если я поменяю свой socketDescriptor. Как я вижу, все в порядке с FD_SET () и FD_CLR ().

Теперь мой сервер отправляет и получает сообщения, но не без проблем. Сервер может возвращать от 2 до 5 "Привет" - сообщений, от клиента сообщения находятся в цикле, и я получаю их до тех пор, пока мой my tv.tv_sec = 5 не истечет. Я не могу ясно понять, как отделить отправку сообщений от цикла while (1), не касаясь send и recv.

Jochen Arndt

Спасибо, что приняли мое решение.

Честно говоря, я не могу понять вашу новую проблему (какие условия / тайм-ауты должны быть обработаны).

GeorgeZv

Извините, я дал неполную информацию.

Ну вот, теперь мой серверный вывод выглядит так:

Setup socket descriptor number: 120
Socket value after "Select": 120
CHECK FD_ISSET value #1: 1
We have 1 user online
Server got connection from 127.0.0.1
CHECK FD_ISSET value #2: 1
Checking now newSocketDescriptor = 120, readfs(j) status = 1
SockOpt Value: 0. Error code: 0
Checking now newSocketDescriptor = 120, readSocketDescriptor status = 1
Recv: retVal = 1, bufferData = Hello, world!
Hello, world!
Hello, world!
Hello, world!

No users online
***************
CHECK FD_ISSET value #1: 1 


И клиент в бесконечном цикле:
Send function - OK
Received text: Hello, world!

Send function - OK
Received text: Hello, world!

Send function - OK
Received text: Hello, world!

Send function - OK
Received text: Hello, world!

Send function - OK
Received text: Hello, world!

Send function - OK
Received text: Hello, world!


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

Jochen Arndt

Если вы хотите остановиться после выхода одного сообщения, закройте соединения на клиенте и сервере после этого.

Клиент:
Снять while(1) цикл (требуется перезапуск приложения для повторного подключения).
Если вы хотите повторно подключиться, не закрывая приложение, вы должны реализовать соответствующий код.

Сервер:
Закрывать newSocketDescriptor в конце концов FD_ISSET() блок. При следующей попытке подключения эта проверка снова будет верной, и при принятии будет создан новый сокет.

Обратите внимание, что в вашем коде есть еще одна ошибка:
При завершении работы приложения, newSocketDescriptor на самом деле он закрыт, но должен быть SocketDescriptor.

GeorgeZv

Извините за долгую паузу,я отсутствовал в течение долгих выходных.
Прежде всего, спасибо за все ваши ответы.
Когда я попытался закрыть SocketDescriptor, мой клиент полностью перестал работать. Я немного переписал свой сервер, не могли бы вы проверить его, пожалуйста? У меня все еще есть проблемы с повторным сообщением, и у меня есть код ошибки в выводе клиента (10054, не могу его исправить).

И я также написал структурный клиент и функцию pushBack, чтобы добавить моих новых подключенных клиентов в список, но не уверен, что это работает нормально. Кажется, я добавляю что-то неправильное там в цикле while(1).

#pragma comment(lib, "ws2_32.lib")
#define _WINSOCK_DEPRECATED_NO_WARNINGS
#define _CRT_SECURE_NO_WARNINGS
#include <winsock2.h>
#include <stdio.h>
#include <stdlib.h>
#include <conio.h>
//#include "list.h"

#define PRINTUSERS if (nclients)\
	printf("We have %d user online\n", nclients);\
	else printf("No users online\n***************\n");

#define PORT 3765
#define BACKLOG 10
#define MAXDATASIZE 1024
#define MAXUSERSONLINE 64

struct client
{
	int numClient;
	struct client* next;
};

void print(struct client* head);
int pushBack(struct client** head, int numClient);
int checkSockOpt(int socket);

int main(int argc, char* argv[])
{
	struct sockaddr_in serverAddr;
	struct sockaddr_in clientAddr;

	SOCKET socketDescriptor;
	fd_set readSocketDescriptor;
	fd_set writeSocketDescriptor;
	int newSocketDescriptor;

	WSADATA wsaData;
	WORD wsaStart;
	timeval tv;

	// LIST TEST #1
	struct client* head = 0;
	pushBack(&head, 111);
	pushBack(&head, 222);
	pushBack(&head, 999);
	print(head); //

	int retVal;
	int clientAddrSize = sizeof(clientAddr);
	char bufferData[MAXDATASIZE] = { 0 };
	int nclients = 0;
	int numBytes = 0;

	if (argc > 1)
	{
		printf("Uses: <chatserver>\n");
		return -1;
	}
	do
	{
		if ((wsaStart = WSAStartup(MAKEWORD(2, 2), &wsaData)) != 0)
		{
			printf("WSAStartup error ");
			break;
		}
		if ((socketDescriptor = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) == INVALID_SOCKET)
		{
			printf("Socket error ");
			closesocket(socketDescriptor);
			break;
		}

		serverAddr.sin_family = AF_INET;
		serverAddr.sin_port = htons(PORT);
		serverAddr.sin_addr.s_addr = INADDR_ANY;

		if (bind(socketDescriptor, (struct sockaddr *)&serverAddr, sizeof(serverAddr)) == SOCKET_ERROR)
		{
			printf("Bind error ");
			closesocket(socketDescriptor);

			break;
		}
		if (listen(socketDescriptor, BACKLOG) == SOCKET_ERROR)
		{
			printf("Listening error ");
			closesocket(socketDescriptor);
			break;
		}

		while (1)
		{

			FD_ZERO(&readSocketDescriptor);
			FD_ZERO(&writeSocketDescriptor);
			FD_SET(socketDescriptor, &readSocketDescriptor);
			FD_SET(socketDescriptor, &writeSocketDescriptor);

			printf("Setup SERVER socket descriptor number: %d\n", socketDescriptor);
			tv.tv_sec = 5;

			// Do not forget to put after WHILE(1)
			if ((retVal = select(socketDescriptor + 1, &readSocketDescriptor, &writeSocketDescriptor, NULL, &tv)) == SOCKET_ERROR)
			{
				printf("Select error ");
				break;
			}
			if ((FD_ISSET(socketDescriptor, &readSocketDescriptor)) != 0)
			{
				printf("CHECK FD_ISSET value #1: %d\n", FD_ISSET(socketDescriptor, &readSocketDescriptor));
				//int clientList[MAXUSERSONLINE] = { 0 };
				//for (int i = 0; i <= MAXUSERSONLINE; i++)
				//{

				if ((newSocketDescriptor = accept(socketDescriptor, (struct sockaddr *)&clientAddr, &clientAddrSize)) == SOCKET_ERROR)
				{
					printf("Accept error ");
					break;
				}
				//	newSocketDescriptor = clientList[i];
				struct client* head = 0;
				pushBack(&head, newSocketDescriptor);
				print(head); //
				nclients++; PRINTUSERS

					printf("Server got connection from %s\n", inet_ntoa(clientAddr.sin_addr));
				FD_SET(newSocketDescriptor, &readSocketDescriptor);
				//FD_CLR(socketDescriptor, &readSocketDes

GeorgeZv

ой, кажется, мне не хватает места для моего кода, я "улучшил" свой вопрос.

Jochen Arndt

Это становится нечитаемым. Комментарии не предназначены для размещения больших блоков кода.

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

Вы также должны попытаться заставить код работать так, как ожидалось, прежде чем добавлять новые функции, такие как pushBack.

GeorgeZv

Еще раз спасибо за внимание.
Меня попросили создать этот список с помощью функции pushBack. Вот новый вопрос: https://www.codeproject.com/Questions/1184365/Multiplexing-I-O-client-server-message-exchange