Как стать автором
Обновить

Как мы пытались создать свою гео-соцсеть

Как-то морозным сибирским вечером под Новый Год я задумался о смысле бытия. Я уже давно понял, что друзья очень важны в нашей жизни. Одно оставалось понять: где их найти — единомышленников? Ведь в нашу пору высокоскоростного Интернета люди переплетены связями так крепко, что часто и не замечаешь то, что они могут находиться на огромных расстояниях от тебя…

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


И тут на ум пришла гениальная идея: почему бы не создать свою иновационную гео-соцсеть, где люди могли бы находить друг друга на карте по GPS? В качестве опознавательных знаков было принято ввести статусы: человек постит свой умный статус, который отражает его текущее состояние души, а его земляки видят это и жмакают по его статусу, а тут открывается целая история статусов — криков его души…

Я понимал, что наверняка что-то подобное существует, но, судорожно открыв поисковик и оглядев несколько результатов, я пришел к выводу, что таких систем для поиска новых людей не существует. Возможно, я сделал этот вывод поспешно, ведь не терпелось почувствовать себя великим человеком и попробовать свои силы в программировании под Android!

Да, я недавно приобрел аппарат на Android и хотелось опробовать программирование под него. В качестве языка программирования для сервера и клиента была выбрана Java. Сразу хочу сказать, что не было задачи написать какую-то высокопроизводительную систему, код которой можно использовать для настоящей социальной сети, хотелось просто создать некий «прототип» и получить немного опыта.

С самого Нового Года я начал писать сервер, попутно придумывая свой протокол (все было на голых сокетах). Одновременно я перебрасывался на клиент, изучая Android API. И, спустя несколько дней, когда много россиян было пьяно алкоголем, я был пьян своим кодом. Я понял, что мне нужен напарник — человек с опытом для координации моих действий. И тут на помощь ко мне пришел специалист из соседнего сибирского города – ForNeVeR (наверняка его знают юзеры jabber-конференций), который значительно помог в развитии проекта, в основном он занимался сервером, а именно связью с СУБД (MySQL).

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

ServerSocket server = new ServerSocket(port);
ExecutorService pool = Executors.newCachedThreadPool();
while (true) {
   Socket sock = server.accept();
   if(connect.isClosed()) {
       connect = DriverManager.getConnection(connectionString);
       userDAO = new UserDAO(connect);
       statusDAO = new StatusDAO(connect);
       coordinateDAO = new CoordinateDAO(connect);
    }
    pool.submit(new SocketHandler(sock, userDAO, statusDAO, coordinateDAO));
}


За новый поток отвечает класс SocketHandler, в котором и происходят главные события. Именно в нем хранятся объекты для связи с базой данных: userDAO, statusDAO, coordinateDAO (далее будет разъяснено, но, думаю, умный читатель уже понял о чем речь). Сразу хочу сказать, что вначале мы делали долгоживущий сокет, т.е когда он открыт все время подключения клиента к серверу. Однако, мы столкнулись с небольшими проблемами. Хотелось, чтобы клиент получал новую информацию пассивно, т.е чтобы они приходили сами, а не клиент постоянно опрашивал сервер. Вдобавок к этому держать постоянно открытыми сокеты оказалось не слишком хорошо. После небольшого обдумывания мы решили воспользоваться Google Cloud Messaging для push-обновлений а сокет сделать короткоживущим для одного запроса с аутентификацией. То есть в начале каждого запроса передается авторизация (логин+пароль) и команда. Команды бывают следующего типа: залогиниться, зарегистрироваться, (после успешного выполнения юзер помечается в базе как онлайн), обновить статус, обновить позицию (да, эти команды пришлось разъединить – ведь, локация обновляется довольно часто, а статус по желанию юзера и для получения истории, нужно получить данные именно из таблицы со статусами), получить все онлайн-статусы юзеров, выйти из сети ну и несколько других… После выполнения команд «обновить статус или позицию» происходит отправка сообщения в GCM с указанием адресатов – онлайн юзеров сей программы, которые берутся из базы, а гугл уже рассылает push-сообщения на девайсы. После выполнения команды сокет закрывается.

			String user = din.readUTF();
			String pass = din.readUTF();
			String command = din.readUTF();
			loggedUser = userDAO.load(user, pass);
			if (command.equals("login")) {
				if (loggedUser == null) {
					writeError(dout, "invalid login or password");
					return;
				}
				writeLoggedIn(dout);
			} else if (command.equals("register")) {
				String email = din.readUTF();
                		String info = din.readUTF();
				loggedUser = userDAO.create(user, pass, email, info);
				if (loggedUser == null) {
					writeError(dout, "cannot register user");
					return;
				}
				writeLoggedIn(dout);
			} else if (loggedUser == null) {
				writeError(dout, "invalid login or password");
				return;
			} else if (command.equals("updateStatus")) {
				double lat = din.readDouble();
				double lng = din.readDouble();
				String status = din.readUTF();
				if (statusDAO.create(loggedUser, lat, lng, status)) {
					writeMessages(dout, "success");
					Message msg = new Message.Builder()     // создание сообщения для отправки на GCM
							.addData("command", "updateStatus")
							.addData("user", loggedUser.getLogin())
							.addData("lat", String.valueOf(lat))
							.addData("lng", String.valueOf(lng))
							.addData("status", status)
							.build();
					List<String> list = userDAO.getOnlineIDs();
					if (!list.isEmpty() && sender != null)
						sender.send(msg, list, 2);
				} else {
					writeError(dout, "error updating status");
				}
			} else if (command.equals("updatePosition")) {
				double lat = din.readDouble();
				double lng = din.readDouble();
				if (coordinateDAO.update(loggedUser, lat, lng)) {
                                  writeMessages(dout, "success");
                                  Message msg = new Message.Builder()
                                           .addData("command", "updatePosition")
                                           .addData("user", loggedUser.getLogin())
                                           .addData("lat", String.valueOf(lat))
                                           .addData("lng", String.valueOf(lng))
                                           .build();
                                  List<String> list = userDAO.getOnlineIDs();
                                  if (!list.isEmpty())
                                      sender.send(msg, list, 2);
				} else {
					writeError(dout, "Error updating coordinates");
				}
			}


Описывать классы userDAO, statusDAO, coordinateDAO, которые отвечают за, соответственно, три таблицы базы данных: юзеров, статусов и координат думаю смысла нет. Единственно стоит отметить, что все они связаны. Статусы ссылаются на координаты, а юзеры – на статусы по user id.

image

На клиенте все тоже предельно просто. Вначале логин или регистрация с указанием e-mail и контактых данных (должен же человек связаться со своим другом). В качестве основного Activity используется MapFragment с гуглокартами. Точнее SupportMapFragment – мы заботились и о девайсах со старым android. Да, и если у аппарата нет GPS, то приложение закрывается. Если же есть, то загружаться все онлайн юзеры, т.е маркеры с их местоположением и статусом и через каждые 30 сек будет происходить отсылка новых координат на сервер, если позиция изменилась хотя бы на 5 метров.

		SupportMapFragment mapFragment = (SupportMapFragment)getSupportFragmentManager().findFragmentById(R.id.map);
		mMap = mapFragment.getMap();
		mMap.setMapType(GoogleMap.MAP_TYPE_HYBRID);

		locationManager = (LocationManager) this.getSystemService(Context.LOCATION_SERVICE);
		
		listener = new LocationListener() {
		    @Override
		    public void onLocationChanged(Location location) {
		    	
		        new SendTask().execute("updatePosition", location.getLatitude(), location.getLongitude()); // новая таска
		    }
		};
		locationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 30000, 5f, listener);


В любой момент юзер может обновить свой текстовый статус, отправив его на сервер. Также можно открыть «страничку» юзера с его прошлыми статусами и контактной информацией. Была мысль сделать показ мини-карты с поинтами, где менялись статусы :). Реализуется это просто, ведь, как уже было сказано, статусы ссылаются на координаты.
Естественно все сделано асинхронно: на каждую задачу будь то логин, или отправка данных на сервер создается Android Task’a

Несколько скриншотов:

imageimageimage

Исходный код проекта лежит в открытом доступе на github. Код далеко не идеален, да и текущее состояние проекта можно лишь с натяжкой назвать альфа-версией. Энтузиазма осталось мало, поэтому я и пишу данный пост сейчас в таком сыром виде проекта. Буду рад, если найдутся люди, заинтересовавшиеся этим поделием и готовые привнести свой вклад. Сейчас есть круглосуточный сервер для тестирования. Также можно запустить у себя локально.

Прошу сильно не ругать за код. Автором сей программы и сего поста является 17-летний школьник.

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