티스토리 뷰

/ 기록용이라 사담이 많을 수 있습니다. 해당 애플리케이션을 개발하면서 겪은 일과 느낀점, 문제점을 적었습니다. /

실장님께서 첫 번째 과제로 소켓 통신을 이용한 안드로이드 채팅 애플리케이션을 만들어보라고 하셨다. 첫날은 소켓 없이 firebase라는 걸로 채팅을 만들려고 했었는데, 다음 날 소켓 통신으로 구현해야 한다고 말씀해 주셔서 첫날은 사실상 "firebase에 대해서 알게 된 걸로" 치고 넘어갔다. 둘째 날은 그래서 구글에 '소켓', '소켓 통신 서버' 별의별 키워드를 다 쳐보면서 구글에 올라온 여러 코드들을 우선 쳐보고 돌렸다.

간만에 내가 예전에 네이버 블로그에 올렸던 JAVA 카테고리의 'TCP/IP 예제'로 올린 소켓 채팅 프로그램

https://varyeun.blog.me/221723947400

 

[명품 JAVA Programming] 제15장 예제

15.3 서버-클라이언트 채팅 프로그램 만들기​- 서버 프로그램 ServerEx.java​- 클라이언트 프로그램 C...

blog.naver.com

을 돌려보며 잊고 있었던 소켓 통신에 대한 기억도 되살렸다.. 이번에 돌려보니 저때 내가 예제를 올렸던 이유는 그냥 내 지식이 부족했던 것임을^^.. 상기할 수 있었고, 그래서 구글에 나온 웬만한 모든 소켓 채팅 코드를 돌려보았는데 (사실 구글링으로 과제를 해결할 수 있을까 하는 바람에....) 전부 우리가 목표로 하는 프로그램은 아니었다.

우리가 목적으로 했던 프로그램은

1. 안드로이드 애플리케이션으로 구현할 것 (클라이언트는 안드로이드)

2. 서버-클라이언트 1:1 채팅이 아니라 서버와 연결된 여러 클라이언트 사이의 채팅일 것!

3. 채팅으로서의 기능을 만족할 것 - 한 클라이언트가 송신한 메시지는 접속중인 모든 클라이언트들이 수신해야한다.

이렇게 세 가지였는데, 구글에 다른 분들이 올려주신 코드는 1. 클라이언트가 안드로이드가 아니거나, 2. 서버-클라이언트 1:1 채팅 애플리케이션이라 해당 과제 해결에는 공부하는 데에 굉장한 도움은 됐지만 이를 그대로 과제로 제출할 수는 없었다. 그래도 구글링 중에 이클립스로 (나는 이제 인텔리제이를 사용하지만) 클라이언트 간 통신을 하는 프로그램을 찾았고, 일반 자바파일로 작성된 클라이언트를 안드로이드 스튜디오의 화면 구성에 맞게 코드를 고쳤다.

겪은 문제점 (사담이 90%)

구현을 다 하고 나니 코드 자체는 생각보다 정말 간단했는데, 초반에는 스레드와 소켓통신에 대한 개념을 제대로 이해하지 못하고 무작정 코드 짜는데만 달려들어서 시간을 많이 소요했다. (배운 시간으로 치는 중 ..) 애플리케이션을 완성했다고 생각하였는데 서버 측에서는 클라이언트와 연결됐다고 나오는데 (연결된 클라이언트 수를 출력하도록 했음) 클라이언트 코드에서는 서버와 연결하며 반환받아야 할 소켓이 자꾸 null로 뜨고, 그렇다고 아예 소켓 반환이 안되는 것도 아니고 어쩔 땐 되고, 어쩔 땐 안되는 문제점이 있었다. 사실상 안되는 때가 더 많았고, 내 스스로는 무엇이 문제인지 찾지도 못하던 상황이라 실장님께 우선 검사맡아야겠다는 생각에 될 때까지 돌리고 되자마자 검사 받았었다.^^; 실장님께서는 내가 프로그램이 정상적으로 작동될 때 검사맡았으니 정상인 줄 아시고 내 코드를 보셨다. 나는 그때 클라이언트들의 메시지를 받는 스레드를 하나 만들어서 돌렸었는데 (메인 스레드에서 서버와 연결한 후 반환받은 소켓을 이 스레드로 넘겨서 while(true)로 돌림) 이 스레드 안에서 소켓을 전달받지 못하는 것이 계속 로그에 찍혔었다. (로그에 찍히는거 아는데 못 고치고 있었음) 실장님은 내 코드를 보시고 이 스레드 필요없다고 하셨는데 아직 우기는 버릇을 못 고쳐서(..) 이 스레드는 이 필요에 의해 쓴거라고 주장하며, 사실 이 스레드 안에서 소켓을 전달 받았다 못 받았다 해서 프로그램도 됐다/안됐다 하는데 못 고치겠다고 말씀드렸다.

실장님께서 내 코드, 정확히 해당 스레드 부분의 문제점을 정확히 짚어주셨는데, 내가 스레드를 a-b 순으로 코드에서 작성해놓았다고 a-b 순으로 돌아가는 것이 아님을 알려주셨다. 해당 스레드 필요 없다고 하신 말씀에 따라 해당 스레드가 하는 동작을 서버와 연결하는 스레드(안드로이드 스튜디오에서는 꼭 이렇게 해야한다고 한다)안에서 소켓 연결 이후 바로 while(true) 그대로 진행하도록 했더니 스레드 사이의 실행순서로 발생했던 내 문제도 해결되면서, 스레드 수도 줄이고 결론적으로 해당 애플리케이션을 완성할 수 있었다.

단순 자바 파일을 액티비티 자바 파일..? 안드로이드로 옮기는 것, 안드로이드로 소켓 통신, 과제가 아니라 실제 어떤 기능을 위한 스레드 사용은 처음이라 헤맸던 것도 프로젝트 소요 시간에 한 몫했다.

이하 해당 프로그램 소스코드이다.

서버 코드는 인텔리제이, 이클립스, vs code 등 본인에게 편한 에디터에서 돌려놓고,

클라이언트 코드는 안드로이드 스튜디오로 돌리면 된다.

ip 주소는 cmd에 ipconfig 쳐서 본인의 ip 주소를 입력하면 된다.

자신의 이름을 입력 후 enter 하면, 입력한 이름으로 채팅이 시작되는 프로그램이다.

따라서, 클라이언트는 2개의 xml과 2개의 java 파일로 구성되었다.

서버를 먼저 돌려놓은 상태에서 클라이언트를 돌려야 작동한다.

서버

MyServer.java

import java.io.IOException;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.ArrayList;

public class MyServer {
    public static ArrayList<PrintWriter> m_OutputList;

    public static void main(String[] args){
        m_OutputList = new ArrayList<PrintWriter>();

        try{
            ServerSocket s_socket = new ServerSocket(8888);
            while(true){
                Socket c_socket = s_socket.accept();
                ClientManagerThread c_thread = new ClientManagerThread();
                c_thread.setSocket(c_socket);

                m_OutputList.add(new PrintWriter(c_socket.getOutputStream()));
                System.out.println(m_OutputList.size());
                c_thread.start();
            }
        }catch(IOException e){
            e.printStackTrace();
        }
    }
}

ClientManagerThread.java

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.Socket;

public class ClientManagerThread extends Thread{

    private Socket m_socket;
    private String m_ID;

    @Override
    public void run(){
        super.run();
        try{
            BufferedReader in = new BufferedReader(new InputStreamReader(m_socket.getInputStream()));
            String text;

            while(true){
                text = in.readLine();
                if(text!=null) {
                    for(int i=0;i<MyServer.m_OutputList.size();++i){
                        MyServer.m_OutputList.get(i).println(text);
                        MyServer.m_OutputList.get(i).flush();
                    }
                }
            }


        }catch(IOException e){
            e.printStackTrace();
        }
    }
    public void setSocket(Socket _socket){
        m_socket = _socket;
    }
}

클라이언트

activity_enter.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context=".EnterActivity">
    <EditText
        android:id="@+id/editText"

        android:layout_width="200dp"
        android:layout_height="wrap_content"
        android:hint="Enter your name"/>
    <Button
        android:id="@+id/enterButton"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="ENTER"/>
</LinearLayout>

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context=".MainActivity">

    <TextView
        android:id="@+id/textView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Hello World!"/>
     <EditText
       android:id="@+id/message"
       android:layout_width="match_parent"
       android:layout_height="wrap_content"
       android:text="Enter message"/>
    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:id="@+id/chatbutton"
        android:text="chat"/>

    <TextView
        android:id="@+id/chatView"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_marginTop="30dp"
        android:background="@color/colorAccent"/>
</LinearLayout>

EnterActivity.java

package com.example.mychat;

import androidx.appcompat.app.AppCompatActivity;

import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;

public class EnterActivity extends AppCompatActivity {
    EditText editText;
    Button enterButton;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_enter);
        enterButton = (Button)findViewById(R.id.enterButton);
        editText = (EditText)findViewById(R.id.editText);
        enterButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Intent intent = new Intent(getApplicationContext(),MainActivity.class);
                String username = editText.getText().toString();
                intent.putExtra("username",username);
                startActivity(intent);
            }
        });

    }
}

MainActivity.java

package com.example.mychat;

import androidx.appcompat.app.AppCompatActivity;

import android.content.Intent;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.DataInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.net.InetAddress;
import java.net.Socket;
import java.net.UnknownHostException;
import java.nio.Buffer;

public class MainActivity extends AppCompatActivity {
    private Handler mHandler;
    InetAddress serverAddr;
    Socket socket;
    PrintWriter sendWriter;
    private String ip = "your ip";
    private int port = 8888;

    TextView textView;
    String UserID;
    Button connectbutton;
    Button chatbutton;
    TextView chatView;
    EditText message;
    String sendmsg;
    String read;

    @Override
    protected void onStop() {
        super.onStop();
        try {
            sendWriter.close();
            socket.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mHandler = new Handler();
        textView = (TextView) findViewById(R.id.textView);
        chatView = (TextView) findViewById(R.id.chatView);
        message = (EditText) findViewById(R.id.message);
        Intent intent = getIntent();
        UserID = intent.getStringExtra("username");
        textView.setText(UserID);
        chatbutton = (Button) findViewById(R.id.chatbutton);

        new Thread() {
            public void run() {
                try {
                    InetAddress serverAddr = InetAddress.getByName(ip);
                    socket = new Socket(serverAddr, port);
                    sendWriter = new PrintWriter(socket.getOutputStream());
                    BufferedReader input = new BufferedReader(new InputStreamReader(socket.getInputStream()));
                    while(true){
                        read = input.readLine();

                        System.out.println("TTTTTTTT"+read);
                        if(read!=null){
                            mHandler.post(new msgUpdate(read));
                        }
                    }
                     } catch (IOException e) {
                    e.printStackTrace();
                } }}.start();

        chatbutton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                sendmsg = message.getText().toString();
                new Thread() {
                    @Override
                    public void run() {
                        super.run();
                        try {
                            sendWriter.println(UserID +">"+ sendmsg);
                            sendWriter.flush();
                            message.setText("");
                        } catch (Exception e) {
                            e.printStackTrace();
                        }
                    }
                }.start();
            }
        });
    }

        class msgUpdate implements Runnable{
            private String msg;
            public msgUpdate(String str) {this.msg=str;}

            @Override
            public void run() {
                chatView.setText(chatView.getText().toString()+msg+"\n");
            }
        }
}

결과화면

 

댓글