티스토리 뷰
/ 기록용이라 사담이 많을 수 있습니다. 해당 애플리케이션을 개발하면서 겪은 일과 느낀점, 문제점을 적었습니다. /
실장님께서 첫 번째 과제로 소켓 통신을 이용한 안드로이드 채팅 애플리케이션을 만들어보라고 하셨다. 첫날은 소켓 없이 firebase라는 걸로 채팅을 만들려고 했었는데, 다음 날 소켓 통신으로 구현해야 한다고 말씀해 주셔서 첫날은 사실상 "firebase에 대해서 알게 된 걸로" 치고 넘어갔다. 둘째 날은 그래서 구글에 '소켓', '소켓 통신 서버' 별의별 키워드를 다 쳐보면서 구글에 올라온 여러 코드들을 우선 쳐보고 돌렸다.
간만에 내가 예전에 네이버 블로그에 올렸던 JAVA 카테고리의 'TCP/IP 예제'로 올린 소켓 채팅 프로그램
https://varyeun.blog.me/221723947400
을 돌려보며 잊고 있었던 소켓 통신에 대한 기억도 되살렸다.. 이번에 돌려보니 저때 내가 예제를 올렸던 이유는 그냥 내 지식이 부족했던 것임을^^.. 상기할 수 있었고, 그래서 구글에 나온 웬만한 모든 소켓 채팅 코드를 돌려보았는데 (사실 구글링으로 과제를 해결할 수 있을까 하는 바람에....) 전부 우리가 목표로 하는 프로그램은 아니었다.
우리가 목적으로 했던 프로그램은
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");
}
}
}
결과화면
'Android' 카테고리의 다른 글
뷰페이저, 페이저타이틀스트립 (PagerTitleStrip) 예시 (0) | 2020.04.26 |
---|---|
액션바, 탭 만들기 예시 (0) | 2020.04.22 |
액션바 예시 (0) | 2020.04.22 |
한 레이아웃에 프래그먼트 여러개 예시 (0) | 2020.04.22 |
프래그먼트 예시 (0) | 2020.04.22 |
- Total
- Today
- Yesterday
- ios
- allcases
- 액션바
- 프래그먼트매니저
- 페이저타이틀스트립
- 스낵바설정
- Objective-C
- objc
- 전화연결하기
- 전화걸기연결
- 다이얼연결
- prepareforreuse
- 비트맵버튼
- subscript
- 안드로이드
- 상태드로어블
- 인플레이터
- 터치리스너
- 제스처디텍터
- CaseIterable
- 뷰페이저
- 어댑터
- 표현패턴
- 프래그먼트
- swift
- 쉐이프드로어블
- 알림대화상자
- 데이터
- 부가데이터
- 카카오톡열기
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | |||||
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |