01 소켓의 주소 체계와 서비스
소켓은 운영체제에서 제공하는 통신 프로토콜을 편리하게 사용할 수 있도록 도와주는 역할을 한다.
1 소켓의 주소 체계
소켓은 프로토콜 종류에 따라 다양한 방식으로 주소를 부여할 수 있다.
1.1 유닉스 주소 체계
- AF_UNIX로 표시된다.
- 하나의 호스트 내부에서 실행되는 프로세스 사이의 통신을 지원한다.
- 사용하는 주소 체계는 파일 시스템의 경로명을 기반으로 한다.
struct sockaddr_un {
short sun_family; // AF_UNIX
char sun_path[108]; // pathname
};
🔼 유닉스 주소 체계를 지원하는 sockaddr_un 구조체의 예
- sun_family : 유닉스 주소 체계를 의미하는 AF_UNIX 값을 갖는다.
- sun_path : 소켓을 구분하는 주소를 표시하기 위해 파일 시스템의 경로명을 기록한다.
1.2 인터넷 주소 체계
- AF_INET로 표시된다.
- 서로 다른 호스트에서 실행되는 프로세스 사이의 통신을 지원한다.
- 소켓이 생성되는 호스트의 IP 주소와 포트 번호를 조합하여 소켓 주소를 표현한다.
struct sockaddr_in {
short sin_family; // AF_INET
u_short sin_port; // 포트 번호
struct in_addr sin_addr; // IP 주소
char sin_zero[8]; // 채우기
};
struct in_addr {
u_long s_addr;
};
🔼 인터넷 주소 체계를 지원하는 sockaddr_in 구조체의 예
- sin_family : 인터넷 주소 체계를 의미하는 AF_INET 값을 갖는다.
- sin_addr과 sin_port 필드에 주소를 표시한다.
- sin_addr : 호스트의 IP 주소
- sin_port : 포트 번호
- sin_zero 필드는 사용되지 않는다.
1.3 통합 주소 체계
여러 소켓 구조체를 통합해 일반 구조체 하나로 정의할 필요가 있다.
struct sockaddr {
u_short sa_family; // AF_UNIX, AF_INET, ... ...
char sa_data[14];
};
🔼 공통 주소 체계를 지원하는 구조체
- 단순히 프로그래밍 환경에서 문법적인 측면만 고려해 정의된 것이다.
struct sockaddr_in addr; // 인터넷 주소 체계로 변수 선언
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = hton1(INADDR_ANY); // IP 주소
addr.sin_port = htons(5010); // 포트 번호
bind(socet, (struct sockaddr *)&addr, sizeof(addr));
- 소켓 변수는 sockaddr_in 구조체를 사용한다.
- 주소를 보관하는 addr 변수에는 인터넷 주소 체계에서 사용하는 IP 주소와 포트 번호 값을 지정한다.
- 소켓 관련 시스템 콜 bind()는 모든 통신 프로토콜에서 공통으로 사용한다.
- addr 변수를 통해 소켓이 인터넷 주소 체계인 sockaddr_in 구조체로 생성되지만, 시스템 콜에서는 문법적으로 sockaddr 구조로 형 변환하여 표현한다.
2 소켓의 서비스
제공하는 서비스에 따른 소켓 유형도 다양하다.
- SOCK_STREAM : 연결형 서비스에서 사용한다.
- SOCK_DGRAM : 비연결형 서비스에서 사용한다.
- SOCK_RAW : IP 프로토콜을 직접 사용해 통신할 때 사용한다.
3 소켓의 기본 함수
소켓 시스템 콜을 이용해 네트워크 프로그래밍을 할 때는 7개의 기본 함수가 사용된다.
- socket(int domain, int type, int protocol)
- 매개변수로 지정한 유형에 따라 소켓을 생성한다.
- bind(int s, const struct sockaddr *name, socklen_t *namelen)
- 생성된 소켓에 주소를 부여한다.
- 첫 번째 매개변수 : 함수의 반환 값인 소켓 디스크립터
- 두 번째 매개변수 : 바인드될 소켓의 주소 값
- listen(int s, int backlog)
- 첫 번째 매개변수로 표시한 소켓을 활성화한다.
- 보통 서버 프로세스에서 실행된다.
- 시스템에서 연결을 거부하지 않고 대기할 수 있는 연결 설정 요구의 최대 수를 지정한다.
- accept(int s, struct sockaddr *addr, socklen_t *addrlen)
- 첫 번째 매개변수로 지정한 소켓에서 클라이언트의 연결 요구가 들어올 때까지 대기한다.
- 보통 서버 프로세스에서 실행된다.
- 클라이언트로부터 연결 설정 요구가 발생하면 연결이 설정되고, 서버에 새로운 소켓이 자동으로 생성된다.
- 클라이언트의 연결 요구가 없으면 서버는 accept() 함수에서 대기한다.
- connect(int s, const struct sockaddr *name, socklen_t namelen)
- 두 번째 매개변수 name이 가리키는 서버와 연결 설정을 시도한다.
- 클라이언트 프로세스에서 사용된다.
- 해당 주소의 서버가 존재하지 않으면 오류 처리되고, 서버가 연결 대기 중이면 연결이 설정된다.
- send(int s, const void *msg, size_t len, int flags)
- 연결형 서비스를 제공하는 환경에서 데이터를 전송하는 역할을 한다.
- recv(int s, void *buf, size_t len, int flags)
- 연결형 서비스에서 데이터를 수신하는 역할을 한다.
- 소켓(s)으로부터 데이터를 수신하고, 수신한 데이터를 버퍼(buf)에 보관한다.
02 소켓 시스템 콜
TCP와 UDP를 사용하려면 소켓 시스템 콜이라는 라이브러리 함수를 이용한다.
1 socket() 함수
- 데이터 전송에 사용되는 소켓을 생성할 때 호출한다.
- 소켓을 성공적으로 생성하면 소켓 디스크립터를 반환한다.
# include <sys/types.h>
# include <sys/socket.h>
int socket(int domain, int type, int protocol);
🔼 함수 사용법
- domain : 사용할 프로토콜의 도메인 (ex. AF_UNIX, AF_INET 등)
- type : 서비스 유형 (ex. SOCK_STREAM, SOCK_DGRAM 등)
- protocol : 대개 0으로 지정하여 시스템에서 적절한 프로토콜을 선택하도록 설정한다.
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); // 인터넷 주소 체계의 비연결형 서비스
🔼 일반적으로 사용되는 socket() 함수의 예
2 bind() 함수
- 소켓 디스크립터를 이용해 상대 프로세스와 통신하려면 먼저 생성된 소켓에 주소를 부여해야 한다. 이때 bind() 함수가 소켓에 주소를 부여한다.
- 이후 connect()와 accept() 함수는 연결을 설정하는 과정에서 바인딩된 소켓 주소를 사용한다.
- AF_UNIX 도메인은 파일 경로명을, AF_INET 도메인은 호스트의 IP 주소와 포트 번호의 조합을 이용해 주소를 부여한다.
#include <sys/types.h>
#include <sys/socket.h>
int bind(int s, const struct sockaddr *name, socklen_t *namelen);
🔼 함수 사용법
- s : 소켓 디스크립터 번호
- name : 바인딩할 소켓 주소
- namelen : name에 보관된 주소의 크기
2.1 유닉스 도메인의 예
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() 함수의 사용
- 데이터 전송에 사용할 소켓을 생성하기 위해 socket() 함수를 실행한 후 반환값을 변수 sd에 보관한다.
- 유닉스 도메인에서 연결형 서비스를 지원한다. (AF_UNIX, SOCK_STREAM)
- addr 변수에는 strcpy() 함수를 이용해, 경로명(/tmp/sock_addr) 주소를 기록한다.
- AF_UNIX 도메인의 주소 체계를 의미하는 sockaddr_un 구조체를 타입으로 addr 변수를 선언했다.
- bind() 함수를 통해 소켓의 주소를 /tmp/sock_addr로 지정한다.
2.2 인터넷 도메인의 예
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);
}
🔼 인터넷 도메인에서 bind() 함수의 사용
- AF_INET 도메인은 호스트의 IP 주소와 포트 번호를 이용해 주소를 표기한다.
- INADDR_ANY라는 호스트 주소 표기 방법을 제공한다. (프로세스가 실행되는 로컬 호스트 자체)
- 데이터를 전송하기 전에 개별 컴퓨터의 바이트 순서를 네트워크 바이트 순서로 변환하는데, 이를 htonl()과 htons() 함수가 담당한다.
- 반대로 데이터를 수신할 때는 네트워크 바이트 순서를 개별 컴퓨터의 바이트 순서로 변환하는데, 이는 ntohl()과 ntohs() 함수가 담당한다.
3 listen() 함수
- 서버 프로세스에서 실행된다.
- 일반적으로 연결형 서비스에서만 사용된다.
#include<sys/types.h>
#include<sys/socket.h>
int listen(int s, int backlog);
🔼 함수 사용법
- s가 가리키는 소켓에서 대기할 수 있는 클라이언트의 연결 요청 개수를 지정한다.
- backlog 값은 보통 5로 지정한다.
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);
}
🔼 listen() 함수의 사용
- socket() 함수로 생성한 연결형 서비스 소켓에 bind() 함수로 주소를 부여하고, 이어 listen() 함수를 실행한다.
4 accept() 함수
- 연결형 서비스를 지원하는 서버 프로세스가 클라이언트의 연결 요청을 받으려면 accept() 함수에서 대기해야 한다.
#include<sys/types.h>
#include<sys/socket.h>
int accept(int s, struct sockaddr *addr, socklen_t *addrlen);
🔼 함수 사용법
- addr에 연결을 요청한 클라이언트의 소켓 주소를 반환한다.
- 성공적으로 연결되면 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);
}
🔼 accept() 함수의 사용
- 원래의 대표 서버 프로세스는 순수하게 연결 요청을 받아들이는 브로커 역할만 하고, 개별 클라이언트 프로세스와의 데이터 송수신은 하위 서버 프로세스가 수행한다.
- 서버 프로세스는 시스템 관리자의 개입이 없으면 영원히 종료되지 않고 수행되므로 while문을 사용한다.
- 연결이 이루어지면 하위 자식 프로세스를 생성해 클라이언트와 통신하고, 자신은 accept()에서 다시 대기한다.
- 자식 프로세스 구현은 fork() 시스템 콜, 수행하는 작업은 work() 함수로 간략히 표현했다. (work() 함수는 응용 서비스 기능에 따라 개별적으로 구현됨)
5 connect() 함수
- 연결형 서비스는 클라이언트 프로세스가 서버 프로세스에 연결 요청을 할 때 connect() 함수를 사용한다.
#include<sys/types.h>
#include<sys/socket.h>
int connect(int s, const struct sockaddr *name, socklen_t namelen);
🔼 함수 사용법
- name에 연결을 원하는 서버 프로세스의 소켓 주소를 표기한다.
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() 함수의 사용
- IP 주소가 211.223.201.30인 서버 호스트의 5010번 포트에 서비스 접속을 시도하는 예시이다.
- 클라이언트 프로세스는 자신이 사용할 소켓을 생성하고, 이 소켓을 이용해 connect() 함수로 서버 프로세스에 연결을 시도한다.
- connect() 함수가 성공적으로 실행되면 연결이 설정되고, 클라이언트 프로세스는 변수 sd가 가리키는 소켓을 사용해 데이터를 송수신할 수 있다.
- IP 주소의 변환은 inet_addr() 함수가 수행한다.
- 컴퓨터 내부에서는 32비트의 이진수 방식으로 처리한다. (IP주소는 십진수 표기)
- 십진수와 이진수 표기를 변환하는 기능을 담당한다.
- inet_ntoa() 함수는 반대의 기능을 수행한다. (이진수 -> 십진수)
6 send() 함수
- 소켓 연결 후 send() 함수를 이용해 데이터를 전송할 수 있다.
- 연결형 서비스에서 데이터 송신에 사용하고, sendto() 함수는 비연결형 서비스에서 사용한다.
#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()
- s : 클라이언트와 서버 사이의 연결 설정이 완료된 상태의 소켓
- 전송되는 데이터는 msg에 미리 기록한다.
- sendto()
- 연결 설정 과정을 거치지 않은 소켓 디스크립터를 사용한다.
- to와 tolen을 이용해 전송 데이터를 수신할 상대 프로세스의 소켓 주소를 표기한다.
- 전송 데이터는 msg와 len으로 표현한다.
- flags는 데이터를 정상적으로 전송하면 0으로 지정하고, out-of-band 데이터이면 MSG_OOB로 지정한다.
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);
}
🔼 send() 함수의 사용
- 클라이언트 프로세스에서 구현되는 프로그램 코드이다.
- connect() 함수가 성공적으로 수행되면 서버와 연결된다.
- 이후 서버와의 연결 관계를 유지하는 sd 소켓을 이용해 data 변수에 보관된 Test Message 데이터를 전송한다.
7 recv() 함수
- recv()와 recvfrom() 함수를 사용하여 상대 프로세스가 전송한 데이터를 소켓을 통해 읽을 수 있다.
- recv() 함수는 연결형 서비스에서, recvfrom() 함수는 비연결형 서비스에서 사용한다.
#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);
🔼 함수 사용법
- 소켓을 통해 읽은 데이터는 buf가 가리키는 공간에 저장되며, 크기는 반환값으로 얻을 수 있다.
- 비연결형 서비스의 경우는 송신 프로세스의 소켓 주소가 recvfrom() 함수의 매개변수 from에 기록되므로 누가 전송한 데이터인지 확인할 수 있다.
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);
}
🔼 recv() 함수의 사용
- 연결형 서비스를 사용하는 클라이언트 프로그램에서 recv() 함수를 이용하여 데이터를 수신한 예이다.
- 읽은 데이터는 data 변수에 보관된다.
03 서버-클라이언트 프로그래밍
서비스를 제공하는 프로그램을 서버 프로세스, 서버와 연결을 시도하여 서비스를 제공받는 프로그램을 클라이언트 프로세스라 한다.
1 연결형 서비스
2개의 독립 프로세스가 네트워크를 통해 통신하려면 논리적인 연관 관계를 맺어주는 소켓이 필요하다.
1.1 서버와 클라이언트의 동작
- 서버의 동작
- 서비스 교신점(호스트의 IP 주소, 포트 번호) 공개
- 클라이언트로부터 발생하는 서비스 요구 대기
- 클라이언트에 서비스 제공
- 해당 클라이언트에 서비스 제공 완료
- 2로 이동
- 클라이언트의 동작
- 원하는 서비스를 제공하는 서버 확인
- 해당 서버와 연결 시도
- 서버에 서비스 요청
- 서비스 이용 완료
1.2 프로그램 컴파일 방법
% cc -o time_client time_client.c -lsocket -lnsl
% cc -o time_server time_server.c -lsocket -lnsl
- 현재의 시간 정보를 제공하는 서버 프로그램과 서버로부터 시간 정보 서비스를 받아 화면에 출력하는 클라이언트 프로그램이다.
- 클라이언트와 서버 프로그램을 따로 컴파일한 후 서버, 클라이언트 순으로 실행한다.
- 컴파일이 성공하면 time_client와 time_server라는 2개의 실행 파일이 현재 디렉토리에 생성된다.
1.3 클라이언트 프로그램
/* 컴파일에 필요한 헤더 파일 */
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#define TIME_SERVER "211.223.201.30" /* 서버 프로그램이 실행되는 호스트의 IP 주소 */
#define TIME_PORT 5010 /* 서버 프로그램의 Well-known 포트 번호 */
main()
{
int sock; /* 생성된 소켓 번호를 보관하기 위한 파일 디스크립터 */
struct sockaddr_in server; /* 서버의 소켓 주소를 생성하기 위한 변수 */
char buf[256]; /* 서버로부터 수신하는 시간 정보를 보관하는 데이터 공간 */
if ((sock = socket(AF_INET, SOCK_STREAM, 0)) == -1) /* 소켓 생성 */
exit(1); /* 오류 발생 시 프로그램 종료 */
/* 서버 프로세스를 지칭하기 위한 소켓 주소를 만드는 과정 */
server.sin_family = AF_INET;
server.sin_addr.s_addr = htonl(inet_addr(TIME_SERVER)); /* htonl() 생략 여부 */
server.sin_port = htons(TIME_PORT);
if (connect(sock, (struct sockaddr *)&server, sizeof(server)) /* 서버 프로세스와 연결 시도 */
exit(1);
if (recv(sock, buf, sizeof(buf), 0) == -1) /* 서버 프로세스로부터 시간 정보 수신 */
exit(1);
printf("Time information from server is %s", buf); /* 수신 정보를 화면에 출력 */
close(sock);
}
🔼 클라이언트 프로그램 (연결형)
- 서버 프로그램이 실행되는 호스트의 IP 주소와 포트 번호는 실행 환경에 따라 적당한 값으로 변경해야 한다.
1.4 서버 프로그램
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<time.h>
#define TIME_PORT 5010 /* 서버 프로그램의 Well-known 포트 번호 */
main()
{
int sock, sock2;
struct sockaddr_in server, client;
int len;
char buf[256];
time_t today;
if ((sock = socket(AF_INET, SOCK_STREAM, 0)) == -1)
exit(1); /* 오류 발생 시 프로그램 종료 */
/* 자신의 소켓 주소를 생성하는 과정 */
server.sin_family = AF_INET;
server.sin_addr.s_addr = htonl(INADDR_ANY);
server.sin_port = htons(TIME_PORT);
/* 소켓에 Well-known 주소로 이름 부여 */
if (bind(sock, (struct sockaddr *)&server, sizeof(server))
exit(1);
if (listen(sock, 5)) /* 서버 프로세스의 대기 큐 */
exit(1);
while(1) { /* 서버 프로세스는 시스템이 동작하는 동안 항상 클라이언트의 요구에 대기 */
/* 클라이언트 프로세스로부터 연결 요구를 기다리는 시스템 콜 */
if ((sock2 = accept(sock, (struct sockaddr *)&client, &len)) == -1)
exit(1);
/* 시스템에서 인지하는 시간 정보를 얻어 문자열 형태로 변환하는 과정 */
time(&today);
strcpy(buf, ctime(&today));
send(sock2, buf, strlen(buf) + 1, 0); /* 클라이언트에 시간 정보 전송 */
close(sock2);
}
}
🔼 서버 프로그램 (연결형)
2 비연결형 서비스
비연결형 서비스에서는 전송 데이터마다 수신자의 소켓 주소를 함께 전송한다.
2.1 클라이언트 프로그램
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#define TIME_SERVER "211.223.201.30"
#define TIME_PORT 5010
main()
{
int sock;
struct sockaddr_in server, client;
int server_len = sizeof(server);
char buf[256];
int buf_len;
server.sin_family = AF_INET;
server.sin_addr.s_addr = htonl(inet_addr(TIME_SERVER));
server.sin_port = htons(TIME_PORT);
if ((sock = socket(AF_INET, SOCK_DGRAM, 0)) == -1)
exit(1);
client.sin_family = AF_INET;
client.sin_addr.s_addr = htonl(INADDR_ANY);
client.sin_port = htons(0);
if (bind(sock, (struct sockaddr *)&client, sizeof(client)) < 0)
exit(1);
buf[0] = '?'; buf[1] = '\0';
buf_len = sendto(sock, buf, strlen(buf) + 1, 0, (struct sockaddr *)&server, server_len);
if (buf_len < 0)
exit(1);
buf_len = recvfrom(sock, buf, 256, 0, (struct sockaddr *)0, (int *)0);
if (buf_len < 0)
exit(1);
printf("Time information from server is %s", buf);
close(sock);
}
🔼 클라이언트 프로그램 (비연결형)
- 클라이언트 프로세스는 자신이 생성한 소켓의 주소를 지정하기 위해 client 변수에 IP 주소와 소켓 주소를 지정한다.
- bind() 함수를 성공적으로 실행하면 클라이언트가 먼저 서버에 문자 데이터를 전송한다.
- recvfrom() 함수를 이용해 서버가 전송한 시간 정보를 전송받아 출력한다.
2.2 서버 프로그램
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<time.h>
#define TIME_PORT 5010
main()
{
int sock;
struct sockaddr_in server, client;
int server_len;
char buf[256];
time_t today;
if ((sock = socket(AF_INET, SOCK_STREAM, 0)) == -1)
exit(1); /* 오류 발생 시 프로그램 종료 */
server.sin_family = AF_INET;
server.sin_addr.s_addr = htonl(INADDR_ANY);
server.sin_port = htons(TIME_PORT);
if (bind(sock, (struct sockaddr *)&server, sizeof(server))
exit(1);
while(1) {
buf_len = recvfrom(sock, buf, 256, 0, (struct sockaddr *)&client, &client_len);
if (buf_len < 0)
exit(1);
printf("Server: Got %s\n", buf);
time(&today);
strcpy(buf, ctime(&today));
send(sock, buf, strlen(buf) + 1, 0, (struct sockaddr *)&client, client_len);
}
}
🔼 서버 프로그램 (비연결형)
- 서버 프로세스는 변수 server를 이용하여 자신의 소켓 주소를 바인드한다.
- 임의의 클라이언트 프로세스로부터 데이터가 입력되기를 기다리는데, udp_client.c에서 전송한 문자가 입력되면 이를 화면에 출력해준다.
- sendto() 함수는 주소를 이용하여 클라이언트에게 시간 정보를 전송할 수 있다. (recvfrom() 함수의 5번째 매개변수에 송신자인 클라이언트의 주소가 입력됨)
Reference
쉽게 배우는 데이터 통신과 컴퓨터 네트워크
'독서 > 쉽게 배우는 데이터 통신과 네트워크' 카테고리의 다른 글
[쉽게 배우는 데이터 통신과 네트워크] CH14. DNS (0) | 2024.06.15 |
---|---|
[쉽게 배우는 데이터 통신과 네트워크] CH13. 웹(WWW) (0) | 2024.06.14 |
[쉽게 배우는 데이터 통신과 네트워크] CH11. 상위 계층의 이해 (0) | 2024.06.11 |
[쉽게 배우는 데이터 통신과 네트워크] CH10. 전송 계층 (0) | 2024.06.09 |
[쉽게 배우는 데이터 통신과 네트워크] CH9. TCP 프로토콜 (0) | 2024.06.05 |