TCP Server Socket Programming
C 언어로 작성된 TCP 서버 소켓 프로그래밍 예시입니다. 서버는 클라이언트 연결을 기다리고, 연결되면 데이터를 주고받습니다.
1. TCP 서버 코드:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <winsock2.h>
#pragma comment(lib, "ws2_32.lib") // Winsock 라이브러리를 연결하기 위한 pragma 지시문
#define MAX_BUFFER_SIZE 1024 // 데이터를 송수신할 때 사용할 버퍼의 최대 크기를 1024 바이트로 설정
int main() {
WSADATA wsaData; // Winsock 초기화를 위한 구조체
SOCKET serverSocket, clientSocket; // 서버 소켓과 클라이언트 소켓 변수 선언
struct sockaddr_in serverAddr, clientAddr; // 서버와 클라이언트의 주소 정보를 저장할 구조체
char buffer[MAX_BUFFER_SIZE]; // 클라이언트로부터 수신할 메시지를 저장할 버퍼
int clientAddrSize; // 클라이언트 주소 크기를 저장할 변수
// Winsock 초기화
if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) {
printf("Failed to initialize winsock.\n");
return -1; // Winsock 초기화 실패 시 프로그램 종료
}
// 소켓 생성 (TCP 프로토콜 사용)
if ((serverSocket = socket(AF_INET, SOCK_STREAM, 0)) == INVALID_SOCKET) {
printf("Failed to create socket.\n");
return -1; // 소켓 생성 실패 시 프로그램 종료
}
// 서버 주소 및 포트 설정
serverAddr.sin_family = AF_INET; // 주소 체계는 IPv4 사용
serverAddr.sin_addr.s_addr = htonl(INADDR_ANY); // 모든 네트워크 인터페이스에서 연결을 허용 (INADDR_ANY)
serverAddr.sin_port = htons(5001); // 포트 번호를 5001로 설정 (호스트 바이트 순서를 네트워크 바이트 순서로 변경)
// 소켓 바인딩 (서버 소켓에 IP 주소와 포트 번호를 할당)
if (bind(serverSocket, (struct sockaddr*)&serverAddr, sizeof(serverAddr)) == SOCKET_ERROR) {
printf("Failed to bind socket.\n");
return -1; // 바인딩 실패 시 프로그램 종료
}
// 클라이언트 연결 요청 대기 (최대 1개의 연결 요청을 큐에 저장)
if (listen(serverSocket, 1) == SOCKET_ERROR) {
printf("Failed to listen on socket.\n");
return -1; // 클라이언트 연결 대기 실패 시 프로그램 종료
}
printf("서버가 실행 중입니다...\n");
// 클라이언트 연결 수락
clientAddrSize = sizeof(clientAddr); // 클라이언트 주소 구조체의 크기를 설정
if ((clientSocket = accept(serverSocket, (struct sockaddr*)&clientAddr, &clientAddrSize)) == INVALID_SOCKET) {
printf("Failed to accept client connection.\n");
return -1; // 클라이언트 연결 수락 실패 시 프로그램 종료
}
printf("클라이언트가 연결되었습니다.\n");
// 메시지 송수신 루프
while (1) {
// 클라이언트로부터 메시지 수신
memset(buffer, 0, MAX_BUFFER_SIZE); // 버퍼 초기화
int bytesRead = recv(clientSocket, buffer, MAX_BUFFER_SIZE, 0); // 클라이언트로부터 메시지를 수신
if (bytesRead == SOCKET_ERROR || bytesRead == 0) {
break; // 수신 오류 또는 클라이언트가 연결을 끊은 경우 루프 종료
}
printf("수신한 메시지: %s\n", buffer); // 수신한 메시지를 출력
// 클라이언트에게 응답 메시지 전송
char response[MAX_BUFFER_SIZE]; // 응답 메시지를 저장할 버퍼
snprintf(response, MAX_BUFFER_SIZE, "서버가 메시지를 수신했습니다: %s", buffer); // 응답 메시지 생성
send(clientSocket, response, strlen(response), 0); // 응답 메시지를 클라이언트에게 전송
}
// 연결 종료
closesocket(clientSocket); // 클라이언트 소켓 닫기
closesocket(serverSocket); // 서버 소켓 닫기
WSACleanup(); // Winsock 리소스 해제
return 0;
}
코드 설명:
1. Winsock 초기화:
• WSAStartup(MAKEWORD(2, 2), &wsaData) 함수로 Winsock을 초기화. 이 단계가 성공해야 네트워크 작업이 가능해.
• 실패하면 -1을 반환하고 프로그램을 종료해.
2. 소켓 생성:
• socket(AF_INET, SOCK_STREAM, 0) 함수로 TCP 소켓을 생성.
• AF_INET: IPv4 주소 체계를 사용.
• SOCK_STREAM: 스트림 기반, 즉 TCP 프로토콜을 의미.
3. 서버 주소 설정:
• sin_family: 주소 체계를 AF_INET(IPv4)로 설정.
• sin_addr.s_addr: htonl(INADDR_ANY)로 모든 인터페이스에서 접속을 허용.
• sin_port: htons(5001)로 포트 번호를 설정. htons는 호스트 바이트 순서를 네트워크 바이트 순서로 변환해.
4. 바인딩:
• bind() 함수로 소켓에 IP 주소와 포트 번호를 바인딩. 실패 시 오류 메시지를 출력하고 종료.
5. 클라이언트 연결 대기:
• listen() 함수로 서버 소켓을 클라이언트 연결 대기 상태로 설정. 최대 1개의 클라이언트를 대기열에 넣음.
6. 클라이언트 연결 수락:
• accept() 함수로 클라이언트 연결을 수락하고, 성공하면 클라이언트와의 통신에 사용할 소켓을 반환.
7. 메시지 송수신:
• 서버는 **recv()**로 클라이언트가 보낸 메시지를 수신하고, 수신된 메시지를 처리한 후 **send()**를 통해 응답 메시지를 전송.
8. 연결 종료:
• closesocket() 함수를 사용하여 클라이언트와 서버 소켓을 닫고, WSACleanup()으로 Winsock 라이브러리 리소스를 해제.
TCP Client Socket Programming
C 언어로 작성된 TCP 클라이언트 소켓 프로그래밍 예시입니다. 클라이언트는 서버에 연결하여 데이터를 전송하고 응답을 받습니다.
2. TCP 클라이언트 코드:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <winsock2.h>
#include <ws2tcpip.h>
#pragma comment(lib, "ws2_32.lib") // Winsock 라이브러리를 연결하기 위한 pragma 지시문
#define MAX_BUFFER_SIZE 1024 // 버퍼의 최대 크기를 1024 바이트로 설정
int main() {
WSADATA wsaData; // Winsock 초기화를 위한 데이터 구조체
SOCKET clientSocket; // 클라이언트 소켓 변수
struct sockaddr_in serverAddr; // 서버 주소 정보를 저장할 구조체
struct in_addr ipAddr; // 서버 IP 주소를 저장할 구조체 (inet_pton 사용 시 필요)
char buffer[MAX_BUFFER_SIZE]; // 서버로부터 수신할 메시지를 저장할 버퍼
char message[MAX_BUFFER_SIZE]; // 서버로 보낼 메시지를 저장할 버퍼
// Winsock 초기화 (WSAStartup 사용)
if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) {
printf("Failed to initialize winsock.\n");
return -1; // Winsock 초기화 실패 시 종료
}
// 소켓 생성 (TCP 소켓 생성)
if ((clientSocket = socket(AF_INET, SOCK_STREAM, 0)) == INVALID_SOCKET) {
printf("Failed to create socket.\n");
return -1; // 소켓 생성 실패 시 종료
}
// 서버 정보 설정 (IPv4, 포트 5001 사용)
serverAddr.sin_family = AF_INET; // 주소 체계를 IPv4로 설정
serverAddr.sin_port = htons(5001); // 포트 번호를 5001로 설정 (htons 사용)
// 서버 IP 주소 설정 (로컬호스트: 127.0.0.1)
// inet_pton 함수를 사용하여 IP 주소를 네트워크 바이트 순서로 변환
if (inet_pton(AF_INET, "127.0.0.1", &serverAddr.sin_addr) <= 0) {
printf("Invalid address/ Address not supported.\n");
return -1; // IP 주소 변환 실패 시 종료
}
// 서버에 연결 시도
if (connect(clientSocket, (struct sockaddr*)&serverAddr, sizeof(serverAddr)) < 0) {
printf("Failed to connect to server.\n");
return -1; // 서버 연결 실패 시 종료
}
// 메시지 송수신 루프
while (1) {
// 사용자로부터 메시지 입력
printf("메시지를 입력하세요 (종료하려면 q 또는 Q): ");
fgets(message, MAX_BUFFER_SIZE, stdin); // 표준 입력으로부터 메시지를 받음
message[strcspn(message, "\n")] = '\0'; // 입력된 문자열에서 개행 문자 제거
if (strcmp(message, "q") == 0 || strcmp(message, "Q") == 0) {
break; // 사용자가 'q' 또는 'Q'를 입력하면 루프 종료 (프로그램 종료)
}
// 서버로 메시지 전송
send(clientSocket, message, strlen(message), 0); // 입력된 메시지를 서버로 전송
// 서버로부터 응답 수신
memset(buffer, 0, MAX_BUFFER_SIZE); // 수신할 버퍼를 초기화
int bytesRead = recv(clientSocket, buffer, MAX_BUFFER_SIZE, 0); // 서버로부터 데이터 수신
if (bytesRead == SOCKET_ERROR || bytesRead == 0) {
break; // 수신 오류 또는 서버가 연결을 종료했을 때 루프 종료
}
printf("서버로부터 받은 응답: %s\n", buffer); // 서버로부터 받은 메시지를 출력
}
// 연결 종료
closesocket(clientSocket); // 클라이언트 소켓 닫기
WSACleanup(); // Winsock 리소스를 해제
return 0;
}
코드 설명:
1. Winsock 초기화:
• WSAStartup(MAKEWORD(2, 2), &wsaData)를 호출하여 Winsock을 초기화해. 이 단계가 성공해야만 네트워크 관련 작업을 할 수 있어.
• 초기화에 실패하면, 프로그램은 -1을 반환하고 종료돼.
2. TCP 소켓 생성:
• socket(AF_INET, SOCK_STREAM, 0)를 호출하여 TCP 소켓을 생성해. TCP는 연결 지향형 프로토콜이므로 데이터를 안전하게 전송할 수 있어.
• 소켓 생성에 실패하면 오류 메시지를 출력하고 종료돼.
3. 서버 주소 및 포트 설정:
• 서버는 IPv4(AF_INET)를 사용하고, 5001 포트로 통신을 하도록 설정돼.
• IP 주소는 127.0.0.1(로컬호스트)로 설정돼 있어서 동일한 컴퓨터 내에서 서버에 연결할 수 있어.
4. 서버에 연결:
• connect()를 호출하여 서버에 연결을 시도해. 서버가 수신 대기 중인 상태여야만 연결이 성립돼.
• 서버와의 연결이 실패하면 프로그램이 종료돼.
5. 사용자 입력 및 서버 통신:
• 루프를 통해 사용자는 서버로 보낼 메시지를 입력할 수 있고, send() 함수를 사용해 메시지를 서버로 전송해.
• 서버는 클라이언트의 메시지를 받고, 그에 대한 응답을 클라이언트로 다시 보내.
• 클라이언트는 recv() 함수로 서버의 응답을 수신한 후 이를 출력해.
6. 프로그램 종료 조건:
• 사용자가 ‘q’ 또는 **‘Q’**를 입력하면 프로그램이 종료되도록 구현돼.
7. 연결 종료:
• 통신이 완료되면 **closesocket()**을 호출해 클라이언트 소켓을 닫고, WSACleanup()을 호출해 Winsock 라이브러리에서 사용한 리소스를 해제해.
Java TCP Client-Server Socket Programming
1. TCP 서버 코드
TCPServer.java
package com.jinsu.server;
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
public class TCPServer {
private static final int PORT = 5001; // 서버가 실행될 포트 번호
private static final int MAX_BUFFER_SIZE = 1024; // 버퍼의 최대 크기 (참고로 사용됨, 실제 코드에선 사용되지 않음)
public static void main(String[] args) {
ServerSocket serverSocket = null; // 클라이언트 연결을 수락하기 위한 서버 소켓
Socket clientSocket = null; // 클라이언트와 통신하기 위한 소켓
try {
// 서버 소켓 생성 (지정된 포트에서 클라이언트 연결을 대기)
serverSocket = new ServerSocket(PORT);
System.out.println("서버가 실행 중입니다...");
// 클라이언트 연결 수락 (클라이언트가 연결할 때까지 대기)
clientSocket = serverSocket.accept();
System.out.println("클라이언트가 연결되었습니다."); // 클라이언트 연결 성공 시 메시지 출력
// 클라이언트로부터 메시지를 수신하기 위한 입력 스트림 생성
InputStream input = clientSocket.getInputStream(); // 클라이언트에서 보낸 데이터를 읽기 위한 스트림
BufferedReader reader = new BufferedReader(new InputStreamReader(input)); // 입력 스트림을 버퍼로 감싸서 읽기
// 클라이언트에게 메시지를 전송하기 위한 출력 스트림 생성
OutputStream output = clientSocket.getOutputStream(); // 클라이언트로 데이터를 보내기 위한 스트림
PrintWriter writer = new PrintWriter(output, true); // 출력 스트림을 PrintWriter로 감싸고, 자동으로 flush 설정
String clientMessage; // 클라이언트가 보낸 메시지를 저장할 변수
// 클라이언트로부터 메시지를 수신하고 응답을 전송하는 루프
while ((clientMessage = reader.readLine()) != null) { // 클라이언트가 메시지를 보내면 읽어들임
System.out.println("수신한 메시지: " + clientMessage); // 수신한 메시지를 서버에 출력
// 클라이언트에게 응답 전송
writer.println("서버가 메시지를 수신했습니다: " + clientMessage); // 클라이언트에게 응답 메시지 전송
}
} catch (IOException e) {
// 예외 처리 (입출력 오류 발생 시)
System.out.println("서버 오류: " + e.getMessage());
e.printStackTrace(); // 오류 스택 추적 출력
} finally {
// 리소스 해제 (클라이언트 소켓과 서버 소켓을 닫음)
try {
if (clientSocket != null) { // 클라이언트 소켓이 null이 아니면 닫음
clientSocket.close();
}
if (serverSocket != null) { // 서버 소켓이 null이 아니면 닫음
serverSocket.close();
}
} catch (IOException ex) {
ex.printStackTrace(); // 리소스 해제 중 발생한 예외 처리
}
}
}
}
코드 설명:
1. 상수 선언
• PORT: 서버가 대기할 포트 번호로, 여기서는 5001번을 사용. 클라이언트는 이 포트로 서버에 연결해.
• MAX_BUFFER_SIZE: 이 변수는 참고용으로 설정되어 있지만 실제 코드에서는 사용되지 않아. 버퍼 크기를 제한하거나 관리할 때 사용될 수 있어.
2. 서버 소켓 및 클라이언트 소켓 생성
• serverSocket: 클라이언트의 연결 요청을 수락하는 역할을 하는 소켓.
• clientSocket: 서버와 연결된 클라이언트와의 통신을 담당하는 소켓.
3. 서버 소켓 생성 및 실행
• serverSocket = new ServerSocket(PORT): 지정된 포트에서 서버 소켓을 생성하고 클라이언트 연결을 대기.
• 서버가 실행되면 "서버가 실행 중입니다..." 메시지를 출력.
4. 클라이언트 연결 수락
• clientSocket = serverSocket.accept(): 서버는 클라이언트의 연결 요청을 수락하고, 연결된 클라이언트와 통신할 수 있도록 clientSocket을 생성.
• 클라이언트가 연결되면 "클라이언트가 연결되었습니다." 메시지를 출력.
5. 클라이언트와의 데이터 송수신 설정
• 입력 스트림(InputStream)과 BufferedReader를 사용해 클라이언트가 보낸 데이터를 읽어들임.
• 출력 스트림(OutputStream)과 PrintWriter를 사용해 클라이언트에게 데이터를 전송함. true 옵션으로 자동 flush를 설정해, 메시지가 즉시 전송됨.
6. 메시지 송수신 루프
• reader.readLine()으로 클라이언트로부터 메시지를 읽어들이고, 서버 콘솔에 출력.
• 클라이언트에게 응답 메시지를 전송. 응답 내용은 "서버가 메시지를 수신했습니다: [클라이언트 메시지]".
7. 예외 처리 및 리소스 해제
• IOException 예외가 발생할 경우, 오류 메시지를 출력하고 예외 스택 추적을 출력함.
• finally 블록에서 클라이언트 소켓과 서버 소켓을 안전하게 닫음. 연결이 종료되면 소켓을 닫아 네트워크 리소스를 해제함.
2. TCP 클라이언트 코드
TCPClient.java
package com.jinsu.client;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.Socket;
public class TCPClient {
// 서버의 주소 및 포트 설정
private static final String SERVER_ADDRESS = "localhost"; // 서버 주소 (로컬호스트)
private static final int SERVER_PORT = 5001; // 서버 포트 번호
private static final int MAX_BUFFER_SIZE = 1024; // 버퍼의 최대 크기 설정
public static void main(String[] args) {
Socket socket = null; // 서버와 통신하기 위한 소켓 객체
BufferedReader reader = null; // 서버로부터 데이터를 읽어오기 위한 BufferedReader
PrintWriter writer = null; // 서버로 데이터를 전송하기 위한 PrintWriter
try {
// 서버에 연결 (소켓 생성 및 연결 시도)
socket = new Socket(SERVER_ADDRESS, SERVER_PORT);
System.out.println("서버에 연결되었습니다."); // 연결 성공 메시지 출력
// 서버로 데이터를 보내기 위한 PrintWriter (자동으로 flush 설정: true)
writer = new PrintWriter(socket.getOutputStream(), true);
// 서버로부터 데이터를 받기 위한 BufferedReader
reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
// 사용자 입력을 받기 위한 BufferedReader
BufferedReader userInputReader = new BufferedReader(new InputStreamReader(System.in));
String userInput; // 사용자의 입력을 저장할 변수
// 메시지 송수신을 위한 루프
while (true) {
System.out.print("메시지를 입력하세요 (종료하려면 q 또는 Q): ");
userInput = userInputReader.readLine(); // 사용자로부터 입력을 받음
// 'q' 또는 'Q'를 입력하면 프로그램 종료
if ("q".equalsIgnoreCase(userInput)) {
break;
}
// 서버로 메시지 전송
writer.println(userInput); // 사용자 입력을 서버로 전송
// 서버로부터 응답 수신
String response = reader.readLine(); // 서버의 응답을 수신
System.out.println("서버로부터 받은 응답: " + response); // 서버의 응답 출력
}
} catch (IOException e) {
// 예외 처리 (입출력 오류 발생 시)
System.out.println("클라이언트 오류: " + e.getMessage());
e.printStackTrace(); // 오류 스택 추적 출력
} finally {
// 리소스 해제 (클라이언트 소켓, 입출력 스트림을 닫음)
try {
if (reader != null) reader.close(); // BufferedReader 닫기
if (writer != null) writer.close(); // PrintWriter 닫기
if (socket != null) socket.close(); // 소켓 닫기
} catch (IOException ex) {
ex.printStackTrace(); // 리소스 해제 중 발생한 예외 처리
}
}
}
}
코드 설명:
1. 상수 선언
• SERVER_ADDRESS: 서버의 IP 주소 또는 도메인 이름 (이 코드에서는 로컬호스트인 "localhost"로 설정).
• SERVER_PORT: 서버의 포트 번호 (5001로 설정). 서버가 이 포트에서 클라이언트의 연결을 대기함.
• MAX_BUFFER_SIZE: 메시지의 최대 크기를 1024 바이트로 설정했으나, 실제로 이 변수는 사용되지 않음 (참고로 사용됨).
2. 소켓 연결 및 입출력 스트림 설정
• socket: 서버와의 연결을 관리하는 TCP 소켓.
• reader: 서버로부터 데이터를 수신하기 위한 BufferedReader.
• writer: 서버로 데이터를 전송하기 위한 PrintWriter. true 인자로 자동 flush가 활성화되어, println() 호출 시 즉시 데이터가 전송됨.
3. 서버와의 연결
• socket = new Socket(SERVER_ADDRESS, SERVER_PORT): 서버에 연결을 시도하고, 성공 시 소켓을 생성.
• 서버에 성공적으로 연결되면 "서버에 연결되었습니다." 메시지를 출력함.
4. 사용자 입력 및 메시지 송수신 루프
• 사용자가 입력한 메시지를 userInput에 저장하고, 이를 서버로 전송.
• 서버로부터 수신된 응답을 **readLine()**으로 읽어와 출력.
• ‘q’ 또는 **‘Q’**를 입력하면 while 루프가 종료되고 프로그램이 종료됨.
5. 리소스 해제
• 프로그램이 종료될 때(예외 발생 시 포함), 사용한 리소스(소켓, 스트림)를 안전하게 닫음.
주의 사항:
• 서버 연결 실패 시: 서버가 해당 포트에서 실행 중이지 않거나, 방화벽 또는 네트워크 문제가 있을 경우 연결에 실패할 수 있어.
• 예외 처리: 네트워크 통신 중 발생할 수 있는 다양한 입출력 예외를 처리하기 위해 try-catch-finally 블록을 사용했어.
'Note-Taking' 카테고리의 다른 글
Spring Data JPA 정리 (0) | 2024.10.28 |
---|---|
Spring MVC 구성 요소와 설정 방법에 대한 정리 (2) | 2024.10.15 |
Spring Framework의 주요 메서드와 기능 정리 (6) | 2024.09.24 |
Java 메서드의 주요 개념과 정리 (2) | 2024.09.24 |
JPQL과 QueryDSL 메서드 정리 (0) | 2024.09.24 |