많은 분들(?)이 제 블로그에 오셔서 질문을 해주셔서 간단한 예제를 통한 설명을....-_- 나중에 저도 참고하려고 기록용-_-

일단, Flash에서 CrossDomain에 걸리는 데이터를 요청할 때 Plicy File인 crossdomain.xml 파일을 root에 정의해둬서 해당 도메인이면 데이터를 허용하게 할 수 있습니다.

소켓도 마찬가지입니다. 해당 도메인에서 해당 포트로 들어온 요청은 받겠다는 정책파일을 작성할 수 있습니다.
최초 소켓이 정책파일을 요청하게 되는 포트는 843포트입니다. 만약 이포트가 열러있지 않다면 현재 연결하려고 하는 포트로 <policy-file-request/>를 날려서 정책파일을 요청하게 됩니다.
그러면 843이든, 해당포트든 간에 정책파일만 날려주면 됩니다.

[code]<?xml version="1.0"?>
<!DOCTYPE cross-domain-policy SYSTEM "/xml/dtds/cross-domain-policy.dtd">
<cross-domain-policy>
   <allow-access-from domain="*" to-ports="10000-10001" />
</cross-domain-policy>[/code]
소켓에 대한 정책파일을 정의한 것인데, domain에는 허용할 도메인을 쓰고, to-ports에는 허용할 포트를 쓰면 됩니다.
이걸 날려주면 이제 연결할 포트로부터 데이터를 주고 받을 수 있습니다.

초간단 에코예제!
일단 서버는 자바로...(그나마 자신있는 언어라서ㅠㅠ)

일단 PlicyFileServer를 하나 돌릴 쓰레드를 만듭니다.
PlicyFileServer.java
[code]import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;


public class PolicyFileServer extends Thread{
   
    private ServerSocket serverSocket;
    private String policyFile = "<?xml version='1.0'?>" +
                                "<!DOCTYPE cross-domain-policy SYSTEM '/xml/dtds/cross-domain-policy.dtd'>" +
                                "<cross-domain-policy>" +
                                "<allow-access-from domain='*' to-ports='10000' />" +
                                "</cross-domain-policy>";
   
    @Override
    public void run() {
        try {
            serverSocket = new ServerSocket(843);
            while (true) {
                final Socket socket = serverSocket.accept();
                new Runnable() {
                    @Override
                    public void run() {
                        try {
                            socket.setSoTimeout(10000);
                            InputStream in = socket.getInputStream();
                            byte[] buffer = new byte[23];
                            if ( in.read(buffer) != -1 && (new String(buffer)).startsWith("<policy-file-request/>") ) {
                                OutputStream out = socket.getOutputStream();
                                out.write(policyFile.getBytes());
                                out.write(0x00);
                                out.flush();
                                out.close();
                            }
                        } catch (IOException e) {
                            e.printStackTrace();
                        } finally {
                            try { socket.close();} catch(Exception ex){}
                        }
                    }
                }.run();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
[/code]
내용을 보면 그냥 서버소켓하나 만들어서 요청이 들어오면 그 소켓으로 policy file을 전송하는 형태입니다. 파일은 만들기 귀찮아서-_- 그냥 String으로 선언-_- 보면 모든도메인에 한해서 10000포트를 열어주는 겁니다.

이제 메인서버!
SocketTest.java
[code]import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;


public class SocketTest {

    private ServerSocket server;
   
    public SocketTest() {
        try{
            server = new ServerSocket(10000);
            System.out.println("접속을 기다립니다.");
           
            while (true){
                final Socket socket = server.accept();
                Thread t = new Thread(new Runnable() {
                    @Override
                    public void run() {
                        try {
                            PrintWriter pw = new PrintWriter(new OutputStreamWriter(socket.getOutputStream(), "UTF-8"));
                            BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream(), "UTF-8"));
                            String line = null;
                            while ((line = br.readLine()) != null) {
                                System.out.println("수신데이터 : " + line);
                                pw.println("Hello! " + line);
                                pw.flush();
                            }
                        } catch (Exception e) {
                            try { if(socket != null) socket.close(); } catch (Exception ex) {}
                        }
                    }
                });
                t.start();
               
            }
        } catch(Exception e){
            System.out.println("Error!");
        }
    }
   
    public static void main(String[] args) {
        new PolicyFileServer().start();
        new SocketTest();
    }
}[/code]
데이터를 받으면 다시 Hello!를 붙여서 다시 전송해주는 echo서버를 하나 만듭니다.
그리고 main함수에서는 PolicyFileServer쓰레드를 하나 시작하고, EchoServer를 돌립니다.

이제 Flex!
[code]<?xml version="1.0" encoding="utf-8"?>
<s:Application xmlns:fx="http://ns.adobe.com/mxml/2009"
               xmlns:s="library://ns.adobe.com/flex/spark"
               xmlns:mx="library://ns.adobe.com/flex/mx" minWidth="955" minHeight="600"
               applicationComplete="application1_applicationCompleteHandler(event)">
    <fx:Script>
        <![CDATA[
            import mx.events.FlexEvent;
           
            import spark.components.mediaClasses.VolumeBar;
           
            private var socket:Socket;
           
            protected function application1_applicationCompleteHandler(event:FlexEvent):void
            {
                socket = new Socket("127.0.0.1", 10000);
                socket.addEventListener(ProgressEvent.SOCKET_DATA, socketDataHandler);
                socket.addEventListener(Event.CONNECT, connectHandler);
            }

            protected function btnSend_clickHandler(event:MouseEvent):void
            {
                // TODO Auto-generated method stub
                socket.writeUTFBytes(inputMessage.text + "\n");
                socket.flush();
            }
           
            private function connectHandler(event:Event):void
            {
                trace("접속완료!");   
                hbox.visible = true;
            }
           
            private function socketDataHandler(event:ProgressEvent):void
            {
                var message:String = socket.readUTFBytes(socket.bytesAvailable);
                trace("수신메세지 : " + message);
                textResult.text = message;
            }

        ]]>
    </fx:Script>
    <fx:Declarations>
        <!-- Place non-visual elements (e.g., services, value objects) here -->
    </fx:Declarations>
   
    <s:layout>
        <s:VerticalLayout />
    </s:layout>
   
    <mx:HBox id="hbox" visible="false" width="100%" horizontalAlign="center">
        <s:TextInput id="inputMessage" />
        <s:Button id="btnSend" label="송신하기" click="btnSend_clickHandler(event)"/>
    </mx:HBox>
   
    <mx:Text id="textResult" width="100%" textAlign="center"/>
</s:Application>
[/code]
Socket만들어서 10000포트로 연결합니다. 그러면 도메인이 다르게 되면 swf가 843포트로 "<policy-file-request/>"를 날려서 정책파일을 달라고하는데, 서버에서 만들어놓은 PolicyFileServer가 정책파일을 내려주면 받게되면 10000포트로 다시 연결해 연결을 시작하게 됩니다.

몬가 별거 없는데 장황하게 설명해놨네.

그리고, 이런식으로 PolicyFileServer를 서버어플에 통합하면 안되겠죠? 나중에 서버어플이 늘어난다면 계속 새로 추가해야하니, PolicyfileServer를 따로 만들어서 띄워놓으면 되겠죠?^^
일단, 여러 폴리시서버 예제는 구글링하면 많이 나와요~
여기 아래주소는 Java, PHP, C#, VB.NET, Python 등등 예제가 있어요.
http://code.google.com/p/assql/wiki/SecurityInformation
이건 c로 만든거!
http://panzergruppe.hp.infoseek.co.jp/fspfd.html
 
Posted by 머드초보

댓글을 달아 주세요

 
ServerSocket이라는 것이 Adobe AIR 2에서 생겼는데요.
뭐 특별히 제약사항같은 것은 없는 것 같군요. 그냥 서버소켓 만들어서 쓰면 됩니다.

우선 Adobe AIR 2 셋팅Flash Builder를 다운로드

서버소켓 생성하는 방법은
new ServerSocket();
serverSocket.bind(포트, "아이피");
serverSocket.listen();
하면 서버소켓이 생성됩니다.

Event.Connect를 이벤트 추가하면 상대방이 연결해왔을 때 호출이 됩니다.
보통 Java에서는 Thread를 통해서 하게 되는데, Actionscript3는 특성상 이벤트기반이기에... 그냥 여러개가 연결이 되도 이벤트가 발생하고, 그 발생한 이벤트객체에서 socket이 들어있어서 그걸 이용하면 되구요.

그 소켓에 다시 이벤트를 걸어주면 됩니다. 그 소켓은 client와 연결된 소켓! 기존 client소켓처럼 쓰면 됩니다^^
데이터는 read, write로 주고 받으면 되죠.

아래는 간단한 예제를....-_-
ServerSocketTest.mxml
[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"
                       applicationComplete="windowedapplication_applicationCompleteHandler(event)">
    <fx:Script>
        <![CDATA[
            import flash.events.Event;
            import flash.events.IOErrorEvent;
            import flash.events.ProgressEvent;
            import flash.events.ServerSocketConnectEvent;
            import flash.net.ServerSocket;
            import flash.net.Socket;
           
            import mx.events.FlexEvent;
           
            private var socketPolicyUtil:SocketPolicyUtil;
           
            private var serverSocket:ServerSocket;
            private var listSocket:Vector.<Socket>;
           
            protected function windowedapplication_applicationCompleteHandler(event:FlexEvent):void
            {
                socketPolicyUtil = new SocketPolicyUtil();
               
                // 소켓리스트 초기화
                listSocket = new Vector.<Socket>;
               
                serverSocket = new ServerSocket();
               
                serverSocket.addEventListener(Event.CONNECT, connectHandler);
                serverSocket.addEventListener(Event.CLOSE, onClose);
       
                serverSocket.bind(10000, "127.0.0.1");
               
                serverSocket.listen();
                trace("Listening on " + serverSocket.port);
            }

            private function connectHandler(event:ServerSocketConnectEvent):void
            {
                // The socket is provided by the event object
                var socket:Socket = event.socket as Socket;
                listSocket.push(socket);
               
                socket.addEventListener(ProgressEvent.SOCKET_DATA, socketDataHandler);
                socket.addEventListener(Event.CLOSE, onClientClose);
                socket.addEventListener(IOErrorEvent.IO_ERROR, onIOError);
               
                socket.writeUTFBytes("Connected.");
                socket.flush();
               
                trace("Sending connect message");
            }
           
            private function socketDataHandler(event:ProgressEvent):void
            {
                var socket:Socket = event.target as Socket;
               
                // Read the message from the socket
                var message:String = socket.readUTFBytes(socket.bytesAvailable);
                trace("Received: " + message);
               
                // 등록된 소켓에 모두 전송
                for each (var s:Socket in listSocket)
                {
                    s.writeUTFBytes(message);
                    s.flush();
                }
            }
           
            private function onClientClose(event:Event):void
            {
                trace("Connection to client closed");
                var socket:Socket = event.target as Socket;
               
                // 등록된 소켓 삭제
                var i:int = 0;
                for each(var s:Socket in listSocket)
                {
                    if (s == socket)
                    {
                        trace("같음");
                        listSocket.splice(i, 1);
                        return;
                    }
                    i++;
                }
            }
           
            private function onIOError(event:IOErrorEvent):void
            {
                trace("IOError: " + event.text);   
            }
           
            private function onClose(event:Event):void
            {
                trace("Server socket closed by OS.");       
            }
        ]]>
    </fx:Script>
   
    <fx:Declarations>
        <!-- Place non-visual elements (e.g., services, value objects) here -->
    </fx:Declarations>
</s:WindowedApplication>
[/code]
그냥 코드만 봐도 그리 어렵지 않아요.
서버소켓을 생성하고, 연결되는 소켓들은 list(Vector)에 추가해서 관리하게 되고, 끊어지면 list에서 빼버리고, 대화요청이 들어오면 받아서 모두에게 뿌려주면 돼요.

여기서 제가 만든 클래스가 하나 있는데요. SocketPolicyUtil클래스인데, 크로스 도메인을 위한 클래스입니다. 아래에서 설명을....-_-


크로스 도메인 설정 클래스
여기서 제가 보안샌드박스를 위한 클래스를 하나 만들었는데요. 크로스도메인에서 socket요청이 들어온 경우에는 crossdomain.xml을 넘겨줘야하는데, 그것도 air에서 다 할 수 있습니다.
타 도메인에서 요청하는 경우, 클라이언트 측에서는 843포트를 통해 crossdomain.xml을 요청하게 되어있습니다. 물론 클라이언트에서 843이 아닌 다른 포트로 요청을 원하면 바꿀 수 있지요.
 클라이언트 측에 이런 코드를 넣으면 되죠. 그러면 10001로 포트를 요청하죠.
[code]Security.loadPolicyFile("xmlsocket://127.0.0.1:10001");[/code]
SocketPolicyUtil.as
[code]package
{
    import flash.events.Event;
    import flash.events.IOErrorEvent;
    import flash.events.ProgressEvent;
    import flash.events.ServerSocketConnectEvent;
    import flash.filesystem.File;
    import flash.filesystem.FileMode;
    import flash.filesystem.FileStream;
    import flash.net.ServerSocket;
    import flash.net.Socket;

    public class SocketPolicyUtil
    {
        private var serverSocket:ServerSocket;
       
        public function SocketPolicyUtil(port:int = 843)
        {
            serverSocket = new ServerSocket();
            serverSocket.bind(port, "127.0.0.1");
            serverSocket.listen();
            serverSocket.addEventListener(Event.CONNECT, connectHandler);
            serverSocket.addEventListener(Event.CLOSE, onClose);
        }
       
        private function connectHandler(event:ServerSocketConnectEvent):void
        {
            var socket:Socket = event.socket as Socket;
           
            socket.addEventListener(ProgressEvent.SOCKET_DATA, socketDataHandler);
            socket.addEventListener(Event.CLOSE, onClientClose);
            socket.addEventListener(IOErrorEvent.IO_ERROR, onIOError);
           
            trace("Policy Connected.");
        }
       
        private function socketDataHandler(event:ProgressEvent):void
        {
            var socket:Socket = event.target as Socket;
           
            var message:String = socket.readUTFBytes(socket.bytesAvailable);
            trace("Policy received : " + message);
           
            var file:File = new File(File.applicationDirectory.nativePath + File.separator + "policy.xml");
            var stream:FileStream = new FileStream();
            stream.open(file, FileMode.READ);
            var data:String = stream.readUTFBytes(stream.bytesAvailable);
            trace("policy data = " + data);
            socket.writeUTFBytes(data);
            socket.writeByte(0);
            socket.flush();
        }
       
        private function onClientClose(event:Event):void
        {
            trace("Policy close");
            removeClientSocketEvent(event.target as Socket);
        }
       
        private function onIOError(event:IOErrorEvent):void
        {
            trace("ioerror = " + event.text);   
        }
       
        private function onClose(event:Event):void
        {
            trace("Server socket closed by OS.");
        }
       
        private function removeClientSocketEvent(socket:Socket):void
        {
            socket.removeEventListener(ProgressEvent.SOCKET_DATA, socketDataHandler);
            socket.removeEventListener(Event.CLOSE, onClientClose);
            socket.removeEventListener(IOErrorEvent.IO_ERROR, onIOError);   
        }
    }
}[/code]
중요한 점은 policy.xml파일 날릴 때 마지막에 writeByte(0)으로 끝을 맺어줘야한다는...
이 클래스를 보면 policy.xml파일은 File.applicationDirectory에서 찾고 있으니 이걸 src폴더에다가 넣어버리고 패키지할 때 같이 묶어버리면 됩니다. 그리고 그냥 843포트로 열어서 crossdomain.xml파일을 그냥 전송해주기만 하면 되죠. *는 권장사항이 아니니...-_- 해당 도메인이랑 포트번호를 정확히 입력하는게...-_-
policy.xml
[code]<?xml version='1.0' encoding='UTF-8'?>
<cross-domain-policy>
    <allow-access-from domain='*' to-ports='*'/>
</cross-domain-policy>[/code]

아래는 클라이언트 코드
ClientSocketTest.mxml
[code]<?xml version="1.0" encoding="utf-8"?>
<s:Application xmlns:fx="http://ns.adobe.com/mxml/2009"
               xmlns:s="library://ns.adobe.com/flex/spark"
               xmlns:mx="library://ns.adobe.com/flex/halo" minWidth="1024" minHeight="768"
               applicationComplete="application1_applicationCompleteHandler(event)">
    <fx:Script>
        <![CDATA[
            import mx.controls.Alert;
            import mx.events.FlexEvent;

            private var socket:Socket;
           
            protected function application1_applicationCompleteHandler(event:FlexEvent):void
            {
                //Security.loadPolicyFile("xmlsocket://127.0.0.1:10001");
            }

            protected function btnDisconnect_clickHandler(event:MouseEvent):void
            {
                if (inputId.text.length < 1)
                {
                    Alert.show("아이디를 입력하세요");
                    return;
                }
                socket = new Socket("127.0.0.1", 10000);
                socket.addEventListener(ProgressEvent.SOCKET_DATA, socketDataHandler);
                currentState = "connect";
                labelId.text = inputId.text;
            }
           
            protected function btnConenct_clickHandler(event:MouseEvent):void
            {
                socket.close();
                currentState = "disconnect";
            }
           
           
            private function socketDataHandler(event:ProgressEvent):void
            {
                var message:String = socket.readUTFBytes(socket.bytesAvailable);
                trace("message = " + message);
                taChat.text += message + "\n";
            }

            protected function inputChat_enterHandler(event:FlexEvent):void
            {
                if (inputChat.text.length < 1)
                {
                    return;
                }
                socket.writeUTFBytes("[" + labelId.text + "] : " + inputChat.text);
                socket.flush();
                inputChat.text = "";
            }

            protected function btnChat_clickHandler(event:MouseEvent):void
            {
                if (inputChat.text.length < 1)
                {
                    return;
                }
                socket.writeUTFBytes("[" + labelId.text + "] : " + inputChat.text);
                socket.flush();
                inputChat.text = "";
            }
        ]]>
    </fx:Script>
    <s:states>
        <s:State name="disconnect"/>
        <s:State name="connect"/>
    </s:states>
   
    <fx:Declarations>
        <!-- Place non-visual elements (e.g., services, value objects) here -->
    </fx:Declarations>
   
    <s:TextInput id="inputId" x="231" y="10" includeIn="disconnect"/>
    <s:Button id="btnConenctClose" x="367" y="11" label="접속" label.connect="종료"
              click.connect="btnConenct_clickHandler(event)" click.disconnect="btnDisconnect_clickHandler(event)"/>
    <s:Label id="labelId" includeIn="connect" x="211" y="10" width="159" height="21"/>
   
    <s:TextArea id="taChat" includeIn="connect" x="10" y="49" width="621" height="402"/>
    <s:TextInput id="inputChat" includeIn="connect" x="10" y="459" width="507" enter="inputChat_enterHandler(event)"/>
    <s:Button id="btnChat" includeIn="connect" x="526" y="459" label="Button" click="btnChat_clickHandler(event)"/>
</s:Application>[/code]

아래 프로그램 설치하고 실행시켜놓은 뒤, 아래사이트를 접속해서 테스트할 수 있어요. 아이피는 127.0.0.1로 하드코딩되어있기 때문에 자기 pc에서 띄워놓고 자기가 접속한 클라이언트에서만 가능해요^^
클라이언트 접속 http://mudchobo.tomeii.com/swf/ClientSocketTest.swf


 
Posted by 머드초보

댓글을 달아 주세요

  1. BlogIcon 지돌스타 2009.11.26 02:08  댓글주소  수정/삭제  댓글쓰기

    멋지고 좋은 글입니다. 잘보고 갑니다.

  2. 푸카 2009.11.29 07:37  댓글주소  수정/삭제  댓글쓰기

    빠른 좋은글 감사합니다^^. 잘보고 갑니다.

  3. z 2010.02.18 15:55  댓글주소  수정/삭제  댓글쓰기

    zzzzzzzzzzzzzzzzzzzzzz

  4. 싸부 2010.06.21 18:39  댓글주소  수정/삭제  댓글쓰기

    이게 air로 서버소켓이 리눅스나 유닉스쪽에서도 가능해진건가요?

    • 머드초보 2010.06.26 15:27  댓글주소  수정/삭제

      음....air에서 소켓은 무조건 gui가 있어야 하기 때문에....
      리눅스쪽에서 가능해지긴 합니다만, gui같은 게 있어야 합니다.
      그냥 air에서 서버가 필요한 이유는 뭐 p2p같은 게 가능하고, 더욱 다양한 어플개발이 가능하기 때문입니다^^

  5. 2010.11.03 18:56  댓글주소  수정/삭제  댓글쓰기

    비밀댓글입니다

    • 머드초보 2010.11.05 21:23  댓글주소  수정/삭제

      음 air에서 그냥serversocket하나 열어놓으시고
      나머지 swf파일들로 거기에 접속하면 될 것 같은데요^^
      이거 Client에서 서버쪽 진짜 아이피로 접속하면 됩니다^^

  6. S 2010.12.02 17:49  댓글주소  수정/삭제  댓글쓰기

    좋은 글 잘 보고 갑니다 ^^

 

XMLSocket은 뭔가 이상하군요-_-;

그냥 Socket클래스는 그냥 쓰던 소켓같은데 XMLSocket은 삽질한 결과 이상하게 주고 받는 것 같아요.

우선 보낼 때 이상하게 나눠서 보내게 되더라구요.

그리고 \0을 끝으로 인식하는 듯합니다. 그래서 자바서버에서 보낼 때 pw.println("blur~blur~")이렇게 보낼 때에는 blur~blur~\r\n이 가게 되는데 저것이 끝이라고 인식을 못하더라구요.
그래서 보낼 때에는 pw.print("blur~blur~\n\0") 이라고 보내야해요.

그리고,
mudchobo : 안녕하세요\n\0 라고 보내면, 이상하게 나눠서 호출이 되더라구요-_-;
mudchobo : 데이터를 받고, 그다음에 안녕하세요\n\0을 받아요-_-;

왜그럴까요-_-; 어쨌든, 편법으로 만든 채팅프로그램입니다.

서버(JAVA)
ChatServer.java
[code]package com.mudchobo.chat;

import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.HashMap;

public class ChatServer {

 private ServerSocket server;
 
 void startServer() {
  try {
   server = new ServerSocket(10001);
   System.out.println("접속을 기다립니다.");
   HashMap<String, PrintWriter> hashMap =
    new HashMap<String, PrintWriter>();
   
   while (true) {
    Socket sock = server.accept();
    ChatThread chatThread =
     new ChatThread(sock, hashMap);
    chatThread.start();
   } // while
  } catch (Exception e) {
   System.out.println(e);
  }
 }
 public static void main(String[] args) {
  ChatServer chatServer = new ChatServer();
  chatServer.startServer();
 }
}
[/code]

ChatThread.java
[code]package com.mudchobo.chat;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.io.UnsupportedEncodingException;
import java.net.Socket;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;

class ChatThread extends Thread {
 private Socket sock;
 private String id;
 PrintWriter pw;
 private BufferedReader br;
 private HashMap<String, PrintWriter> hashMap;

 public ChatThread(Socket sock, HashMap<String, PrintWriter> hashMap)
  throws UnsupportedEncodingException, IOException {
  this.sock = sock;
  this.hashMap = hashMap;
 
  pw = new PrintWriter(
    new OutputStreamWriter(sock.getOutputStream(),"UTF-8"));
  br = new BufferedReader(
    new InputStreamReader(sock.getInputStream(), "UTF-8"));
  id = br.readLine();
  hashMap.put(id, pw);
  broadcast(id + "님이 접속하였습니다.");
  System.out.println("접속한 사용자의 아이디는 " + id + "입니다.");
 }

 public void run() {
  try {
   String line = null;
   while ((line = br.readLine()) != null) {
    if (line.equals("/q")) {
     break;
    } else {
     broadcast(id + " : " + line);
    }
   }
  } catch (Exception ex) {
   System.out.println(ex);
  } finally {
   hashMap.remove(id);
   broadcast(id + " 님이 접속 종료하였습니다.");
   System.out.println(id + " 님이 접속 종료하였습니다.");
   try { if (sock != null) sock.close(); } catch (Exception ex) {}
  }
 }

 public void broadcast(String msg) {
  Collection<PrintWriter> collection = hashMap.values();
  Iterator<PrintWriter> iter = collection.iterator();
  while (iter.hasNext()) {
   PrintWriter pw = (PrintWriter) iter.next();
   pw.print(msg + "\n\0");
   pw.flush();
  }
 }
}
[/code]
저 부분만 바뀌었어요 ^^

이제 클라이언트로-_-;

 
Posted by 머드초보

댓글을 달아 주세요

  1. 계인 2007.11.27 11:53  댓글주소  수정/삭제  댓글쓰기

    좋은 정보가 많네요.. 머드초보님을 싸부로 생각하고 이것저것 해보고 있는데.. 제가 *.java 파일의 위치는 어디로 해야 할지 몰라서... 절대경로 ^^; 완전 초보라..ㅠ_ ㅠ

    • BlogIcon 머드초보 2008.01.26 17:39  댓글주소  수정/삭제

      아...서버쪽에서는 *.java파일을 저위에 package경로에 넣어야되는데요.
      패키지를 만드셔서 com.mudchobo.chat으로 만드셔서 넣으면 되는데요^^
      그냥 패키지안만드시면 위에 package를 없애버리면돼요 ^^
      저 허접해서 싸부로 생각하지마시고 같이 공부하는 동료로 생각해주세요^^
      허접해서 친구한테 맨날 욕먹고 있어요^^
      방문해주셔서 감사해요 ^^

  2. BlogIcon eizt 2010.07.04 22:39  댓글주소  수정/삭제  댓글쓰기

    제가 진행하고 있는데 flex 하고 java를 써야될것 같아서 ㅠㅠ 많이좀 참고좀 하겠습니다ㅠㅠ

    아 어렵다 어려워 ㅠㅠ

  3. BlogIcon 김상훈 2012.10.08 18:45  댓글주소  수정/삭제  댓글쓰기

    "mudchobo : 안녕하세요\n\0 라고 보내면, 이상하게 나눠서 호출이 되더라구요-_-;
    mudchobo : 데이터를 받고, 그다음에 안녕하세요\n\0을 받아요-_-;"

    이문제는 line = br.readLine() 시 종료되는 문자열이 같이 들어가서 생기는 문제였습니다.
    broadcast(id + " : " + line); => broadcast(id + " : " + line.trim()); 으로 해결되더군요.

  4. BlogIcon 김상훈 2012.10.08 18:45  댓글주소  수정/삭제  댓글쓰기

    플래시로 채팅 프로그램을 구현해야 했는데 이 프로그램으로 많은 도움 되었습니다.

 

이번에는 클라이언트를 보겠습니다.

참고로 예제로 배우는 플렉스2에 있는 CODE7_13을 참고했습니다.
(틀만 가져다가 썼습니다 ^^)
FLEX3 BETA2에서 테스트해봤습니다.




클라이언트(FLEX)

ChatClient_Flex.mxml
[code]<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml"
 creationComplete="initApp()">
 
 <mx:Script>
  <![CDATA[
   private var socket:Socket = new Socket();
   [Bindable]
   public var userId:String;
   
   private function initApp():void {
    currentState = "logon";
   }
   
   public function logon():void {
    socket = new Socket("127.0.0.1", 10001);
    socket.addEventListener(ProgressEvent.SOCKET_DATA, socketDataHandler);
    userId = tUserId.text;
    socket.writeUTFBytes(userId + "\n");
    socket.flush();
    currentState = "chat";
   }
   
   private function socketDataHandler(event:ProgressEvent) :void {
          var str:String = socket.readUTFBytes(socket.bytesAvailable);         
          trace(str);
          trace("음");
          var trimstr:String = str.substr(0, str.indexOf("\r\n"));
          log.text += trimstr + "\n";
      }
        
   public function send():void {
    socket.writeUTFBytes(msg.text + "\n");
                socket.flush();
                msg.text = "";
                
   }
  ]]>
 </mx:Script>
 
 <mx:Panel id="panel" width="100%" height="100%" verticalAlign="middle"
  horizontalAlign="center">
  <mx:ControlBar id="cb" height="44" />
 </mx:Panel>
 
 <mx:states>
 
  <mx:State name="logon">
   <mx:AddChild relativeTo="{panel}">
    <mx:HBox>
     <mx:Label text="User Id:" />
     <mx:TextInput id="tUserId" enter="logon()" />
     <mx:Button label="Logon" click="logon()" />
    </mx:HBox>
   </mx:AddChild>
  </mx:State>
 
  <mx:State name="chat">
   <mx:SetProperty target="{panel}" name="title"
    value="접속자 ID : [{userId}]" />
   <mx:AddChild relativeTo="{panel}">
    <mx:TextArea id="log" width="100%" height="100%" editable="false"/>
   </mx:AddChild>
   <mx:AddChild relativeTo="{cb}">
    <mx:HBox width="100%" paddingTop="0" paddingBottom="0">
     <mx:TextInput id="msg" enter="send()" width="100%" />
    </mx:HBox>
   </mx:AddChild>
  </mx:State>
 
 </mx:states>
 
</mx:Application>
[/code]
처음에 소켓이 조금 헷깔렸는데-_-; 이제 좀 알것같네요.
우선 아이디를 입력하고 로그인을 클릭하면 logon함수를 실행하는데 socket에 이벤트리스너를 등록해요.
socket.addEventListener(ProgressEvent.SOCKET_DATA, socketDataHandler);
이거를 등록하게 되면, 서버쪽에서 데이터를 받게 되면 이벤트가 발생되는데 socketDatahandler라는 함수를 호출하라는 얘기죠.
서버가 데이터를 던져주게되면 저함수를 호출해서 readUTFBytes를 호출하면 데이터를 받아올 수 있죠.

※readMultiByte(socket.bytesAvailable, "euc-kr") 이렇게하면
서버쪽에서 UTF-8로 안만들어도 되는데요.
UTF-8이 대세기때문에-_-; UTF-8을 활용합시다-_-;


사용자 삽입 이미지

사용자 삽입 이미지



서버
http://mudchobo.tomeii.com/tt/152
 
Posted by 머드초보

댓글을 달아 주세요

  1. BlogIcon chichs 2008.03.26 17:29  댓글주소  수정/삭제  댓글쓰기

    안녕하세요~
    블로그에 막 입성한 초보블로거라서 잘몰라 요래저래 하다보니 트랙백이 갔네요;;
    요새 플렉스를 열심히 배우고 있어서..ㅎㅎ
    이 글을 고대로 퍼가려다가;; (그런건안데나;;)

    암튼.. 좋은정보 감사하구요~ 앞으로 자주 올께요^^

    • 머드초보 2008.03.27 08:28  댓글주소  수정/삭제

      안녕하세요 ^^ 반갑습니다 ^^
      저도 시작한지 이제 겨우 9개월밖에 안되서 ^^
      저는 트랙백거는거 한 3달만에 터득한 것 같은데 대단하십니다 ^^
      글 퍼가셔도 되는데 출처만 남기시면 돼요 ^^
      그럼 앞으로 블로그를 잘 이끌어나가세요 ^^

  2. 낭만공돌이 2008.05.01 01:40  댓글주소  수정/삭제  댓글쓰기

    적어주신 예제. 간단하고 이해하기 쉬워서 너무 잘 보았습니다!
    다만 한가지 질문이 있는데.
    client 단에서 첫번째로 보낸 메시지를 userID 로 받아서 "접속한 사용자의 ID는 xxx 입니다."
    라고 뿌려주시는 것 같은데.

    저는 아무래도 그 첫번째 메시지가 계속
    <policy-file-request/>
    인 것 같습니다.
    덕분에 첫번째 채팅 메시지가 ID로 인식되는군요 ㅠ

    이거 어떻게 해야 해결될까요..

    인터넷을 뒤져서
    classdomain.xls 파일을 만들라는 조언도 봤는데, 그대로 해도 잘 안되는군요;;;;
    ㅠㅠ

    혹시 도와주실 수 있을까요...ㅠ

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

      아....무슨얘기인지 잘 모르겠네요-_-;
      접속한 아이디라면...아이디를 그냥 서버에 넘겨주면 될듯한데요
      서버단에서 처리해야할 듯 합니다 ^^

  3. jun 2008.06.02 18:53  댓글주소  수정/삭제  댓글쓰기

    crossdomain 문제입니다 ..

    서버단에서 크로스 도매인 문서를 보내주고

    서버에서 접속을 끊어야 합니다

  4. calmtot 2009.03.06 16:42  댓글주소  수정/삭제  댓글쓰기

    좋은 정보 잘 보고 갑니다~~ 고마워요 ^^ ㅋ

  5. 김경순 2009.03.10 16:02  댓글주소  수정/삭제  댓글쓰기

    고맙습니다. 저도 플랙스 채팅 프로그램을 Client/Server로 만드는 중인데 좋은 자료여서
    참고하도록 하겠습니다. 감사합니다.

    • 머드초보 2009.03.13 12:12  댓글주소  수정/삭제

      아넵 도움이 되셨다니 다행이네요^^
      방문해주셔서 감사해요~ ^^

  6. 쿨해요 2010.07.18 19:37  댓글주소  수정/삭제  댓글쓰기

    굿입니다.

 

채팅프로그램은 LCDS를 이용해서 메시지서비스를 하게 되면 매우 간단하게 작성할 수 있습니다.
하지만, 뭔가 자유도가 부족하군요-_-; 그래서 서버를 자바로하고 클라이언트를 FLEX로 한 프로그램을 만들어봤습니다.
플렉스 소켓으로 삽질하시는 분들께 도움이 되고자--;(뭐 도움은 안되겠지만-_-;)
JAVA 6.0에서 테스트했습니다.

서버(JAVA)
ChatServer.java 와 ChatThread.java 두개의 파일입니다.
ChatServer클래스에는 main클래스가 있으며, accept()를 호출해서 클라이언트의 접속을 기다리다가 접속이 되면 스레드를 시작을 하게 되는 구조입니다.

ChatServer.java
[code]package com.mudchobo.chat;

import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.HashMap;

public class ChatServer {

 private ServerSocket server;
 
 void startServer() {
  try {
   server = new ServerSocket(10001);
   System.out.println("접속을 기다립니다.");
   HashMap<String, PrintWriter> hashMap =
    new HashMap<String, PrintWriter>();
   
   while (true) {
    Socket sock = server.accept();
    ChatThread chatThread =
     new ChatThread(sock, hashMap);
    chatThread.start();
   } // while
  } catch (Exception e) {
   System.out.println(e);
  }
 }
 public static void main(String[] args) {
  ChatServer chatServer = new ChatServer();
  chatServer.startServer();
 }
}
[/code]
우선 startServer를 보시면, ServerSocket을 생성합니다. 그리고, HashMap을 하나 생성하는데 이것은 id와 PrintWriter, 즉, 해당 스레드마다 각각의 PrintWriter를 저장해서 다른 클라이언트에게 뿌려줄 때 사용됩니다.
그래서 Thread만들 때 hashMap을 같이 넘겨주도록 합니다.

ChatThread.java
[code]package com.mudchobo.chat;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.io.UnsupportedEncodingException;
import java.net.Socket;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;

class ChatThread extends Thread {
 private Socket sock;
 private String id;
 PrintWriter pw;
 private BufferedReader br;
 private HashMap<String, PrintWriter> hashMap;

 public ChatThread(Socket sock, HashMap<String, PrintWriter> hashMap)
  throws UnsupportedEncodingException, IOException {
  this.sock = sock;
  this.hashMap = hashMap;
 
  pw = new PrintWriter(
    new OutputStreamWriter(sock.getOutputStream(),"UTF-8"));
  br = new BufferedReader(
    new InputStreamReader(sock.getInputStream(), "UTF-8"));
  id = br.readLine();
  hashMap.put(id, pw);
  broadcast(id + "님이 접속하였습니다.");
  System.out.println("접속한 사용자의 아이디는 " + id + "입니다.");
 }

 public void run() {
  try {
   String line = null;
   while ((line = br.readLine()) != null) {
    if (line.equals("/q")) {
     break;
    } else {
     broadcast(id + " : " + line);
    }
   }
  } catch (Exception ex) {
   System.out.println(ex);
  } finally {
   hashMap.remove(id);
   broadcast(id + " 님이 접속 종료하였습니다.");
   System.out.println(id + " 님이 접속 종료하였습니다.");
   try { if (sock != null) sock.close(); } catch (Exception ex) {}
  }
 }

 public void broadcast(String msg) {
  Collection<PrintWriter> collection = hashMap.values();
  Iterator<PrintWriter> iter = collection.iterator();
  while (iter.hasNext()) {
   PrintWriter pw = (PrintWriter) iter.next();
   pw.println(msg);
   pw.flush();
  }
 }
}
[/code]
생성자를 보게 되면 해당클라이언트와 통신을 하도록 하는 PrintWriter와 BufferedReader를 만듭니다.
우선 접속하게 되면 클라이언트쪽에서 id를 보내도록 되어있죠.(나중에 클라이언트할때 설명-_-;)

그리고, run메소드를 보시게 되면, BuferedReader에서 readLine을 하게 되어서, 채팅데이터를 받게 되면, broadcast함수를 호출하게 되어, 연결되어 있는 모든 클라이언트에게 채팅데이터를 보냅니다.
hashmap에 printwriter를 저장해서 가능하죠 ^^

※플렉스는 UTF-8로 데이터를 전송하게 됩니다. 그래서 데이터를 받을 때 UTF-8로 받아야겠죠?
[code]pw = new PrintWriter(
    new OutputStreamWriter(sock.getOutputStream(),"UTF-8"));
  br = new BufferedReader(
    new InputStreamReader(sock.getInputStream(), "UTF-8"));
[/code]
위와 같이 "UTF-8"이라고 해주면 돼요 ^^
UTF-8빼면 한글이 깨져버려요 ^^

길이너무 길어져서 다음 글로-_-;

클라이언트
http://mudchobo.tomeii.com/tt/153
 
Posted by 머드초보

댓글을 달아 주세요

  1. BlogIcon 다크호스 2009.01.12 11:17  댓글주소  수정/삭제  댓글쓰기

    와 투명아이 가족이네요 ㅎ

    플렉스 관심이 많았는데

    잘 보고 갑니다.

    • 머드초보 2009.01.14 17:26  댓글주소  수정/삭제

      앗~ 투명아이 가족이신가보군요 ^^
      저도 투명아이 가족입니다 ^^
      저도 플렉스에 관심이 많아서 삽질 중입니다 ^^
      반갑습니다 ^^

  2. -_-;; 2009.10.27 19:24  댓글주소  수정/삭제  댓글쓰기

    로컬에서는 도는데 결정적으로 원격으로는 문제가 있네요...
    방법을 찾아보고는 있습니다만... 좋은 방법이 없을까요?

    • 머드초보 2009.10.29 12:40  댓글주소  수정/삭제

      아 그문제는 저도 고생했었는데, 보안 샌드박스 문제 입니다.
      소켓으로 원격으로 접속할 때에는 crossdomain.xml파일을 먼저 요청을 합니다. 즉 내가 이서버에 접속해도 되나 하는 보안파일을 먼저 보내주는 프로세스를 만들어야합니다.
      Policy Server프로그램을 하나 만들면 되는데, 만들어진 것이 많아 가져다 쓰셔도 될 듯합니다^^
      http://code.google.com/p/assql/wiki/SecurityInformation

  3. 시원 2010.05.20 22:46  댓글주소  수정/삭제  댓글쓰기

    XMLSocket 보안 샌드박스 문제때문에 이틀째 고생중입니다.
    머드 초보님처럼 저도 Policy Server프로그램을 만들고 있는데 어디서 엉키는건지
    이상하게 접속이 안되네요.....-.-;

    혹시 시간나시면 님이 말씀하신 http://code.google.com/p/assql/wiki/SecurityInformation 예제로
    Policy Server 만든경험을 저와같이 고생하는 중생들을 위해 설명을 곁들여
    올려주시면 안될까용??? ^^;

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

      안녕하세요~
      저도 예전에 그걸로 삽질을 한적이 있었는데요^^
      의외로 간단해요.
      그냥 Policy Server프로그램을 서버에 띄워놓으면 swf가 그 서버에서 plicy파일을 요청합니다. 그걸 던져주는 걸 만들면 되는건데
      음...
      저 예제로 하면 되는데...
      나중에 시간날 때 한번 글을 써보겠습니다 ㅠㅠ

  4. 2010.06.11 10:23  댓글주소  수정/삭제  댓글쓰기

    비밀댓글입니다

    • 머드초보 2010.06.14 12:09  댓글주소  수정/삭제

      네네~
      그건 크로스도메인 문제인데요.
      서버측에서 843포트로 크로스도메인파일을 전송해줘야합니다.
      그거 관련된 것은 검색해보면 나오는데,
      나중에 이 글에 대해서 포스팅을 할께요.
      많이들 헷깔려하시는 것 같아서 ㅠㅠ