기록장
[안드로이드] 네트워킹, Socket (소켓) 본문
네트워킹
인터넷에 연결되어 있는 원격지의 서버 또는 원격지의 단말과 통신해서 데이터를 주고받는 동작들을 포함한다.
원격지의 서버를 연결하는 가장 단순한 방식은 Client와 Server의 1:1로 연결하는 2-tier-C/S 방식이다.
가장 많이 사용하는 네트워킹 방식이며 대부분 Client가 Server에 연결되어 데이터를 요청하고 응답받는 단순한 개념으로 이해할 수 있다.
그 다음 서버를 좀 더 유연하게 구성할 수 있는 3-tier 연결 방식이 있다.
응용 서버와 데이터 서버로 서버를 구성하면 DB를 분리할 수 있어 중간에 비지니스 로직을 처리하는 응용 서버가 좀 더 다양한 역할을 할 수 있다.
Socket 연결은 데이터를 주고 받을 때 Socket에 데이터를 담아 보내는 방식으로 IP주소로 목적지 호스트를 찾아내고 포트로 통신 접속점을 찾아낸다. 통신 방식에는 TCP와 UDP 방식이 있다. 보통 TCP를 사용한다.
* 포트는 통신을 위한 통로 개념으로 공유가 안되고 한정되어 있기 때문에 사용하지 않을 때 쓰지 않을 때는 중단시켜야 다른 프로그램에서 사용 가능
* 안드로이드에서 통신을 하기 위해선 무조건 스레드를 사용해야 한다.
* 그러므로 핸들러 개념도 알고 있어야 한다.
Thread, Handler 개념을 잘 모르겠다면 참고
[안드로이드] Thread, Handler 이해 및 간단한 예제
들어가기 전에.. 내 생각 처음 접하시는 분들은 이걸 왜 써야하는지 잘 모르고 접근하면 이해가 더 안될 것이라 생각되어 사용 목적에 대해 간략이 적어보자면,, (나도 배우는 입장이라 잘 모른
edit0.tistory.com
코드
코드 실행 과정은 client에서 데이터를 보내고 server에서 받아서 다시 돌려주면 client에서 받는 형식으로 진행
(주석 잘 보길 권장함)
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"
tools:context=".MainActivity"
android:orientation="vertical">
<EditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/edittext1"/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="30dp"
android:text="클라이언트"/>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="서버로 전송"
android:id="@+id/button1"/>
<ScrollView
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/textview1"
android:textSize="20dp"/>
</LinearLayout>
</ScrollView>
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="30dp"
android:text="서버"/>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="서버 시작"
android:id="@+id/button2"/>
<ScrollView
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/textview2"
android:textSize="20dp"/>
</LinearLayout>
</ScrollView>
</LinearLayout>
</LinearLayout>
MainActivity.java
public class MainActivity extends AppCompatActivity {
EditText et1;
Button b1, b2;
TextView tv1, tv2;
Handler handler = new Handler();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
et1 = findViewById(R.id.edittext1);
b1 = findViewById(R.id.button1);
b2 = findViewById(R.id.button2);
tv1 = findViewById(R.id.textview1);
tv2 = findViewById(R.id.textview2);
b1.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
final String data = et1.getText().toString();
new Thread(new Runnable() { //네트워킹 기능을 사용하므로 thread를 사용해야함
@Override
public void run() {
send(data);
}
}).start();
}
});
b2.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
new Thread(new Runnable() { //네트워킹 기능을 사용하므로 thread를 사용해야함
@Override
public void run() {
startServer();
}
}).start();
}
});
}
public void send(String data){
try{
int portNumber = 1223; //포트 번호 설정 (통신을 위한 통로? Path라 보면 된다)
Socket socket = new Socket("localhost", portNumber); //소켓 생성 / ip주소는 로컬 호스트, 포트 1223
printClientLog("소켓 연결함");
//소켓에 담아 서버에 데이터를 쓰기 위한 객체 생성
ObjectOutputStream outputStream = new ObjectOutputStream(socket.getOutputStream());
outputStream.writeObject(data); //데이터 쓰기
outputStream.flush(); //스트림에 남아 있는 데이터를 비우는 역할
printClientLog("데이터 전송함");
//응답(데이터)받을 스트림 객체 생성
ObjectInputStream inputStream = new ObjectInputStream(socket.getInputStream());
printClientLog("서버로부터 받음: "+ inputStream.readObject()); //데이터 읽어서 보여주기
socket.close(); //연결 끊기
} catch (Exception e){
e.printStackTrace();
}
}
public void startServer(){
try {
int portNumber = 1223; //포트 넘버는 client와 맞춰준다
ServerSocket server = new ServerSocket(portNumber); //서버 소켓 객체 생성
printServerLog("서버 시작함: "+ portNumber);
//client의 접속 요청이 오면 실행된다.
while(true){
Socket socket = server.accept(); //accept() 메소드를 통해 소켓 객체가 반환
//이 과정으로 client의 소켓 연결 정보를 알 수 있다.
InetAddress clientHost = socket.getLocalAddress(); //client ip정보
int clientPort = socket.getPort(); //client 포트 정보
printServerLog("클라이언트 연결됨: "+ clientHost + " : " + clientPort);
//소켓에 있는 데이터 읽어오기 위한 스트림 객체 생성
ObjectInputStream inputStream = new ObjectInputStream(socket.getInputStream());
Object obj = inputStream.readObject(); //데이터 읽기
printServerLog("데이터 받음: " + obj); //받은 데이터 보여주기
//받은 데이터 다시 client에게 돌려주기 위한 스트림 객체 생성
ObjectOutputStream outputStream = new ObjectOutputStream(socket.getOutputStream());
outputStream.writeObject(obj + " from Server(돌려주기)"); //데이터 쓰기
outputStream.flush();
printServerLog("데이터 보냄");
socket.close(); //연결 끊기
}
}catch (Exception e){
e.printStackTrace();
}
}
public void printClientLog(final String data){
android.util.Log.i("태그", data);
handler.post(new Runnable() {
@Override
public void run() {
tv1.append(data + "\n");
}
});
}
public void printServerLog(final String data){
android.util.Log.i("태그", data);
handler.post(new Runnable() {
@Override
public void run() {
tv2.append(data + "\n");
}
});
}
}
결과
실제 앱에서는 ObjectOutputStream과 ObjectInputStream은 잘 사용되지 않는다고 한다. 이유는 자바가 아닌 다른 언어로 만들어진 서버와 통신할 경우에는 데이터 송수신이 정상적으로 이루어지지 않을 수 있기 때문이다. 일반적으로는 DataOutputStream과 DataInputStream을 많이 사용한다고 한다.
그리고 보통 서버를 만들 때는 백그라운드에서 실행되도록 Service를 만들어 구현하는 것이 좋다. (액티비티에서 만들면 자원이 부족해지면 강제 종료가 될 수 있기 때문)
+
서버가 Service를 통해 실행되도록 구현해보도록 하겠다.
그리고 client와 server도 새로 프로젝트를 만들고 따로따로 분리해서 진행하도록 하겠다.
각 프로젝트 AndroidManifest.xml에 권한 추가
<uses-permission android:name="android.permission.INTERNET"/>
Service는 기본 개념으로 onCreate(), onStartCommand(), onDestroy()가 있다. 보통 onStartCommand()에서 메인 구현을 진행한다. Intent 객체를 통해 Service 호출 가능하다.
Server 프로젝트에서는 Service를 사용하기 전 AndroidManifest.xml의 application 내에 아래 코드를 사용해 꼭 등록해야한다.
<service android:name="service"> //각 Service를 구현한 클래스명
<intent-filter>
<action android:name="패키지명" />
</intent-filter>
</service>
코드
server 프로젝트
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"
tools:context=".MainActivity">
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="서버 시작"
android:id="@+id/button1"/>
</LinearLayout>
MainActivity.java
public class MainActivity extends AppCompatActivity {
Button b1;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
b1 = findViewById(R.id.button1);
b1.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Toast.makeText(getApplicationContext(), "서버 start", Toast.LENGTH_SHORT).show();
Intent intent = new Intent(MainActivity.this, service.class);
startService(intent);
}
});
}
}
service.java
public class service extends Service {
@Nullable
@Override
public IBinder onBind(Intent intent) {
return null;
}
@Override
public void onCreate() {
super.onCreate();
server_class serverClass = new server_class();
serverClass.start();
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
return super.onStartCommand(intent, flags, startId);
}
@Override
public void onDestroy() {
super.onDestroy();
}
class server_class extends Thread{
@Override
public void run() {
super.run();
try {
int portNumber = 1223; //포트 넘버는 client와 맞춰준다
ServerSocket server = new ServerSocket(portNumber); //서버 소켓 객체 생성
//client의 접속 요청이 오면 실행된다.
while(true){
Socket socket = server.accept(); //accept() 메소드를 통해 소켓 객체가 반환
//이 과정으로 client의 소켓 연결 정보를 알 수 있다.
InetAddress clientHost = socket.getLocalAddress(); //client ip정보
int clientPort = socket.getPort(); //client 포트 정보
//소켓에 있는 데이터 읽어오기 위한 스트림 객체 생성
ObjectInputStream inputStream = new ObjectInputStream(socket.getInputStream());
Object obj = inputStream.readObject(); //데이터 읽기
//받은 데이터 다시 client에게 돌려주기 위한 스트림 객체 생성
ObjectOutputStream outputStream = new ObjectOutputStream(socket.getOutputStream());
outputStream.writeObject(obj + " from Server"); //데이터 쓰기
outputStream.flush();
socket.close();
}
}catch (Exception e){
e.printStackTrace();
}
}
}
}
client 프로젝트
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"
tools:context=".MainActivity"
android:orientation="vertical">
<EditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/edittext1"/>
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/button1"
android:text="Client 시작"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/textview1"
android:textSize="30dp"/>
</LinearLayout>
MainActivity.java
public class MainActivity extends AppCompatActivity {
EditText et1;
Button b1;
TextView tv1;
Handler handler = new Handler();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
et1 = findViewById(R.id.edittext1);
b1 = findViewById(R.id.button1);
tv1 = findViewById(R.id.textview1);
b1.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
client_class clientClass = new client_class(et1.getText().toString());
clientClass.start();
}
});
}
class client_class extends Thread{
String string = "";
client_class(String str){
this.string = str;
}
@Override
public void run() {
super.run();
try{
int portNumber = 1223; //포트 번호 설정 (통신을 위한 통로? Path라 보면 된다)
Socket socket = new Socket("localhost", portNumber); //소켓 생성 / ip주소는 로컬 호스트, 포트 1223
//소켓에 담아 서버에 데이터를 쓰기 위한 객체 생성
ObjectOutputStream outputStream = new ObjectOutputStream(socket.getOutputStream());
outputStream.writeObject(string); //데이터 쓰기
outputStream.flush(); //스트림에 남아 있는 데이터를 비우는 역할
//응답(데이터)받을 스트림 객체 생성
final ObjectInputStream inputStream = new ObjectInputStream(socket.getInputStream());
final Object temp = inputStream.readObject();
//핸들러 사용하여 UI 객체에 접근
handler.post(new Runnable() {
@Override
public void run() {
try {
tv1.setText("" + temp);
} catch (Exception e){
e.printStackTrace();
}
}
});
socket.close();
} catch (Exception e){
e.printStackTrace();
}
}
}
}
결과
부족한 점, 피드백 환영합니다.
감사합니다.
참고
정재곤, 『Do it! 안드로이드 앱 프로그래밍 - 개정 6판』, 이지스퍼블리싱(주)
'안드로이드' 카테고리의 다른 글
[안드로이드] Volley(볼리) 통신 / RequestQueue (0) | 2021.01.09 |
---|---|
[안드로이드] 웹, HTTP 통신, 공공데이터 API, JSON 파일 데이터 가져오기 / HttpURLConnection, JSON Parsing(제이슨 파싱), 공공데이터포털 (0) | 2021.01.08 |
[안드로이드] AsyncTask 이해 및 간단한 예제 (0) | 2021.01.07 |
[안드로이드] Thread, Handler 이해 및 간단한 예제 (0) | 2021.01.07 |
[안드로이드] Drawable 드로어블 (0) | 2021.01.05 |