[JAVA, App] 19.통신
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("스레드와 상관없는 코드");
}
}