서비스를 제공하는 쪽이 서버, 제공받는 쪽이 클라이언트
서버기반 모델 : 전용서버를 두는 것
P2P 모델 : 전용서버없이 각 클라이언트가 서버역할까지 동시에 수행하는 것
컴퓨터(host, 호스트)를 구별하는데 사용되는 고유한 주소값, 4byte의 정수 (32bit)
IP주소는 네트워크주소와 호스트주소로 구성됨
네트워크주소가 같은 두 호스트는 같은 네트워크에 존재함
IP주소와 서브넷마스크를 &연산하면 네트워크주소를 얻을 수 있음
IPv4 : 32bit
IPv6 : 128bit
http Default port : 80
https Default port : 443
public class IPTest {
public static void main(String[] args) {
// // IP
try {
// // 로컬 IP
InetAddress local = InetAddress.getLocalHost();
System.out.println(local); // 사용자의 로컬IP
// 외부 IP
InetAddress remote = InetAddress.getByName("naver.com");
InetAddress[] remote2 = InetAddress.getAllByName("naver.com");
System.out.println(remote); // naver.com/223.130.200.104
System.out.println(remote2); // 배열
for(InetAddress address : remote2) {
System.out.println(address);
}
// URL
URL url = new URL("https://naver.com/index.html");
System.out.println(url.getDefaultPort()); // 443
} catch (UnknownHostException e) {
e.printStackTrace();
} catch (MalformedURLException e) {
e.printStackTrace();
}
}
}
URL : 인터넷에 존재하는 서버들의 자원(css, js, media)에 접근할 수 있는 주소
프로토콜 : 자원에 접근하기 위해 서버와 통신하는데 사용되는 통신규약 (http)
호스트명 : 자원을 제공하는 서버의 이름
포트번호 : 통신에 사용되는 서버의 포트번호
경로명 : 접근하려는 자원이 저장된 서버상의 위치
파일명 : 접근하려는 자원의 이름
쿼리 (query) : URL에서 ? 이후 부분, key = value로 구성
참조 (anchor) : URL에서 # 이후 부분
소켓 : 프로세스간의 통신에 사용되는 양쪽 끝단 (end point)
Socket : Client의 소켓
ServerSocket : Server의 소켓
TCP : 연결기반 (연결 후 통신), 1:1 통신 / chat service
UDP : 비연결기반, n:n 통신 / streaming service
public class Client {
public static void main(String[] args) {
// socket
Socket socket = null;
try {
socket = new Socket();
// step01 : 연결 요청
System.out.println("연결 요청 중입니다.");
socket.connect(new InetSocketAddress("localhost", 9999));
System.out.println("서버에 접속했습니다.");
// step02 : 통신1 (클라이언트 -> 서버) 데이터 전송
OutputStream os = socket.getOutputStream(); // 준비
String msg = "오늘 금요일이니까 일찍 끝내요";
os.write(msg.getBytes()); // 문자 -> byte변환
os.flush();
System.out.println("메세지 전송이 완료되었습니다.");
// step03 : 통신2 (서버 -> 클라이언트)
InputStream is = socket.getInputStream();
byte[] responseMsg = new byte[100];
int countByte = is.read(responseMsg);
String msg2 = new String(responseMsg, 0, countByte, "UTF-8");
System.out.println("메세지를 정상적으로 받았습니다: " + msg2);
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
public class Server {
public static void main(String[] args) {
// serverSocket
ServerSocket serverSocket = null;
try {
serverSocket = new ServerSocket();
// bind() : 서버의 ip주소와 port설정하고 통신 시작
serverSocket.bind(new InetSocketAddress("localhost", 9999));
while(true) {
// step01 : 연결
Socket socket = serverSocket.accept();
InetSocketAddress isa = (InetSocketAddress) socket.getRemoteSocketAddress();
System.out.println(isa.getHostName() + "님이 입장하셨습니다.");
// step02 : 통신1 (클라이언트 -> 서버 : 메세지를 확인)
InputStream is = socket.getInputStream();
byte[] requestMsg = new byte[100];
int countByte = is.read(requestMsg);
String msg = new String(requestMsg, 0, countByte, "UTF-8");
System.out.println("메세지를 정상적으로 받았습니다: " + msg);
// step03 : 통신2 (서버 -> 클라이언트)
OutputStream os = socket.getOutputStream();
String severMsg = "서버에서 보내는 메세지 입니다.";
os.write(severMsg.getBytes());
os.flush();
System.out.println("서버 메세지 전송 완료");
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
serverSocket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
스레드의 집합
Vector : ArrayList와 비슷하지만 Synchrolized를 안써도 안정성이 보장됨
public class Client{
static Socket socket;
static void startClient() {
Thread thread = new Thread() {
@Override
public void run() {
try {
socket = new Socket(); // 소켓 생성 및 연결 요청
socket.connect(new InetSocketAddress("localhost", 8888));
} catch(Exception e) {
System.out.println("[서버와 통신이 불가능 합니다]");
if(!socket.isClosed()) {
stopClient();
}
return;
}
receive(); // 서버에서 보낸 데이터 받기
}
};
thread.start(); // 스레드 시작
}
static void stopClient() {
try {
System.out.println("[클라이언트 연결을 종료합니다]");
if(socket!=null && !socket.isClosed()) {
socket.close(); // 연결 끊기
}
} catch (IOException e) {}
}
static void receive() {
while(true) {
try {
byte[] byteArr = new byte[100];
InputStream inputStream = socket.getInputStream();
// 데이터 입력
int readByteCount = inputStream.read(byteArr);
// 정상적으로 close()
if(readByteCount == -1) {
throw new IOException();
}
// 문자열로 변환
String data = new String(byteArr, 0, readByteCount, "UTF-8");
System.out.println("[받은 데이터] " + data);
} catch (Exception e) {
System.out.println("[서버와 통신이 불가능 합니다]");
stopClient();
break;
}
}
}
static void send(String data) {
// 스레드 생성
Thread thread = new Thread() {
@Override
public void run() {
try {
// 데이터 전송
byte[] byteArr = data.getBytes("UTF-8");
OutputStream outputStream = socket.getOutputStream();
// 데이터 쓰기
outputStream.write(byteArr);
outputStream.flush();
System.out.println("[데이터 전송 완료]");
} catch(Exception e) {
System.out.println("[서버와 통신이 불가능 합니다]");
stopClient();
}
}
};
thread.start();
}
public static void main(String[] args) {
startClient();
while(true) {
Scanner sc = new Scanner(System.in);
String msg = sc.nextLine();
// Stop Client 입력 : 클라이언트 연결 종료
if(msg.equals("Stop Client"))
break;
send(msg);
}
stopClient();
}
}
public class Server{
// 스레드풀
static ExecutorService executorService;
static ServerSocket serverSocket;
static List<Client> connections = new Vector<Client>();
// 서버 시작
static void startServer() {
// 스레드풀 생성
executorService = Executors.newFixedThreadPool(
Runtime.getRuntime().availableProcessors()
);
// 서버 소켓 생성 및 바인딩
try {
serverSocket = new ServerSocket();
serverSocket.bind(new InetSocketAddress("localhost", 8888));
} catch(Exception e) {
if(!serverSocket.isClosed()) {
stopServer();
}
return;
}
// 수락 작업 생성
Runnable runnable = new Runnable() {
@Override
public void run() {
System.out.println("[서버를 시작합니다]");
while(true) {
try {
// 연결 수락
Socket socket = serverSocket.accept();
System.out.println("[연결을 수락합니다 : " + socket.getRemoteSocketAddress() + ": " + Thread.currentThread().getName() + "]");
// 클라이언트 접속 객체 저장
Client client = new Client(socket);
connections.add(client);
System.out.println("[연결된 클라이언트 수: " + connections.size() + "]");
} catch (Exception e) {
if(!serverSocket.isClosed()) {
stopServer();
}
break;
}
}
}
};
// 스레드풀의 작업 처리
executorService.submit(runnable);
}
// 서버 종료
static void stopServer() {
try {
// 모든 소켓 close
Iterator<Client> iterator = connections.iterator();
while(iterator.hasNext()) {
Client client = iterator.next();
client.socket.close();
iterator.remove();
}
// 서버 소켓 close
if(serverSocket!=null && !serverSocket.isClosed()) {
serverSocket.close();
}
// 스레드풀 종료
if(executorService!=null && !executorService.isShutdown()) {
executorService.shutdown();
}
System.out.println("[서버 멈춤]");
} catch (Exception e) { }
}
// 클라이언트
static class Client {
Socket socket;
Client(Socket socket) {
this.socket = socket;
receive();
}
// 받기
void receive() {
// 받기 작업 생성
Runnable runnable = new Runnable() {
@Override
public void run() {
try {
while(true) {
byte[] byteArr = new byte[100];
InputStream inputStream = socket.getInputStream();
// 데이터 받기
int readByteCount = inputStream.read(byteArr);
// 클라이언트 Socket의 close()
if(readByteCount == -1) {
throw new IOException();
}
System.out.println("[요청 처리 : " + socket.getRemoteSocketAddress() + ": " + Thread.currentThread().getName() + "]");
// 데이터 변환
String data = new String(byteArr, 0, readByteCount, "UTF-8");
// Stop Server 요청시 서버 종료
if(data.equals("Stop Server")) {
stopServer();
}
// 모든 클라이언트에게 데이터 전송
for(Client client : connections) {
client.send(data);
}
}
} catch(Exception e) {
try {
connections.remove(Client.this);
System.out.println("[클라이언트 통신 불가능 : " + socket.getRemoteSocketAddress() + ": " + Thread.currentThread().getName() + "]");
socket.close();
} catch (IOException e1) {}
}
}
};
// 스레드풀에서 처리
executorService.submit(runnable);
}
// 보내기
void send(String data) {
// 보내기 작업 생성
Runnable runnable = new Runnable() {
@Override
public void run() {
try {
// 데이터 보내기
byte[] byteArr = data.getBytes("UTF-8");
OutputStream outputStream = socket.getOutputStream();
// 데이터 쓰기
outputStream.write(byteArr);
outputStream.flush();
} catch(Exception e) {
try {
System.out.println("[클라이언트 통신 불가눙 : " + socket.getRemoteSocketAddress() + ": " + Thread.currentThread().getName() + "]");
connections.remove(Client.this);
socket.close();
} catch (IOException e2) {}
}
}
};
// 스레드풀에서 처리
executorService.submit(runnable);
}
}
public static void main(String[] args) {
startServer();
}
}
jsoup, lombok 라이브러리를 이용함
크롤링 시 유의 사항 (구글)
https://developers.google.com/search/docs/crawling-indexing/robots/create-robots-txt?hl=ko
Document : 문서
Element(s) : 요소 (다수의 요소)
select, getElements 는 복수 형태로 (전부) 가져옴
// import 생략
public class JsoupTest {
public static void main(String[] args) {
Document doc = null;
try {
// step01 : 기본 사용법
doc = Jsoup.connect("https://en.wikipedia.org/wiki/Main_Page").get();
System.out.println(doc.title());
System.out.println(doc.getElementsByTag("title").get(0).text());
System.out.println(doc.select("head > title"));
System.out.println(doc.select("#In_the_news"));
System.out.println(doc.select(".mw-headline"));
Elements inTheNews = doc.select("#mp-itn > ul > li > b > a");
System.out.println(inTheNews);
for (Element news: inTheNews) {
System.out.println(news.attr("href"));
System.out.println(news.absUrl("href"));
}
// step02
doc = Jsoup.connect("https://www.w3schools.com/java/default.asp").get();
Elements h2Texts = doc.select("#main > h2");
for (Element h2text : h2Texts) {
System.out.println(h2text.ownText());
}
// step03 : 원하는 태그 출력, 객체로 저장하여 출력
doc = Jsoup.connect("https://sports.news.naver.com/wfootball/index.nhn").get();
Elements homeNews = doc.select(".home_news");
System.out.println("-------------------------------------");
System.out.println("제목 : " + homeNews.get(0).text().substring(0, 4));
System.out.println("-------------------------------------");
ArrayList<FootballNews> news = new ArrayList<FootballNews>();
for (Element newsList : homeNews.select("li")) {
String newsTitle = newsList.text();
String newsUrl = newsList.getElementsByTag("a").get(0).absUrl("href");
System.out.println(newsTitle);
System.out.println(newsUrl);
System.out.println();
news.add(new FootballNews(newsTitle, newsUrl));
}
System.out.println(news);
System.out.println("-------------------------------------");
} catch (IOException e) {
e.printStackTrace();
}
}
}
package model.domain;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
@Getter
@Setter
@AllArgsConstructor
@ToString
public class FootballNews {
private String title;
private String url;
}
Socket에 대한 부분은 정말 많이 사용될 것으로 보임
Thread pool은 구조를 이해하는 것으로 끝나서 사용되는 개념을 다시 공부하면 좋을 것 같음
서버와 클라이언트로 나뉘게 되므로 전체적인 로직?을 설계, 이해 할 줄 알아야 될 것 같음
Thread는 정말 까다로운 것 같다.
소켓으로 클라이언트를 3개 까지 늘렸더니 정말 채팅을 구현한 것 같았다.
Thread pool 이나 Network 기본 지식을 좀 더 찾아봐야겠다.
https://xn--3e0bx5euxnjje69i70af08bea817g.xn--3e0b707e/
한국인터넷정보센터 인데 나름 배경지식들을 찾을 수 있었다.