프로그램이 네트워크에서 데이터를 송수신할 수 있도록, "네트워크 환경에 연결할 수 있게 만들어진 연결부"가 바로
"네트워크 소켓(Socket)"이다.
그런데 네트워크에 연결하기 위한 소켓 또한 정해진 규약,
즉, 통신을 위한 프로토콜(Protocol)에 맞게 만들어져야 한다.
보통은 OSI 7 Layer(Open System Interconnection 7 Layer)의 네 번째 계층인 TCP(Transport Control Protocol) 상에서 동작하는 소켓을 주로 사용하는데, 이를 "TCP 소켓" 또는 "TCP/IP 소켓"이라고 부른다.
소켓 생성
서버 측에 연결 요청
서버 소켓에서 ok 하면 데이터를 송수신
끝나면 close
1) 클라이언트 소켓 생성을 통한 서버접속
Socket socket = new Socket("127.0.0.1", 8000); // IP주소, 포트번호
2) 데이터 송수신을 위한 input/output 스트림 생성
InputStream in = socket.getInputStream( );
OutputStream out = socket.getOutputStream( );
3) output 스트림을 통한 데이터 송신 (클라이언트 → 서버)
String outputMessage = "보낼메시지";
out.write(outputMessage.getBytes( ));
out.flush( );
4) input 스트림을 통한 데이터 수신 (서버 → 클라이언트)
byte[ ] inputData = new byte[100];
int length = in.read(inputData);
String inputMessage = new String(inputData, 0, length);
5) 통신 종료
socket.close( );
소켓 생성
생성한 소켓에 서버 ip, port 번호 바인딩 (정보 입력)
클라이언트로부터 연결 요청이 들어오는지 listen
요청이 성공적으로 수신되면 accept 후 데이터 통신을 위한 소켓을 별도로 생성
일단 새로운 소켓을 통해 연결이 수립(ESTABLISHED)되면, 클라이언트와 마찬가지로 데이터 송수신(send/recv) 가능
데이터 송수신이 끝나면 소켓 close
1) 서버소켓 생성
ServerSocket serverSocket = new ServerSocket(8000); // 포트번호
2) 클라이언트 접속 대기
Socket socket = serverSocket.accept( );
3) 데이터 송수신을 위한 input/output 스트림 생성
InputStream in = socket.getInputStream( );
OutputStream out = socket.getOutputStream( );
4) input 스트림을 통한 데이터 수신 (클라이언트 → 서버)
byte[ ] inputData = new byte[100];
int length = in.read(inputData);
String inputMessage = new String(inputData, 0, length);
5) output 스트림을 통한 데이터 송신 (서버 → 클라이언트)
String outputMessage = "보낼메시지";
out.write(outputMessage.getBytes( ));
out.flush( );
6) 통신 종료
socket.close( );
serverSocket.close( );
데이터 전송의 기본단위는 바이트(byte)로 문자의 경우 데이터 전송시 문자를 바이트로, 수신시 바이트를 문자로 변환이 필요하다. 이를 위해서 앞에서는 String클래스의 생성자와 getBytes메소드를 이용했다.
자바에는 이 변환을 편리하게 해주는 클래스로 InputStreamReader와 OutputStreamWriter가 있으며 charset을 지정하여 사용할 수 있다. 또한 데이터 입출력의 효율을 위해 바로 전달하지 않고 중간에 버퍼를 이용하기 위해서 BufferedReader와 BufferedWriter 클래스를 함께 사용할 수 있다.
BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream( )));
BufferedWriter out = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream( )));
데이터 출력의 경우에는 출력 포맷을 편리하게 해주는 기능이 있는 PrintWriter 또는 PrintStream 클래스를 사용할 수 있다.
PrintWriter out = new PrintWriter(socket.getOutputStream( ));
PrintStream out = new PrintStream(socket.getOutputStream( ));
public class MultiServer {
public static void main(String[] args) {
MultiServer multiServer = new MultiServer();
multiServer.start();
}
public void start() {
ServerSocket serverSocket = null;
Socket socket = null;
try {
serverSocket = new ServerSocket(8000);
while (true) {
System.out.println("[클라이언트 연결대기중]");
socket = serverSocket.accept();
// client가 접속할때마다 새로운 스레드 생성
ReceiveThread receiveThread = new ReceiveThread(socket);
receiveThread.start();
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (serverSocket!=null) {
try {
serverSocket.close();
System.out.println("[서버종료]");
} catch (IOException e) {
e.printStackTrace();
System.out.println("[서버소켓통신에러]");
}
}
}
}
}
class ReceiveThread extends Thread {
static List<PrintWriter> list =
Collections.synchronizedList(new ArrayList<PrintWriter>());
Socket socket = null;
BufferedReader in = null;
PrintWriter out = null;
public ReceiveThread (Socket socket) {
this.socket = socket;
try {
out = new PrintWriter(socket.getOutputStream());
in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
list.add(out);
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
public void run() {
String name = "";
try {
// 최초1회는 client이름을 수신
name = in.readLine();
System.out.println("[" + name + " 새연결생성]");
sendAll("[" + name + "]님이 들어오셨습니다.");
while (in != null) {
String inputMsg = in.readLine();
if("quit".equals(inputMsg)) break;
sendAll(name + ">>" + inputMsg);
}
} catch (IOException e) {
System.out.println("[" + name + " 접속끊김]");
} finally {
sendAll("[" + name + "]님이 나가셨습니다");
list.remove(out);
try {
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
System.out.println("[" + name + " 연결종료]");
}
private void sendAll (String s) {
for (PrintWriter out: list) {
out.println(s);
out.flush();
}
}
}
public class MultiClient {
public static void main(String[] args) {
MultiClient multiClient = new MultiClient();
multiClient.start();
}
public void start() {
Socket socket = null;
BufferedReader in = null;
try {
socket = new Socket("localhost", 8000);
System.out.println("[서버와 연결되었습니다]");
String name = "user" + (int)(Math.random()*10);
Thread sendThread = new SendThread(socket, name);
sendThread.start();
in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
while (in != null) {
String inputMsg = in.readLine();
if(("[" + name + "]님이 나가셨습니다").equals(inputMsg)) break;
System.out.println("From:" + inputMsg);
}
} catch (IOException e) {
System.out.println("[서버 접속끊김]");
} finally {
try {
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
System.out.println("[서버 연결종료]");
}
}
class SendThread extends Thread {
Socket socket = null;
String name;
Scanner scanner = new Scanner(System.in);
public SendThread(Socket socket, String name) {
this.socket = socket;
this.name = name;
}
@Override
public void run() {
try {
// 최초1회는 client의 name을 서버에 전송
PrintStream out = new PrintStream(socket.getOutputStream());
out.println(name);
out.flush();
while (true) {
String outputMsg = scanner.nextLine();
out.println(outputMsg);
out.flush();
if("quit".equals(outputMsg)) break;
}
} catch (IOException e) {
e.printStackTrace();
}
}
}