Java

[Java] 소켓 통신 (간단한 채팅 프로그램 구현)

제리 . 2021. 4. 19. 20:55

Socket이란?

socket은 application layer와 transport layer사이의 인터페이스역할을 담당하며 서로 다른 endpoint간 통신을 위해서 사용된다. 

 

Socket의 동작 과정

서버측 동작 과정

1. 서버는 socket를 만든다.

2. 서버에서 ip주소와 port를 지정하여 소켓에 바인딩한다.

3. 서버는 listen상태가 되어 클라이언트의 요청을 기다린다.

4. 클라이언트의 요청을 받아드리고 데이터 송수신을 위한 소켓을 생성한다.

5. 클라이언트와 데이터를 송/수신한다.

6. 연결을 종료한다.

 

클라이언트측 동작 과정

1. socket을 만든다.

2. 서버에게 요청을 보내 connection을 맺는다.

3. 서버와 데이터를 송/수신한다.

4. 연결을 종료한다.

 

Java 프로그램 구현

한가지 유의할 점은 데이터 송수신시 별도의 스레드를 만들어야한다. 데이터 송수신은 blocking방식으로 동작하기때문에 메인스레드로만 구성하면 데이터를 올바르게 수신할 수 없다. 

 

서버

데이터 전송

import java.io.DataOutputStream;
import java.io.IOException;
import java.net.Socket;
import java.util.Scanner;

public class SendThead extends Thread{

  private final Socket socket;
  private Scanner scanner = new Scanner(System.in);

  public SendThead(Socket socket){
    this.socket = socket;
  }

  @Override
  public void run() {
    try {
      DataOutputStream sendWriter = new DataOutputStream(socket.getOutputStream());
      String sendString;
      while(true){
        sendString = scanner.nextLine();
        sendWriter.writeUTF(sendString);
        sendWriter.flush();
      }
    }catch (IOException e){
      e.printStackTrace();
    }
  }
}

서버에서 클라이언트로 데이터를 전송하는 스레드이다. 

별도의 Thread를 만들기위해 Thread클래스를 상속받아 run()메서드를 구현한다.

socket.getOutputStream()을 사용해서 클라이언트에게 데이터를 전송할 수 있다.

 

데이터 수신

import java.io.DataInputStream;
import java.io.IOException;
import java.net.Socket;
import java.net.SocketException;

public class ReceiveThread extends Thread {

  private final Socket socket;

  public ReceiveThread(Socket socket) {
    this.socket = socket;
  }

  @Override
  public void run() {
    try {
      DataInputStream tmpbuf = new DataInputStream(socket.getInputStream());
      String receiveString;
      while (true) {
        receiveString = tmpbuf.readUTF();
        System.out.println("상대방 : " + receiveString);
      }
    } catch (SocketException e1) {
      System.out.println("상대방 연결이 종료되었습니다.");
    } catch (IOException e2) {
      e2.printStackTrace();
    }
  }

}

클라이언트에서 온 데이터를 수신하는 스레드이다. 

마찬가지로 별도의 Thread를 만들기위해 Thread클래스를 상속받아 run()메서드를 구현한다.

socket.getIntputStream()을 사용해서 클라이언트에게 데이터를 수신할 수 있다.

 

메인 함수

import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;

public class SocketServer {

  public static void main(String[] args) throws IOException {
    int port = 8888;
    ServerSocket socketServer = new ServerSocket(port);
    while(true) {
      Socket sock = socketServer.accept();
      System.out.println("ip : " + sock.getInetAddress() + " 와 연결되었습니다.");
      ReceiveThread receiveThread = new ReceiveThread(sock);
      receiveThread.start();
      SendThead sendThead = new SendThead(sock);
      sendThead.start();
    }
  }
}

서버에서는 port를 지정해서 소켓과 바인딩 시킨다. 이후 클라이언트의 요청을 기다리고 요청이 들어오면 데이터 송수신 스레드를 만들어 통신한다.

 

클라이언트

클라이언트의 코드는 거의 서버측과 유사하다.

 

데이터 전송

import java.io.DataOutputStream;
import java.io.IOException;
import java.net.Socket;
import java.util.Scanner;

public class SendThread extends Thread{

  private final Socket socket;
  Scanner scanner = new Scanner(System.in);

  public SendThread(Socket socket){
    this.socket = socket;
  }

  @Override
  public void run() {
    try {
      DataOutputStream sendWriter = new DataOutputStream(socket.getOutputStream());
      String sendString;
      while(true){
        sendString =  scanner.nextLine();
        sendWriter.writeUTF(sendString);
        sendWriter.flush();
      }
    }catch (IOException e){
      e.printStackTrace();
    }

  }

}

데이터 수신

import java.io.BufferedReader;
import java.io.DataInputStream;
import java.io.IOException;
import java.net.Socket;

public class ReceiveThread extends Thread {

  private final Socket socket;
  private String receiveString;

  public ReceiveThread(Socket socket) {
    this.socket = socket;
  }

  @Override
  public void run() {
    try {
      DataInputStream tmpbuf = new DataInputStream(socket.getInputStream());
      while (true) {
        receiveString = tmpbuf.readUTF();
        if (receiveString == null) {
          System.out.println("상대방 연결이 종료되었습니다.");
        } else {
          System.out.println("상대방 : " + receiveString);
        }
      }
    } catch (IOException e) {
      e.printStackTrace();
    }

  }

}

메인 함수

import java.io.IOException;
import java.net.Socket;

public class SocketClient {

  public static void main(String[] args) throws IOException {
    Socket sock = new Socket("localhost", 8888);
    System.out.println("서버와 접속되었습니다.");
    ReceiveThread receiveThread = new ReceiveThread(sock);
    receiveThread.start();
    SendThread sendThead = new SendThread(sock);
    sendThead.start();
  }
}

클라이언트에서는 서버의 ip주소와 port를 사용해 connection을 맺는다. 이후 데이터 송수신 스레드가 실행된다.