네트워크를 통해 두 컴퓨터가 데이터를 주고받기 위한 통신의 출입구(Endpoint) 역할을 하는 소프트웨어 인터페이스.
인터넷을 포함한 대부분의 네트워크에서 사용되는 표준 통신 프로토콜 체계.
TCP(Transmission Control Protocol)
데이터의 신뢰성과 순서를 보장.
데이터를 작은 조각(패킷)으로 나누고, 수신자가 이를 정확하게 재조립하도록 도와주며, 전송 중 손실되면 재전송을 요청한다.
IP(Internet Protocol)
데이터를 목적지까지 전달하는 역할.
패킷들이 인터넷을 통해 올바른 주소로 전달되도록 한다.
데이터의 전송 순서나 도착 여부를 보장하지 않는 비연결형 통신 프로토콜.
실시간 통신에서 주로 사용
구조가 간단하고 오버헤드가 적어 전송 지연이 매우 짧다.
데이터가 유실되거나 순서가 바뀔 수도 있다.(오류 검사나 재전송 같은 기능이 없음.)
accept() 메서드로 클라이언트가 접속하면 Socket 객체를 생성하여 통신을 시작.package lesson09;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
public class Ex01_Server_Main {
public static void main(String[] args) {
int port = 12345;
try(ServerSocket serverSocket = new ServerSocket(port)){
System.out.println("서버 대기 중...");
Socket clientSocket = serverSocket.accept();
System.out.println("클라이언트 연결됨: " + clientSocket.getInetAddress());
BufferedReader in = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));
PrintWriter out = new PrintWriter(clientSocket.getOutputStream(), true);
String input = in.readLine();
System.out.println("클라이언트로부터 받은 메시지: " + input);
out.println(input);
clientSocket.close();
}catch(IOException e){
e.printStackTrace();
}
}
}
package lesson09;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.Socket;
public class Ex01_Client_Main {
public static void main(String[] args) {
String serverIp = "127.0.0.1";
int port = 12345;
try (Socket socket = new Socket(serverIp, port)){
System.out.println("서버에 연결됨");
BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
PrintWriter out = new PrintWriter(socket.getOutputStream(), true);
out.println("안녕하세요, 서버!");
String response = in.readLine();
System.out.println("서버 응답: " + response);
}catch(IOException e){
e.printStackTrace();
}
}
}
package lesson09;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
public class Ex02_ChatServer {
private static final int PORT = 12345;
private static final Set<Ex02_ClientHandler> clients = ConcurrentHashMap.newKeySet();
public static void main(String[] args) {
System.out.println("채팅 서버 시작...");
try(ServerSocket serverSocket = new ServerSocket(PORT)){
while(true){
Socket socket = serverSocket.accept();
System.out.println("클라이언트 연결됨: " + socket.getInetAddress());
Ex02_ClientHandler handler = new Ex02_ClientHandler(socket);
clients.add(handler);
new Thread(handler).start();
}
}catch (IOException e){
e.printStackTrace();
}
}
public static void broadcast(String message, Ex02_ClientHandler sender) {
for(Ex02_ClientHandler client : clients) {
if(client != sender) {
client.sendMessage(message);
}
}
}
public static void removeClient(Ex02_ClientHandler client) {
clients.remove(client);
}
}
package lesson09;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.Socket;
public class Ex02_ClientHandler implements Runnable{
private Socket socket;
private PrintWriter out;
private BufferedReader in;
public Ex02_ClientHandler(Socket socket){
this.socket = socket;
try{
in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
out = new PrintWriter(socket.getOutputStream(), true);
out.println("서버에 연결되었습니다. 닉네임을 입력하세요: ");
}catch(IOException e){
e.printStackTrace();
}
}
public void sendMessage(String msg){
out.println(msg);
}
@Override
public void run() {
try{
String name = in.readLine();
Ex02_ChatServer.broadcast(name + "님이 입장하셨습니다.", this);
String message;
while((message = in.readLine()) != null){
if(message.equalsIgnoreCase("exit")) break;
Ex02_ChatServer.broadcast(name + ": " + message, this);
}
out.println("채팅을 종료합니다.");
}catch(IOException e){
e.printStackTrace();
}finally {
try{
Ex02_ChatServer.removeClient(this);
socket.close();
}catch(IOException e){
e.printStackTrace();
}
}
}
}
package lesson09;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.Socket;
public class Ex02_ChatClient {
public static void main(String[] args) {
String serverIP = "192.168.9.254";
int port = 12345;
try(Socket socket = new Socket(serverIP, port)){
BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
BufferedReader keyboard = new BufferedReader(new InputStreamReader(System.in));
PrintWriter out = new PrintWriter(socket.getOutputStream(), true);
new Thread(()->{
String serverMsg;
try {
while((serverMsg = in.readLine()) != null){
System.out.println(serverMsg);
}
}catch(IOException e){
System.out.println("서버 연결 종료");
}
}).start();
String input;
while((input = keyboard.readLine()) != null){
out.println(input);
if(input.equalsIgnoreCase("exit")) break;
}
}catch(IOException e){
e.printStackTrace();
}
}
}
다른 사람의 서버로 연결하는 법!
다른 사람의 ipv4주소를 찾아(명령 프롬프트에서 ipconfig 하면 찾을 수 있다.) 127.0.0.1 대신 집어 넣고 실행하면, (다른 사람도 서버 실행중이라면) 다른 사람의 서버로 연결하여 재밌는 채팅을 할 수 있다.
혼자서 채팅 테스트를 해보고 싶다면!
인텔리제이 왼쪽 상단 햄버거 메뉴 클릭 -> 실행 -> 구성 편집

새 클라이언트를 만든다! (빌드 및 실행에 client 파일 선택하고, 확인)
그러면 혼자서도 채팅 테스트를 해볼 수 있다!
파일은 바이너리 데이터(0과 1).
BufferedReader, PrintWriter : 텍스트 전송 시 사용
InputStream, OutputStream : 파일 전송 시 사용
1. 파일 전송 명령 전송 (예: /file filename.jpg)
2. 서버가 수신 준비
3. 클라이언트가 파일 크기와 이름을 먼저 보냄
4. 이후 실제 파일 바이너리 전송
package lesson09;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
public class Ex03_ChatServer {
private static final int PORT = 12345;
private static final Set<Ex03_ClientHandler> clients = ConcurrentHashMap.newKeySet();
public static void main(String[] args) {
System.out.println("채팅 서버 시작...");
try(ServerSocket serverSocket = new ServerSocket(PORT)) {
while(true){
Socket socket = serverSocket.accept();
Ex03_ClientHandler handler = new Ex03_ClientHandler(socket);
clients.add(handler);
new Thread(handler).start();
}
} catch(IOException e){
e.printStackTrace();
}
}
public static void broadcast(String message, Ex03_ClientHandler sender){
for(Ex03_ClientHandler client : clients){
if(client != sender){
client.sendMessage(message);
}
}
}
public static void removeClient(Ex03_ClientHandler client){
clients.remove(client);
}
}
package lesson09;
import java.io.*;
import java.net.Socket;
public class Ex03_ClientHandler implements Runnable {
private Socket socket;
private BufferedReader in;
private PrintWriter out;
public Ex03_ClientHandler(Socket socket){
this.socket = socket;
try{
in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
out = new PrintWriter(socket.getOutputStream(), true);
out.println("서버에 연결되었습니다. 닉네임을 입력하세요:");
} catch(IOException e){
e.printStackTrace();
}
}
public void sendMessage(String message) {
out.println(message);
}
private File getUniqueFile(String baseName) {
File file = new File("server_" + baseName);
int count = 1;
while (file.exists()) {
String name = baseName;
int dotIndex = name.lastIndexOf('.');
if (dotIndex != -1) {
name = name.substring(0, dotIndex) + "(" + count + ")" + name.substring(dotIndex);
} else {
name = name + "(" + count + ")";
}
file = new File("server_" + name);
count++;
}
return file;
}
private void receiveFile(String fileName) {
try {
DataInputStream dis = new DataInputStream(socket.getInputStream());
long fileSize = dis.readLong();
File file = getUniqueFile(fileName);
FileOutputStream fos = new FileOutputStream(file);
byte[] buffer = new byte[4096];
long totalRead = 0;
int read, percent = 0;
System.out.println("파일 수신 시작: " + file.getName() + " (" + fileSize + " bytes)");
while (totalRead < fileSize && (read = dis.read(buffer)) != -1) {
fos.write(buffer, 0, read);
totalRead += read;
int newPercent = (int)((totalRead * 100) / fileSize);
if (newPercent > percent) {
percent = newPercent;
System.out.print("\r서버 수신 진행률: " + percent + "%");
}
}
System.out.println("\n파일 저장 완료: " + file.getName());
fos.close();
Ex03_ChatServer.broadcast("파일 [" + file.getName() + "] 수신 완료", this);
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
public void run() {
try {
String name = in.readLine();
Ex03_ChatServer.broadcast(name + "님이 입장하셨습니다.", this);
String message;
while ((message = in.readLine()) != null) {
if (message.startsWith("/file")) {
String fileName = message.substring(6);
receiveFile(fileName);
} else if (message.equalsIgnoreCase("exit")) {
break;
} else {
Ex03_ChatServer.broadcast(name + ": " + message, this);
}
}
out.println("채팅을 종료합니다.");
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
Ex03_ChatServer.removeClient(this);
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
package lesson09;
import java.io.*;
import java.net.Socket;
public class Ex03_ChatClient {
public static void main(String[] args) {
String serverIP = "127.0.0.1";
int port = 12345;
try (Socket socket = new Socket(serverIP, port)) {
BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
BufferedReader keyboard = new BufferedReader(new InputStreamReader(System.in));
PrintWriter out = new PrintWriter(socket.getOutputStream(), true);
// 서버 메시지 수신 스레드
new Thread(() -> {
try {
String line;
while ((line = in.readLine()) != null) {
System.out.println(line);
if (line.startsWith("파일 [") && line.endsWith("] 수신 완료")) {
String fileName = line.substring(4, line.indexOf("]"));
receiveFile(socket, fileName);
}
}
} catch (IOException e) {
System.out.println("서버 연결 종료");
}
}).start();
// 키보드 입력 처리
String input;
while ((input = keyboard.readLine()) != null) {
if (input.startsWith("/file ")) {
String filePath = input.substring(6);
sendFile(socket, filePath, out);
} else {
out.println(input);
if (input.equalsIgnoreCase("exit")) break;
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
private static void sendFile(Socket socket, String filePath, PrintWriter out) {
try {
File file = new File(filePath);
long fileSize = file.length();
String fileName = file.getName();
out.println("/file " + fileName);
DataOutputStream dos = new DataOutputStream(socket.getOutputStream());
FileInputStream fis = new FileInputStream(file);
dos.writeLong(fileSize);
byte[] buffer = new byte[4096];
long totalSent = 0;
int read, percent = 0;
System.out.println("파일 전송 시작: " + fileName + " (" + fileSize + " bytes)");
while ((read = fis.read(buffer)) != -1) {
dos.write(buffer, 0, read);
totalSent += read;
int newPercent = (int)((totalSent * 100) / fileSize);
if (newPercent > percent) {
percent = newPercent;
System.out.print("\r클라이언트 전송 진행률: " + percent + "%");
}
}
fis.close();
dos.flush();
System.out.println("\n파일 전송 완료: " + fileName);
} catch (IOException e) {
e.printStackTrace();
}
}
private static File getUniqueFile(String baseName) {
File file = new File("client_" + baseName);
int count = 1;
while (file.exists()) {
String name = baseName;
int dotIndex = name.lastIndexOf('.');
if (dotIndex != -1) {
name = name.substring(0, dotIndex) + "(" + count + ")" + name.substring(dotIndex);
} else {
name = name + "(" + count + ")";
}
file = new File("client_" + name);
count++;
}
return file;
}
private static void receiveFile(Socket socket, String fileName) {
try {
DataInputStream dis = new DataInputStream(socket.getInputStream());
long fileSize = dis.readLong();
File file = getUniqueFile(fileName);
FileOutputStream fos = new FileOutputStream(file);
byte[] buffer = new byte[4096];
long totalRead = 0;
int read, percent = 0;
System.out.println("파일 수신 시작: " + file.getName() + " (" + fileSize + " bytes)");
while (totalRead < fileSize && (read = dis.read(buffer)) != -1) {
fos.write(buffer, 0, read);
totalRead += read;
int newPercent = (int)((totalRead * 100) / fileSize);
if (newPercent > percent) {
percent = newPercent;
System.out.print("\r클라이언트 수신 진행률: " + percent + "%");
}
}
fos.close();
System.out.println("\n파일 저장 완료: " + file.getName());
} catch (IOException e) {
e.printStackTrace();
}
}
}