1. Модуль 3: Основные понятия объектно-ориентированного
программирования.
Темы лекции: Программирование сокетов в С++. Работа с
графикой.
Практическое задание: Программирование сокетов в С++.
Тренер: Игорь Шкулипа, к.т.н.
C++ Базовый. Занятие 13
3. http://www.slideshare.net/IgorShkulipa 3
Сокет
Сокет (socket) - это конечная точка сетевых коммуникаций. Он
является чем-то вроде «порта», через который можно
отправлять байты во внешний мир.
Приложение просто пишет данные в сокет; их дальнейшая
буферизация, отправка и транспортировка осуществляется
используемым стеком протоколов и сетевой аппаратурой.
Чтение данных из сокета происходит аналогичным образом.
В программе сокет идентифицируется дескриптором - это
просто переменная типа int. Программа получает дескриптор
от операционной системы при создании сокета, а затем
передаёт его сервисам socket API для указания сокета, над
которым необходимо выполнить то или иное действие.
#include <sys/types.h>
#include <sys/socket.h>
4. http://www.slideshare.net/IgorShkulipa 4
Атрибуты сокета
С каждым сокет связываются три атрибута: домен, тип и
протокол. Эти атрибуты задаются при создании сокета и
остаются неизменными на протяжении всего времени его
существования.
Для создания сокета используется функция socket, имеющая
следующий прототип.
#include <sys/types.h>
#include <sys/socket.h>
int socket(int domain, int type, int protocol);
5. http://www.slideshare.net/IgorShkulipa 5
Домен сокета
Домен определяет пространство адресов, в котором располагается сокет,
и множество протоколов, которые используются для передачи данных.
Чаще других используются домены Unix и Internet, задаваемые
константами AF_UNIX и AF_INET соответственно (префикс AF означает
"address family" - "семейство адресов").
◦ При задании AF_UNIX для передачи данных используется файловая
система ввода/вывода Unix. В этом случае сокеты используются для
межпроцессного взаимодействия на одном компьютере и не годятся
для работы по сети.
◦ Константа AF_INET соответствует Internet-домену. Сокеты,
размещённые в этом домене, могут использоваться для работы в
любой IP-сети.
◦ Существуют и другие домены (AF_IPX для протоколов Novell,
AF_INET6 для новой модификации протокола IP - IPv6 и т. д.)
6. http://www.slideshare.net/IgorShkulipa 6
Тип сокета
Тип сокета определяет способ передачи данных по сети:
SOCK_STREAM. Передача потока данных с предварительной установкой
соединения. Обеспечивается надёжный канал передачи данных, при
котором фрагменты отправленного блока не теряются, не
переупорядочиваются и не дублируются.
SOCK_DGRAM. Передача данных в виде отдельных сообщений
(датаграмм). Предварительная установка соединения не требуется.
Обмен данными происходит быстрее, но является ненадёжным:
сообщения могут теряться в пути, дублироваться и
переупорядочиваться. Допускается передача сообщения нескольким
получателям (multicasting) и широковещательная передача
(broadcasting).
SOCK_RAW. Этот тип присваивается низкоуровневым сокетам. Их
отличие от обычных сокетов состоит в том, что с их помощью
программа может взять на себя формирование некоторых заголовков,
добавляемых к сообщению.
7. http://www.slideshare.net/IgorShkulipa 7
Протокол сокета
Последний атрибут определяет протокол, используемый для
передачи данных.
Часто протокол однозначно определяется по домену и типу
сокета. В этом случае в качестве третьего параметра функции
socket можно передать 0, что соответствует протоколу по
умолчанию.
Но, иногда (например, при работе с низкоуровневыми сокетами)
требуется задать протокол явно.
Числовые идентификаторы протоколов зависят от выбранного
домена; их можно найти в документации.
8. http://www.slideshare.net/IgorShkulipa 8
Адрес сокета
Прежде чем передавать данные через сокет, его необходимо
связать с адресом в выбранном домене.
Иногда связывание осуществляется неявно (внутри функций
connect и accept), но выполнять его необходимо во всех
случаях.
Для явного связывания сокета с некоторым адресом
используется функция bind.
int bind(int sockfd, struct sockaddr *addr, int addrlen);
9. http://www.slideshare.net/IgorShkulipa 9
Аргументы bind
В качестве первого параметра передаётся дескриптор сокета,
который мы хотим привязать к заданному адресу.
Второй параметр, addr, содержит указатель на структуру с
адресом, а третий - длину этой структуры.
struct sockaddr
{
unsigned short sa_family;
char sa_data[14];
};
Поле sa_family содержит идентификатор домена, тот же, что и
первый параметр функции socket.
В зависимости от значения этого поля по-разному
интерпретируется содержимое массива sa_data.
10. http://www.slideshare.net/IgorShkulipa 10
Альтернативные адреса
Вместо sockaddr, можно использовать одну из альтернативных
структур вида sockaddr_XX
(XX - суффикс, обозначающий домен: "un" - Unix, "in" - Internet и
т. д.).
При передаче в функцию bind указатель на эту структуру
приводится к указателю на sockaddr.
struct sockaddr_in
{
short int sin_family;
unsigned short int sin_port;
struct in_addr sin_addr;
unsigned char sin_zero[8];
};
11. http://www.slideshare.net/IgorShkulipa 11
Поля sockaddr_in
sin_family соответствует полю sa_family в sockaddr
в sin_port записывается номер порта
в sin_addr - IP-адрес хоста.
sin_addr само является структурой
sin_zero – дополнение до размера основной структуры
struct in_addr
{
unsigned long s_addr;
};
Раньше in_addr представляла собой объединение (union),
содержащее гораздо большее число полей. Сейчас, когда в
ней осталось всего одно поле, она продолжает использоваться
для обратной совместимости.
12. http://www.slideshare.net/IgorShkulipa 12
Преобразование IP адреса в in_addr
Для преобразования IP адреса в in_addr можно использовать
функцию inet_aton из заголовочного файла arpa/inet.h
#include <arpa/inet.h>
int inet_aton(const char *cp, struct in_addr *inp);
13. http://www.slideshare.net/IgorShkulipa 13
Установка соединения со стороны сервера
Установка соединения на стороне сервера состоит из следующих этапов:
1. Сначала сокет создаётся и привязывается к локальному адресу.
2. На следующем шаге создаётся очередь запросов на соединение. При
этом сокет переводится в режим ожидания запросов со стороны
клиентов. Всё это выполняет функция listen.
int listen(int sockfd, int backlog);
Первый параметр - дескриптор сокета, а второй задаёт размер очереди
запросов.
Каждый раз, когда очередной клиент пытается соединиться с сервером,
его запрос ставится в очередь, так как сервер может быть занят
обработкой других запросов. Если очередь заполнена, все
последующие запросы будут игнорироваться.
14. http://www.slideshare.net/IgorShkulipa 14
Установка соединения со стороны сервера
Когда сервер готов обслужить очередной запрос, он использует функцию
accept.
int accept(int sockfd, void *addr, int *addrlen);
Функция accept создаёт для общения с клиентом новый сокет и
возвращает его дескриптор.
Параметр sockfd задаёт слушающий сокет. После вызова он остаётся в
слушающем состоянии и может принимать другие соединения.
В структуру, на которую ссылается addr, записывается адрес сокета
клиента, который установил соединение с сервером.
В переменную, адресуемую указателем addrlen, изначально
записывается размер структуры; функция accept записывает туда
длину, которая реально была использована.
Если вас не интересует адрес клиента, вы можете просто передать NULL
в качестве второго и третьего параметров.
Полученный от accept новый сокет связан с тем же самым адресом, что и
слушающий сокет.
15. http://www.slideshare.net/IgorShkulipa 15
Установка соединения со стороны клиента
На стороне клиента для установления соединения используется функция
connect, которая имеет следующий прототип.
int connect(int sockfd, struct sockaddr *serv_addr, int addrlen);
Здесь sockfd - сокет, который будет использоваться для обмена данными с
сервером, serv_addr содержит указатель на структуру с адресом сервера,
а addrlen - длину этой структуры.
Обычно сокет не требуется предварительно привязывать к локальному
адресу, так как функция connect сделает это за вас, подобрав
подходящий свободный порт.
16. http://www.slideshare.net/IgorShkulipa 16
Отправка данных
Функция send используется для отправки данных:
int send(int sockfd, const void *msg, int len, int flags);
sockfd - это дескриптор сокета
msg - указатель на буфер с данными
len - длина буфера в байтах
flags - набор битовых флагов, управляющих работой функции (если
флаги не используются, можно передать функции 0)
Некоторые флаги:
MSG_OOB - Предписывает отправить данные как срочные.
MSG_DONTROUTE - Запрещает маршрутизацию пакетов. Нижележащие
транспортные слои могут проигнорировать этот флаг.
Функция send возвращает число байтов, которое на самом деле было
отправлено (или -1 в случае ошибки). Это число может быть меньше
указанного размера буфера.
17. http://www.slideshare.net/IgorShkulipa 17
Отправка всего буфера
Для того, чтобы отправить весь буфер целиком, необходимо написать
свою функцию и вызывать в ней send, пока все данные не будут
отправлены.
int sendall(int s, char *buf, int len, int flags)
{
int total = 0;
int n;
while(total < len)
{
n = send(s, buf+total, len-total, flags);
if(n == -1)
{
break;
}
total += n;
}
return (n==-1 ? -1 : total);
}
18. http://www.slideshare.net/IgorShkulipa 18
Получение данных
Для чтения данных из сокета используется функция recv.
int recv(int sockfd, void *buf, int len, int flags);
Принимает дескриптор сокета, указатель на буфер и набор
флагов.
По аналогии с send функция recv возвращает количество
прочитанных байтов, которое может быть меньше размера
буфера.
Существует один особый случай, при котором recv возвращает 0.
Это означает, что соединение было разорвано.
19. http://www.slideshare.net/IgorShkulipa 19
Закрытие сокета
Закончив обмен данными, закройте сокет с помощью функции
close. Это приведёт к разрыву соединения.
int close(int sockfd);
Также можно запретить передачу данных в каком-то одном
направлении, используя shutdown.
int shutdown(int sockfd, int how);
Параметр how может принимать одно из следующих значений:
⚫ 0 - запретить чтение из сокета
⚫ 1 - запретить запись в сокет
⚫ 2 - запретить и то и другое
Всё равно потребуется вызвать close, чтобы освободить
связанные с ним системные ресурсы.
20. http://www.slideshare.net/IgorShkulipa 20
Пример сервера
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <iostream>
#include <sstream>
using namespace std;
class SocketServer
{
public:
SocketServer(string ip, int port);
int Run();
void Close();
private:
sockaddr_in _fullAddress;
int _listener;
string _answers[100];
char _buffer[100];
};
21. http://www.slideshare.net/IgorShkulipa 21
Пример сервера (конструктор)
SocketServer::SocketServer(string ip, int port)
{
//Заполнение структуры адреса
_fullAddress.sin_family = AF_INET;
_fullAddress.sin_port = port;
inet_aton(ip.c_str(), &(_fullAddress.sin_addr));
//Создание сокета
_listener = socket(AF_INET, SOCK_STREAM, 0);
//Создание массива ответов
for (int i=0; i<100; i++)
{
stringstream ss;
ss<<"Answer #"<< i <<"n";
_answers[i]=ss.str();
}
}
22. http://www.slideshare.net/IgorShkulipa 22
Пример сервера (основной метод)
int SocketServer::Run()
{
//Выполняем связывание
int iBindResult=bind(_listener,
(sockaddr*) &_fullAddress,
sizeof(_fullAddress));
if (iBindResult<0)
{
cout<<"Error on bindingn";
}
//Слушаем сокет
listen(_listener, 1);
cout<<"Server startedn";
...
23. http://www.slideshare.net/IgorShkulipa 23
Пример сервера (основной метод, продолжение)
//Переходим в режим приема-передачи данных
while (1)
{
//Проверка приема
int sock = accept(_listener, NULL, NULL);
if (sock < 0)
{
cout<<"Error on acceptingn";
return 0;
}
24. http://www.slideshare.net/IgorShkulipa 24
Пример сервера (основной метод, продолжение 2)
//Чтение буфера данных
while (1) {
int iReadCount = recv(sock, _buffer, 100, 0);
if (iReadCount <= 0) break;
cout<<"Buffer received: "<<iReadCount<<" bytesn";
stringstream ss;
for (int i=0; i<iReadCount; i++)
{
ss<<_buffer[i];
}
int iChoice;
ss>>iChoice;
cout<<"Choice= "<<iChoice<<"n";
cout<<"Answer= "<<_answers[iChoice]<<"n";
int iSent=
send(sock, _answers[iChoice].c_str(), sizeof(_answers[iChoice]), 0);
cout<<"Answer sent: "<< iSent<<" bytesn";
}
}
}
32. http://www.slideshare.net/IgorShkulipa 32
OpenGL
OpenGL - это графический стандарт в области компьютерной графики. На
данный момент он является одним из самых популярных графических
стандартов во всём мире.
OpenGL переводится как Открытая Графическая Библиотека (Open
Graphics Library), это означает, что OpenGL - это открытый и
мобильный стандарт. Программы, написанные с помощью OpenGL
можно переносить практически на любые платформы, получая при
этом одинаковый результат.
Ссылки на примеры и статьи:
1. http://ogldev.atspace.co.uk/
2. http://igorbarbosa.com/articles/opengl-glut-with-linux-mint-and-ubuntu-
12-04/
3. http://how2.org.ua/game-devel/создание-двумерного-opengl-
приложения-в-c.html
4. http://www.rusdoc.ru/material/lang/other/glut.shtml
5. http://www.pvsm.ru/opengl/22610
33. http://www.slideshare.net/IgorShkulipa 33
Как установить OpenGL
Необходимо загрузить файл пакета командой:
# sudo apt-get install freeglut3 freeglut3-dev
Далее, в свойствах проекта, необходимо добавить библиотеку:
/usr/lib/libglut.so
Подключить заголовочный файл:
#include <GL/freeglut.h>
34. http://www.slideshare.net/IgorShkulipa 34
Пример OpenGL приложения
#include <GL/freeglut.h>
#include <string>
using namespace std;
class FirstOGLApplication
{
public:
FirstOGLApplication(int argc, char * argv[]);
FirstOGLApplication(int width, int height, int argc, char * argv[]);
FirstOGLApplication(int width, int height, int startX, int startY, int
argc, char * argv[]);
void Run();
void Run(string strHeader);
private:
int _width; int _height; int _startX; int _startY;
static void DrawMyself();
static void DrawLine(float x1, float y1, float x2, float y2, float r,
float g, float b, int width);
};
35. http://www.slideshare.net/IgorShkulipa 35
Пример OpenGL приложения (конструкторы)
FirstOGLApplication::FirstOGLApplication(int argc, char * argv[])
{
_width=100; _height=100; _startX=100; _startY=100;
this->InitGL(argc, argv);
}
FirstOGLApplication::FirstOGLApplication(int width, int height, int
argc, char * argv[])
{
_width=width; _height=height; _startX=100; _startY=100;
this->InitGL(argc, argv);
}
FirstOGLApplication::FirstOGLApplication(int width, int height, int
startX, int startY, int argc, char * argv[])
{
_width=width; _height=height; _startX=startX; _startY=startY;
this->InitGL(argc, argv);
}
36. http://www.slideshare.net/IgorShkulipa 36
Пример OpenGL приложения (метод InitGL)
void FirstOGLApplication::InitGL(int argc, char* argv[])
{
// Инициализация GLUT
glutInit(&argc, argv);
// Установка режима отображения (RGBA-палитра)
glutInitDisplayMode(GLUT_RGBA);
// Установка размера окна
glutInitWindowSize(_width, _height);
// Установка изначальной позиции окна
glutInitWindowPosition(_startX, _startY);
}
37. http://www.slideshare.net/IgorShkulipa 37
Пример OpenGL приложения (методы запуска)
void FirstOGLApplication::Run()
{
this->Run("");
}
void FirstOGLApplication::Run(string strHeader)
{
// Создание окна с заголовком
glutCreateWindow(strHeader.c_str());
// Очистка цветом 0х00000000 (черный)
glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
// Запуск функции DrawMyself
glutDisplayFunc(FirstOGLApplication::DrawMyself);
// Запуск приложения в режим ожидания событий
glutMainLoop();
}
38. http://www.slideshare.net/IgorShkulipa 38
Пример OpenGL приложения (рисуем себя)
void FirstOGLApplication::DrawMyself()
{
glClear(GL_COLOR_BUFFER_BIT);
// Линия синего цвета толщиной 1
DrawLine(-0.5, -0.5, -0.5, 0.5, 0, 0, 1, 1);
// Линия зеленого цвета толщиной 2
DrawLine(-0.5, 0.5, 0.5, 0.5, 0, 1, 0, 2);
// Линия красного цвета толщиной 3
DrawLine( 0.5, 0.5, 0.5, -0.5, 1, 0, 0, 3);
// Линия белого цвета толщиной 4
DrawLine( 0.5, -0.5, -0.5, -0.5, 1, 1, 1, 4);
glFlush();
glutSwapBuffers();
}
39. http://www.slideshare.net/IgorShkulipa 39
Пример OpenGL приложения (рисование линии)
void FirstOGLApplication::DrawLine(float x1, float y1, float x2,
float y2, float r, float g, float b, int width)
{
// Устанавливаем цвет
glColor3f(r, g, b);
// Устанавливаем толщину линии
glLineWidth(width);
// Рисуем линию
glBegin(GL_LINES);
// Координаты точек в относительных величинах с центром в центре окна
glVertex2f(x1, y1);
glVertex2f(x2, y2);
glEnd();
}
40. http://www.slideshare.net/IgorShkulipa 40
Пример OpenGL приложения (функция main)
int main(int argc, char * argv[])
{
FirstOGLApplication* app=
new FirstOGLApplication(500, 500, 100, 100, argc, argv);
app->Run("Hello, OpenGL!");
return 0;
}
42. http://www.slideshare.net/IgorShkulipa 42
Лабораторная работа №13. Клиент-сервер
Создать консольный калькулятор с архитектурой «клиент-
сервер».
Клиент:
⚫ Отправляет данные на сервер с запросом о выполнении
арифметических операций над числами.
⚫ Выводит меню для управления вычислениями.
⚫ Выводит результаты на экран.
Сервер:
⚫ Обрабатывает запрос клиента и отправляет ему результат