[이것이 자바다] 19장 정리
자바에서의 소켓통신에 대한 개념을 이해하는데 도움이 되는 문제 풀이를 제공합니다. InetAddress를 이용하여 IP 주소를 얻고, ServerSocket 객체 생성을 통해 TCP 서버 프로그램을 개발할 수 있습니다. 서버와 클라이언트 간의 연결 요청 및 종료는 각각 ServerSocket의 accept() 메소드와 Socket의 close() 메소드를 사용합니다.
Jan 22, 2024
IPExam.java
package ch19; import java.net.InetAddress; import java.net.UnknownHostException; public class IPExam { public static void main(String[] args) { try { InetAddress local = InetAddress.getLocalHost(); System.out.println(local.getHostAddress()); InetAddress[] naver = InetAddress.getAllByName("www.naver.com"); for(InetAddress i : naver) { System.out.println(i); } } catch (Exception e) { e.printStackTrace(); } } }
핵심 키워드
- 자바는 IP주소를 java.net패키지의 InetAddress로 표현한다. InetAddress를 이용하면 로컬 컴퓨터의 IP 주소를 얻을 수 있고, 도메인 이름으로 DNS에서 검색한 후 IP 주소를 가져올 수도 있다.
ServerExam.java
package ch19; import java.io.DataInputStream; import java.io.DataOutputStream; import java.net.InetSocketAddress; import java.net.ServerSocket; import java.net.Socket; import java.util.Scanner; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public class ServerExam { private static ServerSocket serverSocket = null; // 스레드풀 설정(동시 접속자 수 10명으로 제한) private static ExecutorService executorService = Executors.newFixedThreadPool(10); public static void main(String[] args) { System.out.println("------------------------------------------"); System.out.println("서버를 종료하려면 Q 또는 q를 입력해주세요."); System.out.println("------------------------------------------"); start(); Scanner sc = new Scanner(System.in); while (true) { String key = sc.nextLine(); if (key.toLowerCase().equals("q")) break; } sc.close(); stop(); } private static void start() { // 스레드 실행 Thread thread = new Thread() { @Override public void run() { try { serverSocket = new ServerSocket(50001); System.out.println("서버 시작됨."); while (true) { // 50001포트로 들어오는 새로운 유저를 기다림, 연결시 유저 정보 담은 객체 생성 System.out.println("[서버] 연결을 기다림"); Socket socket = serverSocket.accept(); // accept 할때마다 스레드풀의 task에 추가. executorService.execute(() -> { try { InetSocketAddress isa = (InetSocketAddress) socket.getRemoteSocketAddress(); System.out.println("[서버] " + isa.getHostName() + "의 연결을 수락함"); DataInputStream dis = new DataInputStream(socket.getInputStream()); String message = dis.readUTF(); DataOutputStream dos = new DataOutputStream(socket.getOutputStream()); dos.writeUTF(message); dos.flush(); System.out.println("서버에서 받은 데이터 \"" + message + "\"를 보냄"); socket.close(); System.out.println("[서버] " + isa.getHostName() + "의 연결을 종료함"); } catch (Exception e) { System.out.println("[서버] " + e.getMessage()); } }); } } catch (Exception e) { System.out.println("[서버] " + e.getMessage()); } } }; thread.start(); } private static void stop() { // 스레드 종료 try { serverSocket.close(); } catch (Exception e) { System.out.println("[서버] " + e.getMessage()); } } }
ClientExam.java
package ch19; import java.io.DataInputStream; import java.io.DataOutputStream; import java.net.Socket; public class ClientExam { public static void main(String[] args) { try { Socket socket = new Socket("localhost",50001); System.out.println("[클라이언트] 연결 성공"); String semdMessage = "서버에게 보낼 메시지"; DataOutputStream dos = new DataOutputStream(socket.getOutputStream()); dos.writeUTF(semdMessage); dos.flush(); DataInputStream dis = new DataInputStream(socket.getInputStream()); String receiveMessage = dis.readUTF(); System.out.println("서버로부터 받은 메시지: "+receiveMessage); socket.close(); System.out.println("[클라이언트] 연결 끊음"); } catch (Exception e) { e.printStackTrace(); } } }
핵심 키워드
- IP 주소로 프로그램들이 통신할 때는 약속된 데이터 전송 규약이 있다. 이것을 전송용 프로토콜이라고 부른다. 인터넷에서 전송용 프로토콜은 TCP와 UDP가 있다.
- TCP 서버 프로그램을 개발하려면 우선 ServerSocket 객체를 생성해야 한다.
- 만약 서버 컴퓨터에 여러 개의 IP가 할당되어 있을 경우, 특정 IP에서만 서비스를 하고 싶다면 InetSocketAddress의 첫 번째 매개값으로 해당 IP를 주면 된다.
ServerSocket serverSocket = new ServerSocket (50001);
Socket socket = serverSocket.accept();
InetSocketAddress isa = (InetSocketAddress) socket.getRemoteSocketAddress(); String clientIp = isa.getHostName(); String portNo = isa.getPort();
serverSocket.close();
- 클라이언트가 서버에 연결 요청을 하려면 Socket 객체를 생성할 때 생성자 매개값으로 서버 IP 주소와 Port 번호를 제공하면 된다. 만약 IP 주소 대신 도메인 이름을 사용하고 싶다면, 생성자 매개값으로 InetAddress를 제공해야 한다.
Socket socket = new Socket("IP", 50001);
socket.close();
- 일반적으로 서버는 다수의 클라이언트와 통신을 한다. 만약 서버에 동시 요청 처리에 대한 코드를 작성하지 않는다면, 먼저 연결한 클라이언트의 요청 처리 시간이 길어질수록 다음 클라이언트의 요청 처리 작업이 지연될 수 밖에 없다.
- 따라서 accept()와 receive()를 제외한 요청 처리 코드를 별도의 스레드에서 작업하는 것이 좋다.
- 스레드를 처리할 때 주의할 점은 클라이언트의 폭증으로 인한 서버의 과도한 스레드 생성을 방지해야 한다는 것이다. 그래서 스레드풀을 사용하는 것이 바람직하다.
- 스레드풀은 작업 처리 스레드 수를 제한해서 사용하기 때문에 갑작스런 클라이언트 폭증이 발생해도 크게 문제가 되지 않는다. 다만 작업 큐의 대기 작업이 증가되어 클라이언트에서 응답을 늦게 받을 수도 있다.
UDPServerExam.java
package ch19; import java.net.DatagramPacket; import java.net.DatagramSocket; import java.net.SocketAddress; import java.util.Scanner; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public class UDPServerExam { private static DatagramSocket datagramSocket = null; private static ExecutorService executorService = Executors.newFixedThreadPool(10); public static void main(String[] args) { System.out.println("------------------------------------------"); System.out.println("서버를 종료하려면 Q 또는 q를 입력해주세요."); System.out.println("------------------------------------------"); start(); Scanner sc = new Scanner(System.in); while (true) { String key = sc.nextLine(); if (key.toLowerCase().equals("q")) break; } sc.close(); stop(); } private static void start() { // 스레드 시작 Thread thread = new Thread() { @Override public void run() { try { datagramSocket = new DatagramSocket(50001); // 포트 바인딩 System.out.println("[서버] UDP 서버 시작됨"); while (true) { DatagramPacket receivePacket = new DatagramPacket(new byte[1024], 1024); // DatagramPacket의 사이즈 // 설정 datagramSocket.receive(receivePacket); executorService.execute(()->{ try { String newsKind = new String(receivePacket.getData(), 0, receivePacket.getLength(), "UTF-8"); SocketAddress socketAddress = receivePacket.getSocketAddress(); // 클라이언트의 ip와 포트 get for (int i = 1; i <= 5; i++) { // 뉴스를 클라이언트로 전송(TCP와 달리 응답 기다리지 않고 보냄) String data = newsKind + ": 뉴스" + i; byte[] bytes = data.getBytes("UTF-8"); DatagramPacket sendPacket = new DatagramPacket(bytes, 0, bytes.length, socketAddress); datagramSocket.send(sendPacket); } }catch(Exception e) { System.out.println("[서버]: " + e.getMessage()); } }); } } catch (Exception e) { System.out.println("[서버]: " + e.getMessage()); } } }; thread.start(); } private static void stop() { datagramSocket.close(); // 포트 언바인딩 System.out.println("[서버] UDP 서버 종료됨"); } }
UDPClientExam.java
package ch19; import java.net.DatagramPacket; import java.net.DatagramSocket; import java.net.InetSocketAddress; import java.util.Scanner; public class UDPClientExam { public static void main(String[] args) { Scanner scan = new Scanner(System.in); try { // DatagramSocket 생성 DatagramSocket datagramSocket = new DatagramSocket(); while (true) { // 전송할 주제 보내기 System.out.print("구독할 뉴스 주제를 입력하세요. 종료를 원하면 q 입력 > "); String data = scan.nextLine(); if (data.toLowerCase().equals("q")) { System.out.println("클라이언트를 종료합니다."); break; } byte[] bytes = data.getBytes("UTF-8"); DatagramPacket sendPacket = new DatagramPacket(bytes, bytes.length, new InetSocketAddress("localHost", 50001)); datagramSocket.send(sendPacket); while(true) { // 데이터 받기(UDP이므로 요청 보내자마자 데이터 받을 준비 안하면 데이터를 못받음) DatagramPacket receivePacket = new DatagramPacket(new byte[1024], 1024); datagramSocket.receive(receivePacket); // 문자열로 변환 String news = new String(receivePacket.getData(), 0, receivePacket.getLength(), "UTF-8"); System.out.println(news); // 원하는 만큼 받고 while문 종료 if (news.contains("뉴스5")) { System.out.println(); break; } } } // DatagramSocket 닫기 datagramSocket.close(); } catch (Exception e) { } } }
핵심 키워드
- UDP는 발신자가 일방적으로 수신자에게 데이터를 보내는 방식으로, TCP처럼 연결 요청 및 수락 과정이 없기 때문에 TCP보다 데이터 전송 속도가 상대적으로 빠르다.
- UDP는 TCP처럼 고정 회선이 아니라 여러 회선을 통해 데이터가 전송되기 때문에 특정 회선의 속도에 따라 데이터가 순서대로 전달되지 않거나 잘못된 회선으로 인해 데이터 손실이 발생할 수 있다.
- 따라서 데이터 전달의 신뢰성보다 속도가 중요하다면 UDP를 사용하고, 데이터 전달의 신뢰성이 중요하다면 TCP를 사용해야 한다.
- 자바는 UDP 네트워킹을 위해 java,net 패키지에서 DatagramSocket과 DatagramPacket 클래스를 제공한다.
- UDP 서버를 위한 DatagramSocket 객체를 생성할 때에는 바인딩할 Port 번호를 생성자 매개값으로 제공해야 한다.
DatagramSocket datagramSocket = new DatagramSocket(50001);
DatagramPacket receivePacket = new DatagramPacket(new byte[1024], 1024); datagramSocket.receive(receivePacket);
byte[] bytes = receivePacket.getData(); int num = receivePacket.getLength;
String data = new String(bytes, 0, num, "UTF-8");
SocketAddress socketAddress = receivePacket.getSocketAddress();
String data = "처리 내용"; byte[] bytes = data.getBytes("UTF-8"); DatagraPacket sendPacket = new DatagraPacket( bytes, 0, bytes.length, socketAddress );
datagramSocket.send( sendPacket );
datagramSocket.close();
- UDP 클라이언트는 서버에 요청 내용을 보내고 그 결과를 받는 역할을 한다. UDP 클라이언트를 위한 DatagramSocket 객체는 기본 생성자로 생성한다. Port 번호는 자동으로 부여되기 때문에 따로 지정할 필요가 없다.
DatagramSocket datagramSocket = new DatagramSocket();
String data = "요청 내용"; byte[] bytes = data.getBytes("UTF-8"); DatagramPacket sendPacket = new DatagramPacket( bytes, bytes.length, new InetSocketAddress("localhost", 50001) );
datagramSocket.send(sendPacket);
datagramSocket.close();
JsonExam.java
package ch19; import java.io.FileWriter; import java.io.Writer; import java.nio.charset.Charset; import org.json.JSONArray; import org.json.JSONObject; public class JsonExam { public static void main(String[] args) throws Exception { Person winter = new Person( "winter", "한겨울", 25, true, new Tel("02-123-1234", "010-1234-1324"), new String[] { "java", "C", "C++" } ); Person summer = new Person( "summer", "한여름", 25, true, new Tel("02-123-1234", "010-1234-1324"), new String[] { "Python", "C", "C++" } ); JSONObject obj1 = createJSON(winter); JSONObject obj2 = createJSON(summer); JSONObject root = new JSONObject(); JSONArray people = new JSONArray(); people.put(obj1); people.put(obj2); root.put("people", people); Writer writer = new FileWriter("C:/Temp2/people.json", Charset.forName("UTF-8")); writer.write(root.toString()); // 내용물을 파일로 쓰고 싶다면 toString 사용. writer.flush(); writer.close(); } private static JSONObject createJSON(Person person) { JSONObject obj = new JSONObject(); obj.put("id", person.getId()); // put으로 데이터 입력 가능. 처음이 키, 뒤가 밸류. obj.put("name", person.getName()); obj.put("age", person.getAge()); obj.put("student", person.isStudent()); JSONObject inner = new JSONObject(); inner.put("home", person.getTel().getHome()); inner.put("mobile", person.getTel().getMobile()); obj.put("tel", inner); // 객체도 객체에 넣을 수 있다. JSONArray skill = new JSONArray(); skill.put(person.getSkill()[0]); skill.put(person.getSkill()[1]); skill.put(person.getSkill()[2]); obj.put("skill", skill); return obj; } }
Tel.java
package ch19; import lombok.AllArgsConstructor; import lombok.Data; @Data @AllArgsConstructor public class Tel { private String home; private String mobile; }
Person.java
package ch19; import lombok.AllArgsConstructor; import lombok.Data; @Data @AllArgsConstructor public class Person { private String id; private String name; private int age; private boolean student; private Tel tel; private String[] skill; }
JSONParsingExam.java
package ch19; import java.io.BufferedReader; import java.io.FileReader; import java.nio.charset.Charset; import org.json.JSONArray; import org.json.JSONObject; public class JSONParsingExam { public static void main(String[] args) throws Exception { // 파일로부터 JSON 읽기 BufferedReader br = new BufferedReader(new FileReader("C:/Temp2/people.json", Charset.forName("UTF-8"))); String people = br.readLine(); br.close(); // JSON 파싱 JSONObject obj = new JSONObject(people); JSONArray arr = obj.getJSONArray("people"); // 메소드 실행 Person person1 = parsePerson(arr.getJSONObject(0)); Person person2 = parsePerson(arr.getJSONObject(1)); System.out.println(person1); System.out.println(person2); } private static Person parsePerson(JSONObject obj) { // 객체 속성 정보 읽기 JSONObject tel = obj.getJSONObject("tell"); // 배열 속성 정보 읽기 JSONArray skill = obj.getJSONArray("skill"); String[] arr = new String[skill.length()]; for (int i = 0; i < skill.length(); i++) { arr[i] = skill.getString(i); } // 속성 정보 읽기 return new Person(obj.getString("id"), obj.getString("name"), obj.getInt("age"), obj.getBoolean("student"), new Tel(tel.getString("home"), tel.getString("mobile")), arr); } }
핵심 키워드
- 네트워크로 전달되는 데이터가 복잡할수록 구조화된 형식이 필요하다. 네트워크 통신에서 가장 많이 사용되는 데이터 형식은 JSON이다.
- 두 개 이상의 속성이 있는 경우에는 객체{ }로 표기하고, 두 개 이상의 값이 있는 경우에는 배열[ ]로 표기한다.
{ "id":"winter", "name":"한겨울", "age":25, "student":true, "tell":{"mobile":"010-1234-1324","home":"02-123-1324"}, "skill":["java","c","c++"] }
결론
해당 문제를 풀면서 자바에서의 소켓통신과 서버의 동시 요청 처리, JSON 파일 작성에 대한 개념을 이해할 수 있었다.
Share article