전공 수업 내용 정리 (문제 발생시 비공개합니다.)
소켓 프로그래밍
소켓
소켓은 네트워크(인터넷)에서의 연결 도구로, 운영체제에 의해 제공되는 소프트웨어적인 장치
소켓을 사용하면 프로그래머는 데이터 송수신에 대한 물리적, 소프트웨어적 세부 사항에 신경 쓰지 않고도 네트워크 프로그래밍을 할 수 있다.
소켓의 비유와 분류
TCP 소켓은 전화기에 비유될 수 있습니다. 전화기를 사용하는 방식에 따라 전화를 거는 용도(클라이언트)의 소켓과 전화를 받는 용도(서버)의 소켓으로 나눌 수 있으며, 이 두 소켓의 생성 방법에는 차이가 있다.
서버 소켓의 생성 (전화를 받는 소켓)
1단계: 소켓 생성 (socket
함수 호출)
서버 소켓은 socket
함수를 호출하여 생성한다.
int server_sock = socket(AF_INET, SOCK_STREAM, 0);
if (server_sock == -1) {
// 에러 처리
}
2단계: IP와 포트 번호의 할당 (bind
함수 호출)
전화기에 전화번호가 부여되듯이 소켓에도 주소 정보가 할당되어야 합니다. 이는 bind
함수를 호출하여 이루어집니다. 성공 시 0을 반환하고, 실패 시 -1을 반환한다.
주소 정보: IP 주소 + 포트 번호
struct sockaddr_in server_addr;
memset(&server_addr, 0, sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_addr.s_addr = htonl(INADDR_ANY); // 모든 IP로부터의 접속 허용
server_addr.sin_port = htons(PORT_NUMBER); // 사용할 포트 번호
if (bind(server_sock, (struct sockaddr*)&server_addr, sizeof(server_addr)) == -1) {
// 에러 처리
}
3단계: 연결 요청 대기 상태로 변경 (listen
함수 호출)
소켓을 연결 요청이 가능한 상태로 변경하여, 걸려오는 전화를 받을 수 있는 상태로 만든다. 이는 listen
함수를 호출하여 수행한다.
if (listen(server_sock, BACKLOG) == -1) {
// 에러 처리
}
4단계: 연결 요청 수락 (accept
함수 호출)
걸려오는 전화에 대해 수화기를 드는 것에 비유할 수 있다.
연결 요청이 수락되어야 데이터의 송수신이 가능합니다. 수락된 이후에는 데이터 송수신이 양방향으로 가능하다.
struct sockaddr_in client_addr;
socklen_t client_addr_size = sizeof(client_addr);
int client_sock = accept(server_sock, (struct sockaddr*)&client_addr, &client_addr_size);
if (client_sock == -1) {
// 에러 처리
}
클라이언트 소켓의 생성 (전화를 거는 소켓)
전화를 거는 상황에 비유할 수 있다.
서버 소켓과 달리 구현 과정이 간단하며, 소켓의 생성과 연결 요청의 두 단계로 구분된다.
1단계: 소켓 생성 (socket
함수 호출)
int client_sock = socket(AF_INET, SOCK_STREAM, 0);
if (client_sock == -1) {
// 에러 처리
}
2단계: 연결 요청 (connect
함수 호출)
서버에 연결을 요청하기 위해 connect
함수를 호출
struct sockaddr_in server_addr;
memset(&server_addr, 0, sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_addr.s_addr = inet_addr("SERVER_IP_ADDRESS"); // 서버 IP 주소
server_addr.sin_port = htons(PORT_NUMBER); // 서버 포트 번호
if (connect(client_sock, (struct sockaddr*)&server_addr, sizeof(server_addr)) == -1) {
// 에러 처리
}
전체 흐름 요약
서버 프로그램의 소켓 생성 과정
- 소켓 생성:
socket
함수 호출 - 주소 할당:
bind
함수 호출 - 연결 요청 대기 상태로 변경:
listen
함수 호출 - 연결 요청 수락:
accept
함수 호출
클라이언트 프로그램의 소켓 생성 과정
- 소켓 생성:
socket
함수 호출 - 연결 요청:
connect
함수 호출
참고: hello_server.c
를 통한 함수 호출 과정 확인
hello_server.c
파일을 통해 앞서 언급한 함수들의 호출 과정을 확인할 수 있다.
서버는 연결을 요청하는 클라이언트보다 먼저 실행되어야 하며, 클라이언트보다 복잡한 실행 과정을 거친다.
저수준 파일 입출력
저수준 파일 입출력 개요
저수준 파일 입출력은 표준 라이브러리가 아닌 운영체제에서 제공하는 함수를 기반으로 파일 입출력을 수행하는 방식
이는 C 표준 입출력 함수(fopen
, fclose
, fprintf
, fscanf
등)보다 더 직접적으로 파일을 다루며, 시스템 호출(system call)을 통해 이루어진다.
저수준 파일 입출력 함수를 사용하면 입출력 버퍼링(buffering)이나 포맷팅(formatting) 없이 데이터를 직접 읽고 쓸 수 있어 효율적인 입출력이 가능하다.
파일 디스크립터 (File Descriptor)
파일 디스크립터는 운영체제가 파일이나 소켓을 구분하기 위해 사용하는 정수 값이다. 프로그램에서 파일이나 소켓을 열면 운영체제는 해당 파일이나 소켓에 대한 파일 디스크립터를 반환하며, 이를 통해 파일이나 소켓에 접근할 수 있다.
- 표준 파일 디스크립터
0
: 표준 입력 (Standard Input)1
: 표준 출력 (Standard Output)2
: 표준 에러 (Standard Error)
- 새로운 파일이나 소켓의 파일 디스크립터
- 새로운 파일이나 소켓을 열 때마다 3부터 시작하는 정수 값이 순차적으로 부여
예시:
파일 디스크립터 | 의미 |
---|---|
0 | 표준 입력 (stdin) |
1 | 표준 출력 (stdout) |
2 | 표준 에러 (stderr) |
3 이상 | 사용자 파일 또는 소켓 등 |
파일 조작 함수
저수준 파일 입출력에서는 다음과 같은 함수들을 사용한다.
파일 열기 (open
함수)
파일을 열고 파일 디스크립터를 반환한다.
#include <fcntl.h>
int fd = open(const char *pathname, int flags, mode_t mode);
if (fd == -1) {
// 에러 처리
}
pathname
: 파일 경로flags
: 파일 열기 옵션 (O_RDONLY
,O_WRONLY
,O_RDWR
,O_CREAT
,O_TRUNC
,O_APPEND
등)mode
: 파일 생성 시 권한 설정 (0666
등), 파일 생성 시에만 사용
파일 닫기 (close
함수)
파일 디스크립터를 닫아 파일이나 소켓을 해제한다.
#include <unistd.h>
int result = close(int fd);
if (result == -1) {
// 에러 처리
}
파일에 데이터 쓰기 (write
함수)
파일 디스크립터를 통해 데이터를 파일에 쓴다.
#include <unistd.h>
ssize_t bytes_written = write(int fd, const void *buf, size_t count);
if (bytes_written == -1) {
// 에러 처리
}
fd
: 파일 디스크립터buf
: 쓸 데이터가 저장된 버퍼count
: 쓰고자 하는 바이트 수
파일에서 데이터 읽기 (read
함수)
파일 디스크립터를 통해 파일에서 데이터를 읽는다.
#include <unistd.h>
ssize_t bytes_read = read(int fd, void *buf, size_t count);
if (bytes_read == -1) {
// 에러 처리
}
fd
: 파일 디스크립터buf
: 읽은 데이터를 저장할 버퍼count
: 읽고자 하는 바이트 수
파일 디스크립터와 소켓
리눅스에서는 파일과 소켓을 동일하게 취급한다. 즉, 소켓도 파일 디스크립터를 통해 다루어지며, 파일 입출력 함수인 read
, write
등을 소켓에 그대로 사용할 수 있다. 이는 리눅스의 모든 것은 파일이다 (Everything is a file) 라는 철학에 기반한다.
소켓 프로그래밍에서의 파일 디스크립터
socket
함수는 파일 디스크립터를 반환한다.accept
함수를 호출하면 새로운 파일 디스크립터를 반환하며, 이를 통해 클라이언트와의 통신이 이루어진다.- 소켓에 대한 읽기와 쓰기도
read
,write
함수를 사용할 수 있다.
예시:
// 소켓 생성
int server_sock = socket(AF_INET, SOCK_STREAM, 0);
// 파일 디스크립터는 정수 값
printf("Server socket FD: %d\n", server_sock);
// 클라이언트 연결 수락
int client_sock = accept(server_sock, (struct sockaddr*)&client_addr, &client_addr_size);
printf("Client socket FD: %d\n", client_sock);
// 소켓에 데이터 쓰기
char message[] = "Hello, Client!";
write(client_sock, message, sizeof(message));
// 소켓으로부터 데이터 읽기
char buffer[1024];
int bytes_received = read(client_sock, buffer, sizeof(buffer));
buffer[bytes_received] = '\0';
printf("Received from client: %s\n", buffer);
// 소켓 닫기
close(client_sock);
close(server_sock);
'CS > Network' 카테고리의 다른 글
[네트워크] IP 주소 개요 (0) | 2025.01.29 |
---|---|
[네트워크] 소켓 프로그래밍: 프로토콜, 클라이언트와 서버의 함수호출 (0) | 2025.01.29 |
[네트워크] TCP 타이머, 옵션 (0) | 2025.01.29 |
[네트워크] TCP segment, control (0) | 2025.01.29 |
[컴퓨터 네트워크] 3가지 주소 지정; 물리, 논리, 포트 (0) | 2023.07.01 |