티스토리 뷰
자바 ! 소켓프로그래밍 (Server, Client, Socket, TCP, UDP.Thread, BroadCasting)
나는연어다 2017. 5. 21. 23:01구분 | 설명 |
서버(Server) |
여러명의 사용자들에게 서비스를 제공하는 컴퓨터 |
클라이언트(Client) |
서비스를 요청해서 사용하는 컴퓨터 |
소켓 프로그래밍이란? 소켓을 이용한 통신 프로그램이다.
* 소켓(Socket): 프로세스 간의 통신에 사용되는 양쪽 끝단을 의미한다.
* java.net 패키지를 통해 소켓프로그래밍을 지원하며 TCP와 UDP를 이용한 소켓프로그래밍이 대표적이다.
항목 | TCP |
UDP |
연결방식 | 연결기반(Connection-Oriented) 연결 후 통신, 1:1 통신 방식 |
비연결기반(Connectionless-Oriented) 연결없이 통신(소포), 1:1, 1:n, n:n 통신 방식 |
특징 | - 데이터의 경계를 구분하지 않는다 (byte-stream) - 신뢰성있는 데이터 전송 - 데이터의 전송순서가 보장된다 - 수신여부를 확인한다.(손실되면 재전송) - 패킷을 관리할 필요가 없다. - UDP보다 전송속도가 느리다. |
- 데이터의 경계를 구분한다(Datagram) - 신뢰성이 없는 데이터 전송 - 데이터의 전송순서가 바뀔 수 있다. - 수신여부를 확인 안한다.(데이터가 손실되어도 알 수 없다.) - 패킷을 관리해줘야 한다. - TCP보다 전송속도가 빠르다. |
관련 클래스 | Socket, ServerSocket |
DatagramSocekt, DatagramPakcet, MulticastSocket |
* TCP 소켓 프로그래밍
- 서버(Server)와 클라이언트(Client)간의 1:1 소켓(Socket) 통신이다.
- 서버가 먼저 실행되어 클라이언트의 연결요청을 기다리고 있어야 한다.
- Socket: 프로세스간의 통신을 담당하며, InputStream과 OutputStream을 가지고 있다.
이 두 스트림을 통해 프로세스간의 통신(입출력)이 이루어진다.
- ServerSocket: 포트와 연결(bind)되어 외부의 연결요청을 기다리다 연결요청이 들어오면 Socket을 생성해서
소켓과 소켓간의 통신이 이루어지도록 한다.
한 포트의 하나의 ServerSocket만 연결할 수 있다.(프로토콜이 다르면 같은 포트를 공유할 수 있다.)
* 순서
1. 서버는 서버소켓을 이용하여 서버의 컴퓨터의 특정 포트에서 클라이언트의 연결요청을 처리할 준비를 한다.
2. 클라이언트는 접속할 서버의 IP주소와 포트정보로 소켓을 생성해서 서버에 연결을 요청한다.
3. 서버소켓은 클라이언트의 요청을 받으면 서버에 새로운 소켓을 생성해서
클라이언트의 소켓과 연결되도록 한다.
4. 클라이언트의 소켓과 새로 생성된 서버의 소켓은 서버소켓과 관계없이 1:1 통신을 한다.
* InetAddress 클래스
- 자바에서 IP 주소를 다루기 위한 클래스
메소드명 | 기능 |
getAllByName() | 호스트가 가지고 있는 모든 인터넷 주소를 얻는다. |
getByName() | 호스트의 인터넷 주소를 얻는다. |
getLocalHost() | 현재 사용중인 지역 호스트에 대한 InetAddress 객체를 얻는다. |
getAddress() | 네트워크상의 IP주소를 byte배열로 얻는다. |
getHostAddress() | IP주소의 점 문자열 형식을 가져온다. |
getHostName() | InetAddress 객체의 호스트명을 얻는다. |
* Socket 클래스
생성자 |
설명 |
Socket(String host, int port) |
호스트 이름이 host이고, 포트 번호가 port인 새로운 소켓을 생성한다. |
Socket(InetAddress address, int port) |
InetAddress에 기술된 주소로 새로운 소켓을 생성한다. |
메소드 | 설명 |
InputStream getInputStream() | 소켓이 사용하는 입력 스트림을 반환한다. |
OutputStream getOutputStream() | 소켓이 사용하는 출력 스트림을 반환한다. |
Inetaddress getInetAddress() | 소켓이 연결되어 있는 인터넷 주소를 반환한다. |
public int getLocalPort() | 소켓이 연결되어 있는 포트 주소를 반환한다. |
public int getPort() | 원격 컴퓨터의 포트 번호를 반환한다. |
public InetAddress getLocalAddress() | 소켓이 연결되어 있는 인터넷 주소를 반환한다. |
* ServerSocket 클래스
생성자 |
설명 |
public ServerSocket(int port) throws IOException |
- 포트번호 port에 대해 ServerSocket의 새로운 인스턴스를 만든다. (포트번호 0은 비어있는 포트번호를 사용한다는 의미이다.) - Queue는 서버가 받을 수 있는 입력 연결 개수를 의미한다. - Addr는 컴퓨터의 인터넷 주소를 나타낸다. |
public ServerSocket(int port, int queue) throws IOException |
|
public ServerSocket(int port, int queue, InetAddress addr) throws IOException |
|
메소드 |
설명 |
public Socket accept() |
접속 요청을 받는다. |
public void close() |
ServerSocket을 닫는다. |
pulbic Inetaddress getInetAddress() |
소켓이 연결되어 있는 인터넷 주소를 반환한다. |
public int getSoTimeout() |
소켓에 대한 타임아웃 값을 밀리 초로 반환하거나 설정한다. |
이론을 마치고 실습을 통해 공부하도록 하겠습니다.
소켓 프로그래밍의 TCP를 통해 다중 실시간 채팅 프로그램을 만들어 볼 것입니다.
일단, 한사람만 이용하는 채팅이 아닌 다중 실시간 채팅 프로그램이니 Thread에 대한 복습을 하고.
진행하도록하겠습니다.
(Link 를 통해 복습하시면 됩니다.)
서버에 접속하여 대화를 나눌 Client 클래스
import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.IOException; import java.io.OutputStream; import java.net.Socket; import java.util.Scanner; public class Client03 { public static void main(String[] args) { Socket socket = null; DataInputStream dis = null; DataOutputStream dos = null; Scanner sc = new Scanner(System.in); String nickName = ""; try { socket = new Socket("192.168.1.2", 8080); System.out.println("닉네임 입력"); nickName = sc.nextLine(); OutputStream out = socket.getOutputStream(); dos = new DataOutputStream(out); dos.writeUTF(nickName); ClientThread03 clientThread = new ClientThread03(socket.getInputStream()); clientThread.start(); String msg = ""; while (!msg.toLowerCase().equals("exit")) { System.out.print(" >> "); msg = sc.nextLine(); // 공백문자를 보내면 아무것도 가지 않도록 if (!msg.trim().equals("")) dos.writeUTF(msg); } } catch (IOException e) { e.printStackTrace(); } finally { try { dos.close(); socket.close(); } catch (IOException e) { e.printStackTrace(); } } } }
많은 Client들이 서로 대화할 수 있는 Thread 클래스
import java.io.*; public class ClientThread03 extends Thread { // Server가 보내는 채팅 메세지를 받기 위한 InputStream private InputStream in; public ClientThread03(InputStream in) { this.in = in; } @Override public void run() { DataInputStream dis = null; try { dis = new DataInputStream(in); String msg = ""; while (true) { msg = dis.readUTF(); System.out.println(msg); } } catch (IOException e) { e.printStackTrace(); } } }
각각의 클라이언트들이 접속하는 서버 클래스
import java.io.*; import java.net.ServerSocket; import java.net.Socket; import java.util.*; public class Server03 { public static final int PORT = 8080; public static void main(String[] args) { ServerSocket sSock = null; // String(닉네임 + IP) 한테 DataOutputStream을 이용하여 데이터를 보내겠다. Map<String, DataOutputStream> mClientMap = new HashMap<>(); // 각각 클라이언트들이 접속할 때마다 발생되는 Thread에서 처리되는 자료구조임 // 때문에 동기화가 필요하다. // 일반 Map을 동기화 된 Map으로 변경함 Collections.synchronizedMap(mClientMap); try { sSock = new ServerSocket(PORT); System.out.println("Client Wait..."); Socket clientSocket = sSock.accept(); ServerThread03 clientThread = new ServerThread03(clientSocket,mClientMap); clientThread.start(); } catch (IOException e) { e.printStackTrace(); } finally { try { sSock.close(); } catch (IOException e) { e.printStackTrace(); } } } }
실시간 채팅을 위한 서버 Thread 클래스
import java.io.*; import java.net.Socket; import java.util.*; public class ServerThread03 extends Thread { private Socket clientSocket = null; private DataInputStream dis = null; private DataOutputStream dos = null; private Map<String, DataOutputStrea> mClientMap; private String clientKey = ""; private String nickName = ""; public ServerThread03(Socket clientSocket, Map<String, DataOutputStream> mClientMap) { this.clientSocket = clientSocket; // 클라이언트가 접속할 때 마다 map에 등록 시킬 것. this.mClientMap = mClientMap; } @Override public void run() { try { // 접속한 클라이언트가 보내는 메세지를 받아내는 작업 dis = new DataInputStream(clientSocket.getInputStream()); // 닉네임 받아오기 nickName = dis.readUTF(); // Map의 키값 설정 (닉네임 + 아이피) clientKey = nickName + clientSocket.getInetAddress().toString(); // 접속한 Client에게 데이터를 전송하기 위한 DataOutputStream 생성 dos = new DataOutputStream(clientSocket.getOutputStream()); // 접속한 Client들에 대한 정보가 들어있는 Map에 추가 mClientMap.put(clientKey, dos); System.out.println(clientSocket + "[" + nickName + "] 님 접속"); // Client가 보내는 채팅 메세지를 받아와야 한다. // Client가 EXIT를 입력하면 방 나가기(채팅 종료) String msg = ""; while (msg.toLowerCase().equals("exit")) { // 1) Client가 메세지를 보내면 받는다. msg = dis.readUTF(); // 2) 받아낸 메세지를 접속한 모든 사람에게 전송한다. sendBroadCast("["+nickName+"]"+msg); } } catch (IOException e) { e.printStackTrace(); } finally { try { // 클라이언트가 채팅을 종료할 경우 먼저 map에서 삭제 mClientMap.remove(clientKey); // 스트림과 소켓 닫아주기. dos.close(); dis.close(); clientSocket.close(); } catch (IOException e) { e.printStackTrace(); } } } // Server 입장에서 접속한 모든 사람에게 데이터를 전송하는 방식 : BroadCasting private void sendBroadCast(String msg) throws IOException { // Map의 키 집합인 keySet 가져오기. Set<String> keyset = mClientMap.keySet(); // KeySet을 순환할 수 있는 Iterator 가져오기. Iterator<String> keyIter = keyset.iterator(); // Iterator를 이용하여 Key값을 이용해 HashMap에 // 저장된 Client의 DataOutputStream을 이용하여 데이터 전송 while (keyIter.hasNext()) { String key = keyIter.next(); DataOutputStream tempDos = mClientMap.get(key); try { tempDos.writeUTF(msg); } catch (IOException e) { // 메세지를 보내다가 에러가 나게 되면 해당하는 사람을 삭제 System.out.println(key + "에게 메세지를 보내던 중 에러 발생"); mClientMap.remove(key); } } } }
'프로그래밍 > Java' 카테고리의 다른 글
JAVA 요약 목록 (3) | 2017.11.02 |
---|---|
자바 ! 파일 입출력2 (Stream, DataInput/Output, ObjectInput/Output) (1) | 2017.05.18 |
자바 ! 파일 입출력1 (Stream, Buffered) (0) | 2017.05.17 |
자바 ! 동기화 (Synchronization , synchronized) (1) | 2017.05.16 |
자바 ! 쓰레드 ( Thread, Start , Run, Priority ) (0) | 2017.05.15 |