TCP/IP 수준의 통신 방식을 제공하는 소켓 통신 방식은 IP 주소로 목적지 호스트를 찾아내고 포트로 통신 접속점을 찾아낸다.
안드로이드는 표준 자바에서 사용하던 java.net 패키지의 클래스들을 그대로 새용할 수 있어 소켓 연결을 쉽게 구현할 수 있다.
안드로이드는 소켓 연결 등을 시도하거나 응답을 받아 처리할 때 스레드를 사용해야 한다.
activity_main.xml 레이아웃을 만들었다.
두 개의 레이아웃으로 나눈 후
위쪽에는 서버로 데이터를 전송하고 받는 Client 영역으로
아래쪽에는 클라이언트로부터 데이터를 받고 처리 후 다시 클라이언트로 전송하는 Server 영역으로
만들었다.
MainActivity.java 에 코드를 작성하였다
public class MainActivity extends AppCompatActivity {
EditText editText;
TextView 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.textView);
textView2 = findViewById(R.id.textView2);
Button button = findViewById(R.id.button);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
final String data = editText.getText().toString();
new Thread(new Runnable() {
@Override
public void run() {
send(data);
}
}).start();
}
});
Button button2 = findViewById(R.id.button2);
button2.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
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");
}
});
}
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();
}
}
}
Client 영역과 Server 영역의 텍스트뷰에 글자를 출력할 수 있도록 하는 메소드 printClientLog와 printServerLog를 정의하였다.
스레드에서 이 메소드들을 호출할 것이므로 핸들러 객체를 이용하였다.
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 메소드를 정의하였다.
여기서는 서버와 클라이언트가 5001번 포트를 사용하도록 하였다.
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();
}
}
접속할 IP 주소는 "localhost"로 포트는 5001번을 사용해 socket 객체를 만들었다.
int portNumber = 5001;
Socket socket = new Socket("localhost", portNumber);
printClientLog("소켓 연결함");
새로 만든 소켓을 통해 데이터를 보내거나 받을 때는 getOutputStream과 getInputStream 메소드로 입출력 스트림 객체를 참조하였다.
ObjectOutputStream outputStream = new ObjectOutputStream(socket.getOutputStream());
outputStream.writeObject(data);
outputStream.flush();
printClientLog("데이터 전송함");
ObjectInputStream inputStream = new ObjectInputStream(socket.getInputStream());
printClientLog("서버로부터 받음 : " + inputStream.readObject());
마지막으로 통신을 끝내기 위해 소켓을 close 한다.
socket.close();
클라이언트가 접속할 서버를 만들 startSerer 메소드를 정의하였다.
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();
}
}
먼저 ServerSocket 클래스로 소켓 서버 객체를 만들었다.
int portNumber = 5001;
ServerSocket server = new ServerSocket(portNumber);
printServerLog("서버 시작함" + portNumber);
while 구문을 이용해 클라이언트의 접속을 기다리다가 클라이언트의 접속 요청이 왔을 때 accept 메소드로 소켓 객체를 반환받아 클라이언트 소켓의 연결 정보와 데이터를 확인한다.
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);
클라이언트에서 보내온 문자열에 " from Server"를 붙여 클라이언트로 다시 보낸다.
ObjectOutputStream outputStream = new ObjectOutputStream(socket.getOutputStream());
outputStream.writeObject(obj + " from Server");
outputStream.flush();
printServerLog("데이터 보냄");
마지막으로 통신을 끝내기 위해 소켓을 close 한다.
socket.close();
버튼을 누르면 위에서 정의한 메소드들을 사용해서 통신을 하도록 하였다.
네트워킹 기능을 사용하므로 스레드를 사용해 동작하도록 하였다.
Button button = findViewById(R.id.button);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
final String data = editText.getText().toString();
new Thread(new Runnable() {
@Override
public void run() {
send(data);
}
}).start();
}
});
Button button2 = findViewById(R.id.button2);
button2.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
new Thread(new Runnable() {
@Override
public void run() {
startServer();
}
}).start();
}
});
AndroidManifest.xml 파일에 INTERNET 권한을 추가한다.
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="org.techtown.socket">
<uses-permission android:name="android.permission.INTERNET" />