안드로이드 파일 공유 앱 개발: 자바 소켓 통신
2020. 6. 14. 00:24
소켓 통신
네트워크 소켓(Network Socket)은 컴퓨터 네트워크를 경유하는 프로세스 간 통신의 종착점이다. 바꿔 말하자면 특정 포트를 통해 들어오고 나가는 연결을 제어하는 객체를 의미한다. 1
자바에서 소켓 통신은 Socket 클래스와 입출력 스트림을 이용한다.
서버 측 코드
서버는 아래 단 한 줄로 열린다.
ServerSocket soc = new ServerSocket(port);
port
에는 1~65535 사이의 정수가 들어가며 다른 프로그램에서 해당 포트를 사용 중이면 BindException
을 뿜으며 서버가 종료된다.
서버가 열렸으면 다음 코드를 통해 클라이언트가 접속할 때까지 대기한다.
Socket socket = soc.accept();
클라이언트와 연결되면 스트림을 통해 데이터를 주고받을 수 있다. 아래 코드를 통해 데이터 스트림을 연다.
InputStream in = socket.getInputStream();
OutputStream out = socket.getOutputStream();
DataInputStream din = new DataInputStream(in);
DataOutputStream dout = new DataOutputStream(out);
InputStream
/OutputStream
만 있어도 되지만 DataStream
을 쓰면 요청을 Data 단위로 알아서 끊어주기 때문에 사용했다.
스트림이 열리면 아래 메소드를 이용해 데이터를 주고 받는다.
String response = din.readUTF(); // data가 들어올 때까지 대기
dout.writeUTF(str); // data 전송
readInt
같이 자료형마다 주고 받는 메소드가 있는데 내가 사용하는 프로토콜은 자료형을 전부 String
으로 통일했기 때문에 저 두 가지만 사용한다.
위 내용을 정리한 서버 코드는 다음과 같다.
class Server {
ServerSocket soc;
Socket socket;
InputStream in;
OutputStream out;
DataInputStream din;
DataOutputStream dout;
public Server(int port) {
try {
soc = new ServerSocket(port);
Log.i("Server", port + "번 포트로 서버 열림");
int status;
if ((status = readyForClient()) == 0) {
standBy();
} else if (status == -2) { // TimeoutException
Log.w("Server", "클라이언트 연결 시간 초과");
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (soc != null) {
try {
soc.close(); // 서버 종료
} catch (IOException ex) {
}
}
}
Log.i("Server", "서버 종료");
}
/**
* 클라이언트의 연결을 대기합니다.
*/
public int readyForClient() {
try {
soc.setSoTimeout(7200000); // 타임아웃 설정
socket = soc.accept(); // 클라이언트의 접속을 받습니다.
Log.i("Server", "클라이언트 연결 수락");
// 데이터 스트림 개통
in = null;
out = null;
in = socket.getInputStream();
out = socket.getOutputStream();
din = new DataInputStream(in);
dout = new DataOutputStream(out);
} catch (IOException e) {
if (e instanceof SocketTimeoutException) {
Log.w("Server", "오랫동안 기기 연결이 감지되지 않아 서버를 종료합니다.");
return -2;
} else {
Log.e("Server", "클라이언트와 연결하는 중 에러가 발생하였습니다!");
e.printStackTrace();
return -1;
}
}
return 0;
}
public void standBy() {
try {
while(true) {
String response = din.readUTF();
Log.v("Server", "Response: " + response);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
Log
는 안드로이드 호환성을 위해 임의로 만든 클래스이다. 소스는 여기서 확인할 수 있다.
클라이언트 측 코드
클라이언트에서 서버에 접속하는 코드는 다음과 같다.
Socket socket = new Socket(address, port);
address
는 서버 주소, port
는 접속할 포트이다.
서버와 연결되면 마찬가지로 스트림을 열어 데이터를 주고받을 수 있도록 한다. 코드는 다음과 같다.
InputStream in = socket.getInputStream();
OutputStream out = socket.getOutputStream();
DataInputStream din = new DataInputStream(in);
DataOutputStream dout = new DataOutputStream(out);
스트림이 열리면 아래와 같은 메소드를 이용해 데이터를 주고 받는다.
String response = din.readUTF(); // data가 들어올 때까지 대기
dout.writeUTF(str); // data 전송
위 내용을 정리한 클라이언트 코드는 다음과 같다.
class Client {
Socket socket;
InputStream in;
OutputStream out;
DataInputStream din;
DataOutputStream dout;
boolean connected = false;
public Client(String address, int port) {
try {
socket = new Socket(address, port);
Log.i("Client", port + "번 포트로 클라이언트 시작됨");
connected = readyForHost();
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 서버에 연결합니다.
*
* @return
*/
public boolean readyForHost() {
try {
socket.setSoTimeout(5000); // 타임아웃 설정
// 스트림 개통
in = socket.getInputStream();
out = socket.getOutputStream();
din = new DataInputStream(in);
dout = new DataOutputStream(out);
Log.i("Client", "서버 연결됨!");
} catch (IOException e) {
Log.e("Client", "서버에 연결하는 중 에러가 발생하였습니다!");
e.printStackTrace();
return false;
}
return true;
}
/*
* 서버로 데이터를 전송합니다.
*
* @param str 전송할 데이터
*/
public void send(String str) {
try {
if (connected)
dout.writeUTF(str);
} catch (IOException e) {
e.printStackTrace();
}
}
}
테스트
위에서 작성한 코드를 테스트하기 위한 메인 클래스 소스이다.
class Main {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
String cmd = sc.nextLine();
// 서버/클라이언트 선택
if("server".equals(cmd)) { // 서버
Server server = new Server(12345); // 1~65535 사이 숫자 아무거나
} else if("client".equals(cmd)) { // 클라이언트
Client client = new Client("127.0.0.1", 12345);
while(true) {
cmd = sc.nextLine();
client.send(cmd);
}
}
}
}
풀 소스는 Github 저장소에 올려놓았다. 깃허브에 올린 소스는 완성본으로 위에 적어놓은 코드와는 다른 부분이 많을 것이다. 그거에 대해서는 다음 번에 설명할 예정이다.
다음 글에서 계속