컴퓨터에 고유한 주소가있다. 이것이 바로 IP(Internet Protocol)다.
네트워크 어댑터(LAN카드)마다 할당되는데, 한개의 컴퓨터에 두개의 랜카드가 장착되어있으면 2개의 IP주소를 할당할수있다.
연결할 상대방의 IP주소를 모른다면 전화번호를 모르는것과 같은 상황이며, 프로그램은 DNS(Domain Name System)을 이용하여 연결할 컴퓨터의 IP주소를 찾는다.
서비스를 제공하는 대부분의 서버는 도메인이름을 가지고있고,
ex) www.naver.com 이라는 도메인이름으로 IP주소 222.122.195.5 를 등록해 놓는다.
사용자는 IP주소보단 도메인 이름을 더 쉽게 기억할수있고, 우리가 흔히보는 광고들만해도 도메인 이름을 홍보하지 IP주소를 홍보하진 않는다.
웹브라우저는 사용자가 입력한 도메인이름을 DNS에 검색하여 IP를 얻은다음 해당IP를 가진 서버로 연결한다.
한대의 컴퓨터에서 하나의 IP를 가지고 웹서버, DBMS, FTP서버등 동시에 실행될수있다. 이경우 클라이언트는 어떤 서버와 통신해야할지 결정해야한다.
IP는 컴퓨터의 네트워크 어댑터까지만 갈수있는 정보이기때문에 컴퓨터에서 실행하는 서버를 선택하기위해서 추가적인 정보가 필요한데, 이 정보가 포트번호이다.
서버가 시작할때는 고정적인 포트번호를 가지고 실행하는데 이것을 포트바인딩이라고 한다.
ex) 웹서버는 기본적으로 80번과 바인딩, FTP서버는 21번과 바인딩한다.
클라이언트가 웹서버로 연결하려면 80번으로 연결요청을 해야하고 FTP서버에 연결하려면 21번에 요청해야한다.
클라이언트 입장에서는 서버에서 보낸정보를 받기위해 포트번호가 필요한데, 이때는 서버같이 고정적인 포트번호가아닌 운영체제가 자동으로 부여하는 동적 포트번호를 사용한다.
TCP(Transmission Control Protocol)는 연결지향적 프로토콜이다.
연결이 된상태에서 데이터를 주고받는 프로토콜인것,
TCP는 데이터를 정확하고 안정적으로 전달한다.
단점은 반드시 연결이 되어있어야한다는점 ( 시간이 가장많이걸림), 통신선로가 최단선이 아닐경우 상대적으로 UDP보다 느릴수있다는점
자바에서는 TCP네트워킹을 위해 Socket 클래스(연결된 클라이언트와 통신을 담당하는 클래스)와 ServerSocket(클라이언트의 연결요청을 기다리면서 연결 수락을 담당하는 클래스) 클래스를 제공하고있다.
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;
public class ServerExample {
public static void main(String[] args) {
// TODO Auto-generated method stub
ServerSocket serverSocket = null;
try {
serverSocket = new ServerSocket();
serverSocket.bind(new InetSocketAddress("localhost", 5001)); // 5001번 포트를 바인딩한다.
while(true) {
System.out.println("연결기다림");
Socket socket = serverSocket.accept(); // 클라이언트의 연결 수락
InetSocketAddress isa = (InetSocketAddress) socket.getRemoteSocketAddress();
System.out.println("[연결 수락함]" + isa.getHostName());
}
}catch(Exception e) {}
if(!serverSocket.isClosed()) { /// ServerSocket이 닫혀짔지않으면
try {
serverSocket.close(); // ServerSocket 을 닫자 (언바인딩 => 다른 프로그램에서 패당포트를 재사용할수있다.)
}catch(IOException e1) {}
}
}
}
InetSocketAddress 객체는 IP와 포트정보를 리턴하는 메소드들이 있다.
String getHostName() : 클라이언트 IP리턴
int getPort(): 클라이언트 포트번호 리턴
String toString() : IP:포트번호 형태의 문자열 리턴
try{
Socket socket = new Socket("localhost", 5001); // 첫번째방법
Socket socket = new Socket(new InetSocketAddress("localhost", 5001); // 두번째방법
}catch( UnKnownHostException e) {
//IP표기 잘못된경우
}catch(IOException e) {
//해당포트의 서버에 연결할수없는경우
}
로컬 pc의 5001포트에 연결 요청하는 코드
=> 연결 요청을 하려면 서버의 IP주소와 바인딩 포트번호를 제공하면 된다.
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;
public class ServerExample {
public static void main(String[] args) {
// TODO Auto-generated method stub
ServerSocket serverSocket = null;
try {
serverSocket = new ServerSocket();
serverSocket.bind(new InetSocketAddress("localhost", 5001)); // 5001번 포트를 바인딩한다.
while(true) {
System.out.println("연결기다림");
Socket socket = serverSocket.accept(); // 클라이언트의 연결 수락
InetSocketAddress isa = (InetSocketAddress) socket.getRemoteSocketAddress();
System.out.println("[연결 수락함]" + isa.getHostName());
}
}catch(Exception e) {}
if(!serverSocket.isClosed()) { /// ServerSocket이 닫혀짔지않으면
try {
serverSocket.close(); // ServerSocket 을 닫자 (언바인딩 => 다른 프로그램에서 패당포트를 재사용할수있다.)
}catch(IOException e1) {}
}
}
}
반복적으로 accept() 메서드를 호출해서 다중 클라이언트 연결을 수락하는 코드
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.Socket;
public class ClientExample {
public static void main(String[] args) {
Socket socket = null;
try {
socket = new Socket(); // 소켓 생성
System.out.println("연결 요청");
socket.connect(new InetSocketAddress("localhost", 5001)); // 연결 요청
System.out.println("연결 수락");
}catch(Exception e) {
}
if(!socket.isClosed()) { // 연결되어있지않다면
try {
socket.close();
}
catch(IOException e1) {}
}
}
}
localhost 5001 포트로 연결을 요청하는 코드 , connect()메소드가 정상적으로 리턴하면 연결이 성공한것이다.
클라이언트가 연결요청하고 서버가 연결 수락했으면, 양쪽의 socket객체로부터 각각 입력스트림, 출력스트림을 얻을수있다.
그리고 이렇게 주고받은과정이 TCP 3-way Handshake 다.
연결을위해 ServerSocket 의 accept() 를 실행하거나 서버 연겨요청을 위해 Socket 생성자 또는 connetct()를 실행할경우 해당작업이 완료될때까지 블로킹이 된다.
결론적으로, ServerSocket 과 Socket은 동기(블로킹) 방식으로 구동된다.
만약 서버를 실행시키는 main스레드가 직접 입출력을 담당하게되면 입출력이 완성될떄까지 다른작업을 할수가없다.
따라서, accept(), connect(), read(), write() 는 별도의 작업 스레드를 생성해서 병렬적 처리하는것이 좋다.
그림과같이 처리핤겠다, 하지만 위그림과같이 스레드로 병렬처리를 수천개의 클라이언트가 동시에 연결하면 서버성능이 급격히 저하되고 다운된다.
스레드풀을 사용해야한다.
1~2 : 클라이언트가 연결요청을 하려면 서버의 스레드풀에서 연결수락을 하고 Socket 을 생성한다.
3~5 클라이언트가 작업처리요청을 하면 서버의 스레드풀에서 요청을 처리하고 응답을 클라이언트로 보낸다.
스레드풀은 스레드 수를 제한적으로 사용하기떄문에 서버성능이 완만히 저하된다.