본문 바로가기

Computer Science/네트워크

[쉽게 배우는 데이터 통신과 네트워크] CH12. 소켓을 이용한 네트워크 프로그래밍

반응형
SMALL

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 서버와 클라이언트의 동작

  • 서버의 동작
    1. 서비스 교신점(호스트의 IP 주소, 포트 번호) 공개
    2. 클라이언트로부터 발생하는 서비스 요구 대기
    3. 클라이언트에 서비스 제공
    4. 해당 클라이언트에 서비스 제공 완료
    5. 2로 이동
  • 클라이언트의 동작
    1. 원하는 서비스를 제공하는 서버 확인
    2. 해당 서버와 연결 시도
    3. 서버에 서비스 요청
    4. 서비스 이용 완료

 

TCP를 이용한 통신 절차

 

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 비연결형 서비스

비연결형 서비스에서는 전송 데이터마다 수신자의 소켓 주소를 함께 전송한다.

 

UDP를 이용한 통신 절차

 

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

쉽게 배우는 데이터 통신과 컴퓨터 네트워크

반응형
LIST