java.net.InetAddress
- ip 정보를 저장하는 클래스
- static 메소드인 getLocalHost(), getByName(String hostname), getAllByName(String hostname)- getLocalHost(): 자기 컴퓨터의 IP 정보를 리턴
- getByName은 host의 ip 정보 1개를 리턴
- getAllByName은 host의 모든 ip 정보를 리턴
 
소켓 통신
- Socket: NIC(Network Interface Card - LAN Card)를 추상화한 클래스
- 네트워크 프로그래밍이라는 용어 대신에 Socket Programming 이라고도 합니다.
1.통신 방법
1) TCP 프로토콜을 사용하는 스트림 소켓
2) UDP 프로토콜을 사용하는 데이터그램 소켓
2.TCP(연결형 통신)
- 수신하는 쪽에서 송신하는 쪽으로 연결을 요청
- 송신하는 쪽에서 연결을 하고 요청하는 데이터에 대한 메타 정보(데이터에 대한 정보 - 데이터 크기 등)를 송신
- 수신하는 쪽에서 그 정보를 보고 다시 요청을 합니다.
- 송신하는 쪽에서 데이터를 전송
- 수신하는 쪽에서 데이터 수신 여부를 송신하는 쪽에 전송하고 통신이 종료
- 신뢰성이 높지만 트래픽이 증가
- HTTP, HTTPS 가 TCP 통신
3.UDP(비연결형 통신)
- 송신하는 쪽에서 수신하는 쪽으로 일방적으로 데이터를 전송하고 통신이 종료
- DHCP(IP 동적 할당), DNS(Domain -> IP)
- 콜 센터, Apple의 APNS(Apple Push Notification Service - 애플 제품의 알림)
- 수신 쪽에서 데이터를 제대로 받았는지 알 수 없음
4.Socket 클래스
1) 생성자
Socket()
Socket(InetAddress addr, int port): addr 의 port 번호에 해당하는 서비스에 접속
Socket(String addr, int port): addr 의 port 번호에 해당하는 서비스에 접속
Socket(InetAddress addr, int port, InetAddress localaddr, int localport): addr의 port 번호에 접속을 하는데 자신의 주소를 localaddr 그리고 port는 localport로 설정해서 접속
- addr 이 잘못되면 NullPointerException 그리고 port 번호가 잘못되면 illegalArgumentException이 발생
2) 메소드
void close()
InetAddress getInetAddress(): 접속한 상대방 IP 정보
int getPort(): 상대방 포트 번호
InputStream getInputStream(): 상대방에게서 정보를 읽어오기 위한 스트림
OutputStream getOutputStream(): 상대방에게 정보를 전송하기 위한 스트림
5.스트림 소켓 - TCP 통신을 위한 소켓
1) 수신 받는 쪽의 소켓 생성과 요청
Socket 소켓변수 = new Socket(서버IP주소, 포트번호); //연결
//요청 전송
OutputStream 출력스트림변수 =  소켓변수.getOutputStream();
//바이트 단위 전송
출력스트림변수.write(byte [] b); 
//문자단위 전송
PrintWriter pw = new PrintWriter(출력스트림변수);
pw.println(String msg);
pw.flush()
//데이터 읽어오기
InputStream 입력스트림변수 =  소켓변수.getInputStream();
//바이트 단위로 읽어오기 - 파일 다운로드
입력스트림변수.read(byte [] b);
//문자열 단위로 읽어오기
BufferedReader br = new BufferedReader(new InputStreamReader(입력스트림변수));
String msg = br.readLine();//한 줄 읽어오기
//null을 리턴할 때 까지 읽으면 전송된 모든 내용을 읽을 수 있습니다.
6.www.daum.net 의 html 가져오기
- www.daum.net 이 호스트 이름이고 http 서버는 기본 포트번호가 80
public class DaumMain {
    public static void main(String[] args) {
        try {
            //daum 의 주소를 생성
            InetAddress addr = InetAddress.getByName("www.daum.net");
            //TCP 소켓 생성
            Socket socket = new Socket(addr, 80);
            //요청 전송
            PrintWriter pw = new PrintWriter(socket.getOutputStream());
            pw.println("GET http://www.daum.net");
            //flush를 호출하지 않으면 전송이 안될 수도 있습니다.
            pw.flush();
            //데이터 읽기  - 문자 단위
            BufferedReader br = 
                new BufferedReader(
                    new InputStreamReader(
                            socket.getInputStream()));
            //읽을 데이터가 없을 때 까지 줄 단위로 읽어오기
            while(true) {
                String line = br.readLine();
                if(line == null) {
                    break;
                }
                System.out.println(line);
            }
            br.close();
            socket.close();
        }catch(Exception e) {
            //예외 메시지 출력
            System.out.println("예외:" + e.getMessage());
            //예외가 발생한 코드를 역추적
            e.printStackTrace();
        }
    }
}
7.TCP Server: 정보를 제공
- ServerSocket을 사용
1) 생성자
ServerSocket()
ServerSocket(int port): port를 개방해서 클라이언트의 요청을 받을 수 있도록 서버가 생성
ServerSocket(int port, int backlog): port를 개방해서 클라이언트의 요청을 받을 수 있도록 서버가 생성되고 최대 접속 개수를 설정
2) 메소드
void close()Socket accept(): 호출하면 클라이언트의 요청이 올 때 까지 대기 상태가 되고 클라이언트의 요청이 오면 클라이언트와 통신할 수 있는 Socket을 리턴하고 다음으로 넘어갑니다.  
3) 통신과정
- ServerSocket을 생성해서 클라이언트의 요청을 기다림
- 클라이언트 쪽에서 Socket을 이용해서 서버에 접속
- Socket의 스트림을 가지고 메시지를 전송
- ServerSocket을 생성할 때 port는 사용 중이 아닌 번호로 설정 - 1024보다 큰 숫자로 설정하는 것을 권장- 서버 소켓을 생성하고 예외가 발생해서 프로그램이 중단되면 이전에 사용한 포트번호를 사용하지 못할 수 도 있습니다.
- 자바로 실행 중인 프로세스를 찾아서 중단해야 이전 포트를 다시 사용 가능합니다.
 
- 현재 사용 중인 포트를 확인: netstat -ano- 자신의 ip 확인: ipconfig(ifconfig - mac)
 
- 다른 컴퓨터에서 자신의 컴퓨터에 접속하도록 할려면 방화벽 해제 되어 있어야 합니다.
4) TCP 통신
- 서버 클래스
public class TCPServer {
    public static void main(String[] args) {
        try {
            //서버 소켓을 생성 - 9000번 포트를 이용해서 클라이언트와 접속
            ServerSocket ss = new ServerSocket(9000);
            while(true) {
                System.out.println("서버 대기 중....");
                //클라이언트의 접속을 기다림
                Socket socket = ss.accept();
                //접속한 클라이언트 정보 확인
                System.out.println("접속한 클라이언트:" + socket.getInetAddress());
                //클라이언트가 전송한 메시지 확인
                BufferedReader br = 
                    new BufferedReader(
                        new InputStreamReader(
                            socket.getInputStream()));
                String msg = br.readLine();
                System.out.println("메시지:" + msg);
                //클라이언트에게 메시지 전송
                PrintWriter pw = new PrintWriter(socket.getOutputStream());
                pw.println("서버가 보내는 메시지");
                pw.flush();
                br.close();
                pw.close();
                socket.close();
            }
        }catch(Exception e) {
            System.out.println("예외:" + e.getMessage());
            e.printStackTrace();
        }
    }
}
- 클라이언트 클래스
public class TCPClient {
    public static void main(String[] args) {
        try {
            //서버에 접속하는 소켓을 생성
            Socket socket = new Socket(InetAddress.getByName("211.183.7.61"),9000);
            //메시지 전송
            Scanner sc = new Scanner(System.in);
            System.out.print("전송할 메시지:");
            String msg = sc.nextLine();
            PrintWriter pw = new PrintWriter(socket.getOutputStream());
            pw.println(msg);
            pw.flush();
            //메시지 읽기
            BufferedReader br = 
                new BufferedReader(
                    new InputStreamReader(
                            socket.getInputStream()));
            String str = br.readLine();
            System.out.println(str);
            br.close();
            pw.close();
            socket.close();
        }catch(Exception e) {
            System.out.println("예외:" + e.getMessage());
            e.printStackTrace();
        }
    }
}
8.UDP
- 비연결형 통신
- 보내는 쪽에서 받는 쪽으로 메시지만 전송하고 통신이 종료
1) 통신방식
- unicast: 1:1 로 통신하는 방식
- multicast: 그룹에 속한 모든 클라이언트와 통신
- broadcast: 자신의 IP 대역과 subnet mask를 이용해서 자신의 IP 대역과 같은 그룹에 속한 모든 클라이언트에게 전송
2) DatagramPacket 클래스와 DatagramSocket 클래스 이용
- DatagramSocket 클래스
- 생성자
DatagramSocket() : 전송을 하기 위한 소켓
DatagramSocket(int port) : 전송을 받기 위한 소켓
- 메소드
void close()
void receive(DatagramPacket packet) : 데이터를 받는 메소드
void send(DatagramPacket packet) : 데이터를 보내는 메소드
- DatagramPacket 클래스
생성자
DatagramPacket(byte [] buf, int length): 전송을 받기 위한 패킷으로 byte 배열에 데이터가 저장됩니다.
DatagramPacket(byte [] buf, int length, InetAddress addr, int port): 전송을 하기 위한 패킷으로 byte 배열의 내용을 length 만큼 addr 의 port에게 전송하기 위한 패킷
메소드
byte [] getData() : 데이터 리턴
int getLength() : 길이 리턴
- String 과 byte 배열 변환- String -> byte 배열 : String.getBytes();
- byte 배열 -> String : new String(byte 배열);
 
3) unicast
- 1:1 통신 
- 받는 쪽 
public class UDPReceive {
    public static void main(String[] args) {
        try {
            //받는 소켓을 생성
            DatagramSocket socket = new DatagramSocket(7777);
            //데이터를 전송받아서 읽기
            while(true) {
                //데이터를 저장할 패킷을 생성
                //이 두개의 문장을 반복문 바깥에 만들면 통신은 되는데 긴 메세지를 보내고 짧은 메시지를 보내면
                //짧은 메시지 뒤에 긴 메시지의 내용이 추가되는 형태가 됩니다.
                //반복문 안에서 계속 사용해야 하는 데이터는 반복문안에서 초기화를 해주어야 합니다.
                byte [] b = new byte[65536];
                DatagramPacket dp = new DatagramPacket(b, b.length);
                //대기하고 있다가 데이터를 전송받으면 동작
                socket.receive(dp);
                //보낸 곳 확인
                System.out.println("보낸 곳:" + dp.getAddress().getHostAddress());
                //데이터 확인
                String msg = new String(b);
                System.out.println(msg);
            }
        }catch(Exception e) {
            System.out.println("예외1:" + e.getMessage());
            e.printStackTrace();
        }
    }
}
- 보내는 쪽
public class UDPSend {
    public static void main(String[] args) {
        try {
            //UDP 전송을 위한 소켓 생성
            DatagramSocket ds = new DatagramSocket();
            Scanner sc = new Scanner(System.in);
            while(true) {
                //메시지 입력
                System.out.print("전송할 메시지:");
                String msg = sc.nextLine();
                //전송할 패킷 생성
                DatagramPacket dp = new DatagramPacket(
                    msg.getBytes(), msg.getBytes().length,
                    InetAddress.getByName("211.183.7.61"), 7777);
                ds.send(dp);
            }
        }catch(Exception e) {
            System.out.println("예외1:" + e.getMessage());
            e.printStackTrace();
        }
    }
}
4) multicast
- 그룹에 속한 모든 단말에게 데이터를 전송하는 방식
- 224.0.0.0 ~ 239.255.255.255 사이의 주소를 이용- 이 주소 대역은 D Class 대역으로 Multicast 용으로 예약된 IP 주소 대역
 
- MulticastSocket 을 이용해서 구현 
- 생성자 
MulticastSocket()
MulticastSocket(int port)
- 메소드
joinGroup(InetAddress addr): 그룹에 참여
leaveGroup(InetAddress addr): 그룹에서 빠져나오는 메소드
- 데이터 전송방식은 DatagramSocket 과 동일 
- multicast 받는 쪽 
public class MultiReceive {
    public static void main(String[] args) {
        try {
            MulticastSocket ms = new MulticastSocket(9999);
            //멀티캐스트에 참여
            ms.joinGroup(InetAddress.getByName("230.100.100.100"));
            System.out.println("멀티 캐스트 시작");
            while(true) {
                //전송받은 데이터를 저장할 바이트 배열 - 크기는 8의 배수로 설정하는 경우가 많음
                byte [] b = new byte[65536];
                //패킷을 생성
                DatagramPacket dp = new DatagramPacket(b, b.length);
                //데이터를 받을 수 있도록 대기
                ms.receive(dp);
                //데이터 읽기
                String msg = new String(dp.getData());
                System.out.println(msg.trim());
            }
        }catch(Exception e) {
            System.out.println("예외1:" + e.getMessage());
            e.printStackTrace();
        }
    }
}
- multicast 보내는 쪽
public class MultiSend {
    public static void main(String[] args) {
        try {
            MulticastSocket ms = new MulticastSocket();
            Scanner sc = new Scanner(System.in);
            System.out.print("닉네임:");
            String nickname = sc.nextLine();
            while(true) {
                System.out.print("전송할 메시지(종료는 end):");
                String msg = sc.nextLine();
                //문자열은 ==로 비교하면 참조를 비교
                //equals 로 비교해야 값을 비교
                if(msg.equals("end")) {
                    System.out.println("종료");
                    break;
                }
                msg = nickname + ":" + msg;
                DatagramPacket dp = 
                    new DatagramPacket(msg.getBytes(), msg.getBytes().length,
                        InetAddress.getByName("230.100.100.100"), 9999);
                ms.send(dp);
            }
        }catch(Exception e) {
            System.out.println("예외1:" + e.getMessage());
            e.printStackTrace();
        }
    }
}
- 채팅 처럼 동시에 주고받는 것이 가능하도록 할려면 보내고 받는 로직을 스레드를 이용해서 작업
콘솔에서는 쉽지 않습니다.- 입력창과 출력창이 같아서 콘솔용 채팅은 동시에 입출력 한계가 있습니다.
 
URL 통신
- 소켓 통신을 저수준의 통신방식이라고 하고 그 이외의 통신 방식들은 고수준이라고 부릅니다. - 성능은 소켓 통신이 우수한데 소켓 통신은 프로그램을 별도로 설치해야만 통신이 가능합니다.
- URL 통신은 브라우저를 통해서 사용이 가능하고 현재는 거의 모든 운영체제가 웹 브라우저를 하나씩 가지고 있습니다.
- 최근에는 웹에서도 소켓 방식의 통신이 가능합니다.
- WebSocket API를 HTML5에서 제공
 
- URL 구성 - 프로토콜://도메인이나IP:포트번호/파일경로?이름=값&이름&값...
- 프로토콜과 도메인은 생략 못함
 
- 포트번호는 서비스의 기본포트를 사용하는 경우에는 생략이 가능 - http:80, https:443
 
- 파일경로를 생략하는 경우가 있는데 이 경우는 서버의 설정을 이용해서 파일을 찾아옵니다. 
- 파일 경로 뒤에 ?는 parameter라고 하는데 클라이언트가 서버에게 넘겨주는 데이터로 key-value 형식으로 대입 
- parameter 전송 방식을 가지고 GET 방식과 POST 방식을 구분 
- 파일 경로 뒤에 #이 붙는 경우는 책갈피입니다. 
- 페이지 내에서 이동
1.java.net.URL 클래스
- URL을 만들기 위한 클래스
1) 생성
URL(String url)
- 없는 url을 대입하면 MalformedURLException이 발생
2) 메소드
- URLConnection openConnection(): URL과 양방향 통신이 가능한 Connection을 리턴
- 여기서 리턴한 Connection 은 HttpURLConnection 이나 JarURLConnection으로 형변환해서 사용해야 합니다.
- URLConnection은 추상 클래스라서 메소드가 구현되어 있지 않습니다.
2.HttpURLConnection
- URL 통신을 하기 위한 클래스
- URL 클래스의 openConnection 메소드를 이용해서 생성
- 메소드 - setConnectTimeout(int timeout): 밀리초 단위로 타임아웃을 설정하는 메소드로 타임아웃 동안 접속이 안되면 접속 실패
 
- setUseCaches(boolean isCache): 이전에 접속했던 URL에 다시 요청할 때 이전 데이터를 가져올 것인지 설정
- 자주 변경되는 URL의 데이터는 반드시 false로 설정을 해주어야 합니다.
setRequestProperty(String field, String value)addRequestProperty(String field, String value)  
- 헤더에 값을 추가하는 메소드
setRequestMethod(String method): 전송 방식을 설정  
int getResponseCode(): 서버로부터의 상태를 리턴  
200번대 정상응답
300번대 리다이렉트 중
400번대 클라이언트 오류(404 - 잘못된 URL)
500번대 서버 오류
InputStream getInputStream() : 데이터를 읽기 위한 스트림 리턴  
3.웹의 데이터 포맷
- XML: 태그 형식으로 표현하는 포맷, 별도의 라이브러리 없이 파싱 가능
- JSON: 자바스크립트 객체 표현 방법으로 데이터를 표현, 별도의 라이브러리를 추가해야 파싱 가능
- CSV: 구분자가 있는 문자열
- HTML: 뷰의 용도로 사용되는 포맷인데 사이트에서 XML이나 JSON 형태로 데이터를 제공해주지 않아서 사용- 파싱을 할려면 별도의 라이브러리를 추가해서 가능
 
4.Open API
- 데이터를 가진 곳에서 일반 사용자들에게 데이터를 사용할 수 있도록 XML 이나 JSON 형식으로 제공하는 것
- 데이터 뿐 아니라 라이브러리나 프레임워크 등을 제공하기도 함 
- 데이터는 대부분의 경우 가입을 해서 키를 받는 형태로 제공 
- 키값을 주소에 넣기도 하고 헤더에 넣기도 합니다.
5.웹 사이트에서 문자열 가져오기
public class StringDownload {
    public static void main(String[] args) {
        try {
            //다운로드 받을 URL을 생성
            URL url = new URL("https://www.naver.com");
            //URL 연결 객체 생성
            HttpURLConnection con = (HttpURLConnection)url.openConnection();
            //연결 옵션 설정
            con.setConnectTimeout(30000); //30초 동안 연결이 안되면 연결 시도 종료
            //캐시 사용을 하지 않음
            con.setUseCaches(false);
            //데이터를 읽어올 스트림을 생성
            BufferedReader br = new BufferedReader(
                    new InputStreamReader(con.getInputStream()));
            //많은 양의 문자열을 읽어야 하는 경우
            StringBuilder sb = new StringBuilder();
            while(true) {
                //한 줄 읽기
                String line = br.readLine();
                //읽은 데이터가 없으면 반복문 중단
                if(line == null) {
                    break;
                }
                //데이터가 있으면 sb에 추가
                sb.append(line + "\n");
            }
            //StringBuilder 의 데이터를 String 으로 변환
            String html = sb.toString();
            System.out.println(html);
        }catch(Exception e) {
            System.out.println("다운로드 예외:" + e.getMessage());
            e.printStackTrace();
        }
    }
}
6.비동기 다운로드
- 위처럼 스레드를 사용하지 않고 다운로드 받는 방식을 동기식 이라고 합니다.- 동기식은 데이터를 다운로드 받는 동안 다른 작업을 할 수 없습니다.
- 데이터 다운로드와 관련없는 작업도 데이터를 다운로드 동안은 수행할 수 없습니다.
- 네트워크 작업은 스레드를 이용해서 비동기식으로 동작하도록 만드는 것을 권장(안드로이드는 필수)
- 다운로드와 관련없는 작업은 다운로드 받는 동안 수행되도록 작성하는 것이 좋습니다.
 
- 이미지 파일을 다운로드 받아서 현재 디렉토리에 저장하기
public class ImageDownload {
    public static void main(String[] args) {
        Thread th = new Thread() {
            public void run() {
                try {
                    String addr = 
                        "https://img0.yna.co.kr/photo/yna/YH/2019/04/07/PYH2019040703010000700_P4.jpg";
                    //파일명을 만들기 위해서 마지막 / 다음의 문자열 가져오기
                    int len = addr.lastIndexOf('/');
                    String filename = addr.substring(len+1);
                    //System.out.println(filename);
                    //현재 디렉토리에 위 파일이 있으면 있다고 출력하고 없다면 다운로드 받아서 저장
                    File f = new File("./" + filename);
                    if(f.exists() == true) {
                        System.out.println("파일이 이미 존재합니다.");
                        return;
                    }else {
                        //주소 객체 생성
                        URL url = new URL(addr);
                        HttpURLConnection con = (HttpURLConnection)url.openConnection();
                        con.setConnectTimeout(30000);
                        con.setUseCaches(false);
                        /*
                        //다운로드 받을 파일의 크기를 가져오기
                        int length = con.getContentLength();
                        //데이터를 저장할 바이트 배열 생성
                        byte [] b = new byte[length];
                        //바이트 단위로 데이터를 읽어올 스트림 생성
                        BufferedInputStream bis = 
                            new BufferedInputStream(con.getInputStream());
                        //데이터를 읽어서 b에 저장
                        bis.read(b);
                        //읽어온 내용을 파일에 저장
                        PrintStream ps = new PrintStream(new FileOutputStream("./" + filename));
                        ps.write(b);
                        */
                        //나누어서 읽어서 기록
                        //바이트 단위로 데이터를 읽어올 스트림 생성
                        BufferedInputStream bis = 
                            new BufferedInputStream(con.getInputStream());
                        PrintStream ps = new PrintStream(new FileOutputStream("./" + filename));
                        while(true) {
                            //512 바이트 배열
                            byte [] b = new byte[512];
                            //내용을 읽어서 b에 저장
                            //읽은 개수를 r에 저장
                            int r  = bis.read(b);
                            //읽은게 없으면 중단
                            if(r <= 0) {
                                break;
                            }
                            //읽은 데이터가 있으면 기록
                            ps.write(b, 0, r);
                            //버퍼에 내용이 남아있는 것을 방지하기 위해서 마지막에 flush를 호출
                            ps.flush();
                        }
                        //사용한 스트림 닫기
                        ps.close();
                        bis.close();
                        //연결 끊기
                        con.disconnect();
                    }
                }catch(Exception e) {
                    System.out.println("다운로드 예외:" + e.getMessage());
                    e.printStackTrace();
                }
            }
        };
        th.start();
        //스레드 동작 중 쉬는 시간이 생기면 동작
        System.out.println("스레드와 상관없는 코드");
    }
}