소켓 통신의 기본 개념과 구현 원리
Android 애플리케이션 개발에서 네트워크 통신은 핵심 요소로, 데이터 전송 및 수신을 담당하는 소켓 기술은 다양한 서비스에 필수적입니다. 이 문서에서는 소켓 통신의 기본 원리부터 실용적인 코드 예제까지 단계별로 설명하며, 안정적인 연결 구조를 설계하는 데 필요한 핵심 지식을 제공합니다.
1. TCP와 UDP 기반 통신의 선택 기준
Android 환경에서 소켓 프로그래밍은 주로 java.net.Socket과 java.net.ServerSocket 클래스를 활용하여 수행됩니다. 이 두 클래스는 모두 전송 계층에서 작동하며, 각각 클라이언트와 서버 역할을 수행합니다.
- TCP 기반 통신: 데이터 손실 없이 정확하게 전달되며, 연결 지향형으로 세션 관리가 가능합니다. 즉시 메시지 전달이 요구되는 채팅 앱, 파일 전송 등에 적합합니다.
- UDP 기반 통신: 속도 우선이며, 패킷 손실을 허용합니다. 실시간 영상 스트리밍, 게임 내 위치 동기화 등에서 효과적입니다.
2. 서버-클라이언트 아키텍처의 구현 예제
다음은 간단한 텍스트 기반 대화 시스템을 구현하기 위한 서버 및 클라이언트 코드 예시입니다.
서버 측 구현 (Java)
public class EchoServer {
private static final int PORT = 8080;
private ServerSocket serverSocket;
public void start() {
try {
serverSocket = new ServerSocket(PORT);
System.out.println("서버 시작: 포트 " + PORT + " 대기 중...");
while (true) {
Socket client = serverSocket.accept();
new ClientHandler(client).start();
}
} catch (IOException e) {
e.printStackTrace();
}
}
private static class ClientHandler extends Thread {
private final Socket clientSocket;
public ClientHandler(Socket socket) {
this.clientSocket = socket;
}
@Override
public void run() {
try (
BufferedReader in = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));
PrintWriter out = new PrintWriter(clientSocket.getOutputStream(), true)
) {
String message;
while ((message = in.readLine()) != null) {
System.out.println("수신: " + message);
out.println("Echo: " + message);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
clientSocket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
클라이언트 측 구현 (Android Activity 기반)
public class SocketClientActivity extends AppCompatActivity {
private EditText inputText;
private TextView outputText;
private Button sendButton;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
inputText = findViewById(R.id.input_text);
outputText = findViewById(R.id.output_text);
sendButton = findViewById(R.id.send_button);
sendButton.setOnClickListener(v -> {
new Thread(() -> {
try (Socket socket = new Socket("192.168.1.100", 8080);
PrintWriter out = new PrintWriter(socket.getOutputStream(), true);
BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()))) {
out.println(inputText.getText().toString());
String response = in.readLine();
runOnUiThread(() -> outputText.setText(response));
} catch (IOException e) {
runOnUiThread(() -> outputText.setText("연결 실패: " + e.getMessage()));
}
}).start();
});
}
}
3. Android 권한 및 보안 설정
네트워크 통신을 위해 반드시 AndroidManifest.xml에 다음 권한을 추가해야 합니다:
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
또한, API 23 이상에서는 실행 시 동적으로 권한 요청이 필요하므로, ActivityCompat.requestPermissions()를 사용해 처리해야 합니다.
4. 성능 최적화 및 오류 대응 전략
- 스레드 풀 사용: 동시에 많은 클라이언트 연결을 처리할 경우,
Executors.newFixedThreadPool(5)등을 통해 스레드 리소스를 효율적으로 관리하세요. - 자동 재연결 로직: 네트워크 불안정 시 자동 재접속을 위한 백오프 전략(예: 1초 후 재시도, 최대 3회)을 구현하세요.
- 자원 해제 보장:
try-with-resources문법을 사용해InputStream,OutputStream,Socket등의 자원을 자동으로 닫으세요.
5. 디버깅 및 테스트 방법
실제 장치 또는 에뮬레이터에서 테스트할 때는 다음과 같은 접근이 유용합니다:
- Wireshark나 Charles Proxy를 이용해 실제 네트워크 패킷을 분석합니다.
- Logcat을 통해
Log.d("Socket", "메시지 전송 완료")와 같은 디버그 메시지를 출력합니다. - 테스트용 서버로는 로컬 머신에서 실행하는
nc -l 8080명령어도 활용 가능합니다.