socket.io는 일단 기본 store가 메모리 기반이여서 프로세스를 하나 띄우면 거기서 밖에 socket.io 접속자들을 공유를 못해요. 그래서 socket.io에서는 store를 redis로 변경해서 여러 프로세스에서도 공유를 할 수 있는 옵션을 제공을 해요.

var RedisStore = require('socket.io/lib/stores/redis')
  , redis  = require('socket.io/node_modules/redis')
  , pub    = redis.createClient()
  , sub    = redis.createClient()
  , client = redis.createClient();

io.set('store', new RedisStore({
  redisPub : pub
, redisSub : sub
, redisClient : client
}));

이렇게 하면 된답니다.

그래서 한 번 해보면, redis를 설치해야해요. 저는 windows환경이라 virtualbox에 ubuntu설치하고 redis를 설치했어요. 아 그리고 나중에 redis도 1대로 못버티면 redis도 clustering해야하는데, 이건 나중에 삽질 해보고....

이게 원리가 redis에서 제공하는 pub/sub기능을 이용해서 하는건데, 하나의 서버에서 pub/sub를 만들고 연결해놓고 나중에 현재 서버에서 접속이 들어오면 다른 서버들에 publish를 해서 알려주고, 다른 서버는 구독중이기에 연결된 데이터를 받을 수 있습니다.

대략 소스는 이러합니다.

server.js

var redisInfo = {
    host: '192.168.56.1',
    port: 6379
};
var app = require('http').createServer(handler),
    io = require('socket.io').listen(app),
    fs = require('fs'),
    RedisStore = require('socket.io/lib/stores/redis'),
    redis = require('socket.io/node_modules/redis'),
    pub = redis.createClient(redisInfo.port, redisInfo.host),
    sub = redis.createClient(redisInfo.port, redisInfo.host),
    client = redis.createClient(redisInfo.port, redisInfo.host);

if (process.argv.length < 3){
    console.log('ex) node app <port>');
    process.exit(1);
}
app.listen(process.argv[2]);

function handler(req, res) {
    fs.readFile(__dirname + '/index.html',
        function (err, data) {
            if (err) {
                res.writeHead(500);
                return res.end('Error loading index.html');
            }
            res.writeHead(200);
            data = data.toString('utf-8').replace('<%=host%>', req.headers.host);
            res.end(data);
        });
}

io.configure(function(){
    io.set('store', new RedisStore({
        redisPub: pub,
        redisSub : sub,
        redisClient : client
    }));
});

io.sockets.on('connection', function (socket) {
    socket.on('message', function(data){
        socket.broadcast.emit('message', data);
    });
});


index.html

<!DOCTYPE html>
<html>
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
    <title>socketio redis store</title>
    <script type="text/javascript" src="http://code.jquery.com/jquery-1.10.1.min.js"></script>
    <script type="text/javascript" src="/socket.io/socket.io.js"></script>
    <script>
        var socket = io.connect('http://<%=host%>');
       
        $(document).ready(function(){
            socket.on('message', function(data){
                $('#chat').append('<li>' + data.message + '</li>');
            });
           
            $('#btnSend').click(function(){
                send();
            });
            $('#inputText').keyup(function(e){
                if (e.keyCode == 13){
                    send();
                }
            });
        });
        function send(){
            var message = $('#inputText').val();
            if (message.length < 1){
                return;
            }
            socket.emit('message', {message:message});
            $('#chat').append('<li>' + message + '</li>');
            $('#inputText').val('');
        }
    </script>
</head>
<body>
    socketio redis store...<br />
    <input type="text" id="inputText" />
    <button id="btnSend">보내기</button>
    <ul id="chat">
    </ul>
</body>
</html>

node app.js 10001

node app.js 10002

두 개 띄워놓고 localhost:10001, localhost:10002 접속하면 두 서버에서 같은 방에 있는 것처럼 채팅을 할 수 있습니다.

redis는 기본적으로 127.0.0.1로 bind를 해서 외부에서 접속을 할 수 없습니다. 그래서 /etc/redis/redis.conf 파일을 수정해서 bind 127.0.0.1을 주석처리하거나 알맞게 수정하시면 됩니다.

socket.io는 참 잘해놓은게 다른 것은 전혀 신경쓸 것이 없이 store옵션만 바꾸어서 socket들의 정보 저장위치를 변경하게 만들어 놨습니다. 나중에 서버 확장을 할 때에도 매우 쉽게 할 수 있습니다.(하지만, redis clustering 조회해보고 있는데 힘들어보이는.....)

소스는 github에.......

https://github.com/mudchobo/nodejs-socketio_redis_store

 
Posted by 머드초보

댓글을 달아 주세요

  1. BlogIcon burningice 2014.03.11 11:53  댓글주소  수정/삭제  댓글쓰기

    redis도 윈도우용이 있답니다. 혹시 도움이 될까 해서요 ^^;
    좋은 정보 감사합니다.

  2. 이승환 2015.09.11 11:11  댓글주소  수정/삭제  댓글쓰기

    와 ㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠ진짜 ㅈ제가 딱 원하는 자로ㅛ들입니다 ㅠ휴ㅠㅠㅠㅠㅠ
    진짜 감사합니다 ㅠㅠㅠㅠㅠㅠㅠ

 
아주 오래전에 Hibernate xml로 삽질했던 기억이 있는데, Annotation으로 해보겠다는 게 세월(?)이 벌써 이렇게 흘렀군요.

하이버네이트는 셋팅이 참 어렵군요. 책을 보면 그냥 hibernate함수를 이용해서 어떻게 이용하는지, 하이버네이트의 특성이 주로 나와있는데, 셋팅에 대한 삽질은 좀 자세하지 않은 듯(내가 못본 것일 수도 있음-_-)

NetBeans 6.8에서 삽질했습니다.
넷빈즈다운로드 : http://netbeans.org/downloads/index.html

일단 소녀시대 테이블만 하나 만들어 놓읍시다.
테이블은 sosi랑 schedule 2개가 1:N의 관계형태로 만드려고 합니다. (DB쪽에 취약해서 맞는지 모르겠네-_- 뭐 다취약하지만-_-)
[code]
DROP TABLE IF EXISTS `sosi`;
CREATE TABLE IF NOT EXISTS `sosi` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `birthYear` int(11) NOT NULL,
  `name` varchar(255) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=MyISAM  DEFAULT CHARSET=utf8 AUTO_INCREMENT=10 ;

INSERT INTO `sosi` (`id`, `birthYear`, `name`) VALUES
(1, 1989, '효연'),
(2, 1990, '윤아'),
(3, 1990, '수영'),
(4, 1989, '유리'),
(5, 1989, '태연'),
(6, 1989, '제시카'),
(7, 1989, '티파니'),
(8, 1990, '써니'),
(9, 1991, '서현');
[/code]

1. 프로젝트 생성
File -> New Project(Ctrl + Shift + N) -> Java -> Java Application -> Project이름은 SosiSchedule

2. 하이버네이트 설정파일 셋팅
 Ctrl + N을 눌러서 새로운 파일을 만듭니다. SosiSchedule을 선택하고, Hibernate -> Hibernate Configuration Wizard선택, File Name은 hibernate.cfg 디폴트로, Database는 자신이 셋팅하고 sosi테이블을 만들어놓은 Mysql 데이터베이스를 선택합니다. 안만들었으면 New Database Connector를 선택해서 Host, Port, Database, User Name Password를 입력하면 됩니다. 이거 하면 자동으로 Library가 추가되나봅니다.
설정 파일이 만들어졌어요. 근데, 하이버네이트에서는 src최상위 폴더에 넣으면 자동으로 설정파일을 인식하나봐요. 설정파일 경로 지정하는 부분이 없는 것 같은데-_-
테이블 자동생성 및 날라가는 sql을 보기위해 아래 두옵션을 추가합니다.
[code]<property name="hibernate.show_sql">true</property>
<property name="hibernate.hbm2ddl.auto">update</property>[/code]

3. HibernateUtl만들기
Ctrl + N을 눌러서 새로운 파일 작성 -> Hibernate -> HibernateUtl.java, ClassName은 HibernateUtl로 package는 sosischedule.util로 finish!

4. 매핑할 ENTITY클래스 작성
일단 entity클래스는 database에 table이 존재하면 자동으로 만들 수 있습니다.
Ctrl + N을 눌러서 새로운 파일 작성 -> Persistence -> Entity Classes  from Database 선택.
Database Connection에서 셋팅한 mysql선택하면  테이블명이 나오는데, sosi테이블을 Add합니다.
package는 알아보기 쉽게 sosischedule.entity로 하고 finish를...-_-
이쁘게 소스가 만들어지네요.
Sosi.java
[code]/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

package sosischedule.entity;

import java.io.Serializable;
import javax.persistence.Basic;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.NamedQueries;
import javax.persistence.NamedQuery;
import javax.persistence.Table;

/**
 *
 * @author mudchobo
 */
@Entity
@Table(name = "sosi")
@NamedQueries({
    @NamedQuery(name = "Sosi.findAll", query = "SELECT s FROM Sosi s"),
    @NamedQuery(name = "Sosi.findById", query = "SELECT s FROM Sosi s WHERE s.id = :id"),
    @NamedQuery(name = "Sosi.findByBirthYear", query = "SELECT s FROM Sosi s WHERE s.birthYear = :birthYear"),
    @NamedQuery(name = "Sosi.findBySosiName", query = "SELECT s FROM Sosi s WHERE s.sosiName = :sosiName")})
public class Sosi implements Serializable {
    private static final long serialVersionUID = 1L;
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Basic(optional = false)
    @Column(name = "id")
    private Long id;
    @Basic(optional = false)
    @Column(name = "birthYear")
    private int birthYear;
    @Column(name = "sosiName")
    private String sosiName;

    public Sosi() {
    }

    public Sosi(Long id) {
        this.id = id;
    }

    public Sosi(Long id, int birthYear) {
        this.id = id;
        this.birthYear = birthYear;
    }

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public int getBirthYear() {
        return birthYear;
    }

    public void setBirthYear(int birthYear) {
        this.birthYear = birthYear;
    }

    public String getSosiName() {
        return sosiName;
    }

    public void setSosiName(String sosiName) {
        this.sosiName = sosiName;
    }

    @Override
    public int hashCode() {
        int hash = 0;
        hash += (id != null ? id.hashCode() : 0);
        return hash;
    }

    @Override
    public boolean equals(Object object) {
        if (!(object instanceof Sosi)) {
            return false;
        }
        Sosi other = (Sosi) object;
        if ((this.id == null && other.id != null) || (this.id != null && !this.id.equals(other.id))) {
            return false;
        }
        return true;
    }

    @Override
    public String toString() {
        return "sosischedule.entity.Sosi[id=" + id + "]";
    }

}
[/code]
각각의 어노테이션들은 구글링을 통해 찾아보는걸로 저도 잘 몰라서-_-
Annotation기반으로 하기전에는 mapping xml파일을 작성했는데, 그걸 그냥 클래스에 보기좋게 해놓은 거라고 보면 될 듯.

스케쥴 클래스는 직접 만들어봅시다.
Ctrl + N을 통해 새로운 파일생성, Persistence -> Entity Class, Class Name은 Schedule, package는 sosischedule.entity선택 후 finish.
그러면 id만 달랑 있습니다. 여기에 칼럼을 만들어 봅시다. 많이 만들면 귀찮아 지니까-_- program명과 소시객체 연동하는 것만 만들어봅시다.
[code]private String program;
private Sosi sosi;[/code]입력하고, Alt + Insert하면 getter, setter자동생성기로 만듭니다.
사실 이렇게만 만들어 놓아도 테이블이 생성됩니다-_- 다 디폴트로 만들어서. 귀찮으니까 이렇게만 만들고 맙시다-_- 아 그리고, 생성자를 두개 추가했습니다. 기본생성자와 property를 세팅해서 만들어주는 생성자.
그리고 테이블과 관계를 맺기 위해 @ManyToOne을 넣어줘야합니다. 안 넣어주면 무슨 Blob으로 그냥 저장해버리는..-_-
Schedule.java
[code]package sosischedule.entity;

import java.io.Serializable;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.ManyToOne;

@Entity
public class Schedule implements Serializable {
    private static final long serialVersionUID = 1L;
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;

    private String program;

    @ManyToOne
    private Sosi sosi;

    public Schedule() {
    }

    public Schedule(String program, Sosi sosi) {
        this.program = program;
        this.sosi = sosi;
    }

    public Sosi getSosi() {
        return sosi;
    }

    public void setSosi(Sosi sosi) {
        this.sosi = sosi;
    }

    public Long getId() {
        return id;
    }

    public String getProgram() {
        return program;
    }

    public void setProgram(String program) {
        this.program = program;
    }

    public void setId(Long id) {
        this.id = id;
    }

    @Override
    public int hashCode() {
        int hash = 0;
        hash += (id != null ? id.hashCode() : 0);
        return hash;
    }

    @Override
    public boolean equals(Object object) {
        if (!(object instanceof Schedule)) {
            return false;
        }
        Schedule other = (Schedule) object;
        if ((this.id == null && other.id != null) || (this.id != null && !this.id.equals(other.id))) {
            return false;
        }
        return true;
    }

    @Override
    public String toString() {
        return "sosischedule.entity.Schedule[id=" + id + "]";
    }
}
[/code]
오...이저 hibernate.cfg.xml파일을 열어서 매핑파일이라고 추가합니다.
hibernate.cfg.xml
[code]<mapping class="sosischedule.entity.Sosi"/>
<mapping class="sosischedule.entity.Schedule"/>[/code]
작성한 클래스 두개. 이제 hql을 날릴 수 있어요. hibernate.cfg.xml파일을 선택 후 오른쪽버튼을 누르면 "Run HQL Query"라는 메뉴가 나와요.
from Sosi때리면 소시멤버데이터가 나오네요.
※여기서 해당 프로젝트에 대해서 한번이라도 run을 때리지 않으면 Sosi is not mapped라고 나오네요. 안되면 한번 실행하고 해보세요.
사용자 삽입 이미지
이제 다 된 것 같으니 dao와 service를 만들어봅시다.

5. Dao생성
sosischedule.dao.SosiScheduleDao.java파일을 생성
SosiScheduleDao.java
[code]package sosischedule.dao;

import java.util.List;
import org.hibernate.Query;
import org.hibernate.Session;
import org.hibernate.Transaction;
import sosischedule.entity.Schedule;
import sosischedule.entity.Sosi;
import sosischedule.util.HibernateUtil;

public class SosiScheduleDao {

    public List<Sosi> getSosiList() {
        Session session = HibernateUtil.getSessionFactory().openSession();
        Query q = session.createQuery("from Sosi");
        List<Sosi> list = q.list();
        session.close();

        return list;
    }

    public Sosi getSosi(int sosiId) {
        Session session = HibernateUtil.getSessionFactory().openSession();
        Sosi sosi = (Sosi) session.get(Sosi.class, new Long(sosiId));
        session.close();

        return sosi;
    }

    public List<Schedule> getSchedule(Long sosiId) {
        Session session = HibernateUtil.getSessionFactory().openSession();
        Query q = session.createQuery("from Schedule s where s.sosi.id = " + sosiId);
        List<Schedule> list = q.list();
        session.close();

        return list;
    }

    public void addSchedule(Schedule schedule) {
        Session session = HibernateUtil.getSessionFactory().openSession();
        Transaction tx = session.beginTransaction();
        session.save(schedule);
        tx.commit();
        session.close();
    }

    public void removeSchedule(int scheduleId) {
        Session session = HibernateUtil.getSessionFactory().openSession();
        Transaction tx = session.beginTransaction();
        Schedule schedule =
                (Schedule) session.load(Schedule.class, new Long(scheduleId));
        session.delete(schedule);
        tx.commit();
        session.close();
    }
}
[/code]
음....분명 이렇게 하는 건 아닌 것 같아-_- 암튼, 일단 셋팅이 목적이니-_- 대충 이렇게도 할 수 있다는 것을..-_-
getSosiList는 소시리스트를 가져오고, getSosi는 소시를 가져오고, getSchedule은 스케쥴리스트를 가져오고, addSchedule은 스케쥴추가하고, removeSchedule은 스케쥴을 삭제하고...

6. 서비스생성
이걸 사용할 서비스를 만들어봅시다.
sosischedule.service.SosiScheduleService.java
[code]package sosischedule.service;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import sosischedule.dao.SosiScheduleDao;
import sosischedule.entity.Schedule;
import sosischedule.entity.Sosi;

public class SosiScheduleService {

    private SosiScheduleDao sosiScheduleDao = new SosiScheduleDao();

    public void menuSosi() {
        List<Sosi> sosiList = sosiScheduleDao.getSosiList();
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        while(true){
            try {
                for (Sosi sosi : sosiList) {
                    System.out.println(sosi.getId() + "." + sosi.getSosiName());
                }
                System.out.println("번호를 입력하세요!(0은 종료) => ");
                int menuNum = Integer.parseInt(br.readLine());
                if (menuNum == 0){
                    System.out.println("종료!");
                    break;
                }
                // 소시데이터 가져오기
                Sosi sosi = sosiScheduleDao.getSosi(menuNum);
                if (sosi != null){
                    menuSchedule(sosi);
                    break;
                } else {
                    System.out.println("없는 번호입니다.");
                }

            } catch (IOException ex) {
                Logger.getLogger(SosiScheduleService.class.getName()).log(Level.SEVERE, null, ex);
            }
        }
    }

    public void menuSchedule(Sosi sosi) {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));

        while(true){
            try {
                List<Schedule> scheduleList = sosiScheduleDao.getSchedule(sosi.getId());
                for (Schedule schedule : scheduleList) {
                    System.out.println(schedule.getId() + "." + schedule.getProgram());
                }
                if (scheduleList.size() <= 0){
                    System.out.println("스케쥴이 없습니다.\n");
                }
                System.out.println("1.스케쥴추가 2.스케쥴삭제 0.뒤로 =>");
                int menuNum = Integer.parseInt(br.readLine());
                if (menuNum == 0) {
                    System.out.println("뒤로!");
                    menuSosi();
                    break;
                }
                else if (menuNum == 1) {
                    System.out.println("스케쥴명 입력 : ");
                    String program = br.readLine();
                    System.out.println("스케쥴 = " + program);
                    sosiScheduleDao.addSchedule(new Schedule(program, sosi));
                    System.out.println("추가완료!");
                }
                else if (menuNum == 2) {
                    System.out.println("스케쥴번호 : ");
                    int scheduleId = Integer.parseInt(br.readLine());
                    sosiScheduleDao.removeSchedule(scheduleId);
                    System.out.println("삭제완료!");
                }
            } catch (IOException ex) {
                Logger.getLogger(SosiScheduleService.class.getName()).log(Level.SEVERE, null, ex);
            }
        }
    }
}[/code]
메인에서 이렇게 사용하면 됩니다.
Main.java
[code]package sosischedule;

import sosischedule.service.SosiScheduleService;

public class Main {

    public static void main(String[] args) {
        SosiScheduleService sosiScheduleService = new SosiScheduleService();
        sosiScheduleService.menuSosi();
    }
}
[/code]

결과
[code]Hibernate: select sosi0_.id as id0_, sosi0_.birthYear as birthYear0_, sosi0_.sos
iName as sosiName0_ from sosi sosi0_
1.효연
2.윤아
3.수영
4.유리
5.태연
6.제시카
7.티파니
8.써니
9.서현
번호를 입력하세요!(0은 종료) =>
5
Hibernate: select sosi0_.id as id0_0_, sosi0_.birthYear as birthYear0_0_, sosi0_
.sosiName as sosiName0_0_ from sosi sosi0_ where sosi0_.id=?
Hibernate: select schedule0_.id as id1_, schedule0_.program as program1_, schedu
le0_.sosi_id as sosi3_1_ from Schedule schedule0_ where schedule0_.sosi_id=5
스케쥴이 없습니다.

1.스케쥴추가 2.스케쥴삭제 0.뒤로 =>
1
스케쥴명 입력 :
태연의 친한친구
스케쥴 = 태연의 친한친구
Hibernate: insert into Schedule (program, sosi_id) values (?, ?)
추가완료!
Hibernate: select schedule0_.id as id1_, schedule0_.program as program1_, schedu
le0_.sosi_id as sosi3_1_ from Schedule schedule0_ where schedule0_.sosi_id=5
Hibernate: select sosi0_.id as id0_0_, sosi0_.birthYear as birthYear0_0_, sosi0_
.sosiName as sosiName0_0_ from sosi sosi0_ where sosi0_.id=?
3.태연의 친한친구
1.스케쥴추가 2.스케쥴삭제 0.뒤로 =>
1
스케쥴명 입력 :
태연의 친한친구2
스케쥴 = 태연의 친한친구2
Hibernate: insert into Schedule (program, sosi_id) values (?, ?)
추가완료!
Hibernate: select schedule0_.id as id1_, schedule0_.program as program1_, schedu
le0_.sosi_id as sosi3_1_ from Schedule schedule0_ where schedule0_.sosi_id=5
Hibernate: select sosi0_.id as id0_0_, sosi0_.birthYear as birthYear0_0_, sosi0_
.sosiName as sosiName0_0_ from sosi sosi0_ where sosi0_.id=?
3.태연의 친한친구
4.태연의 친한친구2
1.스케쥴추가 2.스케쥴삭제 0.뒤로 =>
2
스케쥴번호 :
4
Hibernate: select schedule0_.id as id1_1_, schedule0_.program as program1_1_, sc
hedule0_.sosi_id as sosi3_1_1_, sosi1_.id as id0_0_, sosi1_.birthYear as birthYe
ar0_0_, sosi1_.sosiName as sosiName0_0_ from Schedule schedule0_ left outer join
 sosi sosi1_ on schedule0_.sosi_id=sosi1_.id where schedule0_.id=?
Hibernate: delete from Schedule where id=?
삭제완료!
Hibernate: select schedule0_.id as id1_, schedule0_.program as program1_, schedu
le0_.sosi_id as sosi3_1_ from Schedule schedule0_ where schedule0_.sosi_id=5
Hibernate: select sosi0_.id as id0_0_, sosi0_.birthYear as birthYear0_0_, sosi0_
.sosiName as sosiName0_0_ from sosi sosi0_ where sosi0_.id=?
3.태연의 친한친구
1.스케쥴추가 2.스케쥴삭제 0.뒤로 =>[/code]
와....잘된다....-_-
 
Posted by 머드초보

댓글을 달아 주세요

 
기존에 Flex3에서 HTTPService는 직접 xml을 확인하여 valueobject를 만들던지 자동으로 파싱해주는 객체를 그냥 쓰던지 그런식으로 했었는데요. Flex4에서는 쉽게 Service객체를 만들어주고, ValueObject를 만들어줍니다.

근데, 그냥 기존방식을 쓰는 게 나을 것 같아요-_- 뭔놈의 클래스를 뭐이리 만들지-_-
그래도 제공하는 기능이니까 한번 사용해서 만들어봅시다-_-

다운로드는 여기서....-_-

1. 프로젝트 생성

New -> Flex Project -> Application type은 Desktop(구글의 httpservice를 가져올 것이어서 데탑으로-_-) -> Finish.

2. 서비스생성
구글에서 날씨를 가져오는 Service를 추가해봅시다.
아래 Data/Services탭에서 Connect to Data/Service..클릭.
HTTP선택 -> Opearation 첫번째 항목에 getWeather로 변경. url은 http://www.google.co.kr/ig/api?weather=seoul 입력.
url만 입력하면 ?붙은 파라메터는 자동으로 Parameters탭에 들어갑니다. weather가 들어가는데, 캐쉬문제 때무넹 캐쉬파라메터도 하나 추가합니다.
Add누르고, cache입력하고, DataType은 Number.
Service name은 WeatherService로 하죠. Finish.

여기서 이름 네이밍룰에 주의해야하는데, Service name이랑 parameter name이랑 같은 게 있으면 안됩니다. 에러나더군요.

3. 서비스에 대한 리턴타입 지정
이제 리턴타입을 지정해줘야하는데요.
그전에 데이터를 잘 가져오는지 테스트를 해볼 수 있습니다.
방금 생성한 WeatherService의 getWeather를 선택하고 오른쪽버튼 누르면 Configure Return Type이 있습니다.
이거 선택하기전에 Test Opearation이라고 해서 테스트를 해볼 수 있는 게 있네요.
테스트를 쉽게 해볼 수 있어서 참 좋네요. Test를 누르면 데이터를 가져와서 Tree View 또는 Raw View로 볼 수 있습니다.
사용자 삽입 이미지
근데, 이상한 점을 발견했는데, 네이버오픈API나 몇몇 XML은 이걸로 하면 잘 안돼요. 왜그런지 모르겠네요. 이런 에러메세지를 뿜습니다.
InvocationTargetException:The response is not a valid XML or a JSON string
이거 왜그런지 아시는 분은 답변좀....굽신굽신....-_-

암튼, 날씨API는 되니까 해봅시다-_-
Configure Return Type선택 후, Auto-detect the return type from sample data선택. 뭐 그냥 object로 받아도 되는데, 필요한 데이터만 가져온다고 하면 쉽게 valueobject를 만들 수 있는 장점이 있어요.
파라메터 방식을 선택하고, seoul을 입력합니다.
그러면 결과값이 나오는데, 여기서 필요한 건 현재 날씨이기 때문에 select root에서 current_conditions를 선택합니다. 그리고, Finish를 누르면 valueObject가 만들어지는데, 한개의 node마다 클래스가 2개씩 생겨요-_-

4. UI생성
UI를 만들어봅시다.
[code]<s:Button id="btnClose" label="X"  width="29" x="67" y="1" click="btnClose_clickHandler(event)"/>
<mx:Image x="6" y="32" width="40" height="40" id="imgIcon"/>
<s:Label x="0" y="80" width="100" textAlign="center"  id="labelCondition" />
<s:Label x="59" y="45" text="30º" fontSize="20" id="labelTemp"/>[/code]
초간단 UI...UI라고 불리지도 못하는 UI.

5. 데이터 바인딩
이제 데이터를 불러와야해요.
Design모드로 가서 아무라벨이나 붙잡고, 오른쪽버튼 눌러서 Bind To Data를 선택. 그리고 방금 만든 서비스를 바인딩해요. 전 labelCondition을 선택했는데, 그러면 WeatherService, getWeather의 condition을 선택합니다.
그러면 service태그도 알아서 들어가고 해당 라벨에는 creationComplete이벤트핸들러가 추가되었네요.
getWeather파라메터는 "seoul"과 cache는 랜덤값을 넣어줍니다.
그리고 실제 태그에는 condition.@data에 값이 있기 때문에, label의 text부분에 getWeatherResult.lastResult.condition.data로 변경해주시면 됩니다.
[code]protected function labelCondition_creationCompleteHandler(event:FlexEvent):void
{
    getWeatherResult.token = weatherService.getWeather("seoul", Math.random());
}[/code]

6. 완성된 코드
그럼 1분마다 데이터를 가져오는 타이머도 만들고 윈도우틀도 없애서 위젯형태로 만들어버리고, 나머지 label에도 값에 맞게 바인딩 시키면 완성이 됩니다.
[code]<?xml version="1.0" encoding="utf-8"?>
<s:WindowedApplication xmlns:fx="http://ns.adobe.com/mxml/2009"
                       xmlns:s="library://ns.adobe.com/flex/spark"
                       xmlns:mx="library://ns.adobe.com/flex/halo"
                       xmlns:weatherservice="services.weatherservice.*"
                       width="150" height="150"
                       applicationComplete="windowedapplication_applicationCompleteHandler(event)"
                       showStatusBar="false"
                       backgroundColor="#48FF00">
    <fx:Script>
        <![CDATA[
            import flash.events.MouseEvent;
            import flash.events.TimerEvent;
            import flash.utils.Timer;
           
            import mx.controls.Alert;
            import mx.events.FlexEvent;
           
            import spark.components.mediaClasses.VolumeBar;
           
            private var timer:Timer;
           
            protected function windowedapplication_applicationCompleteHandler(event:FlexEvent):void
            {
                timer = new Timer(1000, 0);
                timer.addEventListener(TimerEvent.TIMER, timerHandler);
                timer.start();
            }
           
            protected function btnClose_clickHandler(event:MouseEvent):void
            {
                nativeWindow.close();
            }

            protected function labelCondition_creationCompleteHandler(event:FlexEvent):void
            {
                requestData();
            }

            protected function labelTitle_mouseDownHandler(event:MouseEvent):void
            {
                nativeWindow.startMove();
            }
           
            protected function timerHandler(event:TimerEvent):void
            {
                requestData();               
            }
           
            private function requestData():void
            {
                getWeatherResult.token = weatherService.getWeather("seoul", Math.random());
            }
        ]]>
    </fx:Script>
    <fx:Declarations>
        <s:CallResponder id="getWeatherResult"/>
        <weatherservice:WeatherService id="weatherService" fault="Alert.show(event.fault.faultString + '\n' + event.fault.faultDetail)"/>
        <!-- Place non-visual elements (e.g., services, value objects) here -->
    </fx:Declarations>
   
    <s:Label id="labelTitle" text="서울날씨"  x="0" y="0" height="22" width="121" verticalAlign="middle" mouseDown="labelTitle_mouseDownHandler(event)"/>
    <s:Button id="btnClose" label="X"  width="30" x="120" y="1" click="btnClose_clickHandler(event)"/>
    <mx:Image x="25" y="47" width="40" height="40" id="imgIcon" source="http://www.google.co.kr{getWeatherResult.lastResult.icon.data}"/>
    <s:Label x="2" y="114" width="148" textAlign="center"  id="labelCondition"  creationComplete="labelCondition_creationCompleteHandler(event)" text="{getWeatherResult.lastResult.condition.data}"/>
    <s:Label x="92" y="58" fontSize="20" id="labelTemp" text="{getWeatherResult.lastResult.temp_c.data}ºC"/>
</s:WindowedApplication>
[/code]

7. Network Monitor로 데이터 확인
HTTPService 요청이 제대로 갔는지 확인할 수 있는 Network Monitor 기능이 추가되었습니다. 기존에는 확인하려면 직접 그냥 노가다로 해보거나 와이어샤크나 firebug 등의 http watcher를 이용해서 했었죠. 근데 이제 Flash Builder에 포함되어져있네요. 그냥 모니터링 하는거라 조금만 만져보면 알 수 있네요. row데이터로 볼 수도 있고, hex데이터로도 볼 수 있고 그러네요. 근데 한글이 깨지네-_-
사용자 삽입 이미지
사용자 삽입 이미지


 
Posted by 머드초보

댓글을 달아 주세요

  1. BlogIcon hansoo 2010.02.09 13:46  댓글주소  수정/삭제  댓글쓰기

    좋은 예제 잘 보고 갑니다.

  2. AZAMARA 2010.05.02 01:25  댓글주소  수정/삭제  댓글쓰기

    안녕하세요 머드초보님 Flex공부하는데 머드초보님 블로그 덕분에 많이 배우네요

    저도 빌드4 XML가져올때 같은 에러때문에 고생중이네요 ㅡㅜ ㅋ 컬렉션형태로오면

    그러네요

    • 머드초보 2010.05.02 10:36  댓글주소  수정/삭제

      음....그렇군요
      컬렉션형태로 가져올 때 그렇게 되는 것이군요.
      방문해주셔서 감사해요~

  3. AZAMARA 2010.05.05 23:41  댓글주소  수정/삭제  댓글쓰기

    확인해보니 jsp에서 xml 반복문 돌릴때만안되네요

    s:iterater c:foreach 스크립트릿 다 써서해봤는데 명령문을 그냥 텍스트로 인식해서 오류라는거같아요

    그냥 적혀있는 xml은 문제되지않네요 ㅇ.ㅇ

    • 머드초보 2010.05.06 00:00  댓글주소  수정/삭제

      음 그렇군요!
      저도 이제 Flash Builder 정식버전을 설치해서!
      삽질을 해봐야겠습니다!
      좋은 정보 감사합니다^^

  4. 쿨해요 2010.07.18 17:08  댓글주소  수정/삭제  댓글쓰기

    오늘 시점의 flash builder 4 trial 버젼에서는 최종 UI소스
    상단을 아래와 같이 고쳐야 에러가 없습니다.

    - 아래-

    <s:WindowedApplication xmlns:fx="http://ns.adobe.com/mxml/2009"
    xmlns:s="library://ns.adobe.com/flex/spark"
    xmlns:mx="library://ns.adobe.com/flex/mx" xmlns:weatherservice="services.weatherservice.*"
    width="150" height="150"
    applicationComplete="windowedapplication_applicationCompleteHandler(event)"
    showStatusBar="false"
    backgroundColor="#48FF00">
    <fx:Script>

 
우선 켄트 백이라는 분이 쓰신 것 같은데요. 즉 번역책입니다.
번역책을 볼 때 마다 느끼는 점은 번역하면 어디까지 변역을 해야할 것인가가 참 의심스럽습니다. 이 책은 몽땅 번역해버렸습니다. 예를 들자면 private, public, static 이런 것을 그냥 전용, 공용, 정적 이런 식으로 다 번역해버렸습니다. JUnit마저 J유닛이라고 번역했군요.
저한테는 저런 것을 번역한 것보다 그냥 영문으로 표기하는 게 더 읽기가 편한 것 같아요. 물론 사람마다 차이가 있겠지만, 이 책은 누구나 읽기 쉽게 말하려고 그렇게 한 듯 싶습니다.

우선 책의 간략한 내용은 읽기 쉬운 코드를 작성하는 77가지 자바 코딩 비법에 대해서 소개합니다. 즉, 프로그램을 개발할 때 코드를 읽기 쉽게 만들어서 나중에 요구사항변경으로 인해 소스를 수정하거나 업데이트를 해야할 일이 발생할 경우 읽기 쉬운 코드로 개발을 하게 되면 비용을 줄일 수 있는 그런 법을 소개하는 것 같습니다.

우선 초보자가 봐도 된다고 생각하고 보게 되었는데 저한테는 글이 잘 읽어지지 않더군요. 몇 번씩 반복해서 읽어야 이해가 가는 부분도 있었고, 이해가 가지 않는 부분도 좀 많았구요. 그리고, 구체적으로 읽기 쉬운 코드를 가르쳐주는 예는 별로 없는 것 같아요. 그냥 이런 식으로 하면 이런 것 할 때 편하다, 하지만, 이런 것을 하게 되면 불편하다 등의 조언을 알려주는 듯해요.

저 같은 경우 남의 소스코드를 볼 일이 참 많았습니다. 볼 때마다 한숨만 푹푹 나왔습니다-_-; 이건 왜 이렇게 되어있지, 이건 없어도 될 듯 싶은데, 이걸 이렇게 하면 더 효율적일 텐데, 등의 코드를 많이 보게 되었습니다. 그래서 전 읽기 쉬운 코드를 작성하는 법에 참 관심이 많았습니다. 하지만, 저도 그런 코드를 작성하는 법은 참 힘들었던 것 같습니다. 저한테는 확실히 읽기 쉬운데, 상대방이 봤을 때 어떨지 느끼는 점은 틀리거든요.

어쨌든 이 책을 통해 쉬운 코드를 작성해봤으면 싶었는데 책이 내용이 좀 어렵네요-_-;
초보자가 읽기에는 좀 힘든 것 같아요 ^^ 그래도 어느정도 도움이 된 듯 싶네요.
마지막에 부록에는 성능측정 이 있었는데, 아직 읽어보지는 않았지만 예전에 보았던 자바성능을 결정짓는 어쩌구 책에서 본 성능체크와 비슷한 것 같네요.

마지막 부분에 보면 JUnit을 이 사람이 개발한 것 같은데-_-; 어쨌든 테스트 기반 개발에 좀 관심을 가지면서 개발하려고 노력중이에요 ^^ 모두 테스트를 만들도록 합시다 ^^
그리고 남들 고생시키지 않게 하기위해서 읽기 쉬운 코드를 작성하도록 합시다 ^^
사용자 삽입 이미지
 
Posted by 머드초보

댓글을 달아 주세요

  1. BlogIcon 검쉰 2008.05.09 10:14  댓글주소  수정/삭제  댓글쓰기

    음... 전용, 공용, 정적 이라.;;;
    읽기 좀 힘들겠습니다.;

    • 머드초보 2008.05.09 18:05  댓글주소  수정/삭제

      조금 읽기 힘들었어요 ^^
      근데 반면에 읽기 편한 사람도 있을 껍니다 ^^

  2. 달룟 2009.08.27 18:54  댓글주소  수정/삭제  댓글쓰기

    켄트 벡의 한국에 옵니다. 관심이 있으실 것 같아 링크를 남깁니다.
    http://www.sten.or.kr/bbs/board.php?bo_table=news&wr_id=1807