TCP vs. UDP
TCP
- 연결지향적 -> 신뢰성이 높은 프로토콜
- tcp의 흐름제어
- 던지고나서 기다려 그다음에 받았다고 응답이오면 또던져 근데 던졌는데 한참후에도 응답이없어 아하 그럼 못받았구먼 다시 똑같은거 또 던져 (=신뢰성있는통신을위해 내부적으로 프로토콜로 구현)
- tcp의 혼잡제어
UDP
- TCP랑 비교해 상대적으로 빠름
- 동영상프레임전송
ServerSocket, Socket
- 소켓
- 네트워크로 연결된 두 대의 호스트간 통신을 위한 양쪽 끝
- Connection을 개설하기 위한 도구
- 내가 소켓을 가지고 서버에 커넥션을 요청함, 서버는 미리 클라이언트를 기다리고있던 상태임, 그럼 내가 요청함으로서 서버는 소켓을 만듬 그럼이제 소켓이 한쌍이니까 소통가능
- Well Known Port
- 7: Echo
- 13: DayTime
- 21: FTP
- 23: Telenet
- 25: SMTP
- 80: HTTP
- 나머지: Well Known Port
ServerSocket 클래스
- 서버 프로그램에서 사용하는 소켓
- 포트를 통해 연결 요청이 오기를 대기
- 요청이 오면 클라이언트와 연결을 맺고 해당 클라이언트와 통신하는 새 소켓을 만드는 일을 한다.
- 새로 만들어진 소켓은 클라이언트 소켓과 데이터를 주고받는다.
Socket 클래스
- 서버 프로그램으로 연결 요청
- 데이터 전송을 담당
TCP 소켓 프로그래밍
스레드 이용하지 않는 예제
TcpServer.java
ServerSocket server = new ServerSocket(7777);
System.out.println("서버가 접속을 기다립니다...")
Socket socket = server.accept();
System.out.println("접속한 클라이언트 정보");
System.out.println("주소 : " + socket.getInetAddress());
OutputStream out = socket.getOutputStream();
DataOutputStream dos = new DataoOutputStream(out);
dos.writeUTF("어솨세요!!^ㅡ^");
System.out.println("메세지를 보냈습니다.");
dos.close();
server.close();
클라이언트 연결 전
클라이언트 연결 후
TcpClient.java
String serverIp = "127.0.0.1";
System.out.println(serverIp + " 서버에 접속 중입니다.");
Socket socket = new Socket(serverIp, 7777);
System.out.println("연결되었습니다.");
InputStream is = socket.getInputStream();
DataInputStream dis = new DataInputStream(is);
System.out.println(dis.readUTF());
System.out.println("연결종료");
dis.close();
socket.close();
결과
스레드 이용 예제1 : 1:1 채팅
- ServerSocket과 Socket은 동기(블로킹)방식
- 서버를 실행하는 main 스레드가 직접 입출력 작업을 담당하게 되면 입출력이 완료될 때까지 다른 작업을 할 수 없는 상태가 됨
- 문제점1: 서버 애플리케이션은 지속적으로 클라이언트의 연결 수락 기능을 수행해야하지만 입출력에서 블로킹되면 이 작업을 할 수 없게 됨
- 문제점2: 클라이언트 1과 입출력하는 동안에는 클라이언트2와 입출력을 할수없음
- ★ accept(), connect(), read(), write() 는 별도의 작업 스레드를 생성해서 병렬적으로 처리하는 것이 좋음
- 스레드풀: 클라이언트의 폭증으로 인해 서버의 과도한 스레드 방지 (p.1067)
Sender.java
public class Sender extends Thread {
private DataOutputStream dos;
private String name;
public Sender(Socket socket) {
name = "[" + socket.getInetAddress() + " : " + socket.getLocalPort() + "]";
try {
dos = new DataOutputStream(socket.getOutputStream());
} catch (Exception e) {
e.printStackTrace();
}
}
@Override
public void run() {
Scanner scan = new Scanner(System.in);
while (dos != null) {
try {
dos.writeUTF(name + " >>> " + scan.nextLine());
} catch (Exception e) {
e.printStackTrace();
}
}
scan.close();
}
}
Receiver.java
- 소켓에서 메시지를 받아서 화면에 출력하는 역할
public class Receiver extends Thread {
private Socket socket;
private DataInputStream dis;
public Receiver(Socket socket) {
this.socket = socket;
try {
dis = new DataInputStream(socket.getInputStream());
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
public void run() {
while(dis != null) {
try {
System.out.println(dis.readUTF());
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
TcpServer2.java
- Sender와 Receiver를 활용한 클래스
public class TcpServer2 {
public static void main(String[] args) throws IOException {
ServerSocket socket = new ServerSocket(7777);
System.out.println("서버 준비 완료...");
Socket socket = server.accept();
System.out.println("서버의 소켓 : " + socket);
Sender sender = new Sender(socket);
Receiver receiver = new Receiver(socket);
sender.start();
receiver.start();
}
}
TcpClient2.java
public class TcpClient2 {
public static void main(String[] args) throws UnknownHostException, IOException {
Socket socket = new Socket("localhost", 7777);
System.out.println("서버에 연결되었습니다.");
System.out.println("클라이언트의 소켓 : " + socket);
Sender sender = new Sender(socket);
Receiver receiver = new Receiver(socket);
sender.start();
receiver.start();
}
}
메세지 주고받기 전 서버
- 접속한 상대방(클라이언트)의 포트: 58383
- 서버의 localport: 7777
메세지 주고받기 전 클라이언트
- 접속한 상대방(서버)의 포트: 7777
- 클라이언트의 localport: 58383
메세지 주고받기
스레드 이용 예제2 : 단체 채팅
- 단체 채팅의 원리
- 1) 유저A: "안녕하세요!"
- 2) 서버가 유저A의 메세지를 들고 간다
- 3) 서버가 갖고 있는 맵의 소켓들을 다 꺼내서 거기에 안녕하세요를 다 write한다
- 4) 그러면 다른 유저 B,C,D...들도 "안녕하세요!" 라는 메시지를 받게된다.
- 뽀인트) 사실 유저A는 서버에 "안녕하세요!" 한건데 서버가 가운데서 열심히 일한 덕택에 B,C,D...까지 인사를 받을 수 있었던 것!!
- 중요한 점
- 소켓을 관리하기 위해 컬랙션 객체 (Map)이용
- Collections의 synchronized 메서드 이용해 Collection객체인 Map을 동기화 처리
- 우리의 프로그램은 멀티스레드 프로그래밍이기 때문에 다수의 유저가 접속하면서 동시에 소켓이 만들어질 때 동시에 put을 하게 되는 상황 발생 이 상황을 방지하고 안전하게 하기 위해 "동기화 처리"
MultiChatServer.java
public class MultiChatServer {
private Map<String, Socket> clients;
public MultiChatServer() {
clients = Collections.synchronizedMap(new HashMap<>());
public void serverStart() {
ServerSocket serverSocket = null;
Socket socket;
try {
serverSocket = new ServerSocket(7777);
System.out.println("서버가 시작되었습니다.");
while (true) {
socket = serverSocket.accept();
System.out.println("[" + socket.getInetAddress() + " : " + socket.getPort() + "] 에서 접속하였습니다."
ServerReceiver receiver = new ServerReceiver(socket);
receiver.start();
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (serverSocket != null) {
try {serverSocket.close();} catch(IOException e) {}
}
}
}
public void sendMessage(String msg) {
Iterator<String> it = clients.keySet().iterator();
while (it.hasNext()) {
try {
String name = it.next();
DataOutputStream out = new DataOutputStream(clients.get(name).getOutputStream());
out.writeUTF(msg);
} catch (IOException e) {
e.printStackTrace();
}
}
}
public void sendMessage(String msg, String from) {
Iterator<String> it = clients.keySet().iterator();
while (it.hasNext()) {
try {
String name = it.next();
DataOutputStream out = new DataOutputStream(clients.get(name).getOutputStream());
out.writeUTF("#" + from + "님의 메세지 : " + msg);
} catch (IOException e) {
e.printStackTrace();
}
}
}
class ServerReceiver extends Thread {
private Socket socket;
private DataInputStream dis;
private String name;
public ServerReceiver(Socket socekt) {
this.socket = socket;
try {
dis = new DataInputStream(socket.getInputStream);
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
public void run() {
try {
name = dis.readUTF();
sendMessage("#" + name + " 님이 입장했습니다.");
clients.put(name, socket);
System.out.println("#현재 서버 접속자 수는 " + clients.size() + "명입니다.");
while (dis != null) {
sendMessage(dis.readUTF(), name);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
sendMessage(name + "님이 나가셨습니다.");
clients.remove(name);
System.out.println("[" + socket.getInetAddress() + " : " + socket.getPort() + "]에서 접속을 종료했습니다.");
System.out.println("현재 접속자 수는 " + clients.size() + "명입니다.");
}
}
}
public static void main(String[] args) {
new MultiChatServer().serverStart();
}
}
MultiChatClient.java
public cass MultiChatClient {
private Scanner scan = new Scanner(System.in);
private String name;
public void clientStart() {
System.out.prin("대화명 : ");
name = scan.next();
Socket socket = null;
try {
socket = new Socket("192.168.45.2", 7777);
System.out.println("서버에 연결되었습니다.");
} catch (UnknownHostException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
class ClientSender extends Thread {
private Socket socket;
private DataOutputStream dos;
private String name;
public ClientSender(Socket socket, String name) {
this.socket = socket;
this.name = name;
try {
dos = new DataOutputStream(socket.getOutputStream());
} catch (IOException e) {
e.printStackTrace();
}
@Override
public void run() {
try {
if(dos != null) {
dos.writeUTF(name);
}
while(dos!= null) {
dos.writeUTF(scan.nextLine());
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
class ClientReceiver extends Thread {
private Socket socket;
private DataInputStream dis;
public ClientReceiver(Socket socket) {
this.socket = socket;
try {
dis = new DataInputStream(socket.getInputStream());
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
public void run() {
while (dis != null) {
try {
System.out.println(dis.readUTF());
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
public static void main(String[] args) {
new MultiChatClient().clientStart();
}
}
파일 전송 예제
TcpFileServer.java
- 서버는 클라이언트가 접속하면 서버 컴퓨터의 D:/D_Other 폴더에 있는 Tulips.jpg 파일을 클라이언트로 전송한다.
public class TcpFileServer {
private ServerSocket server;
private Socket socket;
private FileInputStream fis;
private OutputStream out;
public void serverStart() {
File file = new File("d:/D_Other/Tulips.jpg");
try {
server = new ServerSocket(7777);
System.out.println("서버 준비 완료...");
socket = server.accept();
System.out.println("파일 전송 시작...");
fis = new FileInputStream(file);
out = socket.getOutputStream();
byte[] tmp = new byte[1024];
int c = 0;
while((c = fis.read(tmp)) != -1) {
out.write(tmp, 0, c);
}
out.flush();
System.out.println("파일 전송 완료...");
} catch (IOException e) {
e.printStackTrace();
} finally {
if(fis != null) {
try {fis.close();} catch (IOException e) {}
}
if(out != null) {
try {out.close();} catch (IOException e) {}
}
if(socket != null) {
try {socket.close();} catch (IOException e) {}
}
if(server != null) {
try {server.close();} catch (IOException e) {}
}
}
}
public static void main(String[] args) {
new TcpFileServer().serverStart();
}
TcpFileClient.java
- 클라이언트는 서버에 접속하여 서버가 보내주는 파일을 D:/C_Lib폴더에 저장한다.
public class TcpFileClient {
public void clientStart() {
File file = new File("d:/C_Lib/Tulips.jpg");
try(Socket socket = new Socket("localhost", 7777);
InputStream is = socket.getInputStream();
FileOutputStream fos = new FileOutputStream(file); ) {
System.out.println("파일 다운로드 시작...");
byte[] tmp = new byte[1024];
int length = 0;
while ((length = is.read(tmp)) != -1) {
fos.write(tmp, 0, length);
}
fos.flush();
System.out.println("파일 다운로드 완료...");
} catch (IOException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
new TcpFileClient().clientStart();
}
}