sd = socket(AF_UNIX, SOCK_STREAM, 0); // 유닉스 주소 체계의 연결형 서비스
sd = socket(AF_UNIX, SOCK_DGRAM, 0); // 유닉스 주소 체계의 비연결형 서비스
sd = socket(AF_INET, SOCK_STREM, 0); // 인터넷 주소 체계의 연결형 서비스
sd = socket(AF_INET, SOCK_DGRAM, 0); // 인터넷 주소 체계의 비연결형 서비스
- 운영 체제에서 구현되는 전송 계층 프로토콜인 TCP와 UDP를 사용하려면 소켓 시스템 콜이라는 라이브러리 함수를 이용한다.
- 소켓은 통신을 원하는 프로세스에 할당되는 자원이며, 고유의 소켓 주소가 부여된다.
- 일반적으로 소켓을 포트라고도 부른다. 그리고 각 함수의 사용법에서 해당 함수 위의 헤더 파일은 함수를 사용하는데 필요한 관련 정보가 포함된 파일로 프로그램에 반드시 추가해야 한다.
SOCKET() 함수
SOCKET()함수는 데이터 전송에 사용되는 소켓을 생성할 때 호출하며, 소켓을 성공적으로 생성하면 소켓 디스크립터를 반환한다.
SOCKET()함수의 사용법
- domain 은 AF_UNIX, AF_INET 등과 같이 사용할 프로토콜의 도메인이고, type은 SCOK_STREAM, SOCK_DGRAM 등의 서비스 유형이다.
- protocol은 대개 0으로 지정하여 시스템에서 적절항 프로토콜을 선택하도록 설정하는데, 인터넷 도메인을 의미하는 AF_INET을 예로 들면 연결형 서비스는 TCP 프로토콜을, 비연결형 서비스는 UDP 프로토콜을 선택한다.
#include<sys/types.h>
#include<sys/socket.h>
int socket(int doain, int type, int protocol);
함수 앞에 표시된 헤더 파일은 socket() 함수를 사용하기 위한 관련 정보가 포함된 파일이므로, 프로그램에 반드시 추가해야 한다. 다른 소켓 함수도 마찬가지이다. 예제 프로그램은 모토롤라 계열의 프로세서를 사용하는 SUN 마이크로 시스템에서 실행되었기에 인텔 계열 프로세스를 사용하는 시스템에서는 바이트 순서와 관련된 함수의 사용에 주의해야한다.
일반적으로 사용되는 socket()함수의 예시
sd = socket(AF_UNIX, SOCK_STREAM, 0); // 유닉스 주소 체계의 연결형 서비스
sd = socket(AF_UNIX, SOCK_DGRAM, 0); // 유닉스 주소 체계의 비연결형 서비스
sd = socket(AF_INET, SOCK_STREM, 0); // 인터넷 주소 체계의 연결형 서비스
sd = socket(AF_INET, SOCK_DGRAM, 0); // 인터넷 주소 체계의 비연결형 서비스
인터넷에서 연결형 서비스에는 TCP 프로토콜용 소켓이 할당되고,
비연결형 서비스에는 UDP 프로토콜용 소켓이 할당된다.
bind()함수
- socket()함수로 소켓을 생성하여 소켓 디스크립터가 반환된다. 이 디스크립터를 이용해 상대 프로세스와 통신하려면 먼저 생성된 소켓에 주소를 부여해야 한다. 이때 bind() 함수가 소켓에 주소를 부여한다. 이후에 connect()와 accept()함수는 연결을 설정하는 과정에서 바인딩된 소켓 주소를 사용한다.
- 전화 서비스에서 전화기 자체를 소켓으로 볼 수 있으며 전화기에 전화번호를 부여하는 기능은 bind() 함수가 수행한다.
- AF_UNIX 도메인은 파일 경로명을 이용해 주소를 부여하고 AF_INET 도메인은 호스트의 IP주소와 포트 번호의 조합으로 주소를 부여한다.
bind()함수의 사용법
s는 socket()함수의 반환 값인 소켓 디스크립터 번호이고, name은 바인딩 할 소켓 주소, namelen은 nqme에 보관된 주소의 크기이다.
#include<sys/types.h>
#include<sys/socket.h>
int bind(int s, const struct sockaddr *name, socklen_t *namelen);
유닉스 도메인의 예시
- AF_UNIX 도메인에서 주소를 부여하는 예시로는 먼저 데이터 전송에 사용할 소켓을 생성하기 위해 socket()함수를 실행한 후 반환값을 변수 sd에 보관한다.
- 함수의 매개변수에 AF_UNIX와 SOCK_STREAM을 설정하였으므로 유닉스 도메인에서 연결형 서비스를 지원하는 소켓을 생성한다.
int sd;
struct sockaddr_un addr;
sd = socket(AF_UNIX, SOCK_STREAM, 0);
if(sd == -1) {
perror("socket");
exit(1);
}
addr.sun_family = AF_UNIX;
strcpy(addr.sun_path, "/tmp/sock_addr");
if(bind(sd, (struct sockaddr *)&addr, sizeof(addr)) == -1) {
perror("bind");
exit(1);
}
- bind()함수를 실행하려면 주소 값을 보관하는 변수가 필요하다. 여기서는 AF_UNIX 도메인의 주소 체계를 의미하는 sockaddr_un 구조체를 이용해 addr 변수를 선언하였다. addr 변수에는 strcpy() 함수를 이용해, 경로명인 /tmp/sock_addr 주소를 기록한다. 생성된 소켓의 주소를 /tmp/sock_addr로 지정하려고 bind()함수가 마지막으로 실행되고 있다.
인터넷 도메인의 예시
- AF_INET 도메인은 호스트의 IP주소와 포트 번호를 이용해 주소를 표기한다. bind() 함수에서 사용하는 IP주소는 프로그램이 실행되는 호스트의 IP주소이다.
- 개발된 프로그램이 여러 컴퓨터에서 실행되는 경우, 각 실행 파일에 개별 호스트의 IP 주소를 지정하여 컴파일하기는 현실적으로 불가능하다. 이와 같은 상황을 고려해 INADDR_ANY라는 호스트 주소 표기 방법을 제공한다. 이는 프로세스가 실행되는 로컬 호스트 자체를 의미하기 때문에 프로세스가 실행되는 호스트 IP주소로 자동 대체된다.
- 결과적으로 동일한 프로그램을 여러 호스트에서 실행시키기 위해 IP 주소 변경과 재컴파일 작업을 하지 않아도 된다.
int sd;
struct sockaddr_in addr;
sd = socket(AF_INET, SOCK_STREAM, 0);
if(sd == -1) {
perror("socket");
exit(1);
}
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = htonl(INADDR_ANY);
addr.sin_port = htons(5010);
if(bind(sd, (struct sockaddr *)&addr, sizeof(addr)) == -1) {
perror("bind");
exit(1);
}
- IP주소와 포트번호를 변수에 기록하기 전에 htonl()과 htons() 함수를 사용했다. 이는 컴퓨터 사이의 바이트 저장 순서 차이에 따른 문제점을 해결해주기 위함이다. 컴퓨터마다 정수형 데이터를 저장하는 기억 장소 공간의 바이트 순서가 다를 수 있다. 이 차이를 극복하기 위해 네트워크 바이트 순서라는 전송 문법의 데이터 유형이 필요하다.
- 데이터를 전송하기 전에 개별 컴퓨터 바이트 순서를 네트워크 바이트 순서로 변환하는데, 이를 htonl()과 htons() 함수가 담당한다.
- 반대로 데이터를 수신할 때는 네트워크 바이트 순서를 개별 컴퓨터의 바이트 순서로 변환해야 하며, 이는 ntohl()과 ntohs() 함수가 담당한다.
#include<sys/types.h>
#include<netinet/in.h>
#include<inttypes.h>
uint32_t htonl(unint32_t hostlong);
uint16_t htons(uint16_t hostshort);
uint32_t ntohl(unint32_t netlong);
uint16_t ntohs(uint16_t netshort);
listen()함수
listen() 함수는 서버 프로세스에서 실행된다.
사용법
listen() 함수는 s가 가리키는 소켓에서 대기할 수 있는 클라이언트의 연결 요청 개수를 지정한다. 일반적으로 비연결형 서비스에서는 이 함수를 사용하지 않고 연결형 서비스에서만 사용한다. backlog값은 보통 5로 지정한다.
#include<sys/types.h>
#include<sys/socket.h>
int listen(int s, int backlog);
ex) AF_INET 도메인을 사용하는 서버 프로세스에서 listen()함수를 사용한 예시
int sd;
struct sockaddr_in addr;
sd = socket(AF_INET, SOCK_STREAM, 0);
if(sd == -1) {
perror("socket");
exit(1);
}
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = htonl(INADDR_ANY);
addr.sin_port = htons(5010);
if(bind(sd, (struct sockaddr *)&addr, sizeof(addr)) == -1) {
perror("bind");
exit(1);
}
if(listen(sd, 5) == -1) {
perror("listen");
exit(1);
}
socket()함수로 생성한 연결형 서비스 소켓에 bind()함수로 주소를 부여하고, 이어서 listen() 함수를 실행하고 있다.
accept()함수
연결형 서비스를 지원하는 서버 프로세스가 클라이언트의 연결 요청을 받으려면 accept()함수에서 대기해야 한다. 즉, 클라이언트 - 서버 환경에서 서버 프로세스는 accept()함수를 실행해 클라이언트의 요청을 기다리고, 클라이언트 프로세스의 connect() 요청이 발생하면 연결이 설정된다.
accept()함수의 사용법
클라이언트로부터 연결 요청이 발생하면 둘 사이의 연결이 설정되고, addr에 연결을 요청한 클라이언트의 소켓 주소를 반환한다. 이 값을 확인하면 어느 클라이언트가 연결을 시도했는지 알 수 있고, 경우에 따라서는 클라이언트 주소를 보고 연결 요청을 거부할 수도 있다.
#include<sys/types.h>
#include<sys/socket.h>
int accept(int s, struct sockaddr *addr, socklen_t *addrlen);
- 성공적으로 연결되면 socket()함수로 생성한 원래의 소켓과는 별도의 소켓이 만들어진다. 생성된 소켓의 디스크립터는 accept()함수의 반환값으로 얻을 수 있으며, 클라이언트와 통신할 때는 보통 이 소켓을 사용한다.
- 서버 프로세스가 클라이언트 프로세스로부터 연결 요청이 들어오면 새로운 프로세스를 생성하고, 이 하위 서버 프로세스가 클라이언트와 통신하도록 설계되었다. 따라서 원래의 대표 서버 프로세스는 순수하게 연결 요청을 받아들이는 브로커 역할만 한고, 개별 클라이언트 프로세스와의 데이터 송수신은 하위 서버 프로세스가 수행한다.
- 인터넷 환경에서 대부분의 네트워크 응용 서비스는 이런 구조로 구현된다.
int sd, new;
struct sockaddr_in addr;
struct sockaddr_in client;
int client_len;
sd = socket(AF_INET, SOCK_STREAM, 0);
if(sd == -1) {
perror("socket");
exit(1);
}
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = htonl(INADDR_ANY);
addr.sin_port = htons(5010);
if(bind(sd, (struct sockaddr *)&addr, sizeof(addr)) == -1) {
perror("bind");
exit(1);
}
if(listen(sd, 5) == -1) {
perror("listen");
exit(1);
}
while(new = accept(sd, (struct sockaddr *)&client, &client_len)) != -1) {
if(fork() == 0) {
/* 자식 프로세스 */
close(sd);
work(new); // new를 이용해 클라이언트와 통신
close(new);
exit(0);
}
/* 부모 프로세스 */
close(new);
}
- 서버 프로세스는 시스템 관리자의 개입이 없으면 영원히 종료되지 않고 수행되므로 while 문을 사용한다.
- 클라이언트 프로세스와 연결이 이루어지면 하위의 자식 프로세스를 생성해 클라이언트와 통신할 수 있도록 하고, 자신은 accept()에서 다시 대기한다.
- 자식 프로세스의 구현은 fork() 시스템 콜로 이루어지며, 수행하는 작업은 work() 함수로 간략히 표현하였다. work()함수는 응용 서비스의 기능에 따라 개별적으로 구현된다. 즉, FTP 서비스는 FTP 기능을 수행하고, 웹 서비스는 웹 기능을 수행한다.
connect()함수
연결형 서비스는 클라이언트가 서버 프로세스에 연결 요청을 할 때 connect() 함수를 사용한다.
connect()함수의 사용법은 name에 연결을 원하는 서버 프로세스의 소켓 주소를 표기한다.
#include<sys/types.h>
#include<sys/socket.h>
int connect(int s, const struct sockaddr *name, socklen_t namelen);
ex) IP주소가 211.223.201.30인 서버 호스트의 5010번 포트에 서비스 접속 시도하는 과정의 예시
클라이언트 프로세스는 자신이 사용할 소켓을 생성하고, 이 소켓을 이용해 connect()함수로 서버 프로세스에 연결을 시도한다.
int sd, new;
struct sockaddr_in addr;
sd = socket(AF_INET, SOCK_STREAM, 0);
if(sd == -1) {
perror("socket");
exit(1);
}
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = htonl(inet_addr("211.223.201.30"));
addr.sin_port = htons(5010);
if(connect(sd, (struct sockaddr *)&addr, sizeof(addr)) == -1) {
perror("connect");
exit(1);
}
- connect()함수가 성공적으로 실행되면 클라이언트와 서버 사이에 연결이 설정되고, 위의 클라이언트 프로세스는 변수 sd가 가리키는 소켓을 사용해 데이터를 송수신할 수 있다. IP 주소의 변환은 inet_addr()함수가 수행한다.
- 인터넷 주소는 211.223.201.30 형태로 표기한다. 이러한 십진수 표기는 일반인이 쉽게 확인할 수 있다는 장점이 있지만, 컴퓨터 내부에서는 32비트의 이진수 방식으로 처리한다. 따라서 십진수와 이진수 표기를 변환하는 기능이 필요한데 이를 inet_addr()함수가 담당한다.
- 다음 함수들은 십진수와 이진수 소켓 주소 표기 방식인 sockaddr_in 구조체 내의 in_addr구조(32비트 인터넷 주소)사이의 변환 기능을 수행한다.
#incldue<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
unsigned long inet_addr(const char *cp);
char *inet_ntoa(const struct in_addr in);
inet_addr()함수는 십진수 표기를 32비트 인터넷 주소로 변환하고, inet_ntoa()함수는 반대의 기능을 수행한다.
send()함수
소켓 연결이 이루어지면 send() 함수를 이용해 전송할 수 있다. send()함수는 연결형 서비스에서 데이터 송신에 사용하고, sendto()함수는 비연결형 서비스에서 사용한다.
send() 함수의 사용법
- s는 클라이언트와 서버 사이의 연결 설정이 완료된 상태의 소켓이다. 즉, 서버의 accept()함수와 클라이언트의 connect()함수가 성공적으로 실행된 우의 소켓 디스크립터이다. 전송되는 데이터는 msg에 미리 기록해야 한다.
- 비연결형 서비스에서 사용하는 sendto()함수는 연결 설정 과정을 거치지 않은 소켓 디스크립터를 사용하며, to와 tolen을 이용해 전송 데이터를 수신할 상대 프로세스의 소켓 주소를 표기한다.
- 전송 데이터는 msg와 len으로 표현한다. flags는 데이터를 정상적으로 전송하면 0으로 지정하고, out-of-band 데이터이면 MSG_OOB로 지정한다.
#include<sys/types.h>
#include<sys/socket.h>
ssize_t send(int s, const void *msg, size_t len, int flags);
ssize_t sendto(int s, const void *msg, size_t len, int flags, const struct sockaddr *to, socklen_t tolen);
연결형 서비스에서 send()함수를 이용한 데이터 전송의 예시이며, 클라이언트 프로세스에서 구현되는 프로그램 코드로, connect()함수가 성공적으로 수행되면 서버와 연결된다. 이후 서버와의 연결 관계를 유지하는 sd 소켓을 이용해 data 변수에 보관된 Test Message 데이터를 전송한다.
int sd;
struct sockaddr_in addr;
char *data = "Test Message";
int length = strlen(data) + 1;
sd = socket(AF_INET, SOCK_STREAM, 0);
if(sd == -1) {
perror("socket");
exit(1);
}
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = htonl(inet_addr("211.223.201.30"));
addr.sin_port = htons(5010);
if(connect(sd, (struct sockaddr*)&addr, sizeof(addr)) == -1) {
perror("listen");
exit(1);
}
if(send(sd, data, length, 0) == -1) {
perror("send");
exit(1);
}
recv()함수
- recv()와 recvfrom() 함수를 사용하여 상대 프로세스가 전송한 데이터를 소켓을 통해 읽을 수 있다.
- recv()함수는 연결형 서비스에서 사용하고, recvfrom()함수는 비연결형 서비스에서 사용한다.
recv()와 recvfrom()함수의 사용법
- 소켓을 통해 읽은 데이터는 buf가 가리키는 공간에 저장되며, 크기는 반환값으로 얻을 수 있다.
- 비연결형 서비스의 경우는 송신 프로세스의 소켓 주소가 recvfrom()함수의 매개변수 from에 기록되므로 누가 전송한 데이터인지 확인할 수 있다.
#include<sys/types.h>
#include<sys/socket.h>
ssize_t recv(int s, void *buf, size_t len, int flags);
ssize_t recvfrom(int s, void *buf, size_t len, int flags, struct sockaddr *from, socklen_t *fromlen);
연결형 서비스를 사용하는 클라이언트 프로그램에서 recv()함수를 이용하여 데이터를 수신한 예시이며 읽은 데이터는 data변수에 보관된다.
int sd;
struct sockaddr_in addr;
char data[100];
int length = sizeof(data;
sd = socket(AF_INET, SOCK_STREAM, 0);
if(sd == -1) {
perror("socket");
exit(1);
}
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = htonl(inet_addr("211.223.201.30"));
addr.sin_port = htons(5010);
if(connect(sd, (struct sockaddr*)&addr, sizeof(addr)) == -1) {
perror("listen");
exit(1);
}
if(recv(sd, data, length, 0) == -1) {
perror("recv");
exit(1);
}
Reference
쉽게 배우는 데이터 통신과 컴퓨터 네트워크
'CS > Network' 카테고리의 다른 글
[Chapter 13] 웹 WWW(HTML) (0) | 2025.02.18 |
---|---|
[Chapter 12] 소켓을 이용한 네트워크 프로그래밍 (서버-클라이언트 프로그래밍) (0) | 2025.02.17 |
[Chapter 12] 소켓을 이용한 네트워크 프로그래밍 (소켓의 주소 체계) (0) | 2025.02.17 |
[Chapter 11] 상위 계층의 이해(응용 계층) (0) | 2025.02.17 |
[Chapter 11] 상위 계층의 이해(표현 계층) (0) | 2025.02.17 |