관리 메뉴

막내의 막무가내 프로그래밍 & 일상

[안드로이드] 서버 소켓 프로그래밍 (채팅) 본문

안드로이드/자바 & Previous

[안드로이드] 서버 소켓 프로그래밍 (채팅)

막무가내막내 2019. 12. 18. 02:32
728x90

 

 

안드로이드 소켓 통신을 공부하면서 예시로 간단한 채팅을 구현해봤다.

 

폰 하나는 서버 다른 폰 하나는 클라이언트를 맡고 소켓 통신을 하는 결과를 구현했고 프로젝트는 서버와 클라이언트 두 개 로 나눠서 만들었다.

 

[서버]

 

위의 getLoacalAddress() 메소드를 통해서 현재 내 휴대폰의 ip를 알 수 있고 TextViewset해준다. 클라이언트에서 서버 ip를 알아야 접속이 가능하므로 필요하다.

 

 

 

ServerSocketOpen()Open Server 버튼을 누르면 동작할 온클릭 메소드이다.

[62~78라인]: 소켓을 생성할려면 포트번호가 필요하다. 그러므로 포트번호를 editText에서 입력했는지 검사 후 서버 소켓을 생성한다. 안드로이드에서는 소켓 통신을(네트워크 통신) 할 때 스레드가 필수적으로 사용된다.

 

[81~94라인]: accept()로 서버에 접속하는 클라이언트의 소켓을 얻어온다. (클라이언트가 접속할때까지 대기한다.) 그 후 클라이언트가 접속하면 데이터를 주고받기 위한 통로를 DataInputStreamDataOutputStream으로 구성한다. 또한 클라이언트와 연결되었다고 토스트메세지를 띄워주는데 안드로이드에서는 메인스레드 외에 스레드에서는 UI를 변경하거나 띄워줄수 없기 때문에 runOnUiThread()를 사용해줘야 한다.

 

[96~112라인]: 클라이언트가 접속을 끊을 때까지 while문으로 돌리면서 클라이언트의 메시지를 계속 수신할 수 있게 한다. 메시지는 앞서 선언한 DataInputStream (통로)readUTF()를 통해 받을 수 있다. UTF는 한글을 수신할 수 있기 위해 필요하다. 그리고 수신받은 메시지를 TextView에 띄워주도록한다. 이것도 마찬가지로 메인스레드가 아니므로 runOnUiThread()를 사용하여 UI를 변경하도록 한다.

 

 

메시지 전송버튼을 눌렀을 때 동작하는 온클릭 메소드이다.

[120라인]: DataOutputStream (os) null이면 앞선 코드에서 살펴볼 수 있듯이 클라이언트가 접속되지 않은 것을 의미하므로 메시지를 보낼 수 없다.

 

[123~145라인]: 전송할 메시지를 갖고온 후 소켓 통신 즉 네트워크 통신을 해야 하므로 스레드가 사용된다. DataOutputStream 객체의 writeUTF를 통해 메시지를 보내준다. 그리고 다음 메시지 전송을 위해 DataOutputStream 연결통로의 버퍼를 flush()로 지워준다. 송신한 메시지를 자신의 폰에서 볼 수 있게 setText() 해준다. 이것도 UI를 건드는 작업이므로 runOnUiThread()를 이용한다.

 

 

 

 


 

 

[클라이언트]

 

접속 버튼을 누르면 클라이언트의 소켓을 생성하고 서버 소켓에 연결하는 온클릭 메소드이다.

[48~79라인]: ipportEditText를 통해 받는다. 이때 ip는 서버의 ip와 열어논 포트번호를 입력해야 한다. 그리고 서버에서 소켓을 생성했던 것처럼 소켓을 생성한다. (다른점은 ip를 입력해야 한다.) 통로도 서버에서 했던 것처럼 생성해준다. 서버와 연결됬다면 토스트메세지를 띄워주도록 한다.

 

[82~100라인]: 서버와 접속이 끊킬 때 까지 무한 반복하면서 서버의 메시지를 수신하고 UIsetText() 해준다.. 이것은 서버에서의 코드와 똑같다.

 

 

메시지 전송버튼을 클릭하면 서버에 메시지를 전송해주는 온 클릭 메소드이다. 서버에서의 메시지 전송과 코드가 동일하다. 스레드를 이용해서 네트워크 통신을 하고 stream 통로를 통해 메시지를 전송하고 flush()로 버퍼를 비워준다.

 

 

 

안드로이드 생명주기인 onStop() 시 소켓연결을 close()하도록 해주었다.

 

 

 

 

[P.S]

안드로이드에서 manifest에 다음과 같이 접근권한을 꼭 추가해줘야한다.

 

 

 

다음은 실행결과이다.

https://www.youtube.com/watch?v=OvRQpf1oZ5M&feature=youtu.be

 

 

 

[클라이언트]

package com.mtjin.networkclientsocket;

import androidx.appcompat.app.AppCompatActivity;

import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
import android.widget.Toast;

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.InetAddress;
import java.net.Socket;

public class MainActivity extends AppCompatActivity {
    Socket socket;     //클라이언트의 소켓

    DataInputStream is;
    DataOutputStream os;

    String ip;
    String port;

    TextView text_msg;  //서버로 부터 받은 메세지를 보여주는 TextView
    EditText edit_msg;  //서버로 전송할 메세지를 작성하는 EditText
    EditText edit_ip;   //서버의 IP를 작성할 수 있는 EditText
    EditText edit_port;
    Button btn_connect;
    String msg="";
    boolean isConnected=true;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        edit_ip = findViewById(R.id.ip);
        edit_port = findViewById(R.id.port);
        btn_connect = findViewById(R.id.connect);
        edit_msg = findViewById(R.id.msg);
        text_msg = findViewById(R.id.chatting);
    }

    //클라이언트 소켓 열고 서버 소켓에 접속
    public void ClientSocketOpen(View view) {
        new Thread(new Runnable() {
            @Override
            public void run() {
                // TODO Auto-generated method stub
                try {
                    ip= edit_ip.getText().toString();//IP 주소가 작성되어 있는 EditText에서 서버 IP 얻어오기
                    port = edit_port.getText().toString();
                    if(ip.isEmpty() || port.isEmpty()){
                        MainActivity.this.runOnUiThread(new Runnable() {
                            public void run() {
                                Toast.makeText(MainActivity.this, "ip주소와 포트번호를 입력해주세요.", Toast.LENGTH_SHORT).show();
                            }
                        });
                    }else {
                        //서버와 연결하는 소켓 생성..
                        socket = new Socket(InetAddress.getByName(ip), Integer.parseInt(port));
                        //여기까지 왔다는 것을 예외가 발생하지 않았다는 것이므로 소켓 연결 성공..

                        //서버와 메세지를 주고받을 통로 구축
                        is = new DataInputStream(socket.getInputStream());
                        os = new DataOutputStream(socket.getOutputStream());

                        MainActivity.this.runOnUiThread(new Runnable() {
                            public void run() {
                                Toast.makeText(MainActivity.this, "Connected With Server", Toast.LENGTH_SHORT).show();
                            }
                        });
                    }
                } catch (IOException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }

                //서버와 접속이 끊길 때까지 무한반복하면서 서버의 메세지 수신
                while(isConnected){
                    try {
                        msg= is.readUTF(); //서버 부터 메세지가 전송되면 이를 UTF형식으로 읽어서 String 으로 리턴
                        //runOnUiThread()는 별도의 Thread가 main Thread에게 UI 작업을 요청하는 메소드이다
                        runOnUiThread(new Runnable() {
                            @Override
                            public void run() {
                                // TODO Auto-generated method stub
                                text_msg.setText("[RECV]" +msg);
                            }
                        });
                    } catch (IOException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }
                }//while
            }//run method...
        }).start();//Thread 실행..

    }

    public void SendMessage(View view) {
        if(os==null) return;   //서버와 연결되어 있지 않다면 전송불가..

        //네트워크 작업이므로 Thread 생성
        new Thread(new Runnable() {
            @Override
            public void run() {
                // TODO Auto-generated method stub
                //서버로 보낼 메세지 EditText로 부터 얻어오기
                String msg= edit_msg.getText().toString();
                try {
                    runOnUiThread(new Runnable() {
                        @Override
                        public void run() {
                            String msg= edit_msg.getText().toString();
                            // TODO Auto-generated method stub
                            text_msg.setText("[SEND]" +msg);
                        }
                    });
                    os.writeUTF(msg);  //서버로 메세지 보내기.UTF 방식으로(한글 전송가능...)
                    os.flush();        //다음 메세지 전송을 위해 연결통로의 버퍼를 지워주는 메소드..

                } catch (IOException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
            }//run method..

        }).start(); //Thread 실행..
    }


    @Override
    protected void onStop() {
        super.onStop();
        try {
            socket.close(); //소켓을 닫는다.
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

 

 

 

 

[서버]

package com.mtjin.networkserversocket;

import androidx.appcompat.app.AppCompatActivity;

import android.net.wifi.WifiInfo;
import android.net.wifi.WifiManager;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
import android.widget.Toast;

import java.io.BufferedReader;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.UnknownHostException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;

public class MainActivity extends AppCompatActivity {
    //xml
    TextView ipText;
    TextView portText;
    TextView text_msg; //클라이언트로부터 받을 메세지를 표시하는 TextView
    EditText edit_msg; //클라이언트로 전송할 메세지를 작성하는 EditText


    ServerSocket serversocket;
    Socket socket;
    DataInputStream is;
    DataOutputStream os;

    String msg="";
    boolean isConnected=true;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        ipText = findViewById(R.id.ip);
        portText = findViewById(R.id.port);
        text_msg = findViewById(R.id.chatting);
        edit_msg = findViewById(R.id.msg);

        //내 아이피 확인 및 세팅
        try {
            ipText.setText(getLocalIpAddress());
        } catch (UnknownHostException e) {
            e.printStackTrace();
        }
    }

    public void ServerSocketOpen(View view) throws IOException {
        final String port = portText.getText().toString();
        if(port.isEmpty()){
            Toast.makeText(this, "포트번호를 입력해주세요.", Toast.LENGTH_SHORT).show();
        }else {
            Toast.makeText(this, "Socket Open", Toast.LENGTH_SHORT).show();
            //Android API14버전이상 부터 네트워크 작업은 무조건 별도의 Thread에서 실행 해야함.
            new Thread(new Runnable() {
                @Override
                public void run() {
                    // TODO Auto-generated method stub
                    try {
                        //서버소켓 생성.
                        serversocket = new ServerSocket(Integer.parseInt(port));
                    } catch (IOException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }
                    try {
                        //서버에 접속하는 클라이언트 소켓 얻어오기(클라이언트가 접속하면 클라이언트 소켓 리턴)
                        socket = serversocket.accept(); //서버는 클라이언트가 접속할 때까지 여기서 대기 접속하면 다음으로 코드로 넘어감
                        //클라이언트와 데이터를 주고 받기 위한 통로구축
                        is = new DataInputStream(socket.getInputStream()); //클라이언트로 부터 메세지를 받기 위한 통로
                        os = new DataOutputStream(socket.getOutputStream()); //클라이언트로 메세지를 보내기 위한 통로

                        MainActivity.this.runOnUiThread(new Runnable() {
                            public void run() {
                                Toast.makeText(MainActivity.this, "Connected With Client Socket", Toast.LENGTH_SHORT).show();
                            }
                        });
                    } catch (IOException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }
                    //클라이언트가 접속을 끊을 때까지 무한반복하면서 클라이언트의 메세지 수신
                    while (isConnected) {
                        try {
                            msg = is.readUTF();
                        } catch (IOException e) {
                            // TODO Auto-generated catch block
                            e.printStackTrace();
                        }
                        //클라이언트로부터 읽어들인 메시지msg를 TextView에 출력한다. 안드로이드는 메인스레드가 아니면 UI변경 불가하므로 다음과같이 해줌.(토스트메세지도 마찬가지)
                        runOnUiThread(new Runnable() {
                            @Override
                            public void run() {
                                // TODO Auto-generated method stub
                                text_msg.setText("[RECV]" +msg);
                            }
                        });
                    }//while..
                }//run method...
            }).start(); //Thread 실행..
        }
    }


    //메세지전송
    public void SendMessage(View view) {
        if(os==null) return; //클라이언트와 연결되어 있지 않다면 전송불가
        final String msg= edit_msg.getText().toString();
        //네트워크 작업이므로 Thread 생성
        new Thread(new Runnable() {
            @Override
            public void run() {
                // TODO Auto-generated method stub
                //클라이언트로 보낼 메세지 EditText로 부터 얻어오기

                try {
                    runOnUiThread(new Runnable() {
                        @Override
                        public void run() {

                            // TODO Auto-generated method stub
                            text_msg.setText("[SEND]" +msg);
                        }
                    });

                    os.writeUTF(msg); //클라이언트로 메세지 보내기.UTF 방식으로 한글 전송 가능하게함
                    os.flush();   //다음 메세지 전송을 위해 연결통로의 버퍼를 지워주는 메소드
                } catch (IOException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
            }
        }).start(); //Thread 실행..
    }

    //내 ip 얻기
    private String getLocalIpAddress() throws UnknownHostException {
        WifiManager wifiManager = (WifiManager) getApplicationContext().getSystemService(WIFI_SERVICE);
        assert wifiManager != null;
        WifiInfo wifiInfo = wifiManager.getConnectionInfo();
        int ipInt = wifiInfo.getIpAddress();
        return InetAddress.getByAddress(ByteBuffer.allocate(4).order(ByteOrder.LITTLE_ENDIAN).putInt(ipInt).array()).getHostAddress();
    }
}

 

 

 

728x90
Comments