[Android] 소켓(Socket) 사용하기
모든 내용은 Do it! 안드로이드 앱 프로그래밍을 바탕으로 정리한 것입니다.
소켓(Socket)
IP 주소로 목적지 호스트를 찾아내고 포트로 통신 접속점을 찾아내는 소켓 연결
TCP와 UDP 방식으로 나눌 수 있는데, 일반적인 프로그래밍에서는 대부분 TCP 연결을 사용함
HTTP 프로토콜과 소켓
HTTP 프로토콜은 소켓으로 웹서버에 연결한 후에 요청을 전송하고 응답을 받은 다음 연결을 끊음 → 비연결성(stateless)
실시간으로 데이터를 처리하는 앱은 응답 속도를 높이기 위해 연결성이 있는 소켓 연결을 선호했음
BUT 지금은?
- 인터넷의 속도가 빨라져 HTTP 프로토콜을 사용하는 웹이 일반적
- 속도가 그렇게 느리지 않으면서도 국제 표준을 따를 수 있다는 장점을 가진 웹서버로 많은 서버가 만들어지게 됨
💡 안드로이드는 소켓 연결 등을 시도하거나 응답을 받아 처리할 때 스레드를 사용해야 함!
이전에는 권장사항이었지만 현재 플랫폼 버전에서는 강제사항이 되었음. 따라서 스레드를 사용하지 않으면 네트워킹 기능 자체가 동작하지 않음!
스레드를 사용하면서 UI를 업데이트 하려면 핸들러를 사용해야 함
[예제]
AndroidManifest.xml
<manifest ...>
<uses-permission android:name="android.permission.INTERNET"/>
MainActivity.java
public class MainActivity extends AppCompatActivity {
EditText editText;
TextView textView, textView2;
Handler handler = new Handler();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
editText = findViewById(R.id.edittext);
textView = findViewById(R.id.send_text);
textView2 = findViewById(R.id.server_text);
Button sendButton = findViewById(R.id.send_btn);
sendButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
final String data = editText.getText().toString();
new Thread(new Runnable() {
@Override
public void run() {
send(data);
}
}).start();
}
});
Button serverButton = findViewById(R.id.server_btn);
serverButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
new Thread(new Runnable() {
@Override
public void run() {
startServer();
}
}).start();
}
});
}
public void printClientLog(final String data) {
Log.d("MainActivity", data);
handler.post(new Runnable() {
@Override
public void run() {
textView.append(data+"\n");
}
});
}
public void printServerLog(final String data) {
Log.d("MainActivity", data);
handler.post(new Runnable() {
@Override
public void run() {
textView2.append(data+"\n");
}
});
}
- 첫번째 버튼을 눌렀을 때는 send() 메서드, 두번째 버튼을 눌렀을 때는 startServer() 메서드를 호출하도록 함
- 이 2개의 메서드는 모두 네트워킹 기능을 사용할 것이므로 스레드로 만들어야 함
- 결과를 출력하기 위한 textview, textview2
- printClientLog()는 화면 상단의 클라이언트 레이아웃에 결과를 출력
- printServerLog()는 화면 하단의 서버 레이아웃에 결과를 출력
- 새로 만들어진 스레드에서 이 메서드들을 호출하여 UI를 업데이트 할 것이므로 핸들러 객체를 이용함
- Runnable 객체의 run() 메서드 안에서 TextView를 접근하고 있음
- TextView의 append() 메서드로 전달될 파라미터는 그대로 전달되어야 하므로 final로 정의함
...
public void send(String data) {
try {
int portNumber = 5001;
Socket socket = new Socket("localhost", portNumber);
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 ex) {
ex.printStackTrace();
}
}
public void startServer() {
try {
int portNumber = 5001;
ServerSocket server = new ServerSocket(portNumber);
printServerLog("서버 시작함: "+portNumber);
while (true) {
Socket socket = server.accept();
InetAddress clientHost = socket.getLocalAddress();
int clientPort = socket.getPort();
printServerLog("클라이언트 연결됨: "+clientHost+" : "+clientPort);
ObjectInputStream inputStream = new ObjectInputStream(socket.getInputStream());
Object obj = inputStream.readObject();
printServerLog("데이터 받음: "+obj);
ObjectOutputStream outputStream = new ObjectOutputStream(socket.getOutputStream());
outputStream.writeObject(obj+" from Server.");
outputStream.flush();
printServerLog("데이터 보냄");
socket.close();
}
} catch (Exception ex) {
ex.printStackTrace();
}
}
}
- 클라이언트에서 데이터를 전송하는 send() 메서드
- 예제에서는 서버와 클라이언트가 5001번 포트를 사용하도록 함
- 접속할 IP 주소는 "localhost", 포트는 5001번을 사용하고 있음
- new 연산자로 만든 소켓은 이 IP 주소와 포트 번호를 파라미터로 전달받으며, 새로 만들어진 소켓을 통해 데이터를 보내거나 받고 싶을 때는 getOutputStream()과 getInputStream() 메서드로 입출력 스트림 객체를 참조함
- 예제에서는 문자열 객체를 그대로 보내기 위해 ObjectOutputStream과 ObjectInputStream 클래스를 사용함
- 클라이언트가 접속할 서버는 startServer() 메서드로 구현
- 소켓 서버는 ServerSocket 클래스로 만든 후, 클라이언트로부터 요청을 처리할 수 있는데 포트 번호는 클라이언트에서 접속할 5001번을 그대로 사용함
- while 구문을 사용해서 클라이언트의 접속을 기다리다가 클라이언트의 접속 요청이 왔을 때 accept() 메서드를 통해 소켓 객체가 반환되므로 클라이언트 소켓의 연결 정보를 확인할 수 있음
*실제로 앱을 만들 때는 ObjectInputStream과 ObjectOutputStream을 잘 사용하지 않음!
이 두 클래스는 자바의 객체(Object) 정보를 편리하게 주고 받을 수 있도록 만들어진 것이므로 자바가 아닌 다른 언어로 만들어진 서버와 통신할 경우에는 데이터 송수신이 정상적으로 작동하지 않을 수 있음. 따라서 일반적으로는 DataInputStream과 DataOutputStream을 주로 사용함!
*TCP와 UDP, 스레드와 핸들러에 대한 자세한 내용은 아래 포스팅 참고!