- •57 Програмне забезпечення мереж еом. Частина 3. Відання 2. Програмування інтерфейсу «WinSocket»
- •Лабораторна робота № 1. Основні теоретичні положення (інтерфейс windows sockets)
- •1.1. Сокети, датаграми й канали зв'язку
- •1.1.1. Ініціалізація додатка й завершення його роботи
- •1.1.2. Створення й ініціалізація сокету
- •1.3. Індивідуальні завдання на роботу №1
- •Лабораторна робота № 2. Створення каналу зв'язку
- •2.1. Сторона сервера
- •2.2. Сторона клієнта
- •2.3. Передача й прийом даних
- •2.4. Додаток ServerSocket
- •2.5. Додаток ClientSocket
- •2.6. Індивідуальні завдання на роботу №2
- •Лабораторна робота № 3. Датаграммный протокол
- •3.1. Додаток ServerDSocket
- •3.2. Додаток ClientDSocket
- •3.3. Індивідуальні завдання на роботу №3
Лабораторна робота № 3. Датаграммный протокол
3.1. Додаток ServerDSocket
У деяких випадках доцільно використати протокол негарантованої доставки UDP, тому що він, наприклад, допускає одночасне розсилання пакетів всім вузлам мережі (у режимі broadcast).
Якщо вузли обмінюються даними з використанням датаграммного протоколу UDP, їм не потрібно створювати канал даних, тому процедура ініціалізації виходить простіше.
Сервер UDP повинен створити сокет за допомогою функції socket і прив'язати до нього адреса IP, викликавши функцію bind. Клієнт UDP виконує створення й ініціалізацію сокетів аналогічним образом за допомогою все тих же функцій socket і bind.
Такі відомі з попередніх додатків функції, як connect, listen і accept, у додатках UDP використати не потрібно.
Для обміну даними додатка UDP викликають функції sendto і recvfrom, аналогічні функціям send і recv, але одна відмінність, що мають - при виклику цих функцій їм необхідно задавати додаткові параметри, що мають відношення до адрес вузлів. Функції sendto потрібно вказати адресу, по якому буде відправлений пакет даних, а функції recvfrom - покажчик на структуру, у яку буде записана адреса відправника пакета.
Нижче наведені вихідні тексти додатків ServerDSocket і ClientDSocket, які виконують ті ж завдання, що й тільки що розглянуті додатки ServerSocket і ClientSocket, але при цьому вони передають дані за допомогою датаграммного протоколу UDP.
Вихідний текст додатка ServerDSocket наведений у листинге 3.1.
Листинг 3.1.
Файл ServerDSocket/ ServerDSocketDlg.h
// ServerDSocketDlg.h : header file
//
#if !defined(AFX_SERVERDSOCKETDLG_H__1C346AD0_D15F_4D1E_A567_B2131C6C611D__INCLUDED_)
#define AFX_SERVERDSOCKETDLG_H__1C346AD0_D15F_4D1E_A567_B2131C6C611D__INCLUDED_
#if _MSC_VER > 1000
#pragma once
#endif // _MSC_VER > 1000
#include <winsock2.h>
/////////////////////////////////////////////////////////////////////////////
// CServerDSocketDlg dialog
class CServerDSocketDlg : public CDialog
{
private:
void ServerStart();
void ServerStop();
void ShowData( char * );
public:
CServerDSocketDlg(CWnd* pParent = NULL); // standard constructor
// Dialog Data
//{{AFX_DATA(CServerDSocketDlg)
enum { IDD = IDD_SERVERDSOCKET_DIALOG };
CEdit m_edtShowData;
int portNum;
//}}AFX_DATA
// ClassWizard generated virtual function overrides
//{{AFX_VIRTUAL(CServerDSocketDlg)
protected:
virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV support
//}}AFX_VIRTUAL
// Implementation
protected:
HICON m_hIcon;
// Методи для роботи із сокетуми
BOOL InitLib();
// Змінні для роботи із сокетом
SOCKET srv_socket; //Дескриптор серверного сокету
SOCKET clientSocket; // Дескриптор клієнтського сокету
SOCKADDR_IN clientAddr; //Структура sockaddr_in
int css_len; // розмір структури sockadd_in
// Generated message map functions
//{{AFX_MSG(CServerDSocketDlg)
virtual BOOL OnInitDialog();
afx_msg void OnSysCommand(UINT nID, LPARAM lParam);
afx_msg void OnPaint();
afx_msg HCURSOR OnQueryDragIcon();
afx_msg LRESULT OnWSANetEvent(WPARAM wParam, LPARAM lParam);
afx_msg void OnBtnStart();
afx_msg void OnBtnStop();
afx_msg void OnClose();
//}}AFX_MSG
DECLARE_MESSAGE_MAP()
};
//{{AFX_INSERT_LOCATION}}
// Microsoft Visual C++ will insert additional declarations immediately before the previous line.
#endif // !defined(AFX_SERVERDSOCKETDLG_H__1C346AD0_D15F_4D1E_A567_B2131C6C611D__INCLUDED_)
Файл ServerDSocket/ ServerDSocketDlg.cpp
// ServerDSocketDlg.cpp : implementation file
//
#include "stdafx.h"
#include "ServerDSocket.h"
#include "ServerDSocketDlg.h"
#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif
#define WSA_NETEVENT (WM_USER+2)
/////////////////////////////////////////////////////////////////////////////
// CAboutDlg dialog used for App About
class CAboutDlg : public CDialog
{
public:
CAboutDlg();
// Dialog Data
//{{AFX_DATA(CAboutDlg)
enum { IDD = IDD_ABOUTBOX };
//}}AFX_DATA
// ClassWizard generated virtual function overrides
//{{AFX_VIRTUAL(CAboutDlg)
protected:
virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV support
//}}AFX_VIRTUAL
// Implementation
protected:
//{{AFX_MSG(CAboutDlg)
//}}AFX_MSG
DECLARE_MESSAGE_MAP()
};
CAboutDlg::CAboutDlg() : CDialog(CAboutDlg::IDD)
{
//{{AFX_DATA_INIT(CAboutDlg)
//}}AFX_DATA_INIT
}
void CAboutDlg::DoDataExchange(CDataExchange* pDX)
{
CDialog::DoDataExchange(pDX);
//{{AFX_DATA_MAP(CAboutDlg)
//}}AFX_DATA_MAP
}
BEGIN_MESSAGE_MAP(CAboutDlg, CDialog)
//{{AFX_MSG_MAP(CAboutDlg)
// No message handlers
//}}AFX_MSG_MAP
END_MESSAGE_MAP()
/////////////////////////////////////////////////////////////////////////////
// CServerDSocketDlg dialog
CServerDSocketDlg::CServerDSocketDlg(CWnd* pParent /*=NULL*/)
: CDialog(CServerDSocketDlg::IDD, pParent)
{
//{{AFX_DATA_INIT(CServerDSocketDlg)
portNum = 0;
//}}AFX_DATA_INIT
// Note that LoadIcon does not require a subsequent DestroyIcon in Win32
m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME);
}
void CServerDSocketDlg::DoDataExchange(CDataExchange* pDX)
{
CDialog::DoDataExchange(pDX);
//{{AFX_DATA_MAP(CServerDSocketDlg)
DDX_Control(pDX, IDC_EDIT1, m_edtShowData);
DDX_Text(pDX, IDC_EDT_PORT, portNum);
DDV_MinMaxInt(pDX, portNum, 0, 65535);
//}}AFX_DATA_MAP
}
BEGIN_MESSAGE_MAP(CServerDSocketDlg, CDialog)
//{{AFX_MSG_MAP(CServerDSocketDlg)
ON_WM_SYSCOMMAND()
ON_WM_PAINT()
ON_WM_QUERYDRAGICON()
ON_MESSAGE(WSA_NETEVENT,OnWSANetEvent)
ON_BN_CLICKED(IDC_BTN_START, OnBtnStart)
ON_BN_CLICKED(IDC_BTN_STOP, OnBtnStop)
ON_WM_CLOSE()
//}}AFX_MSG_MAP
END_MESSAGE_MAP()
/////////////////////////////////////////////////////////////////////////////
// CServerDSocketDlg message handlers
BOOL CServerDSocketDlg::OnInitDialog()
{
CDialog::OnInitDialog();
// Add "About..." menu item to system menu.
// IDM_ABOUTBOX must be in the system command range.
ASSERT((IDM_ABOUTBOX & 0xFFF0) == IDM_ABOUTBOX);
ASSERT(IDM_ABOUTBOX < 0x000);
CMenu* pSysMenu = GetSystemMenu(FALSE);
if (pSysMenu != NULL)
{
CString strAboutMenu;
strAboutMenu.LoadString(IDS_ABOUTBOX);
if (!strAboutMenu.IsEmpty())
{
pSysMenu->AppendMenu(MF_SEPARATOR);
pSysMenu->AppendMenu(MF_STRING, IDM_ABOUTBOX, strAboutMenu);
}
}
// Set the icon for this dialog. The framework does this automatically
// when the application's main window is not a dialog
SetIcon(m_hIcon, TRUE); // Set big icon
SetIcon(m_hIcon, FALSE); // Set small icon
portNum = 8888;
UpdateData(FALSE);
// Ініціалізація сокету
if ( !InitLib() )
{
return FALSE;
}
return TRUE; // return TRUE unless you set the focus to a control
}
void CServerDSocketDlg::OnSysCommand(UINT nID, LPARAM lParam)
{
if ((nID & 0xFFF0) == IDM_ABOUTBOX)
{
CAboutDlg dlgAbout;
dlgAbout.DoModal();
}
else
{
CDialog::OnSysCommand(nID, lParam);
}
}
// If you add a minimize button to your dialog, you will need the code below
// to draw the icon. For MFC applications using the document/view model,
// this is automatically done for you by the framework.
void CServerDSocketDlg::OnPaint()
{
if (IsIconic())
{
CPaintDC dc(this); // device context for painting
SendMessage(WM_ICONERASEBKGND, (WPARAM) dc.GetSafeHdc(), 0);
// Center icon in client rectangle
int cxIcon = GetSystemMetrics(SM_CXICON);
int cyIcon = GetSystemMetrics(SM_CYICON);
CRect rect;
GetClientRect(&rect);
int x = (rect.Width() - cxIcon + 1) / 2;
int y = (rect.Height() - cyIcon + 1) / 2;
// Draw the icon
dc.DrawIcon(x, y, m_hIcon);
}
else
{
CDialog::OnPaint();
}
}
// The system calls this to obtain the cursor to display while the user drags
// the minimized window.
HCURSOR CServerDSocketDlg::OnQueryDragIcon()
{
return (HCURSOR) m_hIcon;
}
BOOL CServerDSocketDlg::InitLib()
{
int rc;
WSADATA WSAData;
char szTemp[128];
// Ініціалізація й перевірка версії Windows Sockets
rc = WSAStartup(MAKEWORD(2, 2), &WSAData);
if(rc != 0)
{
MessageBox("WSAStartup Error", "Error", MB_OK);
return FALSE;
}
// Відображаємо опис і версію системи Windows Sockets
// у заголовку вікна
wsprintf(szTemp, "Server use %s %s",
WSAData.szDescription,WSAData.szSystemStatus);
SetWindowText( szTemp );
return TRUE;
}
LRESULT CServerDSocketDlg::OnWSANetEvent(WPARAM wParam, LPARAM lParam)
{
char szTemp[256];
int rc;
SOCKADDR_IN addr;
int nAddrSize;
nAddrSize = sizeof(nAddrSize);
if(WSAGETSELECTEVENT(lParam) == FD_READ)
{
// Приймаємо дані
rc = recvfrom((SOCKET)wParam, szTemp, 256, 0, (PSOCKADDR)&addr, &nAddrSize);
if(rc >0)
{
szTemp[rc] = '\0';
// Відображаємо адресу вилученого клієнта
// і отриману від нього рядок
ShowData( szTemp );
}
}
return 0L;
}
void CServerDSocketDlg::ServerStart()
{
SOCKET old_socket = srv_socket;
srv_socket = socket(AF_INET, SOCK_DGRAM, 0);
if(srv_socket == INVALID_SOCKET)
{
srv_socket = old_socket;
return;
}
sockaddr_in serverAddr;
serverAddr.sin_family = AF_INET;
serverAddr.sin_addr.s_addr = INADDR_ANY;
serverAddr.sin_port = htons(portNum);
// Зв'язуємо адреса IP із сокетом
if( bind(srv_socket, (LPSOCKADDR)&serverAddr,
sizeof(serverAddr)) == SOCKET_ERROR)
{
// При помилці закриваємо сокет
MessageBox("Bind error", "Error", MB_OK);
srv_socket = old_socket;
return;
}
TRACE("Bind port: %d ", portNum);
int rc = WSAAsyncSelect(srv_socket, m_hWnd, WSA_NETEVENT, FD_READ);
if(rc == SOCKET_ERROR )
{
MessageBox("WSAAsyncSelect (Read) Error", "Error", MB_OK);
srv_socket = old_socket;
return;
}
ShowData( "DServer start..." );
}
void CServerDSocketDlg::ServerStop()
{
WSAAsyncSelect(srv_socket, m_hWnd, 0, 0);
closesocket( clientSocket );
closesocket( srv_socket );
ShowData( "DServer stop..." );
}
void CServerDSocketDlg::ShowData(char * szBuf )
{
TRACE("Test1: %s\n", szBuf);
CString strData;
m_edtShowData.GetWindowText( strData );
strData += szBuf;
strData += "\r\n";
TRACE("Test2: %s\n", strData);
m_edtShowData.SetWindowText( strData );
}
void CServerDSocketDlg::OnBtnStart()
{
UpdateData();
ServerStart();
}
void CServerDSocketDlg::OnBtnStop()
{
ServerStop();
}
void CServerDSocketDlg::OnClose()
{
closesocket( srv_socket );
closesocket( clientSocket );
WSACleanup();
CDialog::OnClose();
}
Додаток ServerDSocket багато в чому нагадує додаток ServerSocket, тому можна розглянути тільки відмінності.
Перша відмінність полягає в тім, що при запуску сервера тип створюваного сокету вказується як SOCK_DGRAM:
srv_socket = socket(AF_INET, SOCK_DGRAM, 0);
Далі виконується ініціалізація сокету і його прив'язка до адреси, для чого викликається функція bind. Ця операція, як і у випадку протоколу TCP, необов'язкова.
Після виконання прив'язки можна приступати до одержання пакетів даних від клієнта. Для того щоб не виконувати очікування пакетів у циклі, додаток використає функцію WSAAsyncSelect, указуючи з її допомогою, що при одержанні пакетів даних головне вікно додатка повинне одержувати повідомлення з кодом WSA_NETEVENT:
rc = WSAAsyncSelect(srv_socket, hWnd, WSA_NETEVENT, FD_READ);
На цьому ініціалізація сервера завершується.
Оброблювач повідомлення WSA_NETEVENT читає отриманий пакет за допомогою функції recvfrom:
SOCKADDR_IN addr;
int nAddrSize;
nAddrSize = sizeof(addr);
rc = recvfrom((SOCKET)wParam, szTemp, 256, 0, (PSOCKADDR)&addr, &nAddrSize);
Як передостанній параметр цієї функції передається адреса структури типу SOCKADDR_IN, куди функція записує адресу вузла, що надіслав пакет. Останній параметр функції recvfrom повинен містити розмір зазначеної структури. Нижче наведені можливі коди помилок для функції recvfrom.
Код помилки Опис
WSANOTINITIALISED Перед використанням функції необхідно викликати функциюWSAStartup
WSAENETDOWN Збій у мережі
WSAEFAULT Віршиком мале значення параметра, що визначає розмір буфера для прийому даних
WSAEINTR Робота функції була скасована за допомогою функції WSACancelBlockingCall
WSAEINPROGRESS Виконується функція, що блокує, інтерфейсу Windows Sockets
WSAEINVAL Сокет не був підключений функцією bind
WSAENOTSOCK Зазначений дескриптор не є дескриптором сокету
WSAESHUTDOWN Сокет був закритий функцією shutdown
WSAEWOULDBLOCK Сокет відзначений як що не блокує, але запитана операція приведе до блокування
WSAEMSGSIZE Розмір датаграми занадто великий, тому відповідний блок даних не міститься в буфер. Прийнятий блок даних був обрізаний
WSAECONNABORTED Збій через занадто велику затримку або з іншої причини
WSAECONNRESET Скидання з'єднання вилученим вузлом
При обміні даними з використанням протоколу UDP на кожний виклик функції sendto повинен доводитися один виклик функції recvfrom. Якщо ж передаються дані через канал з використанням протоколу TCP, на один виклик функції send може доводитися кілька викликів функції recv.
Для відображення адреси вузла, що послав пакет UDP, додаток перетворить ця адреса в символьний рядок за допомогою функції inet_ntoa:
lpAddr = inet_ntoa(addr.sin_addr);
Ця функція записує отриманий рядок у статичну область пам'яті, що належить системі Windows Sockets, тому для подальшого використання необхідно скопіювати рядок до наступного виклику будь-якої функції програмного інтерфейсу Windows Sockets.
