JongDDA의 한걸음 한걸음씩
[Java] 네트워킹 본문
네트워킹(networking)이란?
두 대 이상의 컴푸터를 케이블로 연결하여 네트워크를 구성하는 것을 말한다. 네트워킹의 개념은 컴퓨터들을 서로 연결하여 데이터를 손쉽게 주고받거나 또는 자원을 함께 공유하고자 하는 노력에서 시작되었다. 자바에서는 제공되는 java.net 패키지를 사용하면 네트워크 어플리케이션의 데이터 통신 부분을 쉽게 작성이 가능하다.
클라이언트와 서버(client & server)
'클라이언트/서버' 는 컴퓨터간의 관계를 역할로 구분하는 개념이다. 서버(server)는 서비스를 제공하는 컴퓨터이고, 클라이언트(client)는 서비스를 사용하는 컴퓨터가 된다. 서비스는 서버가 클라이턴트로부터 요청받은 작업을 처리하여 그 결과를 제공하는 것을 뜻하며 서버가 제공하는 서비스의 종류에 따라 파일 서버(file server), 메일 서버(mail server), 어플리케이션 서버(application server) 등이 있다. 서버에 접속하는 클라이언트의 수에 따라 하나의 서버가 여러가지 서비스를 제공하기도 하고 하나의 서비스를 여러 대의 서버로 제공하기도 한다.
서버가 서비스를 제공하기 위해서는 서버 프로그램이 있어야 하고 클라이언트가 서비스를 제공받기 위해서는 서버 프로그램과 연결할 수 있는 클라이언트 프로그램이 있어야한다. 일반 PC의 경우 주로 서버에 접속하는 클라이언트 역할을 수행하지만, FTP Serv-U와 같은 FTP 서버프로그램이나 Tomcat과 같은 웹서버 프로그램을 설치하면 서버역할도 수행할 수 있다.
네트워크를 구성할 때 전용서버를 두는 것을 서버기반모델(server-based model)이라 하고 별도의 전용서버없이 각 클라이언트가 서버역할을 동시에 수행하는 것을 P2P모델(peer-to-peer)이라 한다.
서버기반 모델(server-based model) | P2P모델(peer-to-peer) |
안정적인 서비스의 제공이 가능하다 공유 데이터의 관리와 보안이 용이하다 서버구축비용과 관리비용이 든다. |
서버구축 및 운용비용을 절감할 수 있다. 자원의 활용을 극대화 할 수 있다. 자원의 관리가 어렵다 보안이 취약하다 |
IP 주소 (IP address)
IP주소는 컴퓨터(호스트, host)를 구별하는데 사용되는 고유한 값으로 인터넷에 연결된 모든 컴퓨터는 IP를 갖는다. IP 주소는 4 byte(32 bit)의 정수로 구성되어 있다. IP주소는 다시 네트워크 주소와 호스트 주소로 나눌 수 있는데 32 bit(4 byte)의 IP주소 중에서 네트워크 주소와 호스트 주소가 각각 몇 bit를 차지하는 지는 네트워크를 어떻게 구성하였는지에 따라 달라진다. 그리고 서로 다른 두 호스트의 IP주소의 네트워크 주소가 같다는 것은 두 호스트가 같은 네트워크에 포함되어 있다는 것을 의미한다.
InetAddress 클래스
자바에서는 IP주소를 다루기 위한 클래스로 InetAddress를 제공하며 다음과 같은 메서드가 정의되어 있다.
메서드 | 설명 |
byte[] getAddress() | IP주소를 byte배열로 반환한다. |
static InetAddress[] getAllByName(String host) | 도메인명(host)에 지정된 모든 호스트의 IP주소를 배열에 담아 반환한다. |
static InetAddress[] getByAddress(byte[] addr) | byte배열을 통해 IP주소를 얻는다 |
static InetAddress[] getByName(String host) | 도메인명(host)을 통해 IP주소를 얻는다. |
String getCanonocallHostName() | FQDN(fully qualified domain name)을 반환한다. |
Sgring getHostAddress() | 호스트의 IP주소를 반환한다. |
String getHostName() | 호스트의 이름을 반환한다. |
static InetAddress getLocalHost() | 지역 호스트의 IP주소를 반환한다. |
boolean isMulticastAddress() | IP주소가 멀티케스트 주소인지 알려준다. |
boolean isLoopbackAddress() | IP주소가 loopback 주소(127.0.0.1)인지 알려준다. |
URL(Uniform Resource Locator)
URL은 인터넷에 존재하는 여러 서버들이 제공하는 자원에 접근할 수 있는 주소를 표현하기 위한 것으로 '프로토콜://호스트명:포트번호/경로명/파일명?쿼리스트링#참조'의 형태로 이루어져 있다.
프로토콜 : 자원에 접근하기 위해 서버와 통신하는데 사용되는 통신규약(
호스트명 : 자원을 제공하는 서버의 이름
포트번호 : 통신에 사용되는 서버의 포트번호
경로명 : 접근하려는 자원이 저장된 서버상의 위치
파일명 : 접근하려는 자원의 이름
쿼리 : URL에서 '?' 이후의 부분
참조 : URL에서 '#' 이후의 부분
자바에서는 URL을 다루기 위한 클래스로 URL클래스를 제공하며 어플리케이션과 URL간의 통신연결을 나타내는 클래스의 최상위 클래스인 URLCOnnection 클래스를 제공한다.
소켓(socket)프로그래밍
소켓 프로그래밍은 소켓을 이용한 통신 프로그래밍을 뜻하는데, 소켓(socket)이란 프로세스간의 통신에 사용되는 양쪽 끝단(endpoint)을 의미한다. 자바에서는 java.net패키지를 통해 소켓 프로그래밍을 지원하는데, 소켓통신에 사용되는 프로토콜에 따라 다른 종류의 소켓을 구현하여 제공한다.
TCP와 UDP
TCP/IP 프로토콜은 이기종 시스템간의 통신을 위한 표준 프로토콜로 프로토콜의 집합이다. TCP와 UDP 모두 TCP/IP 프로토콜에 포함되어 있으며, OSI 7계층의 전송계층에 해당하는 프로토콜이다.
TCP 와 UDP는 전송 방식이 다르며 어플리케이션의 특징에 따라 적절한 프로토콜을 선택하여 사용해야된다.
항목 | TCP | UDP |
연결방식 | -연결기반(connection-oriented) -연결 후 통신 -1:! 통신방식 |
-비연결기반(onnectionless-oriented) -연결없이 통신 -1:1, 1:n n:n 통신방식 |
특징 | -데이터의 경계를 구분안함(byte-stream) -신뢰성 있는 데이터 전송(데이터의 전송순서가 보장, 데이터의 수신여부를 확인, 패킷 관리필요 없음) -UDP보다 전송속도 느림 |
-데이터의 경계를 구분함(datagram) -신뢰성 없는 데이터 전송 -데이터의 전송순서가 바뀔 수 있음 -데이터의 수신여부를 확인안함(데이터가 손실되어도 알 수 없음) -패킷을 관리해주어야 함 -TCP보다 전송속도가 빠름 |
관련 클래스 | Socket ServerSocket |
DatagramSocket DatagramPacket MulticastSocket |
TCP 소켓 프로그래밍
TCP 소켓 프로그래밍은 클라이언트와 서버간의 일대일 통신이다. 먼저 서버 프로그램이 실행되어 클라이언트 프로그램의 연결요청을 기다리고 있어야 한다. 서버 프로그램과 클라이언트 프로그램간의 통신과정은 단계별로 다음과 같다.
1. 서버 프로그램에서는 서버소켓을 사용해서 서버 컴퓨터의 특정 포트에서 클라이언트의 연결요청을 처리할 준비를 한다.
2. 클라이언트 프로그램은 접속할 서버의 IP주소와 포트 정보를 가지고 소켓을 생성해서 서버에 연결을 요청한다.
3. 서버소켓은 클라이언트의 연결요청을 받으면 서버에 새로운 소켓을 생성해서 클라이언트 소켓과 연결되도록 한다.
4. 클라이언트 소켓과 새로 생성된 서버의 소켓은 서버소켓과 관계없이 일대일 통신을 한다.
Socket 과 ServerSocket
서버소켓은 소켓간의 연결만 처리하고 실제 데이터는 소켓들끼리 서로 주고 받는다. 소켓들이 데이터를 주고받는 연결통로는 바로 입출력스트림이다.
소켓은 두 개의 스트림, 입력스트림과 출력스트림을 가지고 있으며, 이 스트림들은 연결된 상대편 소켓의 스트림들과 교차연결된다. 한 소켓의 입력스트림은 상대편 소켓의 출력스트림과 연결되고, 출력스트림은 입력스트림과 연결된다. 그래서 한 소켓에서 출력스트림으로 데이터를 보내면 상대편 소켓에서는 입력스트림으로 받게 된다.
자바에서는 TCP를 이용한 소켓프로그래밍을 위해 Socket과 ServerSocket클래스를 제공하며 다음과 같은 특징을 갖는다.
Socket : 프로세스간의 통신을 담당하며, InputStream과 OutputStream을 가지고 있다. 이 두 스트림을 통해 프로세스간의 통신(입출력)이 이루어진다.
ServerSocket : 포트와 연결되어 외부의 연결요청을 기다리다 연결요청이 들어오면, Socket을 생성해서 소켓과 소켓간의 통신이 이루어지도록 한다. 한 포트에 하나의 ServerSocket만 연결할 수 있다. (프로토콜이 다르면 같은 포트를 공유할 수 있다.)
예제) 채팅 서버와 클라이언트 만들기
채팅 클라이언트
package day19;
import java.awt.BorderLayout;
import java.awt.CardLayout;
import java.awt.Font;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.net.Socket;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.JTextField;
public class ChatClient extends JFrame implements ActionListener, KeyListener, Runnable {
Socket s;
PrintWriter pw;
BufferedReader br;
JPanel jpCenter, jpSouth;
JPanel jp3;
JLabel jlIp, jlPort;
JTextField jtfIp, jtfPort;
JButton jbtnConnect, jbtnExit;
CardLayout cl;
JScrollPane jsp;
JTextArea jta;
JTextField jtf;
JButton jbtnSubmit;
String ip, port;
public ChatClient() {
// 선언
jta = new JTextArea();
jtf = new JTextField(25);
jbtnSubmit = new JButton("전송");
jsp = new JScrollPane(jta, JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED,
JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED);
jpCenter = new JPanel();
jpSouth = new JPanel();
jp3 = new JPanel();
jlIp = new JLabel("IP");
jlPort = new JLabel("Port");
jtfIp = new JTextField("172.30.1.31");
jtfPort = new JTextField("5000");
jbtnConnect = new JButton("Connect");
jbtnExit = new JButton("종료");
jp3.setLayout(null);
jlIp.setBounds(50, 100, 100, 50);
jlPort.setBounds(50, 300, 100, 50);
jtfIp.setBounds(250, 100, 150, 50);
jtfPort.setBounds(250, 300, 150, 50);
jbtnConnect.setBounds(150, 450, 150, 50);
jbtnExit.setBounds(350, 450, 150, 50);
jp3.add(jlIp);
jp3.add(jlPort);
jp3.add(jtfIp);
jp3.add(jtfPort);
jp3.add(jbtnConnect);
jp3.add(jbtnExit);
// 폰트Ʈ
Font f = new Font("굴림체", Font.BOLD, 25);
jta.setFont(f);
jbtnSubmit.setFont(f);
jtf.setFont(f);
jpCenter.setLayout(new BorderLayout());
// 리스너
jbtnSubmit.addActionListener(this);
jbtnConnect.addActionListener(this);
jbtnExit.addActionListener(this);
jtf.addKeyListener(this);
// 추가
jpCenter.add(jsp);
jpCenter.add(jsp, "Center");
jpSouth.add(jtf);
jpSouth.add(jbtnSubmit);
jpCenter.add(jpSouth, "South");
cl = new CardLayout();
// 현재 프레임에 배치관리자를 CardLayout으로 변경
setLayout(cl);
add(jp3, "Login");
add(jpCenter, "chat");
// 카드레이아웃에서 컨텐츠 패널에 어떤 창을 붙일지 결정
cl.show(getContentPane(), "Login");
setDefaultCloseOperation(EXIT_ON_CLOSE);
setTitle("ChatClient");
setBounds(100, 100, 600, 700);
setVisible(true);
}
public static void main(String[] args) {
ChatClient cc = new ChatClient();
}
@Override
public void actionPerformed(ActionEvent e) {
// String msg = jtf.getText();
// jta.append("[ip] : "+ msg + "\n");
// jtf.setText("");
Object obj = e.getSource();
if (obj == jbtnConnect) {
ip = jtfIp.getText();
port = jtfPort.getText();
// 채팅기능
vchatting();
cl.show(getContentPane(), "chat");
} else if (obj == jbtnExit) {
System.exit(0);
} else if (obj == jbtnSubmit) {
Send();
}
}
private void vchatting() {
// 현재 클라이언트 프로그램을 여러개 띄울수 있도록 멀티 쓰레드로 작성
Thread th = new Thread(this);
th.start();
}
@Override
public void run() {
// 동시에 처리할 코드
// 채팅
try {
s = new Socket(ip, Integer.parseInt(port));
br = new BufferedReader(new InputStreamReader(s.getInputStream()));
pw = new PrintWriter(new BufferedWriter(new OutputStreamWriter(s.getOutputStream())));
String msg = null;
while (true) {
msg = br.readLine();
jta.append(msg + "\n");
}
} catch (Exception e) {
e.printStackTrace();
}
}
@Override
public void keyTyped(KeyEvent e) {
}
@Override
public void keyPressed(KeyEvent e) {
int key = e.getKeyCode();
// 엔터 시 동작
if (key == KeyEvent.VK_ENTER) {
Send();
}
}
@Override
public void keyReleased(KeyEvent e) {
// TODO Auto-generated method stub
}
private void Send() {
// 텍스트 필드에 있는 내용을 가져오기
String msg = jtf.getText();
// 쓰기 객체를 통해서 서버에 전달
jta.append("me : " + msg + "\n");
pw.println(msg);
pw.flush();
// JTextFiled 초기화
jtf.setText("");
jtf.requestFocus();
}
}
채팅서버
package day19;
import java.awt.Font;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.ArrayList;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JScrollBar;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
public class ChatServer extends JFrame implements ActionListener {
JTextArea jta;
JButton jbtnExit;
JScrollPane jsp;
JPanel jpCenter;
ArrayList<MServer> list = new ArrayList<MServer>();
ServerSocket ss;
ChatServer() {
// 창 생성
jta = new JTextArea();
jbtnExit = new JButton("종료");
jsp = new JScrollPane(jta, JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED,
JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED);
// 폰트
Font f = new Font("굴림체", Font.BOLD, 20);
jta.setFont(f);
jbtnExit.setFont(f);
// 종료버튼동작
jbtnExit.addActionListener(this);
// 추가
add(jsp, "Center");
add(jbtnExit, "South");
setDefaultCloseOperation(EXIT_ON_CLOSE);
setTitle("ChatServer");
setBounds(100, 100, 600, 600);
setVisible(true);
vChatStart();
}
private void vChatStart() {
try {
// 서버소켓 5000번 포트 결합
ss = new ServerSocket(5000);
// 연결 대기
while (true) {
// 클라이언트 소켓과 통신할 새로운 소켓 생성
Socket client = ss.accept();
MServer ms = new MServer(client);)
ms.start();
}
} catch (IOException e) {
}
}
class MServer extends Thread {
Socket client;
BufferedReader br;
PrintWriter pw;
String ip;
MServer(Socket client) {
this.client = client;
ip = client.getInetAddress().getHostAddress();
try {
// 입력스트림
br = new BufferedReader(new InputStreamReader(client.getInputStream()));
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
try {
// 출력 스트림
pw = new PrintWriter(new BufferedWriter(new OutputStreamWriter(client.getOutputStream())));
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
@Override
public void run() {
try {
while (true) {
String msg = null;
msg = br.readLine();
// 닉네임 설정
String[] mList = msg.split("/");
if (mList[0].equals("c")) {
ip = mList[1];
} else {
jta.append("[ " + ip + " ]" + msg + "\n");
// 모든 접속자에게 이 메세지 전달
broadcast("[ " + ip + " ]" + msg + "\n");
// pw.println(msg);
// pw.flush();
// 자동 스크롤
JScrollBar sb = jsp.getVerticalScrollBar();
int position = sb.getMaximum();
sb.setValue(position);
}
}
} catch (IOException e) {
// 오류 시 제거
list.remove(this);
}
}
}
private void broadcast(String msg) {
// ArrayList 안에 있는 객체를 하나씩 꺼내서 전송
System.out.println("list : " + list);
for (MServer x : list) {
x.pw.println(msg);
x.pw.flush();
}
}
public static void main(String[] args) {
ChatServer cs = new ChatServer();
}
@Override
public void actionPerformed(ActionEvent e) {
System.exit(0);
}
}
위 영상은 클라이언트 하나로 채팅 서버를 실행시켰지만 멀티쓰레드로 채팅프로그램을 구현하였기에 여러 ip주소의 클라이언트로도 채팅 서버가 구동될 것이다.
'개발 > Java' 카테고리의 다른 글
[Java] 날짜와 시간, Calendar 클래스 (0) | 2021.08.27 |
---|---|
[Java] 예외처리(exception handling) (0) | 2021.08.23 |
[Java] java.lang 패키지 & util classes (0) | 2021.08.21 |
[Java] 내부 클래스(Inner class) (0) | 2021.08.20 |
[Java] 입출력(I/O) (0) | 2021.08.19 |