자바 네트워크 네트워크 입출력 (1) - TCP
1. 네트워크 기초
- 네트워크는 여러 컴퓨터들을 통신 회선으로 연결하는 것
- LAN은 가정,회사,건물,특정 영역에 존재하는 컴퓨터 연결
- WAN은 LAN을 연결한 것이다. (WAN이 흔히 말하는 인터넷이다)
- LAN과 LAN을 라우터가 연결
- 라우터와 LAN은 스위치로 연결
1.2 서버와 클라이언트
- 네트워크에서 유무선으로 컴퓨터가 연결되어 있다면, 실제로 데이터 주고받는 행위는 프로그램들이 한다.
- 서비스를 제공하는 프로그램을 서버, 서비스 요청하는 프로그램을 클라이언트라 부른다.
- 인터넷을 통해서 두 프로그램이 통신하기 위해서는 클라이언트가 서비스 요청, 서버는 처리 결과 응답으로 제공한다.
1.3 ip주소
- 컴퓨터의 고유주소이다. 컴퓨터마다 받는 것이 아니라, 네트워크 어답터(LAN 카드)마다 할당
- 두개의 어댑터가 연결되어 있다면, 두 개의 IP주소를 할당받을 수 있다.
- 연결할 상대의 IP주소도 알아야 통신할 수 있다.
- 프로그램은 DNS서버를 이용해서 컴퓨터 IP 주소를 검색한다.
- DNS는 도메인 이름으로 IP를 등록하는 저장소이다.
- 웹 브라우저는 웹 서버와 통신하는 대표적인 클라이언트 프로그램으로, 사용자가 입력한 도메인 이름으로 DNS서버로 ip주소를 검색해서 웹서버와 연결해서 웹 페이지를 받는다.
* 윈도우 ip설정에 보면, DNS와 ip주소는 자동으로 부여된다. (Switch가 알아서 자동 생성)
동일한 컴퓨터는 대부분 같은 ip를 부여받는데, 이는 switch가 pc의 물리적 주소로(Map address) 구분할 수 있기 때문이다. (어뎁터, LAN카드별 고유 식별번호)
*네트워크 통신을 위해서는 외부 IP도 필요하다 (LAN과 LAN, 라우터 IP 등 인터넷상에서 사용하는 IP필요)
* ip주소나 DNS서버는 네트워크 관리자로부터 받아온다. -> 집에 있는 네트워크 장비는 대부분 스위치로 이를 통해서 자동으로 부여받은 것
1.4 Port번호
- 한대의 컴퓨터에 다양한 서버 프로그램이 실행될 수 있다.
- 클라이언트는 서버 ip뿐 아니라, 서버 프로그램에 할당된 port번호도 알아야 연결될 수 있다.
- port는 운영체제가 관리하는 서버 프로그램의 연결 번호이다. 서버는 특정 포트번호에 바인딩한다.
- 클라이언트도 서버에서 보낸 정보를 얻기 위해선 port가 필요한데, 서버 처럼 고정적인 port번호에 바인딩 되는 것이 아니라, 운영체제에 의해 자동으로 부여되는 번호 사용한다.
- 클라이언트 포트번호는 서버로 요청을 보낼 때 함께 전송되어 서버가 클라이언트로 데이터 보낼 때 사용된다.
2. IP주소 얻기
- 자바는 IP주소를 InetAddress로 표현한다.
- 이를 통해 local, 외부, DNS를 통한 ip를 얻을 수 있다.
public class IPget{
public static void main(String[] args){
try{
//로컬 ip얻기
InetAddress local = InetAddress.getLocalHost();
InetAddress[] iaArr = InetAddress.getAllByName("www.naver.com");
for(InetAddress remote : iaArr){
//..
}
}catch(Exception e){
}
}
}
3. TCP 네트워킹
- IP주소로 프로그램들이 통신할 때는 약속한 데이터 전송 규약이 있다. 이를 전송용 프로토콜이라고 부른다.
- 인터넷 전송용 프로토콜은 TCP,UDP가 있다.
- TCP는 연결형 프로토콜, 상대방이 연결된 상태에서 데이터 주고 받음
- 서버가 연결을 수락하면 통신 회선이 고정되고, 데이터는 고정 회선을 통해 전달됨
- TCP는 보낸 데이터가 순서대로 전달되며 손실이 발생하지 않는다.
- TCP는 웹 브라우저(클라이언트 프로그램)가 웹 서버에 연결할 때 사용된다.
- TCP 네트워킹을 위해 ServerSocket과 Soket 클래스를 제공한다.
- ip/port는 컴퓨터와 프로그램 구분하기 위한 번호
- 실제 연결, 데이터 전송은 Soket을 사용
- 클라이언트에서 서버로 요청을 보내면 서버소켓이 이를 받고, 소켓을 생성 서버에서 생성된 소켓을 통해 통신한다.
3.1 TCP서버
- 서버소켓 생성
ServerSocket serverSocket = new ServerSocket(50001);
//서버소켓 생성 후에 바인드
ServerSocket serverSocket = new ServerSocket();
serverSocket.bind(new InetSocketAddress(50001));
// 여러 개의 ip가 컴퓨터에 할당되어 있는 경우, 특정 ip에서만 서비스하고 싶을 때
ServerSocket serverSocket = new ServerSocket();
serverSocket.bind(new InetSocketAddress("ip주소",50001));
- 만약 Port 이미 다른 프로그램에서 사용 중이라면 BindException이 발생
Socket socket = serverSocket.accept();
InetSocketAddress isa = (InetSocketAddress) socket.getRemoteSocketAddress();
String clientIp = isa.getHostName();
String protNo = isa.getPort();
//소켓 종료
serverSocket.close();
- 서버 프로그램
public class TCPServerExam {
private static ServerSocket serverSocket;
public static void main(String[] args) {
System.out.println("---------------------------------");
System.out.println("서버를 종료하려면, q 혹은 Q 입력하고 Enter를 입력하셔요");
//TCPServer시작
startServer();
Scanner sc = new Scanner(System.in);
while(true){
String input = sc.nextLine();
if(input.toLowerCase().equals("q")) {
break;
}
}
sc.close();
//서버종료
stopServer();
}
public static void startServer(){
//작업스레드 정의
Thread thread = new Thread(){
@Override
public void run(){
//서버소켓+바인딩
try {
serverSocket = new ServerSocket(50001);
System.out.println("서버시작");
while(true){ //여러 클라이언트 연결요청받기 위해 계속 기다림
//하나의 소켓은 하나의 연결요청 기다림
System.out.println("[서버] 연결요청을 기다림\n");
Socket soket = serverSocket.accept();
InetSocketAddress isa = (InetSocketAddress)soket.getRemoteSocketAddress();
System.out.println("[서버]"+isa.getHostString()+"와 연결 요청 수락");
//포트번호는 뭐 별거아닌데, ip주소는 중요한 의미가 있음! 따라서
//ip주소는 저장할 필요가 있다. 중요함 서버입장에서
//연결끊기
soket.close(); //서버소켓과 마찬가지로 종료해줘야함 단, 서버나 클라이언트 소켓 둘중하나만 끊어도됨
System.out.println("[서버]"+isa.getHostName()+"와 연결 끊음");
}
} catch (IOException e) {
System.out.println(e.getMessage());
//소켓익셉션은 io익셉션임
//accept() 때문에 있음 서버소켓 종료시
}
}
};
thread.start();
}
public static void stopServer(){
try {
serverSocket.close();
} catch (IOException e) {
}
}
}
*서버소켓을 생성하고 accept()하는 부분은 쓰레드로 처리해야한다. 이는 서버를 키고 끄는 것과 서버에서 클라이언트 요청 받는 것 동시에 진행되야해서그렇다.
- TCP 클라이언트
Socket socket = new Socket("서버ip",포트번호);
Socket socket = new Socket(new InetAddress.getByName("domainName",포트번호));
- 연결 요청시 두 가지 예외가 발생할 수 있다. UnknownHostException은 ip 주소나 port번호가 잘못 표기
- IOException 제공된 IP와 Port번호로 연결할 수 없을 때 발생
try{
Socket socket = new Socket("서버ip",포트번호);
}catch(UnknownHostException e){
}catch(IOException e){
}
- 클라이언트 프로그램
public class TCPClientExam {
public static void main(String[] args) {
try {
//socket 열기 생성동시에 연결요청
Socket socket = new Socket("localhost",50001);
System.out.println("[클라이언트]연결성공");
//socket닫기
socket.close();
System.out.println("[클라이언트] 연결종료");
} catch (UnknownHostException e) {
//IP나 포트 표기방법 틀림
System.out.println("UnknownEx = "+e.toString());
}catch (IOException e){
//ip나 포트 없음
System.out.println("ioEx = "+e.toString());
}
}
}
> 입출력 스트림으로 데이터 주고 받기
- 클라이언트 연결 요청을 하고 서버가 연결 요청을 수락했다면, Socket을 통해 입출력 스트림을 얻을 수 있다.
InputStream is = socket.getInputStream();
OutputStream os = socket.getOutputStream();
//데이터 쓰기
S;tring data = "보낼데이터"
byte[] bytes = data.getBytes("UTF-8");
os.write(bytes);
os.flush();
//데이터 쓰기 with DataOutputStream
DataOutputStream dos = new DataOutputStream(socket.getOutputStream());
dos.writeUTF(bytes);
dos.flush();
//데이터 읽기
byte[] bytes = new byte[1024];
int count = is.read(bytes);
String message =new String(bytes,0,count,"UTF-8");
//데이터 읽기 with DataInputStream
DataInputStream dis = new DataInputStream( socket.getInputStream());
String data = dis.readUTF();
- 클라이언트
try {
//..
//데이터보내기 with 일반
String message = "나는 잡아가 조아";
byte[] bytes = message.getBytes("UTF-8");
OutputStream os = socket.getOutputStream();
os.write(bytes);
os.flush();
System.out.println("[클라이언트] 메시지 보냄"+message);
//데이터 받기 with 일반
InputStream is = socket.getInputStream();
bytes = new byte[1024];
int count = is.read(bytes);
String recivemessage = new String(bytes,0,count,"UTF-8");
System.out.println("[클라이언트] 메시지 받음 "+recivemessage);
//데이터 쓰기
// String message = "나는 잡아가 조아";
// DataOutputStream dos = new DataOutputStream(socket.getOutputStream());
// dos.writeUTF(message);
// dos.flush();
//데이터 읽기
// DataInputStream dis = new DataInputStream(socket.getInputStream());
// String message = dis.readUTF();
//..
}
-서버
//서버연결
//..
//데이터 받기 (기본 inputStream)
InputStream is = soket.getInputStream();
byte[] bytes = new byte[1024];
int readByteCount = is.read(bytes);
String message = new String(bytes,0,readByteCount,"UTF-8");
//데이터 보내기(기본 output)
OutputStream os = soket.getOutputStream();
bytes = message.getBytes("UTF-8");
os.write(bytes);
os.flush();
System.out.println("[서버] 받은 데이터 다시 보냄 "+message);
//데이터읽기
// DataInputStream dis = new DataInputStream(soket.getInputStream());
// String message = dis.readUTF();
//데이터보내기
// DataOutputStream dos = new DataOutputStream(soket.getOutputStream());
// dos.writeUTF(message);
// dos.flush();
//연결 해제
socket.close();
참고자료: 이것이 자바다 (신용권,임경균)