...

суббота, 8 февраля 2014 г.

Дайджест интересных материалов из мира веб-разработки и IT за последнюю неделю №95 (2 — 8 февраля 2014)


сегодня в 23:14



Только зарегистрированные пользователи могут оставлять комментарии. Войдите, пожалуйста.


This entry passed through the Full-Text RSS service — if this is your content and you're reading it on someone else's site, please read the FAQ at http://ift.tt/jcXqJW.


Светодиодные медали на открытии Олимпиады-2014


сегодня в 21:11


Разве кто-то испытывал особую радость по поводу открытия зимних Олимпийских игр в Сочи? Да и могут ли вселять в сердца граждан оптимизм олимпиард рублей, внезапные шубохранилища, скандалы, интриги, расследования и прорадужные заявления?

Потому большим удивлением для меня оказалось яркое телешоу: надо же, все-таки хватило бюджетных денег сделать не «на коленке». Но разговор не об этом, разговор о светодиодных медалях, болтавшихся на шеях счастливых посетителей стадиона «Фишт».

LED medallion

В интернете и на хабре никаких обсуждений этих девайсов нет, однако для меня странно такое невнимание. Каким образом и откуда они управлялись? Установлен ли на каждой ГЛОНАСС-маячок, или это досужие домыслы?

(Фото: Sports.ru)



Свежий взгляд

на бег


протестируй кроссовки

нового поколения




Стань

первоиспытателем!


Скачай Windows Server 2012 R2

и выиграй почетную футболку!


Скачать



Только зарегистрированные пользователи могут оставлять комментарии. Войдите, пожалуйста.


This entry passed through the Full-Text RSS service — if this is your content and you're reading it on someone else's site, please read the FAQ at http://ift.tt/jcXqJW.


Выездная школа программирования: что можно сделать со студентами за три дня в тёмном лесу

На просторах интернета несложно найти целое множество статей про современное образование, про несоответствие знаний выпускников ИТ-специальностей и требований к ним у компаний, про то, как всё плохо и как всё надо изменить.

В этой статье мы расскажем про нашу попытку перейти от разговоров к реальным делам. У нас нет ресурсов, чтобы изменить ситуацию глобально, поэтому мы действуем точечно. Выездная школа по программированию — это возможность выправить ситуацию, воздействуя на наиболее способных студентов.

Добро пожаловать под кат, если вам интересно, как мы это делаем и что из этого получается.


Мы уже пять лет занимаемся различными активностями, чтобы укрепить позиции ИТ-образования в регионе. За это время мы пришли к разным выводам: например, сформулировали важный принцип «концентрации усилий». Наши ресурсы ограничены, поэтому нужно сосредотачивать свои усилия на наиболее перспективных участках: собирать подающих надежды ребят в одном месте и максимально в них вкладываться.


Несколько лет назад мы впервые провели для наших стажёров-программистов (как правило, это студенты ИТ-специальностей) массовое обучение перед началом стажировки. Получилось. Этот опыт мы повторили. Стало ещё лучше. Так что мы больше не смогли держать это в себе и год назад впервые объявили о проведении школы промышленной разработки под названием Контур.Кампус.


Мы проводили подобные школы уже четыре раза и научились за это время чему-то хорошему. А поскольку хорошим нужно делиться, вы и читаете нашу статью!


Какие правила мы для себя открыли?


Элитарность




Звучит довольно пафосно. На деле это означает, что на нашу школу можно попасть лишь в том случае, если качественно выполнить тестовое задание — деньги, знакомства и связи не помогут! :-) В тестовом оценивается все: идеи, алгоритмы, качество кода, умение декомпозировать задачу, аккуратность с крайними случаями.

Кстати, сложно вести школу на разных языках программирования, поэтому мы фиксируем C# (основной язык разработки у нас в компании) в качестве главного языка программирования на школе и предлагаем на нём же и выполнять тестовое. Это гарантирует, что все, справившиеся с заданием, хотя бы на базовом уровне овладели основами языка, а также открывает больше возможностей по взаимодействию участников и обмену опытом. Да и лекции с практиками в таких условиях могут быть более содержательными и конкретными.


Изоляция





Чтобы полностью погрузиться в процесс, надо изолировать себя от ненужных раздражителей (не будите программиста!). Поэтому для нашей школы мы снимаем удалённый коттедж, куда сложно добраться и откуда ещё сложнее выбраться, чтобы несколько дней студенты не отвлекались ни на что. Тем более получается очень романтичная атмосфера стартапа, такая привлекательная с точки зрения студентов!



Программа школы




Чтобы не отрывать студентов от основной учёбы, мы стараемся отнимать у них как можно меньше учебного времени и проводим школу с пятницы на воскресенье. То есть всего три дня, и ни одной минуты не должно быть потеряно: занятия идут с 10 утра до 11 вечера (включая короткие перерывы на чаепитие и обедоужины). Для некоторых это настоящее испытание — напрягать мозг целый, черт возьми, день! Зато другие сидят до трех ночи, доделывая «домашнее» задание, потому что «интересно же!» и «домашка сама себя не сделает!»

Что же включено в трехдневную программу?

В основном это базовые, необходимые почти всем темы: чистый код, рефакторинг, умение разбивать задачу на подзадачи, тестирование, многопоточность, элементы функционального стиля программирования, системы контроля версий.


Любая лекция, не подкрепленная практикой, стремительно вылетает из головы студента уже через полчаса. Поэтому практика — наше всё. Мы пользуемся следующей эмпирической формулой.



  1. Прямо во время лекции одна-две паузы для решения заготовленных тривиальных задачек по теме. Например, сразу после рассказа про рефакторинг и декомпозицию кода — задание, в котором нужно переименовать несколько переменных и выделить связный блок кода в метод, используя соответствующие команды в IDE; после рассказа про какую-то технологию — сделать что-то очень простое с ее помощью.

  2. В конце лекции нужна одна большая «домашняя» задача по теме на 2-4 часа. Решают ее в специально выделенное для этого время после лекций.




Наибольший эффект получается, если все темы школы связаны друг с другом. Например, темы про чистый код и декомпозицию хорошо согласуются с темой про функциональный стиль программирования; в случае с C# все это хорошо согласуется с LINQ, который, в свою очередь, связывается с темой про многопоточное программирование через pLINQ, и так далее и тому подобное. Чем больше связок, тем лучше усваивается весь материал.

А ещё последний день мы щедро тратим на Code Retreat. На Хабре про него уже писали здесь. Если вы ещё не знаете что это — обязательно прочитайте! Со студентами, которым только что рассказали много нового и полезного, это работает особенно хорошо. По отзывам самих студентов, Code Retreat — это стабильно один из лучших пунктов в программе школы.


Отдых тоже предусмотрен, но на деле является лишь сменой мозговой активности: вместо написания кода мы предлагаем участникам поиграть в шарады или «шляпу» со словами типа «монада» или «инкапсуляция» и подобные развлечения.



Преподаватели




Только практики, только с опытом преподавания, только хардкор. Да, таких сложно найти, но они существуют. И ещё наблюдение: очень неплохие преподаватели получаются из некоторых недавних участников школы. Видимо, им помогает, что они гораздо более на одной волне со студентами, чем взрослые мужики-программисты. Так что мы прямо во время школы присматриваем себе преподавателя на следующую школу из состава текущих участников.


Работа в парах!




Это не только облегчит работу преподавателей, но и научит участников работать в разных командах, советоваться, делиться опытом. Правило простое. Новая тема — новая пара.


За трехдневную школу каждый студент может успеть поработать с десятком других студентов. Так они все друг с другом ещё и перезнакомятся!

Опять же, студенты от парного программирования в полном восторге!



Никакого хантинга, никакой платы




Мы хотим работать с лучшими, поэтому делаем это совершенно бесплатно: никакой платы ни за проживание, ни за учёбу. Наша цель — научить, а не взять на работу. Поэтому отбор и обучение проводятся безо всякой оглядки на то, пойдет этот человек к нам на работу или нет. Конечно, такую позицию довольно сложно объяснить экономическому отделу, но если не заниматься подобной работой, не повышать концентрацию крутанов на квадратный метр, то тем, кто всё-таки вырастает в хорошего специалиста, становится скучно, и они уезжают туда, где концентрация повыше. Таким образом, мы не только рискуем потерять кого-то из тех, кто уж здесь, но и никого не приобретаем.

С другой стороны, мы совершенно не расстроимся, если те, кто пройдёт через школу, потом придут к нам на стажировку и/или на работу, благо, так часто и происходит само! ;)

И самое важное — обратная связь




Важность этого принципа сложно переоценить. Без обратной связи нельзя развиваться. Поэтому последние полчаса-час мы проводим за ретроспективой (да-да, прямо как в Agile). В результате можно очень хорошо понять, какой преподаватель не крут (и его больше не стоит звать), какие темы хорошо пошли, а какие не очень и т.п. Чем жёстче обратная связь, тем лучше!


Конечно, за три дня (даже с условием тринадцатичасового учебного дня) поднять уровень образования невозможно. Но эффект от школы длится гораздо дольше. Студенты понимают, к чему надо стремиться, что же такое в действительности промышленное программирование. Они идут сами и тянут за собой своих друзей и знакомых, потом вырастают, становятся профессиональными разработчиками, затем из некоторых получаются преподаватели, у каждого по 15 новых студентов…

Картина утопичная, но мы в неё верим и прикладываем усилия, чтобы она реализовалась.


Да, кстати!




В феврале мы планируем очередную школу Контур.Кампус для студентов Екатеринбурга и окрестностей. Участие, естественно, бесплатное! Все подробности тут!

This entry passed through the Full-Text RSS service — if this is your content and you're reading it on someone else's site, please read the FAQ at http://ift.tt/jcXqJW.


Рейтинг хабов по постам и подписчикам

На данный момент на хабре около 350 хабов. Функционал сайта позволяет сортировать их по имени и по индексу. А по другим параметрам — например по количеству постов — нет, а хотелось бы.

Меня вдохновила статья рейтинг постов хаба, и я решил сделать похожую, но составить уже рейтинг самих хабов.


В первой половине статьи я представлю вам рейтинги хабов по постам и подписчикам, а также небольшой их анализ. А во второй — подробно распишу, как я на Java с помощью библиотеки JSoup парсил HTML страницы хабры, с какими интересными явлениями и проблемами столкнулся. И в конце статьи выложу полный исходный код программы.






По количеству постов

По количеству подписчиков



Когда я отсортировал хабы, обнаружились интересные вещи. Например я не знал, что существуют хабы с нулевым количеством постов. А их оказалось целых 4 штуки! Причём на каждый из них подписано более 500 человек. Наверно это очень перспективные направления — не имея постов на них уже набежало столько народу. У хаба MySpace нету ни одного поста/события, зато более 55К подписчиков — это о чём-то говорит!

Тройка хабов — Чулан, Я пиарюсь и Веб-разработка — лидируют как по количеству постов, так и по количеству читателей. И в этом нет ничего удивительного — первые два хаба весьма общие и свободные, а уж веб-разработка интересна многим. Далее идёт Информационная безопасность, которая пользуется на хабре бешеной популярностью. За ней — Google, которую можно считать компанией №1 в области IT — по ней больше всего постов, по сравнению с другими. Отчасти благодаря её ОС Android, которой выпало 4 место по количеству подписчиков.


К сожалению, я так и не понял, почему хаб Хабрахабр — оффтопик. По количеству постов он будет на 13 месте, да и подписчиков у него >80К. Получается, что писать на сайте об этом же сайте — отход от темы?


Огорчило, что хаб Java находится не так высоко, как хотелось бы.


Очень долго я пытался разобраться в Хабрахабр Api. Как выяснилось, он закрыт и пока что в процессе разработки. Однако в переписке с support@habrahabr.ru мне сказали, что они не имеют ничего против парсинга их страниц. Собственно, именно так и работают хабраклиенты для Android (на данный момент).

Когда речь идёт о проектах «для себя», я выбираю любимую Джаву. Она и на этот раз меня не подвела — библиотека JSoup позволила в несколько строчек получить необходимые данные с HTML страницы. Но давайте сперва обсудим, как устроены хабы.


Страницы с хабами расположены по адресам http://ift.tt/1eHJyfS, где N — номер от 1 и далее. Посему, если мы хотим получить полный список из всех хабов — нам нужно загружать и анализировать эти страницы, пока они не закончатся. На каждой странице присутствует список из хабов. Формат элемента списка довольно простой и легко анализируется. Выглядит он так:



<div class="hub " id="hub_50">
<div class="habraindex">1 280,58</div>
<div class="info">
<div class="title">
<a href="http://ift.tt/1kOHAit">Информационная безопасность</a>
<span class="profiled_hub" title="Профильный хаб"></span>

</div>
<div class="buttons">
<input type="button" class="mini blue subscribeHub" value="Подписаться" data-id="50">
<input type="button" class="mini hidden unsubscribeHub" value="Подписан" data-id="50" "="">
</div>
<div class="clear"></div>
<div class="stat"><a href="http://ift.tt/1lcYUdX" class="members_count">91741 подписчик</a>, <a href="http://ift.tt/1lcYUdZ">3385 постов</a><a></a></div><a>
</a></div><a>
</a></div>


Давайте напишем метод, который возвращает нам список из всех хабов на сайте:



static List<Hub> getAllHubs() {
ArrayList<Hub> fullHubsList = new ArrayList<>();
String urlHubsIncomplete = "http://ift.tt/1eHJAEv";
int pageNum = 1;

do {
String urlHubs = urlHubsIncomplete + pageNum;

try {
Document doc = Jsoup.connect(urlHubs).get();
Elements hubs = doc.select(".hub");
if (hubs.size() == 0) {
break;
}
for (Element hubElem : hubs) {
Hub hub = new Hub(hubElem);
fullHubsList.add(hub);
}

pageNum++;
} catch (Exception e) {
e.printStackTrace();
break;
}

} while (true);

return fullHubsList;
}




Мы крутим бесконечный цикл while, формируя с каждой итерацией новый URL. Затем, с помощью Jsoup.connect(urlHubs).get() получаем непосредственно HTML-документ со списком хабов и их параметрами. Как несложно заметить — div с информацией о хабе имеет класс hub — и, вызвав doc.select(".hub"), мы получаем список из этих элементов. Если его размер равен нулю — значит мы прошли последнюю страницу и уже проанализировали все хабы — тогда мы выходим из цикла.

Далее — проходим по всем хабам-элементам и для каждого создаём объект типа Hub, передав в конструктор наш org.jsoup.nodes.Element. В нём располагается HTML-код такого же формата, как указан выше. Теперь давайте абстрагируемся от всего. Для этого и существует ООП. Перед нами есть только тот кусочек HTML, представленный выше, и класс, в который его нужно запихнуть. Напишем каркас для нашего класса:



import org.jsoup.nodes.Element;

public class Hub {
String title;
int posts;
boolean profiled;
int membersCount;
float habraindex;
String url;

public Hub(Element hubElem) {

}
}




Напишем конструктор. Для начала сделаем самое простое — получим данные из заголовочного тега. Для этого мы сначала извлекаем сам div вида

<div class="title">
<a href="http://ift.tt/1kOHAit">Информационная безопасность</a>
<span class="profiled_hub" title="Профильный хаб"></span>
</div>




Парсим через

Element titleDiv = hubElem.select(".title").get(0);
Element tagA = titleDiv.getElementsByTag("a").get(0);
title = tagA.text();
url = tagA.attr("href");
profiled = (hubElem.select(".profiled_hub").size() != 0);




Далее, мы хотим пропарсить количество подписчиков и постов — собственно те параметры, по которым мы и будем сортировать. Но сразу же сталкиваемся с первой проблемой — тег содержит строку «91741 подписчик», которую мы не можем просто так взять и преобразовать в Integer — она содержит буквы! Тут нам на помощь приходят регулярные выражения. Быстренько пишем ловкий метод, который получает строку и вырезает из неё всё, кроме цифр, да ещё и преобразует результат в int. \D — это НЕ цифра, а + — «встречается 1 или более раз». Т.е. мы в данном случае заменяем буквы на пустоту.

private int getNumbers(String str) {
String numbers = str.replaceAll("\\D+", "");
return Integer.valueOf(numbers);
}




Вот теперь мы уже можем со спокойной душой получить наши значения:

String membersCountFullStr = hubElem.select(".members_count").get(0).text();
membersCount = getNumbers(membersCountFullStr);

String statFullStr = hubElem.select(".stat").get(0).getAllElements().get(2).text();
posts = getNumbers(statFullStr);




В принципе, на этом можно было остановится, но я решил ради интереса извлечь всю возможную информацию о хабе. Тут возникла весьма интересная вторая проблема, которая будет изюминкой статьи. Как пропарсить хабраиндекс?

Для начала, следует заменить запятую на точку и убрать лишние пробелы. Но этого не достаточно! Парсер всё равно выдаёт ошибку, если скопировать и вставить хабраиндекс в код — Double.valueOf("–1.11"). А если ввести вручную то же самое число — всё окей. Причём визуально в моей IDEA они абсолютно идентично выглядят!


Оказывается, дизайнеры хабры просто использовали dash вместо minus — c иным кодом символа, и парсер его, понятное дело, не ест. Возьмите на заметку. Суть проблемы в следующем:



System.out.println((int)'-');//45
System.out.println((int)'–');//8211




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

Посему, код для извлечения хабраиндекса будет чуть сложнее:



String rawHabraIndex = hubElem.select(".habraindex").get(0).text();//1 265,92
char minus = 45;//'-'
char dash = 8211;//'–'
String niceHabraIndex = rawHabraIndex.replaceAll(" ", "").replace(",", ".").replace(dash,minus);//1266.72
habraindex = Float.valueOf(niceHabraIndex);




Далее, пишем компаратор по постам как вложенный статический класс для Hub

public static class ComparePosts implements Comparator<Hub> {
@Override
public int compare(Hub o1, Hub o2) {
return o2.posts - o1.posts;
}
}




И сортируем по нему где-нибудь в main

List<Hub> hubs = getAllHubs();
Collections.sort(hubs, new Hub.ComparePosts());




Всё, задача выполнена! С количеством подписчиков аналогично. Далее я написал код, который выводит в консоль два списка в таком виде, чтобы их сразу можно было вставить в статью — и сделал это вначале.

На получение всех хабов уходит примерно 10 секунд. Исходный код можно скачать здесь. Собираем и запускаем вот так, не забыв установить Jsoup и заменить путь на ваш:



javac -cp .;"C:\prog\lib\jsoup-1.7.3.jar" com/kciray/habrahubs/Main.java
java -cp .;"C:\prog\lib\jsoup-1.7.3.jar" com.kciray.habrahubs.Main


This entry passed through the Full-Text RSS service — if this is your content and you're reading it on someone else's site, please read the FAQ at http://ift.tt/jcXqJW.


О гипотезе Пуанкаре. Лекция Яндекса

Еще в XIX веке было известно, что если любую замкнутую петлю, лежащую на двумерной поверхности, можно стянуть в одну точку, то такую поверхность легко превратить в сферу. Так, поверхность воздушного шарика удастся трансформировать в сферу, а поверхность бублика – нет (легко вообразить себе петлю, которая в случае с бубликом не стянется в одну точку). Гипотеза, высказанная французским математиком Анри Пуанкаре в 1904 году, гласит, что аналогичное утверждение верно и для трехмерных многообразий.

Доказать гипотезу Пуанкаре удалось только в 2003 году. Доказательство принадлежит нашему соотечественнику Григорию Перельману. Эта лекция проливает свет на объекты, необходимые для формулировки гипотезы, историю поиска доказательства и его основные идеи.


Читают лекцию доценты механико-математического факультета МГУ к. ф-м. н. Александр Жеглов и к. ф.-м. н. Федор Попеленский.






Если не вдаваться в математические подробности, то вопрос, поднимаемый гипотезой Пуанкаре можно следующим образом: как охарактеризовать (трехмерную) сферу? Чтобы правильно понять этот вопрос, нужно познакомиться с одним из важнейших понятий в топологии – гомеоморфизмом. Разобравшись с ним, мы сможем точно сформулировать гипотезу Пуанкаре.



Чтобы совсем уж не залезать в математические подробности формального определения, мы скажем, что две фигуры считаются гомеоморфными, если можно установить такое взаимно-однозначно соответствие между точками этих фигур, при котором близким точкам одной фигуры соответствуют близкие точки другой фигуры и наоборот. Пропущенные нами подробности состоят как раз в адекватной формализации близости точек.


Легко понять, что две фигуры гомеоморфны, если одну из другой можно получить произвольной деформацией, при которой запрещено «портить» поверхности (рвать, сминать области в точку, делать дырки и т.п.).



Например, чтобы получить из диска полусферу, как показано на картинке выше, нам потребуется просто нажать сверху в его центр, придерживая внешний обод. Можно представлять себе, что поверхности сделаны из идеальной резины, так что все фигуры могут сжиматься и растягиваться как угодно. Нельзя делать только две вещи: разрывать и склеивать.



Более точное (но все же не окончательное с точки зрения строгости) представление о гомеоморфных фигурах мы будем иметь, если разрешим еще одну операцию: можно сделать на фигуре разрез, перекрутить, завязать, развязать и т.п., но потом обязательно заклеить разрез как было.



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



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


Для закрепления попробуйте классифицировать буквы латинского алфавита с точностью до гомеоморфизма (т.е. выясните, какие буквы гомеоморфны, а какие — нет). Ответ зависит начертания букв (от типа шрифта или от гарнитуры), и для простейшего варианта начертания он приведен на следующем рисунке:



Из 26 букв у нас получается всего 8 классов.


На следующей картинке изображены гиря, кофейная чашка, бублик, сушка и кренделек. С топологической точки зрения поверхности гири, кофейной чашки, бублика и сушки одинаковы, т.е. гомеоморфны. Что касается кренделька, то он приведен здесь для сравнения с поверхностью, которую в топологии часто называют кренделем (он изображен в правом нижнем углу рисунка). Как вы, наверное, уже понимаете, и топологический крендель, и съедобный крендель отличаются от тора.



Формальная постановка вопроса




Пусть M – замкнутое связное многообразие размерности 3. Пусть на нем любая петля может быть стянута в точку. Тогда M гомеоморфно трехмерной сфере.

Наибольшую трудность для неподготовленного человека здесь вызывает понятие «многообразия размерности 3» и свойства, выраженные словами «замкнутое» и «связное». Поэтому мы попробуем разобраться со всеми этими понятиями и свойствами на примере размерности 2, в этом случаем многое кардинально упрощается.


Гипотеза Пуанкаре для поверхностей




Пусть M – замкнутая связная поверхность (многообразие размерности 2). Пусть на ней любая петля может быть стянута в точку. Тогда поверхность M гомеоморфна двумерной сфере.

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


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



Вот простой пример: если считать, что на картинке выше все треугольники правильные, то после склеивания у нас должен получиться правильный тетраэдр, поверхность которого также гомеоморфна сфере.


Формально нужно требовать, чтобы из любой вершины любого многоугольника после склейки можно было пройти в любую вершину любого многоугольника (по ребрам).


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



Рассмотрим примеры простейших склеек:



В первом случае у нас получится сфера:



Во втором случае у нас получится тор (поверхность бублика, мы встречались с ним раньше):



В третьем случае получится так называемая бутылка Клейна:



Если склеивать не все стороны многоугольника, то получится поверхность с краем:



Важно отметить, что после склейки «шрамы» от нее носят чисто «косметический характер. Все точки поверхности равноправны: у любой точки имеется окрестность гомеоморфная диску.


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


Разберем это утверждение на примере разбиения поверхности куба на части, из которых можно сложить развертку тетраэдра:



Верен и более общий факт: поверхности всех выпуклых многогранников – это сферы.


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



Даже если петля на плоскости или сфере имеет самопересечения, ее все равно можно стянуть:



На плоскости можно стянуть любую петлю:



А вот какие петли бывают на торе:



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


Эйлерова характеристика




Теперь, когда мы познакомились со всеми основными понятиями из формулировки гипотезы Пуанкаре, попробуем приступить к доказательству двумерного случая (лишний раз отметим, что это многократно проще трехмерного случая). А поможет нам в этом эйлерова характеристика.

Эйлеровой характеристикой поверхности M назовем число B−P+Г. Здесь Г — число многоугольников, Р — это число ребер после склейки (в случае рассматриваемых поверхностей это половина числа сторон всех многоугольников), B — это число вершин, которое получается после склейки после склейки.


Если две схемы склейки задают гомеоморфные поверхности, то у этих схем числа B−P+Г одинаковы, т. е. B−P+Г является инвариантом поверхности.


Если поверхность уже как-то задана, то надо нарисовать на ней какой-нибудь граф, чтобы после разрезания по нему поверхность распалась на куски гомеоморфные дискам (например, кольца запрещены). Затем подсчитываем величину B−P+Г — это и есть эйлерова характеристика поверхности.


Будут ли гомеоморфны поверхности с одинаковыми эйлеровыми характеристиками, мы узнаем позже. Но совершенно точно можно утверждать, что если эйлеровы характеристики у поверхностей разные, то поверхности не гомеоморфны.


Знаменитое соотношение B−P+Г=2 для выпуклых многоугольников (теорема Эйлера) является частным случаем этой теоремы. В данном случае речь идет о конкретной поверхности — о сфере. Замечание Обозначение: Эйлерову характеристику поверхности M будем обозначать через χ(M): χ(M) = B − P + Γ


Если поверхность M связна, то χ(M) ≤ 2, причем χ(M) = 2 тогда и только тогда, когда M гомеоморфна сфере.


Посмотрев лекцию до конца, вы узнаете, как же все-таки доказывается гипотеза Пуанкаре в размерности 2, и как Григорию Перельману удалось доказать ее в размерности 3.


This entry passed through the Full-Text RSS service — if this is your content and you're reading it on someone else's site, please read the FAQ at http://ift.tt/jcXqJW.


Основы создания 2D персонажа в Unity 3D 4.3. Часть 2: бегущий персонаж

Часть 1: заготовка персонажа и анимация покоя

Часть 2: бегущий персонаж

Всем привет. Продолжаем дело, начатое в первой части. Сейчас у нас есть платформа и стоящий на ней персонаж с анимацией покоя. Настало время научить нашего персонажа бегать вправо-влево по платформе.



Загрузим сцену из первой части. Напомню, что в прошлый раз мы импортировали несколько спрайтов в папку AssetsSprites. На всякий случай, внизу поста еще раз приведу ссылку на спрайты. Среди них должен быть спрайт под названием Run. Мы будем использовать его для создания анимации бега. Для этого нам надо проделать те же действия по превращению одиночного спрайта в коллекцию, как и при создании анимации покоя. Вкратце напомню: выделяем спрайт, в окне Inspector устанавливаем свойство Sprite Mode как Multiple, нажимаем ниже Sprite Editor, нарезаем изображение в режиме Grid или Automatic.



Теперь в окне Hierarchy выбираем Character и переходим в окно Animation. Нажимаем на поле с анимацией Idle и выбираем Create New Clip, чтобы создать анимацию бега. Сохраним файл анимации в папке AssetsAnimations под именем Run.



Новая созданная анимация Run стала текущей в окне Animation. Разворачиваем спрайт Run в окне Project, выделяем все фалы Run_0… Run_9 и перетаскиваем в окно Animation. Установим пока значение Sample равное 24.



Все это мы уже делали в первой части, а теперь будет нечто новое. Перейдем в окно Animator. Сейчас там отображены три анимации: Any State, Idle и Run. Нам предстоит задать условия перехода из анимации Idle в анимацию Run, то есть из состояния покоя в состояние бега. В нижнем левом углу есть поле Parameters. Нажимаем на плюсик, выбираем Float и называем новый параметр как Speed. Тем самым мы создали параметр типа число с плавающей запятой, обозначающий скорость перемещения персонажа. Именно в зависимости от значения этого параметра будет происходить переключение из анимации покоя в анимацию бега. Теперь нажимаем правой кнопкой мыши на анимацию Idle, выбираем Make Transition и нажимаем левой кнопкой мыши на анимацию Run. Между анимациями появится линия со стрелкой. Передвиньте мышкой прямоугольники анимации, если плохо видно. Кликнем по линии со стрелкой. В окне Inspector отобразятся свойства перехода между анимациями. Обратим внимание на низ окна, в раздел Conditions. Кликнем на параметр Exit Time и поменяем его на Speed. Второе поле Greater оставим без изменений, а в третьем введем значение 0.01. Мы создали условие перехода из анимации покоя в анимацию бега — оно происходит, когда значение параметра скорости становится немногим больше нуля.



Теперь нужно сделать обратный переход — из Run в Idle. Делаем все с точностью наоборот: Make Transition от Run к Idle, выделяем переход, в Conditions устанавливаем SpeedLess0.01.



Теперь у нас есть две анимации и условия перехода между ними. Но пока ничего работать не будет, потому что все что мы сделали нужно «оживить» при помощи скрипта. Давайте перейдем в окно Project и создадим в папке Assets подпапку Scripts. Добавим в нее новый C# Script, назовем его CharacterControllerScript и откроем на редактирование.



Я приведу полный листинг скрипта с подробными комментариями, а ниже еще поясню, что в нем происходит.



using UnityEngine;
using System.Collections;

public class CharacterControllerScript : MonoBehaviour
{
//переменная для установки макс. скорости персонажа
public float maxSpeed = 10f;
//переменная для определения направления персонажа вправо/влево
private bool isFacingRight = true;
//ссылка на компонент анимаций
private Animator anim;

/// <summary>
/// Начальная инициализация
/// </summary>
private void Start()
{
anim = GetComponent<Animator>();
}

/// <summary>
/// Выполняем действия в методе FixedUpdate, т. к. в компоненте Animator персонажа
/// выставлено значение Animate Physics = true и анимация синхронизируется с расчетами физики
/// </summary>
private void FixedUpdate()
{
//используем Input.GetAxis для оси Х. метод возвращает значение оси в пределах от -1 до 1.
//при стандартных настройках проекта
//-1 возвращается при нажатии на клавиатуре стрелки влево (или клавиши А),
//1 возвращается при нажатии на клавиатуре стрелки вправо (или клавиши D)
float move = Input.GetAxis("Horizontal");

//в компоненте анимаций изменяем значение параметра Speed на значение оси Х.
//приэтом нам нужен модуль значения
anim.SetFloat("Speed", Mathf.Abs(move));

//обращаемся к компоненту персонажа RigidBody2D. задаем ему скорость по оси Х,
//равную значению оси Х умноженное на значение макс. скорости
rigidbody2D.velocity = new Vector2(move * maxSpeed, rigidbody2D.velocity.y);

//если нажали клавишу для перемещения вправо, а персонаж направлен влево
if(move > 0 && !isFacingRight)
//отражаем персонажа вправо
Flip();
//обратная ситуация. отражаем персонажа влево
else if (move < 0 && isFacingRight)
Flip();
}

/// <summary>
/// Метод для смены направления движения персонажа и его зеркального отражения
/// </summary>
private void Flip()
{
//меняем направление движения персонажа
isFacingRight = !isFacingRight;
//получаем размеры персонажа
Vector3 theScale = transform.localScale;
//зеркально отражаем персонажа по оси Х
theScale.x *= -1;
//задаем новый размер персонажа, равный старому, но зеркально отраженный
transform.localScale = theScale;
}
}


Итак, мы завели несколько переменных: для задания максимальной скорости перемещения, для определения направления (вправо/влево) и для работы с компонентом Animator. Почти все действия происходят в методе FixedUpdate. В нем мы получаем значение оси Х, которое меняется при нажатии на клавиатуре клавиш влево-вправо или A-D (если не меняли соответствующие настройки проекта!). Затем устанавливаем это значение параметру Speed компонента Animator. Обратите внимание, что мы берем модуль этого значения при помощи метода Mathf.Abs, так как при создании условий перехода между анимациями покоя и бега мы сравниваем значение параметра с положительным числом 0.01. Нам здесь не важно, в какую сторону бежит персонаж. Важно лишь величина значения. Далее задаем скорость перемещения по оси Х в соответствии со значением максимальной скорости. И, наконец, проверяем, в какую сторону бежит персонаж, и в какую сторону он в этот момент повернут. Если он бежит вправо, а повернут влево — разворачиваем его вправо путем инвертирования его размера по оси Х. И наоборот. Этим нехитрым способом мы избавились от необходимости делать две анимации вместо одной: для бега вправо и для бега влево.


Сохраняем скрипт. В Unity перетаскиваем его на нашего Character в окне Hierarchy. Запускаем игру, нажимаем влево-вправо или A-D.



Капитан Коготь теперь умеет бегать! Скорость анимации получилась быстроватой. Ее можно снизить путем уменьшения значения Sample в окне Animation для анимации Run (значение 12 будет нормально). Если одновременно с игрой у вас видно окно Animator, то вы увидите, что во время покоя работает анимация Idle (бегает синий прогрессбар), а во время бега происходит переход на анимацию Run, и, соответственно, работает она.


На этом пока все. Нам осталось разобраться с прыжками… и узнать при этом еще несколько новых вещей!

Ссылка на спрайты.


This entry passed through the Full-Text RSS service — if this is your content and you're reading it on someone else's site, please read the FAQ at http://ift.tt/jcXqJW.


Перевод Code Club Scratch Curriculum, краудсорсинг


Добрый день.


Данный пост — предложение заняться краудсорс-переводом отличного учебного курса Code Club по среде Scratch и основам программирования для ребят 9-11 лет (примерно).



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

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


Сам перевод осуществляется на гитхабе, надо сделать форк проекта, взять оригинал en-GB версии и перевести Markdown файлы (.md) соответствующего урока. Желательно сохранять оригинальный стиль оформления, по идее они на основе .md файлов потом будут делать локализованные версии курса на html, в виде сайта. После окончания перевода — сделать pull-request в оригинальный проект.


Для организации взаимодействия, чтобы не брать перевод, который уже кто-то начал, предлагаю синхронизировать темы в Google Docs. Если вы берете какую-то тему на перевод, сделайте новую строчку в таблице, чтобы показать, что тема уже взята и в процессе. Перевод одного урока занимает часа 2.


Первые два семестра — это Scratch. Третий — html. Четвертый — Python. У кого будет желание — можно перевести и эти курсы, думаю, не пропадет, качественные материалы в простой, интересной и доступной детям форме — это будет очень полезно всему нашему образованию.


Так же это хороший повод разобраться с Git'ом и способами участия в open source проектах.


Если есть предложения или вопросы — буду рад ответить в комментариях.


Спасибо!


This entry passed through the Full-Text RSS service — if this is your content and you're reading it on someone else's site, please read the FAQ at http://ift.tt/jcXqJW.


Qwt и Qt Creator. Быстро и просто. Часть 2: элементы отображения и управления


сегодня в 14:20



В примерах использованы Qt Creator 3.0.0 (MinGW) и Qwt-6.1.0.

Для понимания этой статьи читателю желательно:



  • иметь начальный опыт разработки windows-приложений в среде Qt Creator;

  • понимать концепцию «сигнал-слот»;

  • познакомиться с частью №1 цикла моих статей про Qwt: http://ift.tt/1foEieX


Qwt – графическая библиотека, позволяющая значительно упростить процесс визуализации данных в программе. Упрощение заключается в следующем: нет необходимости вручную прописывать элементы отображения, такие как шкалы координат, сетки, кривые данных и проч. Следует лишь задавать параметры этих элементов.


В части №1 (постепенно разрастающегося) цикла статей мы:

• подключили Qwt к Qt Creator;

• построили график;

• настроили оси координат;

• изменяли масштаб графика (приближали/удаляли его);

• перемещались по полю графика;

• отображали координаты рядом с курсором по щелчку мышкой.


В части №2 мы расширим функциональность нашего визуализатора:

• добавим строку состояния;

• сохраним координаты клика в переменных и отобразим их в строке состояния;

• добавим кнопку на панель управления;

• добавим на панель управления QwtCounter (поле для номера, значение которого можно изменять стрелками, см. картинку).

• зададим с помощью QwtCounter смещение графика x;

• нажатием на кнопку сместим график на ранее заданную величину х.


Примечание: В рамках этой статьи при добавлении элементов управления GUI не используется.


Подготовка: разбиение кода, представленного в статье №1, на функции
В предыдущей статье код шел целиком в конструкторе MainWindow. Теперь разделим код на функции. Присутствует небольшое изменение: если раньше мы прописывали непосредственно координаты точек кривой, то теперь мы их читаем из массива (см. комментарий).

Важно: если вы создаете новый проект, то не забудьте добавить в .pro файл строчку

CONFIG += qwt


и после этого запустить qmake.
Вот такое содержание должно быть у файла mainwindow.h


#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>

#include <qwt_plot.h>
#include <qwt_plot_grid.h>

#include <qwt_legend.h>

#include <qwt_plot_curve.h>
#include <qwt_symbol.h>

#include <qwt_plot_magnifier.h>

#include <qwt_plot_panner.h>

#include <qwt_plot_picker.h>
#include <qwt_picker_machine.h>


namespace Ui {
class MainWindow;
}

class MainWindow : public QMainWindow
{
Q_OBJECT

public:
explicit MainWindow(QWidget *parent = 0);
~MainWindow();


private:
Ui::MainWindow *ui;

QwtPlot *d_plot;
void setPlot();

QwtPlotGrid *grid;
void setPlotGrid();


QwtPlotCurve *curve;
QwtSymbol *symbol;

void setCurveParameters();

// новый массив точек кривой
double pointArray[5][2];
QPolygonF points;
void addPointsToCurveAndShow();

QwtPlotMagnifier *magnifier;
void enableMagnifier();


QwtPlotPanner *d_panner;
void enableMovingOnPlot();

QwtPlotPicker *d_picker;
void enablePicker();


};

#endif // MAINWINDOW_H



Вот требуемое содержание файла mainwindow.cpp.


#include "mainwindow.h"
#include "ui_mainwindow.h"



MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
ui->setupUi(this);

// Создать поле со шкалами для отображения графика
setPlot();

// Включить масштабную сетку
setPlotGrid();

// Кривая
setCurveParameters();
addPointsToCurveAndShow();

// Включить возможность приближения/удаления графика
enableMagnifier();

// Включить возможность перемещения по графику
enableMovingOnPlot();

// Включить отображение координат курсора и двух перпендикулярных
// линий в месте его отображения
enablePicker();

}

void MainWindow::setPlot()
{
// (this) - разместить поле на текущем окне
// #include <qwt_plot.h>
d_plot = new QwtPlot( this );

setCentralWidget(d_plot); // привязать поле к границам окна

d_plot->setTitle( "Qwt demonstration" ); // заголовок
d_plot->setCanvasBackground( Qt::white ); // цвет фона

// Параметры осей координат
d_plot->setAxisTitle(QwtPlot::yLeft, "Y");
d_plot->setAxisTitle(QwtPlot::xBottom, "X");
d_plot->insertLegend( new QwtLegend() );
}

void MainWindow::setPlotGrid()
{
// #include <qwt_plot_grid.h>
grid = new QwtPlotGrid();
grid->setMajorPen(QPen( Qt::gray, 2 )); // цвет линий и толщина
grid->attach( d_plot ); // добавить сетку к полю графика
}


void MainWindow::setCurveParameters()
{
//#include <qwt_plot_curve.h>
curve = new QwtPlotCurve();
curve->setTitle( "Demo Curve" );
curve->setPen( Qt::blue, 6 ); // цвет и толщина кривой
curve->setRenderHint
( QwtPlotItem::RenderAntialiased, true ); // сглаживание

// Маркеры кривой
// #include <qwt_symbol.h>
symbol = new QwtSymbol( QwtSymbol::Ellipse,
QBrush( Qt::yellow ), QPen( Qt::red, 2 ), QSize( 8, 8 ) );
curve->setSymbol( symbol );
}


void MainWindow::addPointsToCurveAndShow()
{

// Добавить точки на ранее созданную кривую
// Значения точек записываются в массив, затем считываются
// из этого массива
for (int i = 0; i < 5; i++) {
pointArray[i][0] = 1.0 + 0.5*i;
pointArray[i][1] = 1.0 + 0.5*i;

points << QPointF( pointArray[i][0], pointArray[i][1]);
}

curve->setSamples( points ); // ассоциировать набор точек с кривой

curve->attach( d_plot ); // отобразить кривую на графике
}


void MainWindow::enableMagnifier()
{
// #include <qwt_plot_magnifier.h>
magnifier = new QwtPlotMagnifier(d_plot->canvas());
// клавиша, активирующая приближение/удаление
magnifier->setMouseButton(Qt::MidButton);
}

void MainWindow::enableMovingOnPlot()
{
// #include <qwt_plot_panner.h>
d_panner = new QwtPlotPanner( d_plot->canvas() );
// клавиша, активирующая перемещение
d_panner->setMouseButton( Qt::RightButton );
}

void MainWindow::enablePicker()
{
// #include <qwt_plot_picker.h>
// настройка функций
d_picker =
new QwtPlotPicker(
QwtPlot::xBottom, QwtPlot::yLeft, // ассоциация с осями
QwtPlotPicker::CrossRubberBand, // стиль перпендикулярных линий
QwtPicker::AlwaysOn, // всегда включен
d_plot->canvas() ); // ассоциация с полем

// Цвет перпендикулярных линий
d_picker->setRubberBandPen( QColor( Qt::red ) );

// цвет координат положения указателя
d_picker->setTrackerPen( QColor( Qt::black ) );

// непосредственное включение вышеописанных функций
d_picker->setStateMachine( new QwtPickerDragPointMachine() );
}

MainWindow::~MainWindow()
{
delete ui;
}








Добавим строку состояния
Прописываем прототип функции в mainwindow.h:

void setStatusBar();


Добавляем код в mainwindow.cpp



void MainWindow::setStatusBar()
{

#ifndef QT_NO_STATUSBAR
( void )statusBar();
#endif

}


Вызываем функцию из конструктора MainWindow:



setStatusBar();





Сохраним координаты клика в переменных и отобразим их в строке состояния
Отображать координаты можно как по клику мышкой, так и в режиме реального времени. Так как в реальном времени координаты у нас уже отображаются возле курсора (небольшая поправочка кода части №1), то реализуем первый вариант.

Клик – это событие. Следовательно, создадим слот, который будет принимать это событие (сигнал). В mainwindow.h добавим новую приватную секцию и следующий код:

private Q_SLOTS:
void click_on_canvas( const QPoint &pos );


Реализуем слот (функцию) в mainwindow.cpp:



void MainWindow::click_on_canvas( const QPoint &pos )
{
// считываем значения координат клика
double x = d_plot->invTransform(QwtPlot::xBottom, pos.x());
double y = d_plot->invTransform(QwtPlot::yLeft, pos.y());

QString info = "x= " + QString::number(x) +
"; y = " + QString::number(y);

// отображаем информацию в строке состояния
statusBar()->showMessage(info);
}


В конструкторе MainWindow создаем пару «сигнал-слот».



// коннектить нужно именно к d_picker, но не к d_plot!
connect( d_picker, SIGNAL( appended( const QPoint & ) ),
SLOT( click_on_canvas( const QPoint & ) ) );


Откомпилируем и кликнем в любом месте экрана (показан нижний кусочек графика):


Как вы уже поняли, координаты клика, записанные в переменные double, можно использовать как угодно.




Создадим панель управления
Добавляем приватную переменную в mainwindow.h

QToolBar *toolBar;




Прописываем прототип функции в mainwindow.h:

void setToolBar ();




Добавляем код в mainwindow.cpp

void MainWindow::setToolBar()
{
toolBar = new QToolBar( this );

addToolBar( toolBar );
}




Вызываем функцию из конструктора MainWindow:

setToolBar();




Результат: в верхней части появилась тонкая полоска — панель инструментов

(пока пустая).


Добавим кнопку на панель управления
В начало mainwindow.h добавляем:

#include <QToolButton>




Добавляем приватные переменные в mainwindow.h и приватную функцию-прототип:

QToolButton *toolButton;
void addCorrectionButton();


Добавляем код в mainwindow.cpp



void MainWindow::addCorrectionButton()
{
toolButton = new QToolButton( toolBar );

toolButton->setText( "Change x" );
toolButton->setCheckable( true );

toolBar->addWidget( toolButton ); // добавить кнопку на панель инструментов
}




Вызываем функцию из конструктора MainWindow:

addCorrectionButton();



Добавим на панель управления QwtCounter
В начало mainwindow.h добавляем:

#include <QHBoxLayout>
#include <qwt_counter.h>


Добавляем приватные переменные в mainwindow.h и приватную функцию-прототип:



QWidget *hBox;
QHBoxLayout *layout;
QwtCounter *cntDamp;
void addQwtCounter();




Добавляем код в mainwindow.cpp

void MainWindow::addQwtCounter()
{
// настраиваем параметры установщика смещения
cntDamp = new QwtCounter();
cntDamp->setRange( -50, 50 );

// шаг изменения числа при нажатии одинарной стрелки
// при нажатии двойной стрелки число изменяется на порядок больше
cntDamp->setSingleStep( 1.0 );
cntDamp->setValue( 0 ); // начальное значение

cntDamp->setEnabled(true);

// Размещение элемента на панели инструментов

// новый виджет
hBox = new QWidget();

// "контейнер", организущий виджеты в горизонтальной последовательности
// ассоциируется с объектом типа QWidget.
layout = new QHBoxLayout( hBox );

// помещаем в контейнер установщик смещения
layout->addWidget(cntDamp);

// без этой строчки установщик смещения будет растянут на всю панель
layout->addWidget( new QWidget(hBox) , 10 ); // spacer

// помещаем виджет на уже имеющуюся панель инструментов
( void )toolBar->addWidget( hBox );
}


Вызываем функцию из конструктора MainWindow:



addQwtCounter();




Результат:




Зададим с помощью QwtCounter смещение графика x
В mainwindow.h в секцию private Q_SLOTS добавим еще один слот:

void setPlotCorrection( double coeff );


В mainwindow.h добавим приватную переменную:



double changeXValue;


Реализуем слот (функцию) в mainwindow.cpp:



void MainWindow::setPlotCorrection( double coeff )
{
changeXValue = coeff;
}


В конструкторе MainWindow инициализируем добавленную переменную и создаем пару «сигнал-слот»:



changeXValue = 0.0;
connect( cntDamp, SIGNAL( valueChanged( double ) ),
SLOT( setPlotCorrection( double ) ) );





Нажатием на кнопку сместим график на ранее заданную величину х
В mainwindow.h в секцию private Q_SLOTS добавим еще один слот:

void changeX();




реализуем слот (функцию) в mainwindow.cpp:

void MainWindow::changeX()
{
// очистить контейнер точек
points.clear();

for (int i = 0; i < 5; i++) {
pointArray[i][0] += changeXValue;

points << QPointF( pointArray[i][0], pointArray[i][1]);
}

curve->setSamples( points ); // ассоциировать набор точек с кривой

d_plot->replot();

}


После добавления этих строчек в нашем коде появляется дублирование. Однако, программа имеет демонстрационный характер, поэтому рефакторинг не производился.


В конструкторе MainWindow создаем пару «сигнал-слот»:



connect( toolButton, SIGNAL(toggled(bool)),
SLOT( changeX() ) );


Примечание: кнопка имеет два различных положения (нажата/не нажата), однако, в программе это состояние не отслеживается.




Результаты и выводы
Запустим программу. Установим стрелочками любое число и нажмем на кнопку. График успешно смещается по полю на заданную величину.

Выводы:

В данной статье на простом примере показано, как добавлять элементы управления вручную, и с помощью них производить модификацию графика.

Очевидно, добавление управляющих элементов вручную –процесс, требующий дополнительного ручного кодирования. Хотелось бы использовать GUI. Именно этому будет посвящена моя следующая статья.




Спасибо за внимание!


P.S. Просьба оставить комментарии по поводу оформления статьи. Удобно ли было ее читать? Может быть, изменить представление материала? Как?


Ссылки:


Архив с исходниками данного примера: http://ift.tt/NjbexP


Официальный ресурс Qwt: qwt.sourceforge.net


Сборник решений разнообразных пробем c Qwt:
http://ift.tt/1lb7Ccv


Часть №1 цикла статей про Qwt: http://ift.tt/1foEieX


Вариант библиотеки, альтернативный Qwt (спасибо, GooRoo!)

www.qcustomplot.com





Свежий взгляд

на бег


протестируй кроссовки

нового поколения




Стань

первоиспытателем!


Скачай Windows Server 2012 R2

и выиграй почетную футболку!


Скачать



Только зарегистрированные пользователи могут оставлять комментарии. Войдите, пожалуйста.


This entry passed through the Full-Text RSS service — if this is your content and you're reading it on someone else's site, please read the FAQ at http://ift.tt/jcXqJW.


Организация MDIF объявила о создании бесплатной сети Outernet


сегодня в 12:49



Скрытый текст

странно, что новости до сих пор нет на хабре



В понедельник организация MDIF объявила о создании новой мировой сети доступа в Интернет. Проект получил название «Outernet» и предполагает размещение на околоземной орбите миниатюрных спутников CubeSats, покрывающих сетью Wi-FI все континенты.



Основанный директором по инновациям MDIF Сайедом Каримом, Outernet будет использовать сеть из маленьких спутников для передачи интернет данных — аудио, видео, текста и приложений — на любое Wi-Fi совместимое устройство, включая мобильные телефоны, в любой точке мира и совершенно бесплатно.






Мы начинаем по-настоящему революционный проект — говорит MDIF CEO Харлан Мандель,- Outernet обойдёт цензуру, обеспечит конфиденциальность и всемирный доступ к информации для каждого, включая тех, кто сегодня находится вне географической досягаемости Интернета или просто не может себе позволить его.



Суть проекта в размещении на низкой околоземной орбите сотен дешёвых, миниатюрных спутников, каждый из которых будет принимать данные от наземных станций и непрерывно передавать их клиентам. Для этих целей будет использоваться протокол UDP поверх Wi-Fi. Однако до сих пор непонятно, насколько эта связка оправдана, особенно, если речь идёт об одном получателе.


Построить сеть грозятся к июню 2015 года. Кстати, уже принимаются пожертвования.


Ссылки




Свежий взгляд

на бег


протестируй кроссовки

нового поколения




Стань

первоиспытателем!


Скачай Windows Server 2012 R2

и выиграй почетную футболку!


Скачать



Только зарегистрированные пользователи могут оставлять комментарии. Войдите, пожалуйста.


This entry passed through the Full-Text RSS service — if this is your content and you're reading it on someone else's site, please read the FAQ at http://ift.tt/jcXqJW.


Polyfill для обхода проблемы с браузерным автозаполнением форм


сегодня в 12:39


Год назад в репозитории AngularJS был создан issue #1460 «Form model doesn't update on autocomplete».

Браузер, при автозаполнении формы ранее сохраненными данными (например, вводим email — браузер заполняет пароль автоматически) никак не извещает javascript о новых данных в поле.



За время существование этой проблемы были написаны различные костыли, которые можно оценить в комментариях к issue. Сейчас же появился костыль официальный. И пока разработчики браузеров на проблему внимания не обращают, это оптимальное решение — Autofill event polyfill.


Придется подключать еще один скрипт. Принцип работы таков — по «DOMContentLoaded» скрипт пробегает по полям и запоминает их текущее значение value, после таймаута в 200 милисекунд сравнивает сохраненное ранее значение с текущим, если они отличаются — вызывается событие «change» для поля. По событию «blur» для одного из полей, снова запускается проверка.


Ссылки:

Autofill event polyfill

issue #1460





Свежий взгляд

на бег


протестируй кроссовки

нового поколения




Стань

первоиспытателем!


Скачай Windows Server 2012 R2

и выиграй почетную футболку!


Скачать



Только зарегистрированные пользователи могут оставлять комментарии. Войдите, пожалуйста.


This entry passed through the Full-Text RSS service — if this is your content and you're reading it on someone else's site, please read the FAQ at http://ift.tt/jcXqJW.


«Какие ваши доказательства?» — по мотивам заседаний Генпрокуратуры РФ о криптовалютах


сегодня в 07:25


Заставь дурака Богу молиться, он и лоб расшибёт!

image


В удивительной стране я живу! И прежде всего, удивительно богатой кретинами, особенно во власти!


Недавние действия Генпрокуратуры РФ лишнее тому подтверждение!


Первое, что хотелось-бы донести до читателя этих строк — криптовалюты это НЕ денежные суррогаты и НЕ деньги как товар с абсолютной ликвидность!


Криптовалюты это товар полученный в результате труда на который появился спрос!


Точно таким же товаром является, например, программа скомпилированная компилятором.



И вот, что характерно в отношении программы:

— ничего не стоит, пока на неё нет спроса;

— можно передать по сети в том числе анонимно;

— можно продавать;

— как любой товар, может использовать для отмывания фиатных денег;

— можно быть украдена;

— может непосредственно использоваться для различных целей в том числе для противоправных и террористических;


Исходя из выше перечисленного может поступим по итальянски — подадим коллективный запрос в генпрокуратуру с требованием запретить все программы произведённые негосударственными организациями и частными лицами, так же как это недавно сделано с криптовалютами!





Свежий взгляд

на бег


протестируй кроссовки

нового поколения




Стань

первоиспытателем!


Скачай Windows Server 2012 R2

и выиграй почетную футболку!


Скачать



Только зарегистрированные пользователи могут оставлять комментарии. Войдите, пожалуйста.


This entry passed through the Full-Text RSS service — if this is your content and you're reading it on someone else's site, please read the FAQ at http://ift.tt/jcXqJW.


IBM может избавиться от полупроводникового бизнеса

По данным Financial Times корпорация IBM обратилась к банку Goldman Sachs для поиска возможных покупателей на свой полупроводниковый бизнес.

В IBM ещё не приняли окончательного решения в связи с продажей и могут преобразовать полупроводниковый бизнес в совместное предприятие с кем-либо.

Источники в IBM говорят, что корпорация уже давно и планомерно избавляется от «сырьевого» бизнеса, продавая аппаратные направления одно за другим. Однако по их словам, серверный бизнес по-прежнему важен для компании и даже в будущем IBM не планирует уходить из выпуска серверов как таковых.


Отчасти продажа полупроводникового бизнеса обусловлена тем, что IBM сталкивается с серьезной конкуренцией в производстве чипов. И запуск собственных производственных заводов по выпуску чипов с новыми технологическим нормами в одиночку становится слишком затратным даже для IBM.


Новые техпроцессы по производству чипов IBM уже давно развивала в сотрудничестве с другими компаниями, и в частности с AMD, которая затем выделила своё полупроводниковое производство в отдельную контрактную компанию GlobalFoundries — см.: «IBM — старый новый партнер AMD в разработке техпроцессов».



Интересно то, что IBM уже несколько лет возглавляет созданный ею альянс чипмейкеров Common Platform, который объединяет всех основных конкурентов Intel: IBM, Samsung, GlobalFoundries, а с недавнего времени ещё и UMC — см.: «UMC будет разрабатывать 10-нанометровый техпроцесс вместе с IBM».


Последние годы на этом рынке свои позиции значительно усилили азиатские контрактные производители, такие как TSMC и Samsung, которые по заказам занимаются выпуском процессоров, чипов памяти и другой микроэлектроники. И им пока удаётся поддерживать неплохую маржинальность в такой низкодоходной сфере, как производство чипов.


IBM последнее время активизировалась в продвижении на рынок своих POWER-чипы — см.: «IBM заручилась поддержкой Google, NVIDIA и другими для противостояния с Intel», и ещё см.: «IBM лицензирует процессоры Power».


И в целом эксперты сходятся во мнении, что даже если IBM и избавится от полупроводникового направления, компания не откажется от проектирования современных чипов архитектуры POWER, которые работают в её высокомаржинальных аппаратных решениях, таких как суперкомпьютер IBM Watson.


Источник новости:

* «IBM может продать полупроводниковый бизнес».


Что вы думаете по этому поводу?


This entry passed through the Full-Text RSS service — if this is your content and you're reading it on someone else's site, please read the FAQ at http://ift.tt/jcXqJW.


Кроссплатформенный https сервер с неблокирующими сокетами. Часть 2

Эта статья является продолжением статей:

Простейший кросcплатформенный сервер с поддержкой ssl

Кроссплатформенный https сервер с неблокирующими сокетами

В этих статьях я постепенно из простенького примера, входящего в состав OpenSSL стараюсь сделать полноценный однопоточный веб-сервер.

В предыдущей статье я «научил» сервер принимать соединение от одного клиента и отсылать обратно html страницу с заголовками запроса.

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



Для начала я разобью код на два файла: serv.cpp и server.h

При этом файл serv.cpp будет содержать такой вот «высокоинтелектуальный» код:

#include "server.h"

int main()
{
server::CServer();
return 0;
}


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


Переходим к файлу server.h

В его начало я перенес все заголовки, макросы и определения, которые раньше были в serv.cpp, и добавил еще пару заголовков из STL:



#ifndef _SERVER
#define _SERVER
#include <stdio.h>
#include <stdlib.h>
#include <memory.h>
#include <errno.h>
#include <sys/types.h>

#ifndef WIN32
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#else
#include <io.h>
#include <Winsock2.h>
#pragma comment(lib, "ws2_32.lib")
#endif

#include <openssl/rsa.h> /* SSLeay stuff */
#include <openssl/crypto.h>
#include <openssl/x509.h>
#include <openssl/pem.h>
#include <openssl/ssl.h>
#include <openssl/err.h>

#include <vector>
#include <string>
#include <sstream>
#include <map>
#include <memory>

#ifdef WIN32
#define SET_NONBLOCK(socket) \
if (true) \
{ \
DWORD dw = true; \
ioctlsocket(socket, FIONBIO, &dw); \
}
#else
#include <fcntl.h>
#define SET_NONBLOCK(socket) \
if (fcntl( socket, F_SETFL, fcntl( socket, F_GETFL, 0 ) | O_NONBLOCK ) < 0) \
printf("error in fcntl errno=%i\n", errno);
#define closesocket(socket) close(socket)
#define Sleep(a) usleep(a*1000)
#define SOCKET int
#define INVALID_SOCKET -1
#endif


/* define HOME to be dir for key and cert files... */
#define HOME "./"
/* Make these what you want for cert & key files */
#define CERTF HOME "ca-cert.pem"
#define KEYF HOME "ca-cert.pem"

#define CHK_ERR(err,s) if ((err)==-1) { perror(s); exit(1); }



Дальше создаем сначала классы CServer и CClient внутри namespace server:



using namespace std;
namespace server
{
class CClient
{
//Дескриптор клиентского сокета
SOCKET m_hSocket;
//В этом буфере клиент будет хранить принятые данные
vector<unsigned char> m_vRecvBuffer;
//В этом буфере клиент будет хранить отправляемые данные
vector<unsigned char> m_vSendBuffer;

//Указатели для взаимодействия с OpenSSL
SSL_CTX* m_pSSLContext;
SSL* m_pSSL;

//Нам не понадобится конструктор копирования для клиентов
explicit CClient(const CClient &client) {}
public:
CClient(const SOCKET hSocket) :
m_hSocket(hSocket), m_pSSL(NULL), m_pSSLContext(NULL) {}
~CClient()
{
if(m_hSocket != INVALID_SOCKET)
closesocket(m_hSocket);
if (m_pSSL)
SSL_free (m_pSSL);
if (m_pSSLContext)
SSL_CTX_free (m_pSSLContext);
}
};

class CServer
{
//Здесь сервер будет хранить всех клиентов
map<SOCKET, shared_ptr<CClient> > m_mapClients;

//Нам не понадобится конструктор копирования для сервера
explicit CServer(const CServer &server) {}
public:
CServer() {}
};
}

#endif


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

Для каждого клиента инициируется свой контекст SSL, очевидно делать это нужно в конструкторе класса CClient



CClient(const SOCKET hSocket) : m_hSocket(hSocket), m_pSSL(NULL), m_pSSLContext(NULL)
{
#ifdef WIN32
const SSL_METHOD *meth = SSLv23_server_method();
#else
SSL_METHOD *meth = SSLv23_server_method();
#endif
m_pSSLContext = SSL_CTX_new (meth);
if (!m_pSSLContext)
ERR_print_errors_fp(stderr);

if (SSL_CTX_use_certificate_file(m_pSSLContext, CERTF, SSL_FILETYPE_PEM) <= 0)
ERR_print_errors_fp(stderr);
if (SSL_CTX_use_PrivateKey_file(m_pSSLContext, KEYF, SSL_FILETYPE_PEM) <= 0)
ERR_print_errors_fp(stderr);

if (!SSL_CTX_check_private_key(m_pSSLContext))
fprintf(stderr,"Private key does not match the certificate public key\n");
}


Инициализацию библиотек, создание и привязку слушающего сокета перенесем с минимальными изменениями в конструктор CServer:



CServer()
{
#ifdef WIN32
WSADATA wsaData;
if ( WSAStartup( MAKEWORD( 2, 2 ), &wsaData ) != 0 )
{
printf("Could not to find usable WinSock in WSAStartup\n");
return;
}
#endif
SSL_load_error_strings();
SSLeay_add_ssl_algorithms();

/* ----------------------------------------------- */
/* Prepare TCP socket for receiving connections */

SOCKET listen_sd = socket (AF_INET, SOCK_STREAM, 0); CHK_ERR(listen_sd, "socket");
SET_NONBLOCK(listen_sd);

struct sockaddr_in sa_serv;
memset (&sa_serv, '\0', sizeof(sa_serv));
sa_serv.sin_family = AF_INET;
sa_serv.sin_addr.s_addr = INADDR_ANY;
sa_serv.sin_port = htons (1111); /* Server Port number */

int err = ::bind(listen_sd, (struct sockaddr*) &sa_serv, sizeof (sa_serv)); CHK_ERR(err, "bind");

/* Receive a TCP connection. */

err = listen (listen_sd, 5); CHK_ERR(err, "listen");
}


Дальше в этом же конструкторе я предлагаю принимать входящие TCP соединения.

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

После каждого вызова accept мы можем что-нибудь сделать с вновь подключившимся и с уже подключенными клиентами, вызвав callback функцию.

Добавим в конструктор CServer после функции listen код:




while(true)
{
Sleep(1);

struct sockaddr_in sa_cli;
size_t client_len = sizeof(sa_cli);
#ifdef WIN32
const SOCKET sd = accept (listen_sd, (struct sockaddr*) &sa_cli, (int *)&client_len);
#else
const SOCKET sd = accept (listen_sd, (struct sockaddr*) &sa_cli, &client_len);
#endif
Callback(sd);
}


А сразу после конструктора, собственно callback функцию:



private:
void Callback(const SOCKET hSocket)
{
if (hSocket != INVALID_SOCKET)
m_mapClients[hSocket] = shared_ptr<CClient>(new CClient(hSocket)); //Добавляем нового клиента

auto it = m_mapClients.begin();
while (it != m_mapClients.end()) //Перечисляем всех клиентов
{
if (!it->second->Continue()) //Делаем что-нибудь с клиентом
m_mapClients.erase(it++); //Если клиент вернул false, то удаляем клиента
else
it++;
}
}


На этом код класса CServer закончен! Вся остальная логика приложения будет в классе CClient.

Важно заметить, что для критичных к скорости проектов, вместо перебора всех клиентов в цикле, надо перебирать только тех клиентов, чьи сокеты готовы для чтения или записи.

Сделать этот перебор легко с помощью функций select в Windows или epoll в Linux. Я покажу как это делается в следующей статье,

А пока (рискуя опять нарваться на критику) все таки ограничусь простым циклом.


Переходим к основной «рабочей лошадке» нашего сервера: к классу CClient.

Класс CClient должен хранить в себе не только информацию о своем сокете, но и информацию о том, на каком этапе находится его взаимодействие с сервером.

Добавим в определение класса CClient следующий код:



private:
//Перечисляем все возможные состояния клиента. При желании можно добавлять новые.
enum STATES
{
S_ACCEPTED_TCP,
S_ACCEPTED_SSL,
S_READING,
S_ALL_READED,
S_WRITING,
S_ALL_WRITED
};
STATES m_stateCurrent; //Здесь хранится текущее состояние

//Функции для установки и получения состояния
void SetState(const STATES state) {m_stateCurrent = state;}
const STATES GetState() const {return m_stateCurrent;}
public:
//Функция для обработки текужего состояния клиента
const bool Continue()
{
if (m_hSocket == INVALID_SOCKET)
return false;

switch (GetState())
{
case S_ACCEPTED_TCP:
break;
case S_ACCEPTED_SSL:
break;
case S_READING:
break;
case S_ALL_READED:
break;
case S_WRITING:
break;
case S_ALL_WRITED:
break;
default:

return false;
}
return true;
}


Здесь Continue() это пока только функция-заглушка, чуть ниже мы ее научим выполнять все действия с подключенным клиентом.


В конструкторе изменим:



CClient(const SOCKET hSocket) : m_hSocket(hSocket), m_pSSL(NULL), m_pSSLContext(NULL)




на

CClient(const SOCKET hSocket) : m_hSocket(hSocket), m_pSSL(NULL), m_pSSLContext(NULL), m_stateCurrent(S_ACCEPTED_TCP)


В зависимости от текущего состояния, клиент вызывает разные функции. Договоримся, что состояния клиента можно менять только в конструкторе и в функции Continue(), это немного увеличит размер кода, но зато сильно облегчит его отладку.


Итак первое состояние, которое клиент получает при создании в конструкторе: S_ACCEPTED_TCP.

Напишем функцию, которая будет вызываться клиентом до тех пор, пока у него это состояние:

Для этого строки:



case S_ACCEPTED_TCP:
break;


изменим на следующие:



case S_ACCEPTED_TCP:
{
switch (AcceptSSL())
{
case RET_READY:
printf ("SSL connection using %s\n", SSL_get_cipher (m_pSSL));
SetState(S_ACCEPTED_SSL);
break;
case RET_ERROR:
return false;
}

return true;
}


А так же добавим следующий код в класс CClient:



private:
enum RETCODES
{
RET_WAIT,
RET_READY,
RET_ERROR
};
const RETCODES AcceptSSL()
{
if (!m_pSSLContext) //Наш сервер предназначен только для SSL
return RET_ERROR;

if (!m_pSSL)
{
m_pSSL = SSL_new (m_pSSLContext);

if (!m_pSSL)
return RET_ERROR;

SSL_set_fd (m_pSSL, m_hSocket);
}

const int err = SSL_accept (m_pSSL);

const int nCode = SSL_get_error(m_pSSL, err);
if ((nCode != SSL_ERROR_WANT_READ) && (nCode != SSL_ERROR_WANT_WRITE))
return RET_READY;

return RET_WAIT;
}


Теперь функция AcceptSSL() будет вызываться клиентом до тех пор, пока не произойдет зашифрованное подключение или пока не возникнет ошибка.


1. В случае ошибки функция CClient::AcceptSSL() вернет код RET_ERROR в вызваашую ее функцию CClient::Continue(), которая в этом случае вернет false вызвавшей ее функции CServer::Callback, которая в этом случае удалит клиента из памяти сервера.

2. В случае удачного подключения функция CClient::AcceptSSL() вернет код RET_READY в вызвавшую ее функцию CClient::Continue(), которая в этом случае изменит состояние клиента на S_ACCEPTED_SSL.


Теперь добавим функцию обработки состояния S_ACCEPTED_SSL. Для этого строки




case S_ACCEPTED_SSL:
break;


исправим на следующие:



case S_ACCEPTED_SSL:
{
switch (GetSertificate())
{
case RET_READY:
SetState(S_READING);
break;
case RET_ERROR:
return false;
}

return true;
}


И добавим в CClient функцию:



const RETCODES GetSertificate()
{
if (!m_pSSLContext || !m_pSSL) //Наш сервер предназначен только для SSL
return RET_ERROR;

/* Get client's certificate (note: beware of dynamic allocation) - opt */

X509* client_cert = SSL_get_peer_certificate (m_pSSL);
if (client_cert != NULL)
{
printf ("Client certificate:\n");

char* str = X509_NAME_oneline (X509_get_subject_name (client_cert), 0, 0);
if (!str)
return RET_ERROR;

printf ("\t subject: %s\n", str);
OPENSSL_free (str);

str = X509_NAME_oneline (X509_get_issuer_name (client_cert), 0, 0);
if (!str)
return RET_ERROR;

printf ("\t issuer: %s\n", str);
OPENSSL_free (str);

/* We could do all sorts of certificate verification stuff here before
deallocating the certificate. */

X509_free (client_cert);
}
else
printf ("Client does not have certificate.\n");

return RET_READY;
}


Эта функция, в отличие от предыдущей, вызовется всего один раз и вернет в CClient::Continue либо RET_ERROR либо RET_READY. Соответственно CClient::Continue вернет либо false, либо изменит состояние клиента на S_READING.


Дальше все аналогично: изменим код



case S_READING:
break;
case S_ALL_READED:
break;
case S_WRITING:
break;


на такой:



case S_READING:
{
switch (ContinueRead())
{
case RET_READY:
SetState(S_ALL_READED);
break;
case RET_ERROR:
return false;
}

return true;
}
case S_ALL_READED:
{
switch (InitRead())
{
case RET_READY:
SetState(S_WRITING);
break;
case RET_ERROR:
return false;
}

return true;
}
case S_WRITING:
{
switch (ContinueWrite())
{
case RET_READY:
SetState(S_ALL_WRITED);
break;
case RET_ERROR:
return false;
}

return true;
}


И добавляем соответствующие функции обработки состояний:



const RETCODES ContinueRead()
{
if (!m_pSSLContext || !m_pSSL) //Наш сервер предназначен только для SSL
return RET_ERROR;

unsigned char szBuffer[4096];

const int err = SSL_read (m_pSSL, szBuffer, 4096); //читаем данные от клиента в буфер
if (err > 0)
{
//Сохраним прочитанные данные в переменной m_vRecvBuffer
m_vRecvBuffer.resize(m_vRecvBuffer.size()+err);
memcpy(&m_vRecvBuffer[m_vRecvBuffer.size()-err], szBuffer, err);

//Ищем конец http заголовка в прочитанных данных
const std::string strInputString((const char *)&m_vRecvBuffer[0]);
if (strInputString.find("\r\n\r\n") != -1)
return RET_READY;

return RET_WAIT;
}

const int nCode = SSL_get_error(m_pSSL, err);
if ((nCode != SSL_ERROR_WANT_READ) && (nCode != SSL_ERROR_WANT_WRITE))
return RET_ERROR;

return RET_WAIT;
}

const RETCODES InitRead()
{
if (!m_pSSLContext || !m_pSSL) //Наш сервер предназначен только для SSL
return RET_ERROR;

//Преобразуем буфер в строку для удобства
const std::string strInputString((const char *)&m_vRecvBuffer[0]);

//Формируем html страницу с ответом сервера
const std::string strHTML =
"<html><body><h2>Hello! Your HTTP headers is:</h2><br><pre>" +
strInputString.substr(0, strInputString.find("\r\n\r\n")) +
"</pre></body></html>";

//Добавляем в начало ответа http заголовок
std::ostringstream strStream;
strStream <<
"HTTP/1.1 200 OK\r\n"
<< "Content-Type: text/html; charset=utf-8\r\n"
<< "Content-Length: " << strHTML.length() << "\r\n" <<
"\r\n" <<
strHTML.c_str();

//Запоминаем ответ, который хотим послать
m_vSendBuffer.resize(strStream.str().length());
memcpy(&m_vSendBuffer[0], strStream.str().c_str(), strStream.str().length());

return RET_READY;
}
const RETCODES ContinueWrite()
{
if (!m_pSSLContext || !m_pSSL) //Наш сервер предназначен только для SSL
return RET_ERROR;

int err = SSL_write (m_pSSL, &m_vSendBuffer[0], m_vSendBuffer.size());
if (err > 0)
{
//Если удалось послать все данные, то переходим к следующему состоянию
if (err == m_vSendBuffer.size())
return RET_READY;

//Если отослали не все данные, то оставим в буфере только то, что еще не послано
vector<unsigned char> vTemp(m_vSendBuffer.size()-err);
memcpy(&vTemp[0], &m_vSendBuffer[err], m_vSendBuffer.size()-err);
m_vSendBuffer = vTemp;

return RET_WAIT;
}

const int nCode = SSL_get_error(m_pSSL, err);
if ((nCode != SSL_ERROR_WANT_READ) && (nCode != SSL_ERROR_WANT_WRITE))
return RET_ERROR;

return RET_WAIT;
}


Наш сервер пока предназначен лишь для того, чтобы показывать клиенту заголовки его http запроса.

После того, как сервер выполнил свое предназначение, он может закрыть соединение и забыть про клиента.

Поэтому в наш код осталось внести последнее небольшое изменение:




case S_ALL_WRITED:
break;


нужно исправить на



case S_ALL_WRITED:
return false;


Вот и все! Теперь у нас есть кроссплатформенный однопоточный https сервер на неблокирующих сокетах, который может обрабатывать произвольное (ограниченное лишь памятью и настройками операционной системы) количество соединений.


Архив с проектом для Visual Studio 2012 можно скачать здесь: 00.3s3s.org

Чтобы скомпилировать в Linux надо скопировать в одну директорию файлы: serv.cpp, server.h, ca-cert.pem и в командной строке набрать: «g++ -std=c++0x -L/usr/lib -lssl -lcrypto serv.cpp»


This entry passed through the Full-Text RSS service — if this is your content and you're reading it on someone else's site, please read the FAQ at http://ift.tt/jcXqJW.