суббота, 18 августа 2007 г.

Я и Tcp/IP

Превед, кодер! )
Лето в самом разгаре. Море, пиво.. ну вобщем-то все атрибуты этого времени года :)
Однако, это не мешает нам продолжать заниматься любимым делом, и расти в профессиональном плане. Этим летом я попытался осуществить свою давнюю мечту - написать приолжение клиент-сервер.
Что-же мы будем делать? А делать мы будем простенькую программу, которая получает с удаленной машины, скажем, имя текущего юзера в системе.

Для начала немного теории относительно того, как это всё будет работать. Сначала на определённый порт вешается сервер (устанавливается логическое соединение сервера с сокетом, к этому сокету привязываются порт и адресс, далее сокет помечается как прослушиваемый и далее ожидается входящее подключение), ждёт подключения клиента.
Далее, шлёт клиенту строчку "ready", после чего принимает от клиента нужные нам данные (имя юзера с удалённой машины).

Теперь о работе клиента. Программа-клиент получает дескриптор сокета, с которым после получения дескриптора устанавливает соединение. Далее принимаем от сервера сообщение (ту самую строчку "ready"), и шлём имя юзера, предварительно получив его при помощи winapi функции GetUserName.

Итак, начнём с написания северной части.


#include <stdio.h>
#include <winsock2.h>
#include <conio.h>

int InitConnection(int port, char* addr); // прототип функции, которая устанавливает соединение с сокетом.

int main()
{
WORD wVersionRequested;
WSADATA wsaData;
SOCKET hSocket, client_socket;


sockaddr_in client_addr;

char msg[25];

int msg_len, client_addr_len;


wVersionRequested = MAKEWORD( 2, 0 );
WSAStartup(wVersionRequested, &wsaData);

В этом участке кода ключевую роль играет функция WSAStartup(), которая сообщает windows что мы собираемся использовать wsocks. Данная функция требует два параметра - указатель на структуру WSADATA и требуемую версию.
Теперь о объявленных переменных:
hSocket - логический дескриптор сокета (с которым сервер установит соединение)
client_socket - дескриптор сокета, через который будет происходить общение сервера с клиентом.

hSocket = InitConnection(7600, "127.0.0.1");
if( hSocket == -1 )
{
getch();
exit(1);

}
puts("Connected");


Тут вызывается функция InitConnection (нами-же и написаная) которая устанавливает соединение и "ставит на прослушку" порт. В качестве параметров принимает номер порта (целое число) и адресс (char*). Здесь мы указали 127.0.0.1 - т.е. localhost. Что-бы тестить на нашей локальной машине.

client_addr_len = sizeof(client_addr);
while(true)
{
puts("Waiting for connection...");
client_socket = accept(hSocket, (LPSOCKADDR)&client_addr, &amp;amp;amp;amp;amp;client_addr_len);
if (client_socket == INVALID_SOCKET)
continue;

break;

}


В этом цикле ожидается подключение. Функция accept(), которая ожидает подключения, "вешает" программу до тех пор пока не подключится клиент. В качестве параметров принимает дескриптор серверного сокета, адресс структуры sockaddr_in, в которую будет записан адресс подключившегося клиента. Последний параметр указывает на размер данной структуры.
После того как клиент поключился, в случае если client_socket == INVALID_SOCKET цикл продолжается, в противном случае мы выходим из цикла.

send(client_socket, "ready", strlen("ready"), 0);
msg_len = recv(client_socket, msg, 25, 0);
msg[ msg_len ] = '\0';

printf("User name is %s\n", msg);

closesocket(hSocket);
WSACleanup();

getch();


return 0;
}


Тут уже всё просто. Функция send() шлёт клиенту буффер данных (второй параметр) определённого размера (третий параметр). Далее, принимает от клиента сообщение при помощи функции recv(). Второй параметр этой функции указывает на буффер куда будут помещены принятые данные, тертий параметр - размер входящих данных.
(тут мы задали размер данных 25 байт. однако в более сложных задачах лучше сначала принимать от клиента размер сообщения который он собирается послать, после чего принимать это сообщение, зная его размер)
Функция recv() dвозвращает длину полученого сообщения, поэтому в необходимо в этот (последний) байт нашего буфера поместить '\0'. Далее печатаем полученое сообщение, закрываем сокет, завершаем работу с сокетами ( WSACleanup()) и выходим из программы.

Ниже представлена функция InitConnection(int port, char* addr):

InitConnection(int port, char* addr)
{
struct sockaddr_in sock_struct; int res;
SOCKET hSocket;

sock_struct.sin_family = AF_INET;

sock_struct.sin_port = htons(port);
sock_struct.sin_addr.s_addr = inet_addr(addr);

hSocket = socket(AF_INET, SOCK_STREAM, 0);
if(hSocket<0)
{
printf("err! Can not get socket handle\n");

return -1;

}

res = bind(hSocket, (struct sockaddr*)&sock_struct, sizeof(sock_struct));
if(res<0)> {
printf("Can not bind\n");
return -1;

}

res = listen(hSocket, 5);
if(res)

{
printf("Can not listen()\n");
return -1;
}

return hSocket; //Success

}


Сначала заполняется структура
sockaddr_in, далее при помощи функции socket() устанавливается логическое соединение с сокетом, производится привязка адреса и порта к сокету (функция bind()) и вызывается функция listen() для пометки порта как прослушиваемого. В случае успеха возвращается дескриптор сокета.
Это всё относительно того что касается сервера.

Теперь поговорим о клиенте. Отрезок программы от начала до вызова InitConnection() идентичен. Ниже представлен код, на момент когда клиент уже успешно подключился к серверу:

msg_len = recv(hSocket, msg, 25, 0);
msg[ msg_len ] = '\0';
GetUserName(msg, &nSize);

send(hSocket, msg, strlen(msg), 0);


Тут, думаю, всё понятно. Получается первое сообщение от сервера (та самая строчка "ready", при желании её можно вывести на экран). Далее вызывается функция
GetUserName(msg, &nSize) (nSize - переменная типа DWORD, которая объявлена как DWORD nSize = 25. Не забываем подключить windows.h для этой функции). После шлём полученое имя серверу. После, как и в случае с сервером, закрываем соединение, сообщаем об окончании использования wsocks и завершаем программу.

Функция InitConnection() опять-же очень похожа на ту что мы писали для сервера. Поэтому, привожу её код без комментариев:

int InitConnection(int port, char* addr)
{
struct sockaddr_in sock_struct;
int connect_res;
SOCKET hSocket;

char c[10];


sock_struct.sin_family = AF_INET;

sock_struct.sin_port = htons(port);
sock_struct.sin_addr.s_addr = inet_addr(addr);

hSocket = socket(AF_INET, SOCK_STREAM, 0);

if(hSocket<0)
{
printf("err! Can not get socket handle\n");
return -1;
}

connect_res = connect(hSocket, (struct sockaddr*)&sock_struct, sizeof(sock_struct));


if(connect_res!=0)
{

wsprintf(c, "%d", GetLastError());
printf("Can not connect to the socket. Error %s\n", c);

return -1;

}

return hSocket; //Success

}


Единственное сушественное различие, это вызов функции connect(), при помощи которой устанавливается соединение с сервером.

Вот, собственно, и всё чем я хотел с вами поделиться.
Надо отметить что за tcp/ip я взялся пару дней назад, так что не судите строго, если что :)

зы не забываем подключить необходимую либу для работы с сокетами (по-моему WS2_32.lib )))

Комментариев нет:

[Кодер]::Лого :) - просто как всё гениальное.