All About Sockets 2
Reading from and Writing to a Socket
import java.net.*;
public class EchoClient {
public static void main(String[] args) throws IOException {
Socket echoSocket = null;
PrintWriter out = null;
BufferedReader in = null;
try {
echoSocket = new Socket("taranis", 7);
out = new PrintWriter(echoSocket.getOutputStream(), true);
in = new BufferedReader(new InputStreamReader(
echoSocket.getInputStream()));
} catch (UnknownHostException e) {
System.err.println("Don't know about host: taranis.");
System.exit(1);
} catch (IOException e) {
System.err.println("Couldn't get I/O for "
+ "the connection to: taranis.");
System.exit(1);
}
BufferedReader stdIn = new BufferedReader(
new InputStreamReader(System.in));
String userInput;
while ((userInput = stdIn.readLine()) != null) {
out.println(userInput);
System.out.println("echo: " + in.readLine());
}
out.close();
in.close();
stdIn.close();
echoSocket.close();
}
}
클라이언트 쪽에서 Socket을 사용하는 방법은 매우 간단합니다.
먼저 Socket 객체를 만들기 위해서 생성자에 서버의 IP 와 포트넘버를 생성자에 넘겨줍니다.
그리고 서버에 어떤 메시지를 보내기 위해서는 PrintWriter를 사용하고, 서버로 부터 데이터를 받아 오기 위해서는 BufferedReader를 사용하면 됩니다. 물론 두 객체는 모두 Socket을 사용하여 생성합니다.
마지막으로 열려있는 자원들을 close 해줌으로써 메모리 누수를 방지합니다.
클라이언트 프로그램 구현 절차는 다음과 같습니다.
1. Open a socket.
2. Open an input stream and output stream to the socket.
3. Read from and write to the stream according to the server's protocol.
4. Close the streams.
5. Close the socket.
Writing the Server Side of a Socket
import java.net.*;
import java.io.*;
public class KnockKnockServer {
public static void main(String[] args) throws IOException {
ServerSocket serverSocket = null;
try {
serverSocket = new ServerSocket(4444);
} catch (IOException e) {
System.err.println("Could not listen on port: 4444.");
System.exit(1);
}
Socket clientSocket = null;
try {
clientSocket = serverSocket.accept();
} catch (IOException e) {
System.err.println("Accept failed.");
System.exit(1);
}
PrintWriter out = new PrintWriter(clientSocket.getOutputStream(), true);
BufferedReader in = new BufferedReader(
new InputStreamReader(
clientSocket.getInputStream()));
String inputLine, outputLine;
KnockKnockProtocol kkp = new KnockKnockProtocol();
outputLine = kkp.processInput(null);
out.println(outputLine);
while ((inputLine = in.readLine()) != null) {
outputLine = kkp.processInput(inputLine);
out.println(outputLine);
if (outputLine.equals("Bye."))
break;
}
out.close();
in.close();
clientSocket.close();
serverSocket.close();
}
}
위의 코드에서 주목해야 할 부분에 빨간색으로 표시해 두었습니다.
ServerSocket를 생성하여 클라이언트의 접속을 받아들일 준비를 합니다. 이 때 클라이언트의 요청을 받을 포트 번호를 생성자에 넘겨줍니다.
그런 다음 클라이언트에서 요청이 오면(serverSocket.accept()) 새로운 Socket 객체를 만들고, 그 Socket과 클라이언트 간에 의사소통을 위한 PrintWriter와 BufferedReader 객체를 생성합니다.
그리고 나서 서버에서는 클라이언트로 부터 BufferedReader 객체를 사용하여 데이터를 읽어오고, 읽어온 데이터를 처리하고, 마지마긍로 PrintWriter를 사용하여 다시 클라이언트에게 결과를 보내줍니다.
여기까지는 매우 간단합니다.
만약에 위와 같은 상황에서 클라이언트 100가 하나의 서버에 접속하게 되면 어떻게 될까요? 100번째 클라이언트는 앞에 있는 99개의 클라이언트와 서버의 접속이 끝날 때까지 기다려야 합니다. 이럴 때 쯤 등장하는 녀석이 바로 멀티쓰레드.
위의 서버 클래스는 클라이언트의 접속을 받은 다음 새로운 소켓을 만들어서 Thread로 넘겨주는 일만 하고, 실제 클라이언트와 열심히 읽고 쓰는 일은 Thread가 알아서 하도록 하면 100번째 클라이언트가 기다리는 시간은 오직 자신을 위해 일해 줄 서버의 소켓을 생성하는 시간밖에 되지 않을 겁니다.