Java - 14. 네트워크 - ServerSocket, Socket, TCP
TCP vs. UDP
- 이것이 자바다 p.1056
TCP
- 연결지향적 -> 신뢰성이 높은 프로토콜
- 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
// 1. TCP 소켓통신을 하기 위해 ServerSocket 객체를 생성
// 서버소켓을 가지고 클라이언트의 소켓 접속을 기다릴 것임
ServerSocket server = new ServerSocket(7777); // ~1023은 WellKnown이라 사용불가
System.out.println("서버가 접속을 기다립니다...")
// 2. ServerSocket객체의 accept()메서드를 호출,
// Client로부터 연결요청이 올 때까지 계속 기다린다. (=main스레드 블락(wait))
// 3. 연결요청이 들어오면 새로운 Socket객체를 생성하여 Client의 Socket과 연결한다
Socket socket = server.accept();
// ----------------------------
// 이 이후는 클라이언트와 연결된 후의 작업을 진행하면 된다.
System.out.println("접속한 클라이언트 정보");
System.out.println("주소 : " + socket.getInetAddress()); // toString() 오버라이드
// 4. Socket객체의 Stream객체(InputStream, OutputStream)를 이용하여 메시지를 주고 받는다.
// OutputStream객체를 구성하여 전송
// 접속한 Socket의 getOutputStream()메서드를 이용하여 구한다.
OutputStream out = socket.getOutputStream();
DataOutputStream dos = new DataoOutputStream(out);
// ▶ OutputStreamReader를 쓸 수 도 있음
dos.writeUTF("어솨세요!!^ㅡ^"); // 메시지 보내기
System.out.println("메세지를 보냈습니다.");
// 5. 사용이 완료된 소켓은 close()메서드를 이용하여 종료 처리한다.
dos.close();
server.close(); // 소켓도 스트림처럼 사용 후에 닫아줘야 한다.
클라이언트 연결 전
클라이언트 연결 후
TcpClient.java
String serverIp = "127.0.0.1"; // loopback주소
// 자기 자신의 컴퓨터를 나타내는 방법
// IP : 127.0.0.1
// 컴이름 : localhost
System.out.println(serverIp + " 서버에 접속 중입니다.");
// 1. 소켓을 생성하여 서버에 연결을 요청한다.
Socket socket = new Socket(serverIp, 7777);
// 2. 연결이 정상적으로 이루어지면 생성된 소켓 객체를 이용하여 서버와 메시지를 주고받음
// 생성된 소켓으로부터 스트림(InputStream, OutputStream)객체를 가져와 이용
System.out.println("연결되었습니다.");
// 서버에서 보내온 메시지 받기
// 메시지를 받기 위해 InputStream객체를 생성
// Socket의 getInputStream()메서드 이용
InputStream is = socket.getInputStream();
DataInputStream dis = new DataInputStream(is);
// 서버로부터 받은 메시지 출력하기
System.out.println(dis.readUTF());
System.out.println("연결종료");
// 3. 사용이 완료된 소켓은 close() 메서드를 이용하여 종료처리한다.
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()); // scanner: 엔터치기전까지 block
} catch (Exception e) {
e.printStackTrace();
}
}
scan.close();
}
} // class
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());
// -> Sender에서 writeUTF 호출 전까지 block,
// writeUTF 호출되면 그때부터 read하기 시작
} catch (IOException e) {
e.printStackTrace();
}
}
}
} // class
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); // toString() 오버라이드 되어있음
Sender sender = new Sender(socket);
Receiver receiver = new Receiver(socket);
sender.start();
receiver.start();
// 여기까지하면 이제 3개(main+sender+receiver)의 스레드가 작동하는 멀티스레드 프로그램
} // main
} // class
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 {
// 1. 소켓을 관리하기 위한 컬렉션 객체 정의
// - 대화명, 클라이언트의 Socket을 저장하기 위한 Map 변수 선언
private Map<String, Socket> clients; // String: 대화명, Socket: 해당 유저의 소켓
public MultiChatServer() {
// 2. 다수의 유저가 접속했을 시, 동시에 소켓이 만들어지고 Map에 저장하게 된다
// 이를 위해 미리 객체생성시에 미리 동기화 처리가 가능하도록 처리해준다.
clients = Collections.synchronizedMap(new HashMap<>());
// 3. 서버를 시작하는 메서드
public void serverStart() {
ServerSocket serverSocket = null; // try문 밖에서 close하기 위해 밖에서 선언
Socket socket;
try {
serverSocket = new ServerSocket(7777);
System.out.println("서버가 시작되었습니다.");
while (true) {
// 4. 클라이언트의 접속을 대기한다.
socket = serverSocket.accept();
System.out.println("[" + socket.getInetAddress() + " : " + socket.getPort() + "] 에서 접속하였습니다."
// 12. 메시지 전송 처리를 하는 스레드 생성 및 실행
ServerReceiver receiver = new ServerReceiver(socket);
receiver.start();
}
} catch (IOException e) {
e.printStackTrace();
} finally {
// 서버 소켓 닫기
if (serverSocket != null) {
try {serverSocket.close();} catch(IOException e) {}
}
}
}
// 13. 메세지 전송 메서드
/**
* 대화방 즉, Map에 저장된 전체 유저에게 '안내'메시지를 전송하는 메서드
* @param msg
*/
public void sendMessage(String msg) {
// 13-1. Map에 저장된 유저의 대화명 리스트를 추출 (key값 구하기)
Iterator<String> it = clients.keySet().iterator();
while (it.hasNext()) {
try {
String name = it.next(); // 13-2. 대화명(key)값 구하기
// 13-3. 대화명에 해당하는 Socket의 OutputStream객체 구하기
DataOutputStream out = new DataOutputStream(clients.get(name).getOutputStream());
out.writeUTF(msg); // 13-4. 메시지 보내기
} catch (IOException e) {
e.printStackTrace();
}
}
}
// 14. 13번 오버로딩 : 대화명 + 내용
/**
* 대화방 즉, Map에 저장된 전체 유저에게 '대화'메시지를 전송하는 메서드
* @param msg 보낼 메시지
* @param from 보낸 사람 대화명
*/
public void sendMessage(String msg, String from) {
// 14-1. Map에 저장된 유저의 대화명 리스트를 추출 (key값 구하기)
Iterator<String> it = clients.keySet().iterator();
while (it.hasNext()) {
try {
String name = it.next(); // 14-2. 대화명(key)값 구하기
// 14-3. 대화명에 해당하는 Socket의 OutputStream객체 구하기
DataOutputStream out = new DataOutputStream(clients.get(name).getOutputStream());
out.writeUTF("#" + from + "님의 메세지 : " + msg); // 14-4. 메시지 보내기
} catch (IOException e) {
e.printStackTrace();
}
}
}
// 5. 클라이언트의 이름과 메세지를 읽어들여 다시 클라이언트에게 메시지를 전송할 Thread를 Inner클래스로 정의
// 내부 클래스 장점: Inner클래스에서 부모클래스의 멤버들을 직접 사용할 수 있음.
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 {
// 6. 사용자가 들어오자마자 대화명을 String 으로 입력할 것
// -> 먼저 String부터 처리해준다
// = 서버에서 클라이언트가 보내는 최초의 메세지(=대화명)을 수신
name = dis.readUTF();
// 7. 대화명을 받아서 다른 모든 클라이언트들에게 대화방 참여 메시지 전송
sendMessage("#" + name + " 님이 입장했습니다.");
// 8. 대화명과 소켓정보를 Map에 지정한다.
clients.put(name, socket);
System.out.println("#현재 서버 접속자 수는 " + clients.size() + "명입니다.");
// 9. 이후의 메시지는 반복문으로 처리한다.
// 한 클라이언트가 보낸 메시지를 다른 모든 클라이언트에게 보내준다.
while (dis != null) {
sendMessage(dis.readUTF(), name);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
// 10. 이 finally 영역이 실행된다는 것은 클라이언트의 접속이 종료되었다는 의미이다.
sendMessage(name + "님이 나가셨습니다.");
// 11. Map에서 해당 대화명을 삭제한다.
clients.remove(name);
System.out.println("[" + socket.getInetAddress() + " : " + socket.getPort() + "]에서 접속을 종료했습니다.");
System.out.println("현재 접속자 수는 " + clients.size() + "명입니다.");
}
}
} // ServerReceiver class
public static void main(String[] args) {
new MultiChatServer().serverStart();
}
} // MultiChatServer class
MultiChatClient.java
public cass MultiChatClient {
private Scanner scan = new Scanner(System.in);
private String name;
// 1. 시작 메서드
public void clientStart() {
// 2. 대화명 입력받기
System.out.prin("대화명 : ");
name = scan.next();
Socket socket = null;
try {
socket = new Socket("192.168.45.2", 7777);
System.out.println("서버에 연결되었습니다.");
// 8.
// 9.
} catch (UnknownHostException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
// 서버의 메시지를 receive & 내 메시지 send -> sender, receiver 둘다 필요
// 3. 메시지를 전송하는 Thread (내부클래스)
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 {
// 4. 시작하자마자 자신의 대화명을 서ㅓ로 전송
if(dos != null) {
dos.writeUTF(name);
}
while(dos!= null) {
// 5. 키보드로 입력받은 메시지를 서버로 전송
dos.writeUTF(scan.nextLine());
}
} catch (IOException e) {
e.printStackTrace();
}
}
} // 내부클래스
// 6. 수신용 Thread 클래스
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 {
// 7. 서버로부터 수신한 메시지 출력하기
System.out.println(dis.readUTF());
} catch (IOException e) {
e.printStackTrace();
}
}
}
}// 내부클래스
public static void main(String[] args) {
new MultiChatClient().clientStart();
}
} // MultiChatClient class
파일 전송 예제
TcpFileServer.java
- 서버는 클라이언트가 접속하면 서버 컴퓨터의 D:/D_Other 폴더에 있는 Tulips.jpg 파일을 클라이언트로 전송한다.
public class TcpFileServer {
private ServerSocket server;
private Socket socket;
private FileInputStream fis;
private OutputStream out;
// 1. 서버 시작
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(); // 문자열이 아닌 바이너리 데이터를 보내기 위해 DataOutputStream 안씀
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); ) {
// close 필요한 객체들을 try 안에서 생성하면 finally 영역에서 자동으로 close해줌
// (JDK 1.7 이상, try-with-resource)
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();
}
}
Author And Source
이 문제에 관하여(Java - 14. 네트워크 - ServerSocket, Socket, TCP), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다
https://velog.io/@godkimchichi/Java-14-네트워크-Socket
저자 귀속: 원작자 정보가 원작자 URL에 포함되어 있으며 저작권은 원작자 소유입니다.
우수한 개발자 콘텐츠 발견에 전념
(Collection and Share based on the CC Protocol.)
- 던지고나서 기다려 그다음에 받았다고 응답이오면 또던져 근데 던졌는데 한참후에도 응답이없어 아하 그럼 못받았구먼 다시 똑같은거 또 던져 (=신뢰성있는통신을위해 내부적으로 프로토콜로 구현)
- tcp로 보내면 왠만한건 제대로감
- 소켓
- 네트워크로 연결된 두 대의 호스트간 통신을 위한 양쪽 끝
- Connection을 개설하기 위한 도구
- 전화기, 연결매체, 양끝단, 양쪽다필요...
- 내가 소켓을 가지고 서버에 커넥션을 요청함, 서버는 미리 클라이언트를 기다리고있던 상태임, 그럼 내가 요청함으로서 서버는 소켓을 만듬 그럼이제 소켓이 한쌍이니까 소통가능
- Well Known Port
- 7: Echo
- 13: DayTime
- 21: FTP
- 23: Telenet
- 25: SMTP
- 80: HTTP
- 나머지: Well Known Port
ServerSocket 클래스
- 서버 프로그램에서 사용하는 소켓
- 포트를 통해 연결 요청이 오기를 대기
- 요청이 오면 클라이언트와 연결을 맺고 해당 클라이언트와 통신하는 새 소켓을 만드는 일을 한다.
- 새로 만들어진 소켓은 클라이언트 소켓과 데이터를 주고받는다.
Socket 클래스
- 서버 프로그램으로 연결 요청
- 데이터 전송을 담당
TCP 소켓 프로그래밍
스레드 이용하지 않는 예제
TcpServer.java
// 1. TCP 소켓통신을 하기 위해 ServerSocket 객체를 생성
// 서버소켓을 가지고 클라이언트의 소켓 접속을 기다릴 것임
ServerSocket server = new ServerSocket(7777); // ~1023은 WellKnown이라 사용불가
System.out.println("서버가 접속을 기다립니다...")
// 2. ServerSocket객체의 accept()메서드를 호출,
// Client로부터 연결요청이 올 때까지 계속 기다린다. (=main스레드 블락(wait))
// 3. 연결요청이 들어오면 새로운 Socket객체를 생성하여 Client의 Socket과 연결한다
Socket socket = server.accept();
// ----------------------------
// 이 이후는 클라이언트와 연결된 후의 작업을 진행하면 된다.
System.out.println("접속한 클라이언트 정보");
System.out.println("주소 : " + socket.getInetAddress()); // toString() 오버라이드
// 4. Socket객체의 Stream객체(InputStream, OutputStream)를 이용하여 메시지를 주고 받는다.
// OutputStream객체를 구성하여 전송
// 접속한 Socket의 getOutputStream()메서드를 이용하여 구한다.
OutputStream out = socket.getOutputStream();
DataOutputStream dos = new DataoOutputStream(out);
// ▶ OutputStreamReader를 쓸 수 도 있음
dos.writeUTF("어솨세요!!^ㅡ^"); // 메시지 보내기
System.out.println("메세지를 보냈습니다.");
// 5. 사용이 완료된 소켓은 close()메서드를 이용하여 종료 처리한다.
dos.close();
server.close(); // 소켓도 스트림처럼 사용 후에 닫아줘야 한다.
클라이언트 연결 전
클라이언트 연결 후
TcpClient.java
String serverIp = "127.0.0.1"; // loopback주소
// 자기 자신의 컴퓨터를 나타내는 방법
// IP : 127.0.0.1
// 컴이름 : localhost
System.out.println(serverIp + " 서버에 접속 중입니다.");
// 1. 소켓을 생성하여 서버에 연결을 요청한다.
Socket socket = new Socket(serverIp, 7777);
// 2. 연결이 정상적으로 이루어지면 생성된 소켓 객체를 이용하여 서버와 메시지를 주고받음
// 생성된 소켓으로부터 스트림(InputStream, OutputStream)객체를 가져와 이용
System.out.println("연결되었습니다.");
// 서버에서 보내온 메시지 받기
// 메시지를 받기 위해 InputStream객체를 생성
// Socket의 getInputStream()메서드 이용
InputStream is = socket.getInputStream();
DataInputStream dis = new DataInputStream(is);
// 서버로부터 받은 메시지 출력하기
System.out.println(dis.readUTF());
System.out.println("연결종료");
// 3. 사용이 완료된 소켓은 close() 메서드를 이용하여 종료처리한다.
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()); // scanner: 엔터치기전까지 block
} catch (Exception e) {
e.printStackTrace();
}
}
scan.close();
}
} // class
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());
// -> Sender에서 writeUTF 호출 전까지 block,
// writeUTF 호출되면 그때부터 read하기 시작
} catch (IOException e) {
e.printStackTrace();
}
}
}
} // class
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); // toString() 오버라이드 되어있음
Sender sender = new Sender(socket);
Receiver receiver = new Receiver(socket);
sender.start();
receiver.start();
// 여기까지하면 이제 3개(main+sender+receiver)의 스레드가 작동하는 멀티스레드 프로그램
} // main
} // class
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 {
// 1. 소켓을 관리하기 위한 컬렉션 객체 정의
// - 대화명, 클라이언트의 Socket을 저장하기 위한 Map 변수 선언
private Map<String, Socket> clients; // String: 대화명, Socket: 해당 유저의 소켓
public MultiChatServer() {
// 2. 다수의 유저가 접속했을 시, 동시에 소켓이 만들어지고 Map에 저장하게 된다
// 이를 위해 미리 객체생성시에 미리 동기화 처리가 가능하도록 처리해준다.
clients = Collections.synchronizedMap(new HashMap<>());
// 3. 서버를 시작하는 메서드
public void serverStart() {
ServerSocket serverSocket = null; // try문 밖에서 close하기 위해 밖에서 선언
Socket socket;
try {
serverSocket = new ServerSocket(7777);
System.out.println("서버가 시작되었습니다.");
while (true) {
// 4. 클라이언트의 접속을 대기한다.
socket = serverSocket.accept();
System.out.println("[" + socket.getInetAddress() + " : " + socket.getPort() + "] 에서 접속하였습니다."
// 12. 메시지 전송 처리를 하는 스레드 생성 및 실행
ServerReceiver receiver = new ServerReceiver(socket);
receiver.start();
}
} catch (IOException e) {
e.printStackTrace();
} finally {
// 서버 소켓 닫기
if (serverSocket != null) {
try {serverSocket.close();} catch(IOException e) {}
}
}
}
// 13. 메세지 전송 메서드
/**
* 대화방 즉, Map에 저장된 전체 유저에게 '안내'메시지를 전송하는 메서드
* @param msg
*/
public void sendMessage(String msg) {
// 13-1. Map에 저장된 유저의 대화명 리스트를 추출 (key값 구하기)
Iterator<String> it = clients.keySet().iterator();
while (it.hasNext()) {
try {
String name = it.next(); // 13-2. 대화명(key)값 구하기
// 13-3. 대화명에 해당하는 Socket의 OutputStream객체 구하기
DataOutputStream out = new DataOutputStream(clients.get(name).getOutputStream());
out.writeUTF(msg); // 13-4. 메시지 보내기
} catch (IOException e) {
e.printStackTrace();
}
}
}
// 14. 13번 오버로딩 : 대화명 + 내용
/**
* 대화방 즉, Map에 저장된 전체 유저에게 '대화'메시지를 전송하는 메서드
* @param msg 보낼 메시지
* @param from 보낸 사람 대화명
*/
public void sendMessage(String msg, String from) {
// 14-1. Map에 저장된 유저의 대화명 리스트를 추출 (key값 구하기)
Iterator<String> it = clients.keySet().iterator();
while (it.hasNext()) {
try {
String name = it.next(); // 14-2. 대화명(key)값 구하기
// 14-3. 대화명에 해당하는 Socket의 OutputStream객체 구하기
DataOutputStream out = new DataOutputStream(clients.get(name).getOutputStream());
out.writeUTF("#" + from + "님의 메세지 : " + msg); // 14-4. 메시지 보내기
} catch (IOException e) {
e.printStackTrace();
}
}
}
// 5. 클라이언트의 이름과 메세지를 읽어들여 다시 클라이언트에게 메시지를 전송할 Thread를 Inner클래스로 정의
// 내부 클래스 장점: Inner클래스에서 부모클래스의 멤버들을 직접 사용할 수 있음.
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 {
// 6. 사용자가 들어오자마자 대화명을 String 으로 입력할 것
// -> 먼저 String부터 처리해준다
// = 서버에서 클라이언트가 보내는 최초의 메세지(=대화명)을 수신
name = dis.readUTF();
// 7. 대화명을 받아서 다른 모든 클라이언트들에게 대화방 참여 메시지 전송
sendMessage("#" + name + " 님이 입장했습니다.");
// 8. 대화명과 소켓정보를 Map에 지정한다.
clients.put(name, socket);
System.out.println("#현재 서버 접속자 수는 " + clients.size() + "명입니다.");
// 9. 이후의 메시지는 반복문으로 처리한다.
// 한 클라이언트가 보낸 메시지를 다른 모든 클라이언트에게 보내준다.
while (dis != null) {
sendMessage(dis.readUTF(), name);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
// 10. 이 finally 영역이 실행된다는 것은 클라이언트의 접속이 종료되었다는 의미이다.
sendMessage(name + "님이 나가셨습니다.");
// 11. Map에서 해당 대화명을 삭제한다.
clients.remove(name);
System.out.println("[" + socket.getInetAddress() + " : " + socket.getPort() + "]에서 접속을 종료했습니다.");
System.out.println("현재 접속자 수는 " + clients.size() + "명입니다.");
}
}
} // ServerReceiver class
public static void main(String[] args) {
new MultiChatServer().serverStart();
}
} // MultiChatServer class
MultiChatClient.java
public cass MultiChatClient {
private Scanner scan = new Scanner(System.in);
private String name;
// 1. 시작 메서드
public void clientStart() {
// 2. 대화명 입력받기
System.out.prin("대화명 : ");
name = scan.next();
Socket socket = null;
try {
socket = new Socket("192.168.45.2", 7777);
System.out.println("서버에 연결되었습니다.");
// 8.
// 9.
} catch (UnknownHostException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
// 서버의 메시지를 receive & 내 메시지 send -> sender, receiver 둘다 필요
// 3. 메시지를 전송하는 Thread (내부클래스)
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 {
// 4. 시작하자마자 자신의 대화명을 서ㅓ로 전송
if(dos != null) {
dos.writeUTF(name);
}
while(dos!= null) {
// 5. 키보드로 입력받은 메시지를 서버로 전송
dos.writeUTF(scan.nextLine());
}
} catch (IOException e) {
e.printStackTrace();
}
}
} // 내부클래스
// 6. 수신용 Thread 클래스
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 {
// 7. 서버로부터 수신한 메시지 출력하기
System.out.println(dis.readUTF());
} catch (IOException e) {
e.printStackTrace();
}
}
}
}// 내부클래스
public static void main(String[] args) {
new MultiChatClient().clientStart();
}
} // MultiChatClient class
파일 전송 예제
TcpFileServer.java
- 서버는 클라이언트가 접속하면 서버 컴퓨터의 D:/D_Other 폴더에 있는 Tulips.jpg 파일을 클라이언트로 전송한다.
public class TcpFileServer {
private ServerSocket server;
private Socket socket;
private FileInputStream fis;
private OutputStream out;
// 1. 서버 시작
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(); // 문자열이 아닌 바이너리 데이터를 보내기 위해 DataOutputStream 안씀
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); ) {
// close 필요한 객체들을 try 안에서 생성하면 finally 영역에서 자동으로 close해줌
// (JDK 1.7 이상, try-with-resource)
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();
}
}
Author And Source
이 문제에 관하여(Java - 14. 네트워크 - ServerSocket, Socket, TCP), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다
https://velog.io/@godkimchichi/Java-14-네트워크-Socket
저자 귀속: 원작자 정보가 원작자 URL에 포함되어 있으며 저작권은 원작자 소유입니다.
우수한 개발자 콘텐츠 발견에 전념
(Collection and Share based on the CC Protocol.)
// 1. TCP 소켓통신을 하기 위해 ServerSocket 객체를 생성
// 서버소켓을 가지고 클라이언트의 소켓 접속을 기다릴 것임
ServerSocket server = new ServerSocket(7777); // ~1023은 WellKnown이라 사용불가
System.out.println("서버가 접속을 기다립니다...")
// 2. ServerSocket객체의 accept()메서드를 호출,
// Client로부터 연결요청이 올 때까지 계속 기다린다. (=main스레드 블락(wait))
// 3. 연결요청이 들어오면 새로운 Socket객체를 생성하여 Client의 Socket과 연결한다
Socket socket = server.accept();
// ----------------------------
// 이 이후는 클라이언트와 연결된 후의 작업을 진행하면 된다.
System.out.println("접속한 클라이언트 정보");
System.out.println("주소 : " + socket.getInetAddress()); // toString() 오버라이드
// 4. Socket객체의 Stream객체(InputStream, OutputStream)를 이용하여 메시지를 주고 받는다.
// OutputStream객체를 구성하여 전송
// 접속한 Socket의 getOutputStream()메서드를 이용하여 구한다.
OutputStream out = socket.getOutputStream();
DataOutputStream dos = new DataoOutputStream(out);
// ▶ OutputStreamReader를 쓸 수 도 있음
dos.writeUTF("어솨세요!!^ㅡ^"); // 메시지 보내기
System.out.println("메세지를 보냈습니다.");
// 5. 사용이 완료된 소켓은 close()메서드를 이용하여 종료 처리한다.
dos.close();
server.close(); // 소켓도 스트림처럼 사용 후에 닫아줘야 한다.
String serverIp = "127.0.0.1"; // loopback주소
// 자기 자신의 컴퓨터를 나타내는 방법
// IP : 127.0.0.1
// 컴이름 : localhost
System.out.println(serverIp + " 서버에 접속 중입니다.");
// 1. 소켓을 생성하여 서버에 연결을 요청한다.
Socket socket = new Socket(serverIp, 7777);
// 2. 연결이 정상적으로 이루어지면 생성된 소켓 객체를 이용하여 서버와 메시지를 주고받음
// 생성된 소켓으로부터 스트림(InputStream, OutputStream)객체를 가져와 이용
System.out.println("연결되었습니다.");
// 서버에서 보내온 메시지 받기
// 메시지를 받기 위해 InputStream객체를 생성
// Socket의 getInputStream()메서드 이용
InputStream is = socket.getInputStream();
DataInputStream dis = new DataInputStream(is);
// 서버로부터 받은 메시지 출력하기
System.out.println(dis.readUTF());
System.out.println("연결종료");
// 3. 사용이 완료된 소켓은 close() 메서드를 이용하여 종료처리한다.
dis.close(); // 스트림닫기
socket.close();
- 문제점1: 서버 애플리케이션은 지속적으로 클라이언트의 연결 수락 기능을 수행해야하지만 입출력에서 블로킹되면 이 작업을 할 수 없게 됨
- 문제점2: 클라이언트 1과 입출력하는 동안에는 클라이언트2와 입출력을 할수없음
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()); // scanner: 엔터치기전까지 block
} catch (Exception e) {
e.printStackTrace();
}
}
scan.close();
}
} // class
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());
// -> Sender에서 writeUTF 호출 전까지 block,
// writeUTF 호출되면 그때부터 read하기 시작
} catch (IOException e) {
e.printStackTrace();
}
}
}
} // class
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); // toString() 오버라이드 되어있음
Sender sender = new Sender(socket);
Receiver receiver = new Receiver(socket);
sender.start();
receiver.start();
// 여기까지하면 이제 3개(main+sender+receiver)의 스레드가 작동하는 멀티스레드 프로그램
} // main
} // class
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();
}
}
- 1) 유저A: "안녕하세요!"
- 2) 서버가 유저A의 메세지를 들고 간다
- 3) 서버가 갖고 있는 맵의 소켓들을 다 꺼내서 거기에 안녕하세요를 다 write한다
- 4) 그러면 다른 유저 B,C,D...들도 "안녕하세요!" 라는 메시지를 받게된다.
- 뽀인트) 사실 유저A는 서버에 "안녕하세요!" 한건데 서버가 가운데서 열심히 일한 덕택에 B,C,D...까지 인사를 받을 수 있었던 것!!
- 소켓을 관리하기 위해 컬랙션 객체 (Map)이용
- Collections의 synchronized 메서드 이용해 Collection객체인 Map을 동기화 처리
- 우리의 프로그램은 멀티스레드 프로그래밍이기 때문에 다수의 유저가 접속하면서 동시에 소켓이 만들어질 때 동시에 put을 하게 되는 상황 발생 이 상황을 방지하고 안전하게 하기 위해 "동기화 처리"
public class MultiChatServer {
// 1. 소켓을 관리하기 위한 컬렉션 객체 정의
// - 대화명, 클라이언트의 Socket을 저장하기 위한 Map 변수 선언
private Map<String, Socket> clients; // String: 대화명, Socket: 해당 유저의 소켓
public MultiChatServer() {
// 2. 다수의 유저가 접속했을 시, 동시에 소켓이 만들어지고 Map에 저장하게 된다
// 이를 위해 미리 객체생성시에 미리 동기화 처리가 가능하도록 처리해준다.
clients = Collections.synchronizedMap(new HashMap<>());
// 3. 서버를 시작하는 메서드
public void serverStart() {
ServerSocket serverSocket = null; // try문 밖에서 close하기 위해 밖에서 선언
Socket socket;
try {
serverSocket = new ServerSocket(7777);
System.out.println("서버가 시작되었습니다.");
while (true) {
// 4. 클라이언트의 접속을 대기한다.
socket = serverSocket.accept();
System.out.println("[" + socket.getInetAddress() + " : " + socket.getPort() + "] 에서 접속하였습니다."
// 12. 메시지 전송 처리를 하는 스레드 생성 및 실행
ServerReceiver receiver = new ServerReceiver(socket);
receiver.start();
}
} catch (IOException e) {
e.printStackTrace();
} finally {
// 서버 소켓 닫기
if (serverSocket != null) {
try {serverSocket.close();} catch(IOException e) {}
}
}
}
// 13. 메세지 전송 메서드
/**
* 대화방 즉, Map에 저장된 전체 유저에게 '안내'메시지를 전송하는 메서드
* @param msg
*/
public void sendMessage(String msg) {
// 13-1. Map에 저장된 유저의 대화명 리스트를 추출 (key값 구하기)
Iterator<String> it = clients.keySet().iterator();
while (it.hasNext()) {
try {
String name = it.next(); // 13-2. 대화명(key)값 구하기
// 13-3. 대화명에 해당하는 Socket의 OutputStream객체 구하기
DataOutputStream out = new DataOutputStream(clients.get(name).getOutputStream());
out.writeUTF(msg); // 13-4. 메시지 보내기
} catch (IOException e) {
e.printStackTrace();
}
}
}
// 14. 13번 오버로딩 : 대화명 + 내용
/**
* 대화방 즉, Map에 저장된 전체 유저에게 '대화'메시지를 전송하는 메서드
* @param msg 보낼 메시지
* @param from 보낸 사람 대화명
*/
public void sendMessage(String msg, String from) {
// 14-1. Map에 저장된 유저의 대화명 리스트를 추출 (key값 구하기)
Iterator<String> it = clients.keySet().iterator();
while (it.hasNext()) {
try {
String name = it.next(); // 14-2. 대화명(key)값 구하기
// 14-3. 대화명에 해당하는 Socket의 OutputStream객체 구하기
DataOutputStream out = new DataOutputStream(clients.get(name).getOutputStream());
out.writeUTF("#" + from + "님의 메세지 : " + msg); // 14-4. 메시지 보내기
} catch (IOException e) {
e.printStackTrace();
}
}
}
// 5. 클라이언트의 이름과 메세지를 읽어들여 다시 클라이언트에게 메시지를 전송할 Thread를 Inner클래스로 정의
// 내부 클래스 장점: Inner클래스에서 부모클래스의 멤버들을 직접 사용할 수 있음.
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 {
// 6. 사용자가 들어오자마자 대화명을 String 으로 입력할 것
// -> 먼저 String부터 처리해준다
// = 서버에서 클라이언트가 보내는 최초의 메세지(=대화명)을 수신
name = dis.readUTF();
// 7. 대화명을 받아서 다른 모든 클라이언트들에게 대화방 참여 메시지 전송
sendMessage("#" + name + " 님이 입장했습니다.");
// 8. 대화명과 소켓정보를 Map에 지정한다.
clients.put(name, socket);
System.out.println("#현재 서버 접속자 수는 " + clients.size() + "명입니다.");
// 9. 이후의 메시지는 반복문으로 처리한다.
// 한 클라이언트가 보낸 메시지를 다른 모든 클라이언트에게 보내준다.
while (dis != null) {
sendMessage(dis.readUTF(), name);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
// 10. 이 finally 영역이 실행된다는 것은 클라이언트의 접속이 종료되었다는 의미이다.
sendMessage(name + "님이 나가셨습니다.");
// 11. Map에서 해당 대화명을 삭제한다.
clients.remove(name);
System.out.println("[" + socket.getInetAddress() + " : " + socket.getPort() + "]에서 접속을 종료했습니다.");
System.out.println("현재 접속자 수는 " + clients.size() + "명입니다.");
}
}
} // ServerReceiver class
public static void main(String[] args) {
new MultiChatServer().serverStart();
}
} // MultiChatServer class
public cass MultiChatClient {
private Scanner scan = new Scanner(System.in);
private String name;
// 1. 시작 메서드
public void clientStart() {
// 2. 대화명 입력받기
System.out.prin("대화명 : ");
name = scan.next();
Socket socket = null;
try {
socket = new Socket("192.168.45.2", 7777);
System.out.println("서버에 연결되었습니다.");
// 8.
// 9.
} catch (UnknownHostException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
// 서버의 메시지를 receive & 내 메시지 send -> sender, receiver 둘다 필요
// 3. 메시지를 전송하는 Thread (내부클래스)
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 {
// 4. 시작하자마자 자신의 대화명을 서ㅓ로 전송
if(dos != null) {
dos.writeUTF(name);
}
while(dos!= null) {
// 5. 키보드로 입력받은 메시지를 서버로 전송
dos.writeUTF(scan.nextLine());
}
} catch (IOException e) {
e.printStackTrace();
}
}
} // 내부클래스
// 6. 수신용 Thread 클래스
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 {
// 7. 서버로부터 수신한 메시지 출력하기
System.out.println(dis.readUTF());
} catch (IOException e) {
e.printStackTrace();
}
}
}
}// 내부클래스
public static void main(String[] args) {
new MultiChatClient().clientStart();
}
} // MultiChatClient class
public class TcpFileServer {
private ServerSocket server;
private Socket socket;
private FileInputStream fis;
private OutputStream out;
// 1. 서버 시작
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(); // 문자열이 아닌 바이너리 데이터를 보내기 위해 DataOutputStream 안씀
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();
}
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); ) {
// close 필요한 객체들을 try 안에서 생성하면 finally 영역에서 자동으로 close해줌
// (JDK 1.7 이상, try-with-resource)
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();
}
}
Author And Source
이 문제에 관하여(Java - 14. 네트워크 - ServerSocket, Socket, TCP), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://velog.io/@godkimchichi/Java-14-네트워크-Socket저자 귀속: 원작자 정보가 원작자 URL에 포함되어 있으며 저작권은 원작자 소유입니다.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)