Pull to refresh

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

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

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


И тут на ум пришла гениальная идея: почему бы не создать свою иновационную гео-соцсеть, где люди могли бы находить друг друга на карте по 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-летний школьник.

Спасибо за внимание.
Tags:
Hubs:
You can’t comment this publication because its author is not yet a full member of the community. You will be able to contact the author only after he or she has been invited by someone in the community. Until then, author’s username will be hidden by an alias.