Мультиплексирование ввода-вывода клиент-сервер (обмен сообщениями)
Я заявил, что изучаю язык Си, и теперь пытаюсь разобраться с 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 дней, в чем проблема. Не могли бы вы помочь мне, пожалуйста?
Я также не понимаю, почему я получаю несколько сообщений в серверной и клиентской частях (каждый раз в клиентской части есть случайный символ, тоже не понимаю почему). Почему?
Спасибо за внимание, вопрос большой, извините за это.