Programming/Java

[Java] 네트워크 프로그래밍 - 2(Socket, TCP/UDP)

kmindev 2023. 12. 10. 19:44

 

남궁성님의 Java의 정석(3rd Edition)을 보고 정리한 글입니다.

1. 소켓(Socket) 프로그래밍


  • 소켓은 프로세스간 통신을 위해 사용되며, 양쪽 끝 간 데이터를 주고 받을 수 있는 데이터 통로를 통해 통신한다.
  • 소켓은 역할에 따라 서버 소켓, 클라이언트 소켓으로 구분한다.

 

 

2. TCP와 UDP


  • TCP/IP 프로토콜에 포함된 프로토콜로 OSI 7계층에 전송계층에 해당된다.
항목 TCP UDP
연결방식 연결기반
- 연결 후 통신
- 1:1 통신방식
비연결형기반
- 연결없이 통신
- 1:1, 1:n, n:n 통신방식
특징 - 데이터의 경계를 구분안함
- 신뢰성 있는 데이터 전송
- 데이터의 수신여부를 확인함(손실되면 재전송)
- 패킷을 관리할 필요가 없음 - UDP보다 느림
- 데이터 경계 구분함
- 신뢰성 없는 데이터 전송
- 데이터의 전송순서가 바뀔 수 있음
- 데이터의 수신여부를 확인 안함.
- 패킷을 관리해줘야 함.
- TCP보다 빠름
관련 클래스 - Socket
- ServerSocket
- DatagramSocekt
- DatagramPacket
- MulticastSocket

 

 

 

3. TCP소켓 프로그래밍 예제


  1. 서버는 서버소켓을 사용해서 특정포트에서 클라이언트이 연결요청을 처리할 준비를 한다.
  2. 클라이언트는 접속할 서버의 IP주소와 포트정보로 소켓을 생성해서 서버에 연결을 요청한다.
  3. 서버소켓은 클라이언트의 연결요청을 받으면 서버에 새로운 소켓을 생성해서 클라이언트이 소켓과 연결되도록 한다.
  4. 이제 클라이언트와 소켓과 새로 생성된 서버의 소켓은 서버소켓과 관계없이 1:1 통신 한다.

 

a. TcpChatServer

public class TcpChatServer {
    HashMap clients;

    TcpChatServer() {
        clients = new HashMap();
        Collections.synchronizedMap(clients);
    }

    public void start() {
        ServerSocket serverSocket = null;
        Socket socket = null;

        try {
            serverSocket = new ServerSocket(7777);
            System.out.println("서버가 시작됨.");

            while (true) {
                socket = serverSocket.accept();
                System.out.println("[" + socket.getInetAddress() + " : " + socket.getPort() + " ] " +
                        "에서 접속하였습니다.");
                ServerReceiver receiver = new ServerReceiver(socket);
                receiver.start();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    void sendToAll(String msg) {
        Iterator it = clients.keySet().iterator();

        while (it.hasNext()) {
            try {
                DataOutputStream out = (DataOutputStream) clients.get(it.next());
                out.writeUTF(msg);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    class ServerReceiver extends Thread {
        Socket socket;
        DataInputStream in;
        DataOutputStream out;

        ServerReceiver(Socket socket) {
            this.socket = socket;
            try {
                in = new DataInputStream(socket.getInputStream());
                out = new DataOutputStream(socket.getOutputStream());
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

        public void run() {
            String name = "";
            try {
                name = in.readUTF();
                sendToAll("#" + name + "님이 들어오셨습니다.");

                clients.put(name, out);
                System.out.println("현재 서버접속자 수는 " + clients.size() + " 입니다.");

                while (in != null) {
                    sendToAll(in.readUTF());
                }
            } catch (IOException e) {
            } finally {
                sendToAll("#" + name + "님이 나가셨습니다.");
                clients.remove(name);
                System.out.println(socket.getInetAddress() + "에서 접속을 종료하였습니다.");
                System.out.println("현재 서버접속자 수는 " + clients.size() + " 입니다.");
            }
        }
    }

    public static void main(String[] args) {
        new TcpChatServer().start();
    }
}

 

b. TcpChatClient

public class TcpChatClient {
    public static void main(String[] args) {
        if (args.length != 1) {
            System.out.println("USAGE: java TcpChatClient 대화명");
            System.exit(0);
        }

        try {
            String serverIp = "127.0.0.1";

            Socket socket = new Socket(serverIp, 7777);
            System.out.println("서버에 연결되었습니다.");
            Thread sender = new Thread(new ClientSender(socket, args[0]));
            Thread receiver = new Thread(new ClientReceiver(socket));

            sender.start();
            receiver.start();
        } catch (UnknownHostException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    static class ClientSender extends Thread {
        Socket socket;
        DataOutputStream out;
        String name;

        ClientSender(Socket socket, String name) {
            this.socket = socket;
            try {
                out = new DataOutputStream(socket.getOutputStream());
                this.name = name;
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

        public void run() {
            Scanner sc = new Scanner(System.in);
            try {
                if (out != null) {
                    out.writeUTF(name);
                }

                while (out != null)
                    out.writeUTF("[" + name + "]" + sc.nextLine());
            } catch (IOException e) {
            }
        }
    }

    static class ClientReceiver extends Thread {
        Socket socket;
        DataInputStream in;

        ClientReceiver(Socket socket) {
            this.socket = socket;
            try {
                in = new DataInputStream(socket.getInputStream());
            } catch (IOException e) {
            }
        }

        public void run() {
            while (in != null) {
                try {
                    System.out.println(in.readUTF());
                } catch (IOException e) {
                }
            }
        }
    }
}

 

c. 실행결과

 

 

4. UDP소켓 프로그래밍 예제


  • 연결지향적이지 않으므로 연결 요청을 받아줄 서버소켓이 필요없다.
  • DatagramSocket간에 데이터(DatagramPacket)를 send/receive로 주고 받는다.

 

a. UdpServer

public class UdpServer {
    public UdpServer(int port) {
        try {
            DatagramSocket ds = new DatagramSocket(port);
            while (true) {
                byte buffer[] = new byte[512];
                DatagramPacket dp = new DatagramPacket(buffer,buffer.length);
                System.out.println("ready");
                ds.receive(dp);
                String str = new String(dp.getData());
                System.out.println("수신된 데이터 : " + str);

                InetAddress ia = dp.getAddress();
                port = dp.getPort();
                System.out.println("client ip : " + ia + " , client port : " + port);
                dp = new DatagramPacket(dp.getData(),dp.getData().length, ia,port);
                ds.send(dp);
            }
        } catch (IOException ioe) {
            ioe.printStackTrace();
        }
    }

    public static void main(String[] args) {
        new UdpServer(7777);
    }
}

 

 

b. UdpClient

public class UdpClient {
    private String str;
    private BufferedReader file;
    private static int SERVER_PORT = 7777;

    public UdpClient(String ip) {
        try {
            InetAddress ia = InetAddress.getByName(ip);
            DatagramSocket ds = new DatagramSocket();
            System.out.print("message : ");
            file = new BufferedReader(new InputStreamReader(System.in));
            str = file.readLine();
            byte buffer[] = str.getBytes();
            DatagramPacket dp = new DatagramPacket(
                    buffer, buffer.length, ia, SERVER_PORT);
            ds.send(dp);

						// 데이터 수신
            buffer = new byte[512];
            dp = new DatagramPacket(buffer, buffer.length);
            ds.receive(dp);
            System.out.println("server ip : " + dp.getAddress() + " , server port : " + dp.getPort());
            System.out.println("수신된 데이터 : " + new String(dp.getData()).trim());
        } catch (IOException ioe) {
            ioe.printStackTrace();
        }
    }

    public static void main(String[] args) {
        new UdpClient("localhost");
    }
}

 

c. 실행결과