소켓프로그래밍이란 것을 전공수업을 들을 때 몇번 들어본 기억은 있지만 시험과 크게 관련이 없었고, 깊이 공부하지 않아 이름 정도만 익숙할 뿐 어떻게 돌아가는지 이해하지 못하고 넘어갔었다. 하지만 졸업을 위해 캡스톤 주제를 받아 소켓프로그래밍과 컴퓨터간 통신 프로토콜을 공부하게 되었다. 공부하면서 알게된 내용들을 기록하겠다.
컴퓨터를 사용할 때 콘센트의 전원을 꼽고 ip를 이용해 인터넷을 연결하여 서로 통신을 한다. 전원연결을 위한 콘센트를 영어로 소켓이라 한다. 프로그래밍에서 소켓 또한 이와 비슷하다. 전기소켓이 110v, 220v 등으로 나눠져 있는것과 마찬가지로, 네트워크 연결하기 위한 소켓도 정해진 프로토콜(규약)에 맞게 만들어 져야 한다. 보통 osi7 layer의 네 번째 계층인 TCP상에서 동작하는 소켓을 사용하는데, 이를 "TCP/IP소켓"이라 한다.
client는 server에게 정보를 전송하는 역할을 한다. 서로 통신하는 컴퓨터는 클라이언트와 서버의 역할을 바꿔가며 서로가 클라이언트가 되기도 하고 서버가 되기도 한다.
출처 https://recipes4dev.tistory.com/153
클라이언트는 서버와 통신하기 위한 소켓을 생성한다.
int socket(int domain, int type, int protocol);
- domain : 소켓이 사용할 프로토콜 체계를 지정한다. IPV4 인터넷 프로토콜이면 'AF_INET' 상수를 입력한다.
- type : 소켓의 타입을 지정한다. TCP 소켓을 사용하려면 'SOCK_STREAM'을, UDP소켓을 사용하려면 'SOCK_DGRAM'을 지정한다.
- protocol : 사용할 프로토콜을 지정한다. 보통 0을 사용하여 'socket()함수에 프로토콜을 자동으로 생성한다.
int client_socket = socket(AF_INET, SOCK_STREAM, 0);
if (client_socket < 0) {
perror("Socket creation failed");
exit(EXIT_FAILURE);
}
TCP 소켓을 생성하고, 생성된 소켓의 파일 디스크립터를 client_socket 변수에 저장한다. 소켓 생성에 실패하면 오류 메시지를 출력하고 프로그램을 종료한다.
서버측에 연결을 요청한다.
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
- sockfd : 연결을 시도할 파일의 디스크립터이다. socket()함수로 생성된 소켓의 파일 디스크립터를 사용한다.
- addr : 연결하려는 서버의 주소 정보를 포함하는 'sockaddr' 구조체에 대한 포인터이다.
- addrlen : 구조체의 크기를 나타내는 매개변수이다.
connect() 함수는 지정된 소켓(sockfd)을 사용하여 지정된 서버 주소(addr)에 연결을 시도한다. 연결이 성공하면 0을 반환하고, 실패하면 -1을 반환한다.
struct sockaddr_in server_addr;
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(PORT);
inet_pton(AF_INET, "127.0.0.1", &server_addr.sin_addr);
if (connect(client_socket, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0) {
perror("Connection failed");
exit(EXIT_FAILURE);
}
다음은 서버 주소를 설정하고, connect() 함수를 호출하여 클라이언트 소켓을 서버에 연결한다. 연결이 실패하면 오류 메시지를 출력하고 프로그램을 종료한다.
연결이 받아들여지면 서버로 데이터를 송신한다.
ssize_t send(int sockfd, const void *buf, size_t len, int flags);
- sockfd : 데이터를 전송할 소켓의 파일 디스크립터이다. socket() 함수로 생성된 소켓의 파일 디스크립터를 사용한다.
- buf : 전송할 데이터가 포함된 버퍼의 포인터.
- len : 전송할 데이터의 길이.
- flags : 전송에 대한 추가 옵션을 지정한다. 보통 0으로 설정된다.
send() 함수는 지정된 소켓(sockfd)을 사용하여 데이터를 버퍼(buf)에서 서버로 전송한다. 전송된 데이터의 길이를 반환하며, 실패 시 -1을 반환한다.
const char *message = "Hello, server!";
int message_length = strlen(message);
if (send(client_socket, message, message_length, 0) < 0) {
perror("Send failed");
exit(EXIT_FAILURE);
}
다음은 send() 함수를 사용하여 클라이언트 소켓으로 "Hello, server!" 메시지를 서버에 전송한다. 전송이 실패하면 오류 메시지를 출력하고 프로그램을 종료한다.
서버로부터 데이터를 수신한다.
ssize_t recv(int sockfd, void *buf, size_t len, int flags);
- sockfd : 데이터를 수신할 소켓의 파일 디스크립터이다. 이는 socket() 함수로 생성된 소켓의 파일 디스크립터를 사용한다.
- buf : 수신한 데이터를 저장할 버퍼의 포인터이다.
- len : 수신할 데이터의 최대 길이.
- flags : 수신에 대한 추가 옵션을 지정. 보통 0으로 설정된다.
recv() 함수는 지정된 소켓(sockfd)을 사용하여 서버로부터 데이터를 수신하여 버퍼(buf)에 저장한다. 수신된 데이터의 길이를 반환하며, 실패 시 -1을 반환한다.
char buffer[MAX_BUFFER_SIZE];
ssize_t bytes_received;
bytes_received = recv(client_socket, buffer, MAX_BUFFER_SIZE, 0);
if (bytes_received < 0) {
perror("Receive failed");
exit(EXIT_FAILURE);
}
printf("Received message from server: %s\n", buffer);
다음은 recv() 함수를 사용하여 서버로부터 데이터를 수신한다. 수신된 데이터는 버퍼에 저장되고, 수신된 바이트 수가 반환된다. 이후 수신된 데이터를 처리하거나 출력하는 등의 작업을 수행한다. 만약 수신에 실패하면 오류 메시지를 출력하고 프로그램을 종료한다.
모든 처리가 완료되면 소켓을 닫는다.
int close(int sockfd);
- sockfd : 닫을 소켓의 파일 디스크립터입니다. 이는 socket() 함수로 생성된 소켓의 파일 디스크립터를 사용합니다.
close() 함수는 지정된 소켓(sockfd)의 연결을 종료한다. 이 함수를 호출하면 소켓은 더 이상 통신을 수행할 수 없게 되므로 데이터 송수신이 완료된 후에 이 함수를 호출하여 소켓을 닫는 것이 좋다.
서버의 socket() 함수는 네트워크 통신을 위한 소켓을 생성하는 역할을 한다.
int socket(int domain, int type, int protocol);
- domain: 사용할 네트워크 프로토콜 패밀리를 지정합니다. 예를 들어, AF_INET은 IPv4, AF_INET6는 IPv6를 의미합니다.
- type: 소켓의 타입을 지정합니다. 대표적으로 SOCK_STREAM은 TCP 소켓, SOCK_DGRAM은 UDP 소켓을 의미합니다.
- protocol: 특정 프로토콜을 지정할 수 있으며, 0으로 설정하면 type에 따른 기본 프로토콜을 사용합니다.
소켓이 생성되면 소켓 디스크립터(socket descriptor)라는 양의 정수 값이 반환되며, 이 값을 통해 생성된 소켓을 식별할 수 있다. 실패할 경우 -1이 반환된다.
bind() 함수는 생성된 소켓에 IP 주소와 포트 번호를 할당하는 역할을 한다. 이를 통해 클라이언트가 해당 IP 주소와 포트 번호로 접속할 수 있게 된다.
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
- sockfd : socket() 함수에서 반환된 소켓 디스크립터.
- addr : 할당할 IP 주소와 포트 번호 정보가 포함된 sockaddr 구조체를 가리키는 포인터.
- addrlen : addr이 가리키는 sockaddr 구조체의 크기를 나타낸다.
sockaddr 구조체는 다음과 같이 정의된다.
// IPv4
struct sockaddr_in {
sa_family_t sin_family; // 주소 패밀리 (AF_INET)
uint16_t sin_port; // 16비트 포트 번호
struct in_addr sin_addr; // 32비트 IP 주소
};
// IPv6
struct sockaddr_in6 {
sa_family_t sin6_family; // 주소 패밀리 (AF_INET6)
uint16_t sin6_port; // 16비트 포트 번호
uint32_t sin6_flowinfo; // IPv6 트래픽 클래스와 flow 라벨
struct in6_addr sin6_addr; // 128비트 IPv6 주소
uint32_t sin6_scope_id; // 유니캐스트 스코프 ID
};
bind() 함수 호출이 성공하면 0, 실패할 경우 -1을 반환한다. 소켓에 IP 주소와 포트를 바인딩한 후에는 listen() 함수를 호출하여 연결 요청을 받을 준비를 한다.
listen() 함수는 소켓을 수신 대기 모드로 전환하여 클라이언트의 연결 요청을 대기하도록 한다.
int listen(int sockfd, int backlog);
- sockfd : socket() 함수에서 반환된 소켓 디스크립터.
- backlog : 동시에 수용할 수 있는 최대 연결 요청 큐의 길이를 지정.
listen()은 bind()로 IP 주소와 포트 번호를 할당한 후에 호출해야 한다. 성공적으로 호출되면 0, 실패할 경우 -1을 반환한다.
accept() 함수는 대기 중인 클라이언트의 연결 요청을 수락하고, 해당 클라이언트와의 통신을 위한 새로운 소켓을 생성한다.
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
- sockfd : listen() 함수가 호출된 소켓의 디스크립터.
- addr : 연결된 클라이언트의 주소 정보가 저장될 sockaddr 구조체의 포인터.
- addrlen : addr이 가리키는 주소 구조체의 크기를 지정하는 값의 포인터.
accept() 함수는 listen() 함수 이후에 호출되어야 한다. 대기 중인 클라이언트의 연결 요청이 있으면 해당 요청을 수락하고, 새로운 소켓 디스크립터를 반환한다. 이 새로운 소켓을 통해 클라이언트와 데이터를 송수신할 수 있다.
accept() 함수가 성공적으로 호출되면 새로운 소켓 디스크립터를 반환하고, addr과 addrlen 인자에 연결된 클라이언트의 주소 정보가 저장된다. 실패할 경우 -1을 반환.
모든 처리가 완료되면 소켓을 닫는다.
int close(int sockfd);
- sockfd : 닫을 소켓의 파일 디스크립터입니다. 이는 socket() 함수로 생성된 소켓의 파일 디스크립터를 사용합니다.
close() 함수는 지정된 소켓(sockfd)의 연결을 종료한다. 이 함수를 호출하면 소켓은 더 이상 통신을 수행할 수 없게 되므로 데이터 송수신이 완료된 후에 이 함수를 호출하여 소켓을 닫는 것이 좋다.
글 잘 봤습니다.