ФЕДЕРАЛЬНОЕ АГЕНТСТВО ПО ОБРАЗОВАНИЮ БРЯНСКИЙ ГОСУДАРСТВЕННЫЙ ТЕХНИЧЕСКИЙ УНИВЕРСИТЕТ Кафедра «Информатика и программное обеспечение» Разработка приложения «Чат» КУРСОВОЙ ПРОЕКТ по дисциплине «Сети ЭВМ и телекоммуникации» Выполнил гр. 02-ПО2 Степуро С. А. Преподаватель Дроздов А. В. БРЯНСК 2006 Задание Разработать клиент – серверное приложение «Чат» для общения в локальной сети в режиме реального времени. Основанием для разработки служит задание на курсовой проект. Описание работы Для работы чата, должны быть запушены на удаленных машинах серверная часть (для этого необходимо запустить Chat_Server.exe. рис.1) и клиентская - Chat_Client.exe (максимум 50 клиентов(Max_Socket_Conect 50)). Рис.1 Интерфейс серверной части При нажатии кнопки Start Server – окно программы автоматически сворачивается: void CChat_ServerDlg::OnBnClickedButton1() { ShowWindow(SW_MINIMIZE); //Установить окно в свёрнутый вид Now_Socket_Conect=-1; AfxBeginThread(Start_Server, NULL, THREAD_PRIORITY_NORMAL); //Старт потока } и сервер начинает свою работу: //Начало работы сервера. Инициализация сокетов, подключение клиентов. UINT Start_Server (LPVOID pParam) { BOOL Run=TRUE; BOOL NO_Error=TRUE; char buf[5]; WSADATA wsaData; int iResult = WSAStartup(MAKEWORD(2,2), &wsaData); //Инициализация библиотеки WS2_32.DLL для сокетов if (iResult != NO_ERROR) printf("Error at WSAStartup()\n"); SOCKET ServSocket; sockaddr saClient; int iClientSize = sizeof(saClient); sockaddr_in saServer; hostent* localHost; char* localIP; // создание слушающего сокета ServSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); if (ServSocket == INVALID_SOCKET) { printf("Error at socket(): %ld\n", WSAGetLastError()); WSACleanup(); } Сервер автоматически узнает IP адрес машины на которой он запущен // Получение информации о Айпи-адресе localHost = gethostbyname(""); localIP = inet_ntoa (*(struct in_addr *)*localHost->h_addr_list); // Set up the sockaddr structure saServer.sin_family = AF_INET; saServer.sin_addr.s_addr = inet_addr(localIP); saServer.sin_port = htons(1000); // Связь сокета с портом if (bind( ServSocket, (SOCKADDR*) &saServer, sizeof(saServer)) == SOCKET_ERROR) { printf("bind() failed.\n"); closesocket(ServSocket); } И производит установку сокета в режим прослушивания на предмет подключения: if (listen( ServSocket, Max_Socket_Conect ) == SOCKET_ERROR) AfxMessageBox("Error listening on socket.\n"); При запуске клиентской части загружается форма для ввода имени и IP-адреса сервера: form.DoModal(); (рис.2). Рис.2 форма для ввода имени и IP-адреса сервера Осуществляется проверка на не пустое имя: if (strlen(form.Your_Name)==0) return FALSE; и запуск функции инициализации данных и старта потока приёма сообщений от сервера: Start_Get_Data(); Соединение с сервером происходит следующим образом: if ( connect( ConnectSocket, (SOCKADDR*) &clientService, sizeof(clientService) ) == SOCKET_ERROR) { AfxMessageBox("FAILED Connected "); WSACleanup(); return FALSE; AfxMessageBox("Connect to server is Successful"); } Connect=TRUE; При успешном подключении ожидаем когда сервер передаст клиенту его номер iResult= recv( ConnectSocket, buf, sizeof(buf), 0); if (iResult == SOCKET_ERROR) { AfxMessageBox("Server is lost. Reconnect to server."); WSACleanup(); return FALSE; } ID=atoi(buf); return TRUE; } Сервер же, при попытке подключения к нему нового клиента, выполняет следующие действия: while(Run) { если число подключенцев меньше чем максимальное if (Now_Socket_Conect!=Max_Socket_Conect+1) { NO_Error=TRUE; while( NO_Error ) { увеличить число подключенцев Now_Socket_Conect++; //Ожидание соединения AcceptSocket[Now_Socket_Conect] = accept(ServSocket, &saClient, &iClientSize); if (AcceptSocket[Now_Socket_Conect] == SOCKET_ERROR) { NO_Error=FALSE;; break; } Передает клиенту его порядкового номера itoa(Now_Socket_Conect,buf,10);//конвертация в строку iResult=send( AcceptSocket[Now_Socket_Conect], buf, strlen(buf), 0/*MSG_DONTROUTE*/); if (iResult == SOCKET_ERROR) { NO_Error=FALSE; AfxMessageBox("FAILED send data"); WSACleanup(); } else И запускает поток для принятия сообщений от клиента AfxBeginThread(Listen_Thread, (LPVOID)AcceptSocket[Now_Socket_Conect], THREAD_PRIORITY_NORMAL); NO_Error=FALSE; } } } WSACleanup(); return 0; } Приём сообщений от клиента UINT Listen_Thread (LPVOID pParam) //содержит номер сокета клиента { int iResult; char lpBuffer[SEND_BUFFER_SIZE]; char lpBuffer_ID[4]; char lpBuffer_Name[20]; пока сервер запущен while ( Run ) { получаем от клиента сообщения Result=recv((SOCKET)pParam, lpBuffer, SEND_BUFFER_SIZE, 0); if (iResult == SOCKET_ERROR) { //AfxMessageBox("FAILED receive data ID "); Run=FALSE; break; //если клиент отключился мы просто его забываем } //если получено число байт равное числу кодового сообщения if (iResult == 24) { //то скопировать их в буфер memcpy(lpBuffer_ID,lpBuffer,4); memcpy(lpBuffer_Name,lpBuffer+4,20); //и проверить их на "выход" if (!memcmp( lpBuffer_Name, "Exit", 4 )) Del_Client(atoi(lpBuffer_ID));//если да то удалить клиента } else//если клиент всё же прислал сообщение то разослать его всем { AfxBeginThread(Send_Message_to_All, (LPVOID)lpBuffer, THREAD_PRIORITY_NORMAL); } } return 0; } Отправка сообщений клиентом происходит следующим образом: //Функция отправки сообщений на сервер void CChat_ClientDlg::OnBnClickedButton1() { CString In; CString Out; char lpBuf[5]; char lpBuffer[SEND_BUFFER_SIZE]; int len = Type_String.LineLength(Type_String.LineIndex(0)); //получение размера сообщения int iResult; Если сообщение не пустое, то заполнить буфер для отправки на сервер данными о номере клиента его имени и сообщением if (len>0) { Type_String.GetLine(0, In.GetBuffer(len), len);//Записать его в буфер In.Left(1000);//и обрезать до 1000 символов itoa(ID,lpBuf,10); // memcpy(lpBuffer,lpBuf,4); memcpy(lpBuffer+4,form.Your_Name,20); memcpy(lpBuffer+24,In,1000); Заполненный буфер отправляется на сервер: iResult=send(ConnectSocket, lpBuffer, SEND_BUFFER_SIZE, 0); В случае ошибки подключения выдается сообщение if (iResult == SOCKET_ERROR) { AfxMessageBox("Server not found. Reconnect to server."); WSACleanup(); } Рис.3 Ошибка подключения Сервер, получив сообщение от клиента, рассылает его всем остальным клиентам: //Отправка сообщений от одного клиента всем клиентам, через сервер. UINT Send_Message_to_All (LPVOID pParam) //содержит сообщение от клиента { int iResult; char lpBuffer[SEND_BUFFER_SIZE]; memcpy(lpBuffer,pParam,SEND_BUFFER_SIZE); //Получаем в буфер сообщение от клиента for (int i=0;i<Now_Socket_Conect;i++) //Всем клиентам из массива сокетов { //отправляем сообщение iResult=send(AcceptSocket[i], lpBuffer, SEND_BUFFER_SIZE, 0/*MSG_DONTROUTE*/); if (iResult == SOCKET_ERROR) { AfxMessageBox("FAILED send data All"); return FALSE; } } return 0; } Приём сообщений от сервера пока клиент запущен while(!STOP) { //Ожидать и получать сообщения от сервера iResult= recv(mS.socket, lpBuffer, SEND_BUFFER_SIZE, 0); if (iResult == SOCKET_ERROR) { AfxMessageBox("Server is lost. Reconnect to server."); return FALSE; } посылаем окну сообщение ::PostMessage(HWND(pParam),WM_USERMSG_DRAW,0,0); Вывод сообщения в окно LRESULT CChat_ClientDlg::Draw_Data(WPARAM wParam, LPARAM lParam) { char lpBuffer[SEND_BUFFER_SIZE]; char lpBuffer_Name[20]; char lpBuffer_Data[1000]; //Формируем 2 выводных буфера (Имя + сообщение) memcpy(lpBuffer,mS.Data,SEND_BUFFER_SIZE); memcpy(lpBuffer_Name,lpBuffer+4,20); memcpy(lpBuffer_Data,lpBuffer+24,1000); //Выводим в окно Chat_Windows.ReplaceSel("FROM: ",0); //От кого: Chat_Windows.ReplaceSel(lpBuffer_Name,0); //Имя Chat_Windows.ReplaceSel(" MESSAGE: ",0); //Сообщение: Chat_Windows.ReplaceSel(lpBuffer_Data,0);//Текст сообщения Chat_Windows.ReplaceSel("\r\n",0);//Перевод каретки return 0; } При необходимости пользователь может очистить окно чата, нажав на кнопку Clear. Очистка окна: void CChat_ClientDlg::OnBnClickedButton2() { Chat_Windows.SetWindowText(""); } Закрытие клиента: void CChat_ClientDlg::OnClose() { STOP=TRUE; if(Connect)//Если клиент был подключён к серверу { char lpBuffer[24]; char lpBuffer_ID[4]; int iResult; //Формируем специальное сообщение для сервера itoa(ID,lpBuffer_ID,10); memcpy(lpBuffer,lpBuffer_ID,4); //Номер клиента Формируем сообщение для сервера, о том что клиент закрывается: memcpy(lpBuffer+4,"Exit",4); И отправляем его: iResult=send(ConnectSocket, lpBuffer, 24, 0); if (iResult == SOCKET_ERROR) { //AfxMessageBox("Server is lost. Reboot programm."); WSACleanup(); } } OnCancel(); } Сервер, получив сообщение «на выход» клиента удаляет его if (!memcmp( lpBuffer_Name, "Exit", 4 )) Del_Client(atoi(lpBuffer_ID)); Если какой-либо из клиентов отключился, то остальные клиенты меняют свой номер: //Изменение номера клиента LRESULT CChat_ClientDlg::Change_ID(WPARAM wParam, LPARAM lParam)//содержит новый номер клиента { ID=(int)wParam; mS.ID=(int)wParam; return 0; } Отключение сервера void CChat_ServerDlg::OnBnClickedButton2() { Run=FALSE; OnCancel(); } Список литературы 1. Компьютерные сети. Принципы, технологии, протоколы / В.Г. Олифер, Н.А. Олифер. – Спб; Издательство «Питер», 2000. 2. Фролов А. В., Фролов Г. В. Локальные сети персональных компьютеров. Использование протоколов IPX, SPX, NetBIOS. – М.: Диалог-МИФИ, 1995. – 158 с. 3. Программирование в Microsoft Visual / И. И. Данилов –Спб; Издательство «Питер».