На главную
В этой статье описывается создание двух простых консольных приложений,
одно из которых (intcp) является сервером и получает сообщения, а другое
(outtcp) является клиентом и отправляет сообщение серверу, используя
протокол TCP/IP. Сообщения успешно отправляются и принимаются на одном
компьютере и на разных компьютерах в одной локальной сети.
Взаимодействие этих приложений через интернет автором пока не
проверялось.
Приложение Outtcp
Код приложения содержит 3 функции:
1) функция ctime() возвращает текущее время
2) функция main() содержит основной код приложения
3) функция getSocketError() выдает сообщение об ошибке
Функция main()
Приведем полностью код функции main():
#pragma argsused int main(int argc, char* argv[]) { char clientRequest[REQUEST_MSG_SIZE]; sockaddr_in serverAddr; //server's socket address int sockAddrSize; //size of socket address structure SOCKET sHandle; //socket descriptor unsigned nbyte; //number of sended bytes double starttime, endtime; unsigned mlen; //message's length char* serverName; //server's name WSADATA wsaData; //data structure that is to receive //details of the Windows Sockets //implementation
Здесь мы объявляем переменные, используемые функцией.
serverName = argv[1];
В этой строке мы присваиваем серверу имя, которое представляет собой
IP-адрес, полученный в качестве параметра при запуске программы. Если
программа будет запущена без параметров, возникнет ошибка. При наличии
более одного параметра, остальные параметры будут проигнорированы. Если
оба приложения (outtcp и intcp) будут запускаться на одном компьютере,
то в качестве имени сервера следует указывать 127.0.0.1
//initiate use of WS2_32.DLL by a process if(WSAStartup(MAKEWORD(1, 1), &wsaData)) { getSocketError(); getch(); return 0; }
Функция WSAStartup() должна
быть вызвана в первую очередь любым приложением или библиотекой DLL,
которые используют сокеты. В качестве первого параметра указывается
версия Windows Sockets, которую может использовать вызывающая программа.
При успешном завершении этой функции структура wsaData заполняется информацией,
характеризующей данную реализацию Windows Sockets.
Здесь мы создаем сокет с помощью функции socket(). Первый параметр этой
функции - формат, в котором будет задан адрес, а второй параметр - тип
создаваемого сокета. Для версии Windows Sockets 1.1 можно указывать
только один из двух типов: SOCK_STREAM (создается двунаправленный поток
байтов, используется протокол TCP) или SOCK_DGRAM (поддерживает передачу
дейтаграмм, используется протокол UDP). С помощью третьего параметра
можно указать протокол, используемый сокетом.
Здесь мы преобразуем полученный в качестве параметра при запуске
программы адрес (который пока представлен в виде строки) в формат,
соответствующий протоколу. Если адрес является недействительным, функция inet_addr() возвращает INADDR_NONE.
//connect to server if(connect(sHandle, (sockaddr *)&serverAddr, sockAddrSize) == -1) { getSocketError(); closesocket(sHandle); getch(); return 0; }
Функция connect() получает в
качестве параметров дескриптор сокета, адрес структуры, содержащей
информацию об адресе сервера, и размер этой структуры.
//fill string to send printf("Introduce the string to send:\n"); gets(clientRequest); mlen = strlen(clientRequest); if(mlen >= REQUEST_MSG_SIZE) { printf("Max length of message is %d bytes !\n", REQUEST_MSG_SIZE-1); getch(); return 0; }
Запрашиваем у пользователя ввод строки, которую нужно передать серверу.
Отправляем серверу введенную пользователем строку. Функция send() возвращает количество
переданных байт или значение SOCKET_ERROR при неуспешном завершении.
if(nbyte != mlen) printf("Write only %d (%d) bytes\n", nbyte, mlen);
Выводим время, затраченное на пересылку данных и закрываем сокет с
помощью функции closesocket().
printf("Press any key\n"); getch(); return 0; }
Функция
getSocketError()
void getSocketError() { LPVOID lpMsgBuf; if (!FormatMessage( FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, NULL, GetLastError(), MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // Default language (LPTSTR) &lpMsgBuf, 0, NULL )) { MessageBox(NULL, "Error number not found", "Error", MB_OK | MB_ICONSTOP ); return; } // Display the string. MessageBox(NULL, (LPCTSTR)lpMsgBuf, "Error", MB_OK | MB_ICONSTOP); // Free the buffer. LocalFree(lpMsgBuf); }
Функция getSocketError()
выводит диалоговое окно с сообщением об ошибке. Текст сообщения мы
получаем с помощью функции FormatMessage(),
которая анализирует код ошибки, возвращаемый функцией Windows API GetLastError().
Приложение intcp
Код серверного приложения intcp содержит функции main() и getSocketError().
Функция main()
int main() { sockaddr_in serverAddr; // server's socket address sockaddr_in clientAddr; // client's socket address int sockAddrSize; // size of socket address structure SOCKET sHandle; // socket file descriptor SOCKET newSHandle; // socket descriptor from accept WSADATA wsaData; //data structure that is to receive details //of the Windows Sockets implementation int nbyte = 0; //number of sended bytes char clientRequest[REQUEST_MSG_SIZE];
// set up the local address sockAddrSize = sizeof(sockaddr_in); memset(&serverAddr, 0, sockAddrSize); serverAddr.sin_family = AF_INET; serverAddr.sin_port = htons(SERVER_PORT_NUM); serverAddr.sin_addr.s_addr = INADDR_ANY;
Структура serverAddr заполняется почти так же, как и в предыдущем
приложении, но полю serverAddr.sin_addr.s_addr присваивается значение
INADDR_ANY. Это означает, что IP-адрес клиента заранее не определен.
// initiate use of WS2_32.DLL by a process if(WSAStartup(MAKEWORD(1, 1), &wsaData)) { getSocketError(); getch(); return 0; }
// bind socket to local address if(bind(sHandle, (sockaddr *)&serverAddr, sockAddrSize) == -1 ) { getSocketError(); closesocket(sHandle); getch(); return 0; }
Использование функций WSAStartup()
и socket() аналогично
предыдущему приложению. После удачного завершения функции socket() мы вызываем функцию bind(), которая связывает локальный
адрес с сокетом.
Функция listen() переводит
сокет в состояние ожидания запросов от клиентов. Эту функцию следует
использовать для сокетов типа SOCK_STREAM. Функция listen() обычно используется
серверными приложениями.
// accept new connect requests and spawn tasks to process them // Note: newSHandle socket will take propreties of // sHandle socket if((newSHandle = accept(sHandle, (sockaddr *)&clientAddr, &sockAddrSize)) == INVALID_SOCKET) { getSocketError(); closesocket(sHandle); getch(); return 0; }
Функция accept() извлекает из
очереди запросов первый запрос для данного сокета, затем создает новый
сокет, который и должен обработать этот запрос. Созданный сокет обладает
точно такими же свойствами, что и первоначальный сокет, дескриптор
которого передается функции в качестве первого параметра. При успешном
завершении функция accept()
возвращает дескриптор нового сокета.
Здесь мы получаем сообщение от клиента с помощью функции recv(). Если четвертый параметр
функции имеет значение MSG_PEEK, это означает, что функция будет
считывать входящие данные. При этом данные копируются в буфер clientRequest (второй параметр), но
не удаляются из очереди входящих данных. Если функция завершается
удачно, то возвращаемое значение равно количеству полученных байт. В
противном случае возвращается SOCKET_ERROR.
Примечание:
сначала следует запускать intcp. В противном случае при запуске outtcp
будет выдано сообщение об ошибке: "Подключение не установлено, т.к.
конечный компьютер отверг запрос на подключение". После запуска intcp
запустите outtcp (указав адрес сервера), введите строку, которая будет
передана приложению intcp и нажмите Enter. Строка будет выведена в окне
приложения intcp.