GeorgeZv Ответов: 1

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


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

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

Позвольте мне показать вам мой код сервера и клиента, и я объясню, что и как я хочу сделать и с какими проблемами я столкнулся.
Серверный код:
<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;
}

Клиентский код:
<pre>
#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;

	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, Earth!\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;
}

Клиент: не уверен, но, кажется, все почти в порядке. Я получаю свой sockDescriptor, делаю свой сокет неблокирующим с помощью функции ioctlsocket, а затем устанавливаю соединение с помощью connect. Поскольку мне нужно отправить сообщение, Я устанавливаю свой набор записей с помощью FD_ZERO и FD_SET и использую функцию select. Кажется, все в порядке (или нет?)

После того, как я должен записать свою логику в цикле while(1). Я настраиваю свои дескрипторы чтения и записи, функцию выбора пользователя (правильно ли она используется?) и проверяю свой дескриптор записи, готов ли он к отправке сообщения. Затем посылать. И то же самое с recv. После этого мне нужно добавить символ конца строки "\0 " и распечатать мой текст, полученный с сервера.

Сервер: прежде всего я хочу поговорить о списке подключенных клиентов. Я написал структуру (клиент) и несколько функций для работы с ними (в конце этого поста я покажу свою функцию списка, как она должна работать). Функция буксировки составить список, распечатать, очевидно, печатать список. Как добавить моего нового клиента в этот список? И еще мне нужно очистить память. Поскольку я не понял, как добавить моего клиента, я не знаю, как это очистить.

Тогда мой сервер логика: гнездо -и GT; персонализация -&ГТ; слушать и в то время как(1) где мне настроить мой readfds и writefds и GT;. Затем я использую select для своего socketDescriptor и, если все в порядке, проверяю, готов ли он к работе. Затем примите для моего нового клиента пользователя (newSocketDescriptor - это то, что я должен добавить в свой список?). Затем я пытаюсь добавить в свой список (даже не уверен, что он работает нормально). nclients-это мой клиентский счетчик. Затем я делаю FD_SET для своего newSocketDescriptor, и после этого вот моя проблема, когда я даже не уверен, что начало происходить. Я использую recv, проверяю, отключен ли пользователь, а если нет-отправляю сообщение и FD_CLR мой newSocketDescriptor.
Сервер вывод в cmd:
Setup SERVER socket descriptor number: 256
CHECK FD_ISSET value #1: 1
Client socket 264 successfully added to the list
We have 1 user online
Server got connection from 127.0.0.1
CHECK FD_ISSET value #2: 1
newSocketDescriptor = 264, READ_FD status #1 after accept = 1
SockOpt Value: 0. Error code: 0
Client socket 264 successfully added to the list
Client socket 264 successfully added to the list
Recv: retVal = 1, bufferData = Hello, Earth!
Hello, Earth!
Hello, Earth!

newSocketDescriptor = 264, READ_FD status #2 after recv = 1
newSocketDescriptor = 264, READ_FD status #3 after send = 1
Setup SERVER socket descriptor number: 256

Клиент выход:
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 function - OK
Received text: ä
Send function - OK
eceived text: ä

Send error - Error code: 10054

Как вы можете видеть, у меня есть ошибка с кодом 10054, но я не мог получить ее в течение 1,5 дней, в чем проблема. Не могли бы вы помочь мне, пожалуйста?

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

Спасибо за внимание, вопрос большой, извините за это.

1 Ответов

Рейтинг:
12

Patrice T

Цитата:
Я также не понимаю, почему я получаю несколько сообщений в серверной и клиентской частях (каждый раз в клиентской части есть случайный символ, тоже не понимаю почему). Почему?

Когда вы не понимаете, что делает ваш код или почему он делает то, что делает, ответ таков: отладчик.
Используйте отладчик, чтобы увидеть, что делает ваш код. Просто установите точку останова и посмотрите, как работает ваш код, отладчик позволяет вам выполнять строки 1 на 1 и проверять переменные по мере их выполнения, это невероятный инструмент обучения.

Отладчик-Википедия, свободная энциклопедия[^]
Освоение отладки в Visual Studio 2010 - руководство для начинающих[^]
Базовая отладка с помощью Visual Studio 2010-YouTube[^]
Отладчик здесь для того, чтобы показать вам, что делает ваш код, и ваша задача-сравнить его с тем, что он должен делать.
В отладчике нет никакой магии, он не находит ошибок, он просто помогает вам. Когда код не делает того, что ожидается, вы близки к ошибке.