Мультиплексирование ввода-вывода клиент-сервер (язык 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
Ладно, спасибо. Я все исправлю.