ВУЗ: Московский технический университет связи и информатики
Категория: Методичка
Дисциплина: Программирование
Добавлен: 23.10.2018
Просмотров: 2549
Скачиваний: 31
Сокет(разъем)
Чтобы использовать сокеты, вы должны включить эту строку в свою программу:
Import java.net. *;
Конструктор
Socket (String host, int port) создает новый сокет из строки, представляющей хост и номер порта, и устанавливает соединение.
Методы
-
Void setSoTimeout (int timeout) задает тайм-аут сокета в миллисекундах. Вы должны вызвать это после создания сокета, чтобы он знал, сколько времени ждать передачи данных с другой стороны. В противном случае он будет ждать вечно, что, вероятно, не является хорошей идеей, если мы хотим использовать эффективный сканер.
-
InputStream getInputStream () возвращает InputStream, связанный с Socket. Это позволяет Socket получать данные с другой стороны соединения.
-
OutputStream getOutputStream () возвращает OutputStream, связанный с Socket. Это позволяет Socket отправлять данные на другую сторону соединения.
-
Void close () закрывает Socket.
Потоки
Чтобы использовать потоки, вы должны включить эту строку в свою программу:
Import java.io. *;
Чтобы эффективно использовать сокеты, вы захотите преобразовать InputStream и OutputStream, связанные с Socket, в нечто более полезное. Объекты InputStream и OutputStream являются очень примитивными объектами. Они могут читать только байты или массивы байтов (даже не символы). Поскольку вы хотите читать и писать символы, вы должны иметь объекты, которые преобразуют байты и символы и печатают целые строки. К сожалению, Java API делает это несколько различными способами для ввода и вывода.
Входные потоки
Для входных потоков вы можете использовать классы InputStreamReader следующим образом:
InputStreamReader in = new InputStreamReader (my_socket.getInputStream ());
Теперь in является InputStreamReader, который может читать символы из Socket. Тем не менее, это все еще не очень дружелюбно, потому что вам по-прежнему приходится работать с отдельными символами или массивами символов. Было бы неплохо читать целые строки. Для этого вы можете использовать класс BufferedReader. Вы можете создать BufferedReader, указав экземпляр InputStreamReader, а затем вызвать метод readLine в BufferedReader. Это будет прочитано в целой строке с другого конца сокета.
Потоки вывода
Потоки вывода немного проще. Вы можете создать экземпляр PrintWriter непосредственно из объекта OutputStream сокета, а затем вызвать его метод println для отправки строки текста на другой конец сокета. вы должны использовать этот конструктор:
PrintWriter (OutputStream out, boolean autoFlush)
с установкой autoFlush на true. Это очистит буфер вывода после каждого println.
Строковые методы
Вы найдете эти методы String полезными. См. документацию по API.
-
Boolean equals (Object anObject)
-
String substring (int beginIndex)
-
String substring (int beginIndex, int endIndex)
-
Boolean startsWith (String prefix)
ПРИМЕЧАНИЕ. Не используйте == для сравнения строк для равенства! Он будет возвращать true, только если две строки будут одним и тем же объектом. Если вы хотите сравнить содержимое двух строк, используйте метод equals.
Списки
Списки очень похожи на массивы объектов, за исключением того, что они могут легко расширяться или сокращаться при необходимости, и у них нет скобки-обозначения для поиска отдельных элементов. Чтобы использовать Списки, вы должны включить эту строку в свою программу:
Import java.util. *;
Вы должны хранить пары (URL, depth) в LinkedList, которые является конкретной реализацией List. Создайте его следующим образом:
LinkedList <URLDepthPair> myList = новый LinkedList <URLDepthPair> ();
А затем увидеть API для множества полезных методов в списках и различных реализаций списков. (В частности, вы заметите, что разные реализации List предоставляют разные функции. Именно поэтому рекомендуется LinkedList, некоторые из его функций особенно хорошо подходят для этого назначения.)
Специальный синтаксис для создания LinkedList выше, использует новую поддержку Java 1.5 generics. Этот специальный синтаксис означает, что вам не нужно бросать объекты, которые вы храните или извлекаете из списка.
Исключения
Когда вы найдете что-то, похожее на URL-адрес, то не начинайте с «http: //», вы должны выдать MalformedURLException, который является частью Java API.
Советы по проектированию
Ниже приведены некоторые рекомендации по разработке вашего поискового робота.
Пары URL-глубины
Как упоминалось выше, вы должны создать специальный класс URLDepthPair, каждый экземпляр которого включает в себя поле String, представляющее URL, и int, представляющее глубину поиска. Вы также должны иметь метод toString, который распечатает содержимое пары. Это значительно упрощает вывод результатов вашего веб-сканирования.
Отдельные URL-адреса необходимо разбить на части. Этот синтаксический разбор URL и манипуляция должны быть частью созданного вами класса пар URL-глубины. Хороший объектно-ориентированный дизайн диктует, что если какой-то конкретный класс собирается хранить определенный тип данных, тогда любые манипуляции с этими данными также должны быть реализованы в этом классе. Итак, если вы пишете какие-либо функции для разрыва URL-адреса или для проверки, является ли URL-адрес допустимым, поместите его в этот класс!
Сканеры
Как уже упоминалось выше, вы должны спроектировать класс Crawler, который будет реализовывать основные функциональные возможности приложения. Этот класс должен иметь метод getSites, который будет возвращать список всех пар URL-глубины, которые были посещены. вы можете вызвать это в вашем основном методе, как только обход завершен; Получить список, затем выполнить итерацию и распечатать все URL-адреса.
Самый простой способ отслеживания посещенных сайтов состоит в том, чтобы иметь два списка, по одному для всех сайтов, рассмотренных до сих пор, и один, который включает только сайты, которые еще не обработаны. Вам следует проделать итерацию по всем сайтам, которые не были обработаны, удалить каждый сайт перед загрузкой его содержимого, и каждый раз, когда вы находите новый URL, вы должны поместить его в необработанный список. Когда необработанный список пуст, вы все сделали - вы нашли все сайты.
Хотя вы можете подумать про себя: «Открытие сокета по URL-адресу - операция, связанная с URL-адресом, и, следовательно, должна быть реализована в классе пар URL-глубины», что было бы слишком специализированным для целей парного класса. Это действительно просто место для хранения URL-адресов и значений глубины, а также нескольких дополнительных утилит. Искатель - это класс, который перемещает веб-страницы и ищет URL-адреса, поэтому класс искателя должен содержать код, который фактически открывает и закрывает сокеты.
Вам нужно будет создать новый экземпляр Socket для каждого URL, с которого вы загружаете текст. Обязательно закройте сокет, когда вы закончите сканирование этой веб-страницы, чтобы операционная система не исчерпала сетевые ресурсы! (Существует так много сокетов, что компьютер может оставаться открытым сразу). Кроме того, не используйте рекурсию для поиска более глубоко вложенных веб-страниц; Реализовать эту функцию в виде цикла. Это также сделает ваш поисковый робот более эффективным с точки зрения использования ресурсов.
Константы!
В вашей программе, несомненно, будут строки типа «http: //» и «a href = \», и вы, вероятно, будете испытывать желание просто повторять эти строки везде, где вам это нужно. Кроме того, вам понадобятся эти длины для разных строк операций, так что у вас также возникнет соблазн жестко «закодить» длины этих строк в ваш код. Не делайте этого! Это делает код очень неподдерживаемым! Если вы делаете опечатку или позже меняете поиск, вам придется обновлять много разных строк кода.
Вместо этого создайте строковые константы в своих классах. Например, у вас может быть следующее:
Public static final string URL_PREFIX = "http: //";
Теперь, если вам нужна эта строка, вместо того, чтобы напрямую ее кодировать, используйте константу URL_PREFIX. Если вам нужна его длина, вам повезло - URL_PREFIX является объектом String, поэтому вы можете вызвать URL_PREFIX.length () и вернуть его длину.
Вы также должны думать о том, где вы ставите эти константы. Вам нужно, чтобы каждая константа отображалась один раз во всем вашем проекте, и вы должны поместить константу там, где она имеет смысл. Например, поскольку префикс URL необходим, чтобы определить, действителен ли URL-адрес, вы должны поместить эту константу в свой класс пары URL-глубины. Если у вас есть еще одна константа для HTML-ссылок, поместите ее в свой класс искателя. Если поисковик когда-либо нуждается в префиксе URL, он может просто ссылаться на константу пары URL-depth, вместо того, чтобы дублировать эту константу.
Дополнительное задание
-
Добавьте код, чтобы добавлять сайты в неподготовленный список, если они не были замечены ранее.
-
Расширьте возможности поиска гиперссылок поискового робота, используя поиск регулярных выражений на собранных данных. Вам также понадобится больше логики, чтобы решить, какую машину подключить к следующей. Ваш искатель должен иметь возможность перемещаться по ссылкам на различных популярных сайтах. Использование регулярных выражений требует больше знаний, но на самом деле гораздо проще, чем вручную искать подстроки в строках.
-
Создайте пул из пяти (или более!) Искателей, каждый в своем потоке, каждый из которых может получить URL для просмотра, и каждый из них вернет список ссылок по окончании. Посылайте новые URL-адреса в этот пул, как только они становятся доступными.
-
Расширьте свой многопоточный поисковый робот, чтобы выполнить поиск на глубину до 1 000 000. Сохраняйте результаты каждого обхода в базе данных через JDBC и обратите внимание, сколько раз каждая конкретная уникальная страница ссылается на другие. Включите интеллектуальный алгоритм для «поиска смысла» на каждой странице путем взвешивания слов и фраз на основе повторяемости, близости к началу абзацев и разделов, размера шрифта или стиля заголовка и мета-ключевых слов, по крайней мере.
Задача № 8: лучший Web Crawler
Прошлый поисковый робот не был особенно эффективен. Вы расширите свой поисковый робот, чтобы использовать поточную обработку Java, так, чтобы многократные веб-страницы могли быть проверены параллельно. Это произведет значительное повышение производительности, потому что время, которое каждый поток поискового робота тратит на ожидание сетевых операций, чтобы завершить их, может быть перекрыт с другими операциями по обработке в других потоках.
Расширение Web Crawler
Вы расширите и измените свою предыдущую программу.
-
Реализуйте класс под названием URLPool, который сохранит список всех URL, которые будут искать, вместе с относительным "уровнем" каждого из тех URL (также известный как "поисковая глубина"). Первый URL, который вы ищете, будет на поисковой глубине 0, URL, найденные на той странице, будут поисковой глубиной 1 и т.д. вы должны сохранить URL и их поисковую глубину вместе, как экземпляры класса под названием URLDepthPair, как вы делали ранее. LinkedList рекомендуют для хранения элементов, так как это поможет выполнить требуемые операции очень эффективно.
-
Должен быть путь к пользователю класса URLPool, чтобы выбрать пару глубины URL от пула и удалить его из списка одновременно. Должен также быть способ добавить пару глубины URL к пулу. Обе из этих операций должны быть ориентированы на многопотоковое исполнение, так как многократные потоки будут взаимодействовать с URLPool одновременно.
-
В отличие от примера FIFO, обсужденного в классе, у пула URL не должно быть максимального предела размера. Этому пулу действительно просто нужны списки незаконченных URL, спискм отсканированных URL.
-
Чтобы выполнить веб-проверку в многократных потоках, вы должны создать класс CrawlerTask, который реализует интерфейс Runnable. У каждого экземпляра CrawlerTask должна быть ссылка на один экземпляр класса URLPool, описанного выше. (Обратите внимание на то, что вся доля экземпляров CrawlerTask это единственный пул!) Задание поискового робота должно быть таким:
1) получите пару глубины URL от пула, ожидая, если вы не сразу доступны;
2) получите веб-страницу, упомянутую URL;
3) ищите страницу больше URL. Для каждого URL, найденного страницей, задача поискового робота должна добавить новую пару глубины URL к пулу URL;
4) вернитесь к шагу 1.
Это должно продолжаться, пока больше нет (URL, глубина) пар в пуле, чтобы проверить.
Так как Ваш поисковый робот будет порождать некоторое количество потоков поискового робота, обновить Вашу программу, чтобы принять третий параметр командной строки, определяя количество потоков поискового робота. Ваша основная функция будет управлять чем-то вроде этого:
1) обработайте параметры командной строки. Сообщение пользователю о любых входных ошибках;
2) создайте экземпляр пула URL и поместите определенный пользователями URL в пул с глубиной 0;
3) создайте количество задач поискового робота (и потоки, чтобы выполнить их) требуемых пользователю. Каждой задаче поискового робота нужно дать ссылку на пул URL, который вы инсталлировали;
4) ожидайте веб-проверки, чтобы завершиться;
5) распечатайте получающийся список URL, которые были найдены.
Удостоверьтесь, что синхронизировали свой объект пула URL в любой и всех критических точках, так как это должно теперь быть ориентировано на многопотоковое исполнение.
Поисковые роботы, постоянно опрашивают пул URL относительно другого URL. Вместо этого делайте, чтобы ожидание было эффективно, когда никакой URL не будет доступен. Вы должны реализовать, это при наличии пула URL "добирается, URL" метод ожидает () внутренне, если никакой URL не в настоящее время доступен. Соответственно, пул URL, "добавляя URL" метод, должен уведомить (), когда новый URL добавлен к пулу.
Обратите внимание на то, что потоки поискового робота не будут самостоятельно выступать, любой из них синхронизируется / ожидает / уведомляет операции. Это по той же причине, что пул URL скрывает детали того, как URL сохранены и получены: инкапсуляция! Точно так же, как вы хотите, чтобы пользователи пула URL не волновались о деталях реализации.
Совет проекта
Вот некоторые советы для успешного выполнения задачи №7:
Вы можете использовать некоторые части кода с прошлого выполнения, но с небольшим изменением. Класс URLDepthPair не должен быть изменен вообще. Также, большая часть веб-кода может быть снова использована. Основные отличия - то, что код URL-download/page-crawl находится теперь в классе и будет получать и добавлять ссылки на экземпляр URLPool.
Вы должны синхронизировать доступ к внутренним полям URLPOOL, поскольку к нему будут обращаться из нескольких потоков. Как обсуждалось в классе, самый простой подход заключается в использовании synchronized методов, а не в попытке синхронизировать части кода внутри методов класса. Вам не нужно синхронизировать конструктор URLPool. Подумайте о том, какие методы должны быть синхронизированы.
Опишите методы вашего URLPOOL, для того чтобы использовать wait() и notify() так, чтобы потоки поисковика могли ожидать новые URL, чтобы стать доступными.
Дополнительное задание
-
Обновите свою пару глубины URL, чтобы использовать java.net. Класс URL и обновление Ваш поисковый робот, чтобы следовать за относительными URL, а также абсолютными URL.
-
Выход из вашей программы System.exit () не самый лучший. После проверки всей программы найдите более быстрый способ выхода.
-
Реализуйте список URL-адресов, которые не будут просмотрены, и избегайте возврата к старым ссылкам. Используйте один из классов коллекций java, чтобы сделать это проще. Какой-то набор, который поддерживает постоянное время поиска и вставки, будет наиболее подходящим.
-
Добавьте другой дополнительный параметр командной строки, чтобы определить, сколько времени поток поискового робота должен ожидать сервера, чтобы возвратить требуемую веб-страницу. Используйте это время в качестве старта для запуска получения страницы, и затем поддерживайте дополнительный параметр maxPatience, указывающий точку, в которой задача поискового робота прервет проверку страницы и перейдет к следующей.