Программирование на C++ глазами хакера

Передача данных по сети с помощью CSocket


Как я уже говорил, работа с сокетами происходит по технологии "клиент-сервер". Сервер запускается на определенном порту и начинает ожидать соединение. Клиент подключается на этот порт, и после этого может обмениваться данными с сервером.

Посмотрим, как передача данных выглядит на практике. Создайте новый проект MFC Application и назовите его MFCSendText. В мастере измените параметры так же, как и в предыдущем примере со сканером портов (см. разд. 4.4). Точно так же добавьте класс от TSocket. Точнее сказать, два класса: один для клиента, а другой — для сервера, и будут они называться CClientSocket и CServerSocket соответственно. Как видите, из одного класса CSocket выводятся два класса: для сервера и для клиента.

Теперь оформим главное окно программы. Для этого откройте в редакторе ресурсов диалоговое окно IDD_MFCSENDTEXT_DIALOG и поместите на него четыре кнопки с заголовками Create Server (IDC_BUTTON1), Connect to Server (IDC_BUTTON2), Send Data (IDC_BUTTON3), Disconnect (IDC_BUTTON4). Внизу окна поместите Static Text для вывода сообщений.

Для кнопки Send Data создайте переменную. Для этого надо щелкнуть по ней правой кнопкой мышки и в появившемся меню выбрать пункт Add Variable. В окне Мастера создания переменной в поле Variable name укажите m_SendButton.

Теперь переходим к программированию. Для начала рассмотрим файл ServerSocket.h, в котором находится объявление класса CServerSocket. Его содержимое вы можете увидеть в листинге 4.6.

Листинг 4.6. Содержимое файла ServerSocket.h
#pragma once

#include "MFCSendTextDlg.h"

// CServerSocket command target // (Определение класса CServerSocket)

class CServerSocket : public CSocket { public: CServerSocket(CMFCSendTextDlg* Dlg); virtual ~CServerSocket(); virtual void OnAccept(int nErrorCode); virtual void OnConnect(int nErrorCode); protected: CMFCSendTextDlg* m_Dlg; public: virtual void OnClose(int nErrorCode); };



Первое, что я изменил — это конструктор. Теперь CServerSocket имеет один параметр Dlg типа CMFCSendTextDlg. Через этот параметр будет передаваться указатель на основной класс, чтобы была возможность обращаться к нему из класса CServerSocket. В разделе protected объявлена переменная для хранения указателя на класс главного окна.


class CClientSocket : public CSocket { public: CClientSocket(CMFCSendTextDlg* Dlg); virtual ~CClientSocket(); virtual void OnReceive(int nErrorCode); virtual void OnClose(int nErrorCode); protected: CMFCSendTextDlg* m_Dlg; };

Здесь также модифицирован конструктор, чтобы сохранять информацию о классе, создавшем класс клиента CClientSocket. Для этого заведена такая же переменная m_Dlg.

Помимо этого, введены два метода: OnReceive (вызывается, когда по сети пришли новые данные) и OnClose (вызывается, когда соединение завершено).

Теперь посмотрим, как все это реализовано в файле ClientSocket.cpp (листинг 4.9).

Листинг 4.9. Содержимое файла ClientSocket.cpp
// ClientSocket.cpp : implementation file

#include "stdafx.h" #include "MFCSendText.h" #include "ClientSocket.h"

// CClientSocket

CClientSocket::CClientSocket(CMFCSendTextDlg* Dlg) { m_Dlg = Dlg; }

CClientSocket::~CClientSocket() { }

void CClientSocket::OnReceive(int nErrorCode) { char recstr[1000]; int r=Receive(recstr,1000); recstr[r]='\0'; m_Dlg-SetDlgItemText(IDC_STATIC, recstr);

CSocket::OnReceive(nErrorCode); }

void CClientSocket::OnClose(int nErrorCode) { m_Dlg-m_SendButton.EnableWindow(FALSE);

CSocket::OnClose(nErrorCode); }

Самое важное находится в методе OnReceive. Он вызывается каждый раз, когда для клиента пришли по сети какие-то данные. Для чтения полученных данных используется метод Receive. У него два параметра:

буфер, в который будут записаны полученные данные, — переменная recstr;

размер буфера.

Метод возвращает количество полученных по сети данных. Это значение записывается в переменную r. Теперь в переменной recstr находятся полученные данные, но по правилам языка С строки должны заканчиваться нулевым символом. Добавим его в буфер за последним полученным символом:

recstr[r]='\0';

Теперь полученный текст копируем в компонент Static Text на диалоговом окне с помощью следующей строки кода:

m_Dlg-SetDlgItemText(IDC_STATIC, recstr);

Метод OnClose вызывается каждый раз, когда соединение завершено. В его коде кнопку Send Data надо сделать недоступной, потому что без соединения с сервером нельзя отправлять данные.



m_Dlg-m_SendButton.EnableWindow(FALSE);

Сейчас перейдем к рассмотрению главного модуля программы — MFCSendTextDlg. Начнем разбор с заголовочного файла (листинг 4.10).

Листинг 4.10. Заголовочный файл MFCSendTextDlg.h
// MFCSendTextDlg.h : header file

#pragma once #include "afxwin.h"

class CServerSocket; class CClientSocket;

class CMFCSendTextDlg : public CDialog { // Construction (Коструктор) public: // standard constructor // (стандартный конструктор) CMFCSendTextDlg(CWnd* pParent = NULL);

// Dialog Data (Данные диалога) enum { IDD = IDD_MFCSENDTEXT_DIALOG };

protected: // DDX/DDV support (Поддержка обмена данными) virtual void DoDataExchange(CDataExchange* pDX);

// Implementation protected: HICON m_hIcon; CServerSocket* m_sSocket; CClientSocket* m_cSocket; CClientSocket* m_scSocket;

// Generated message map functions // (Сгенерированные функции карты сообщений) virtual BOOL OnInitDialog(); afx_msg void OnSysCommand(UINT nID, LPARAM lParam); afx_msg void OnPaint(); afx_msg HCURSOR OnQueryDragIcon(); DECLARE_MESSAGE_MAP() public: afx_msg void OnBnClickedButton1(); afx_msg void OnBnClickedButton2(); afx_msg void OnBnClickedButton3(); CButton m_SendButton; afx_msg void OnBnClickedButton4(); void AddConnection(); };

Здесь введены три переменные в разделе protected:

m_sSocket — указатель на класс CServerSocket;

m_cSocket и m_scSocket — указатели на класс CClientSocket.

А в разделе public добавлен один метод void AddConnection().

Теперь создайте поочередно обработчики события для всех кнопок диалогового окна. Для этого необходимо щелкнуть на кнопке правой кнопкой мышки и в появившемся меню выбрать пункт Add Event Handler. Давайте рассмотрим каждый обработчик события в отдельности.

Для кнопки Create Server будет следующий обработчик:

void CMFCSendTextDlg::OnBnClickedButton1() { // TODO: Add your control notification handler code here m_sSocket=new CServerSocket(this); m_sSocket-Create(22345); m_sSocket-Listen(); SetDlgItemText(IDC_STATIC, "Server started"); }



Здесь необходимо создать сервер и запустить прослушивание порта (ожидание соединений клиентов). В первой строке инициализируется переменная m_sSocket. Она имеет тип класса CServerSocket, поэтому в качестве параметра надо передать конструктору указатель на текущий класс. Это делается с помощью ключевого слова this.

После этого вызывается метод Create, у которого в качестве единственного параметра необходимо указать номер порта, на котором будет работать сервер. Теперь можно запускать прослушивание с помощью метода Listen.

Сервер запущен, и через компонент Static Text в окне диалога выводится соответствующее сообщение.

В обработчике события для кнопки Connect To Server надо написать следующий код:

void CMFCSendTextDlg::OnBnClickedButton2() { // ТОDО: Add your control notification handler code here m_cSocket = new CClientSocket(this); m_cSocket-Create(); if (m_cSocket-Connect("127.0.0.1", 22345)) m_SendButton. EnableWindow( TRUE); }

В первой строке инициализируется переменная m_cSocket. Следующей строкой кода создается класс. Теперь можно соединяться с сервером. Для этого используется метод Connect. Существует несколько реализаций данного метода, и они отличаются количеством и типом передаваемых параметров. В нашем случае используются следующие параметры:

IP-адрес в виде строки;

порт, на который необходимо подключиться.

Если соединение прошло успешно, то метод вернет ненулевое значение. Проверяется результат, и если все нормально, то кнопка Send Data делается доступной.

Отправка данных происходит, когда пользователь нажимает кнопку Send Data. Код, который должен находиться в обработчике события, выглядит следующим образом:

void CMFCSendTextDlg::0nBnClickedButton3() { // TODO: Add your control notification handler code here m_cSocket-Send("Hello", 100);

int err=m_cSocket-GetLastError(); if(err0) { CString ErrStr; ErrStr.Format("errcode=%d",err); AfxMessageBox ( ErrStr ); } }

Отправка данных происходит с помощью метода Send объекта-клиента m_cSocket. У него два параметра:



данные, которые надо отправить, — строка "Hello";

размер данных. В данном случае нужно было бы указать 5, потому что отправляемое слово содержит 5 символов (5 байт), но в примере указано 100. Это не приведет к ошибке, но позволит вам легко изменять отправляемую строку. В реальных приложениях обязательно указывайте истинную длину строки.

До этого я практически не проверял производимые действия на ошибки. А при запуске сервера это делать необходимо, потому что, если сервер уже запущен, то повторная попытка приведет к ошибке. Кроме того, может возникнуть ситуация, когда на компьютере пользователя не установлен протокол TCP, тогда тоже будут ошибки.

При отправке данных такая проверка есть. С помощью метода GetLastError можно получить код ошибки последней операции в классе m_cSocket. Если результат метода GetLastError больше нуля, то была ошибка.

В обработчике события кнопки Disconnect выполняется следующий код:

void CMFCSendTextDlg::OnBnClickedButton4() { // ТODO: Add your control notification handler code here SetDlgItemText(IDC_STATIC, "Disconnected"); m_cSocket-Close(); }

Первой строкой в текстовое поле в окне диалога выводится сообщение о том, что соединение разорвано. Во второй строке вызывается метод Close, который закрывает соединение с сервером.

Теперь самое интересное — метод AddConnection, который я уже использовал, когда произошло соединение с сервером. Посмотрим, что в нем происходит:

void CMFCSendTextDlg::AddConnection() { m_scSocket = new CClientSocket(this); m_sSocket-Accept(*m_scSocket); }

Как видите, здесь создается новый объект типа CClientSocket. После этого он присоединяется к серверу m_sSocket методом Accept. Так переменная класса CClientSocket связывается с новым соединением. Именно через эту переменную сервер может отослать данные к клиенту, и именно через нее он принимает данные.

Получается, что один класс CClientSocket используется на клиенте для соединения с сервером и отправки ему данных, а на сервере — для получения и возврата данных. Класс CServerSocket используется только для прослушивания порта и получения соединения.



Данный пример будет хорошо работать только тогда, когда один клиент соединяется с сервером. Если второй клиент попытается соединиться, то переменная m_scSocket будет перезаписана для нового клиента. Именно поэтому на сервере вы должны хранить динамический массив классов типа CClientSocket. При подключении клиента вы должны создавать новый класс типа CClientSocket и сохранять его в массиве, а при отключении клиента соответствующий класс должен уничтожаться из массива.

Напоследок хочется заметить, что я нигде не указывал протокол, по которому будут работать клиент с сервером. По умолчанию класс CSocket использует TCP/IP.

Примечание
Исходный код примера, описанного в этом разделе, вы можете найти на компакт - диске в каталоге \Demo\Chapter4\MFCSendText.

Содержание раздела