Commit a9ca04c2 authored by Iskandar Setiadi's avatar Iskandar Setiadi
Browse files

Initial commit

parents
File added
# APPLICATION NAME
By:
# Client Side
By:
## Development Environment
## Requirements
-
## How to Run
-
## Additional Information
-
Last Updated:
# Server Side
By:
## Development Environment
## Requirements
-
## How to Run
-
## Additional Information
-
Last Updated:
# LoliHorizon - Tracker
By: Assistants of IF3230 - Parallel and Distributed Systems
## Development Environment
- Linux x86_64
- Go Language 1.4.2
## Requirements
** Environment Set **
- Set the following lines to ```/etc/profile```:
export PATH=$PATH:/usr/local/go/bin
export GOPATH=$HOME/go
** Download MySQL Driver **
$go get github.com/go-sql-driver/mysql
** For 1st MySQL use **
$sudo chmod -R 755 /var/lib/mysql/
$sudo mkdir /var/run/mysqld
$sudo touch /var/run/mysqld/mysqld.sock
$sudo chown -R mysql /var/run/mysqld/
$sudo /etc/init.d/mysql start
## How to Run
- In release environment, please use ```nohup go run main.go & > my.log 2>&1&``` for running tracker process in background. After that, use ```echo $! > save_pid.txt``` to get Process ID.
## Additional Information
- Developer note: On release (32.46), use account 'freedomofkeima' for MySQL username and '167.205.32.46' for IP address.
package main
import (
"bytes"
"errors"
"fmt"
"net"
"strconv"
"strings"
"sync"
"time"
"database/sql"
"encoding/binary"
"encoding/json"
_ "github.com/go-sql-driver/mysql"
)
func Ip2long(ipAddr string) (uint32, error) {
ip := net.ParseIP(ipAddr)
if ip == nil {
return 0, errors.New("wrong ipAddr format")
}
ip = ip.To4()
return binary.BigEndian.Uint32(ip), nil
}
func Long2ip(ipLong uint32) string {
ipByte := make([]byte, 4)
binary.BigEndian.PutUint32(ipByte, ipLong)
ip := net.IP(ipByte)
return ip.String()
}
var (
id int
ip uint32
port int
)
type LogValue struct {
Ip string `json:"ip"`
Port int `json:"port"`
}
type MessageInJSON struct {
Status string `json:"status"`
Value []LogValue `json:"value"`
}
type MessageOutJSON struct {
Method string `json:"method"`
Server []LogValue `json:"server"`
}
var db *sql.DB // database global_variable
var ip_address string // current IP address
var current_port int // current Port
/** Insert data to DB */
func InsertData(conn net.Conn, ip uint32, port int) {
_, err := db.Query("INSERT IGNORE INTO client_info(ip, port) VALUES(?, ?)", ip, port)
if err != nil {
if conn != nil {
RespondWithRealm(conn, errors.New("Error in database connection"))
} else {
fmt.Println(err.Error())
}
}
}
func DeleteData(ip uint32, port int) {
_, err := db.Query("DELETE FROM client_info WHERE ip=? AND port=?", ip, port)
if err != nil {
fmt.Println(err.Error())
}
}
/** Retrieve all available clients from DB */
func RetrieveData(conn net.Conn, code int) string { // code 0 = inbound; 1 = outbound
rows, err := db.Query("SELECT * FROM client_info")
if err != nil {
RespondWithRealm(conn, errors.New("Error in database connection"))
}
defer rows.Close()
messageObject := new(MessageInJSON)
messageObject2 := new(MessageOutJSON)
for rows.Next() {
err := rows.Scan(&ip, &port)
if err != nil {
RespondWithRealm(conn, errors.New("Error in reading parameters"))
}
ip_str := Long2ip(ip)
if err != nil {
RespondWithRealm(conn, errors.New("IP address is not valid"))
}
if code == 0 {
messageObject.Value = append(messageObject.Value, LogValue{Ip: ip_str, Port: port})
} else if code == 1 {
messageObject2.Server = append(messageObject2.Server, LogValue{Ip: ip_str, Port: port})
}
}
var encodedMessage []byte
if code == 0 {
messageObject.Status = "ok";
encodedMessage, _ = json.Marshal(messageObject)
} else if code == 1 {
messageObject2.Method = "serverStatus";
encodedMessage, _ = json.Marshal(messageObject2)
}
err = rows.Err()
if err != nil {
RespondWithRealm(conn, errors.New("Error in JSON Marshal"))
}
return string(encodedMessage)
}
/** Handling connection from client */
func HandleConnection(conn net.Conn) {
var ip_long string
var port_int float64
remoteAddress := conn.RemoteAddr().String() // broadcast statusServer except this address
fmt.Println("**Initiate connection with " + remoteAddress + "**")
readBuf := make([]byte, 4096)
conn.Read(readBuf) // accept JSON input from clients
length := bytes.Index(readBuf, []byte{0})
// Convert []byte to map interface
var value map[string]interface{}
err := json.Unmarshal(readBuf[:length], &value)
if err != nil {
RespondWithRealm(conn, errors.New("Error in JSON parsing"))
}
// Process data
if value["method"] == nil { // check method exists
RespondWithRealm(conn, errors.New("Missing parameter"))
}
method_string, ok:= value["method"].(string)
if !ok {
RespondWithRealm(conn, errors.New("Invalid 'method' datatype"))
}
data := RetrieveData(conn, 0)
switch method_string {
case "join":
if value["ip"] == nil || value["port"] == nil { // check params exist
RespondWithRealm(conn, errors.New("Missing parameter"))
}
ip_long, ok = value["ip"].(string)
if !ok {
RespondWithRealm(conn, errors.New("Invalid 'ip' datatype"))
}
ip_uint32, err := Ip2long(ip_long)
if err != nil {
RespondWithRealm(conn, errors.New("Unknown IP address"))
}
port_int, ok = value["port"].(float64)
if !ok {
RespondWithRealm(conn, errors.New("Invalid 'port' datatype"))
}
InsertData(conn, ip_uint32, int(port_int))
var wg sync.WaitGroup
rows, err := db.Query("SELECT * FROM client_info")
if err != nil {
RespondWithRealm(conn, errors.New("Error in database connection"))
}
defer rows.Close()
for rows.Next() {
err := rows.Scan(&ip, &port)
if err != nil {
RespondWithRealm(conn, errors.New("Error in reading parameters"))
}
ip_str := Long2ip(ip)
port_str:= strconv.Itoa(port)
if ip_long != ip_str || port != int(port_int) { // exclude requester
wg.Add(1) // increment the wait group counter
go TimeoutCheck(ip_str + ":" + port_str, &wg)
}
}
wg.Wait() // wait
port_str := strconv.Itoa(int(port_int))
go broadcastAll(ip_long + ":" + port_str) // async broadcast
default:
RespondWithRealm(conn, errors.New("Unsupported Method"))
}
data = RetrieveData(conn, 0) // retrieve new data
_, err = conn.Write([]byte(data)) // send response
if err != nil {
RespondWithRealm(conn, errors.New("Error in writing response"))
}
conn.Close()
fmt.Println("**Connection with " + remoteAddress + " ended**")
}
func sendServerStatus(address string, data string) {
fmt.Println("Send to: " + address)
conn, err := net.DialTimeout("tcp", address, 3 * time.Second) // 3 secs
if err != nil {
// handle errors
}
_, err = conn.Write([]byte(data)) // send response
if err != nil {
RespondWithRealm(conn, errors.New("Error in writing response"))
}
readBuf := make([]byte, 4096)
conn.Read(readBuf)
length := bytes.Index(readBuf, []byte{0})
fmt.Println(address + " : " + string(readBuf[:length]))
conn.Close()
}
func broadcastAll(address string) { // exclude requester address
rows, err := db.Query("SELECT * FROM client_info")
data := RetrieveData(nil, 1)
if err != nil {
// handle errors
}
defer rows.Close()
for rows.Next() {
err := rows.Scan(&ip, &port)
if err != nil {
// handle errors
}
ip_str := Long2ip(ip)
port_str:= strconv.Itoa(port)
currentAddress := ip_str + ":" + port_str
if address != currentAddress { // exclude requester
go sendServerStatus(currentAddress, data)
}
}
fmt.Println("Send data: " + data)
}
func TimeoutCheck(address string, wg *sync.WaitGroup) {
defer wg.Done()
fmt.Println("Now checking " + address)
connTest, err := net.DialTimeout("tcp", address, 3 * time.Second) // 3 secs
if err != nil {
fmt.Println("TimeoutCheck: " + err.Error())
// remove from DB
slc := strings.Split(address, ":")
ip_long, err := Ip2long(slc[0])
if err != nil {
// handle error
}
port_int, err := strconv.Atoi(slc[1])
if err != nil {
// handle error
}
DeleteData(ip_long, port_int)
} else {
fmt.Println("TimeoutCheck: " + address + " is still active")
connTest.Close()
}
}
func main() {
var err error
addrs, err := net.InterfaceAddrs()
if err != nil {
// handle error
}
for _, a := range addrs {
if ipnet, ok := a.(*net.IPNet); ok && !ipnet.IP.IsLoopback() {
if ipnet.IP.To4() != nil {
ip_address = ipnet.IP.String()
}
}
}
fmt.Println("**Initialize**")
current_port = 8000
fmt.Println("Your address is: " + ip_address + ":" + strconv.Itoa(current_port))
/*
ip_long, err := Ip2long(ip_address)
if err != nil {
// handle error
}
*/
db, err = sql.Open("mysql","root@tcp(127.0.0.1:3306)/sister_tracker")
// InsertData(nil, ip_long, current_port) // add own IP address information
fmt.Println("**Finish Initializing**")
// Open Connection
ln, err := net.Listen("tcp", ":" + strconv.Itoa(current_port))
if err != nil {
// handle error
}
for {
conn, err := ln.Accept() // this blocks until connection or error
if err != nil {
// handle error
continue
}
go HandleConnection(conn) // a goroutine handles conn so that the loop can accept other connections
}
defer db.Close()
}
/** Send error response to client */
func RespondWithRealm(conn net.Conn, err error) {
errorMessage := err.Error()
message := "{\"status\": \"error\", \"description\": \""+ errorMessage +"\"}"
_, err = conn.Write([]byte(message)) // send response
if err != nil {
// handle error
}
conn.Close()
}
-- MySQL dump 10.13 Distrib 5.5.40, for debian-linux-gnu (x86_64)
--
-- Host: localhost Database: sister_tracker
-- ------------------------------------------------------
-- Server version 5.5.40-0+wheezy1
/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;
/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;
/*!40101 SET NAMES utf8 */;
/*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */;
/*!40103 SET TIME_ZONE='+00:00' */;
/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */;
/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */;
/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */;
/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */;
--
-- Current Database: `sister_tracker`
--
CREATE DATABASE /*!32312 IF NOT EXISTS*/ `sister_tracker` /*!40100 DEFAULT CHARACTER SET latin1 */;
USE `sister_tracker`;
--
-- Table structure for table `client_info`
--
DROP TABLE IF EXISTS `client_info`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `client_info` (
`ip` int(10) unsigned NOT NULL,
`port` int(5) NOT NULL,
PRIMARY KEY (`ip`,`port`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
/*!40101 SET character_set_client = @saved_cs_client */;
--
-- Dumping data for table `client_info`
--
LOCK TABLES `client_info` WRITE;
/*!40000 ALTER TABLE `client_info` DISABLE KEYS */;
/*!40000 ALTER TABLE `client_info` ENABLE KEYS */;
UNLOCK TABLES;
/*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */;
/*!40101 SET SQL_MODE=@OLD_SQL_MODE */;
/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */;
/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */;
/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;
/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;
/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;
/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */;
-- Dump completed on 2015-03-04 8:20:22
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment