12. 네트워크 프로그래밍
01 소켓의 주소 체계
소켓: 운영체제에서 제공하는 통신 프로토콜을 편리하게 사용할 수 있도록 도와줌
1. 소켓 주소
프로토콜 종류에 따라 다양한 방식으로 주소 부여
유닉스 주소 체계
AF_UNIX
하나의 호스트 내부에서 실행되는 프로세스 사이의 통신 지원
파일 시스템의 경로명 기반 주소 체계
struct sockaddr_un {
short sun_family;
char sun_path[108];
};
sun_family: 유닉스 주소 체계, AF_UNIX 값
sun_path: 소켓을 구분하는 주소, 파일 시스템의 경로명 기록
인터넷 주소 체계
서로 다른 호스트에서 실행되는 프로세스 사이의 통신 지원
AF_INET로 표시
소켓이 생성되는 호스트의 IP 주소와 포트 번호를 조합하여 소켓 주소 표현
struct sockaddr_in {
short sin_family; /* AF_INET 인터넷 주소 체계 */
u_short sin_port; /* 포트 번호 */
short in_adr sin_addr; /* IP 주소 */
char sin_zero[8];
};
struct in_addr {
u_long s_addr; /* IP 주소 기록, u_long 형의 s_addr 필드로 재지정됨*/
};
특정 프로세스를 구분하는 유일한 주소: IP 주소+포트 번호의 조합
통합 주소 체계
소켓 주소 체계는 전송 계층의 프로토콜에 따라 달라짐
소켓 주소: 통일된 형식의 소켓 시스템 콜을 통해 사용
여러 소켓 구조체를 통합해 일반 구조체 하나로 정의
struct sockaddr {
u_short sa_family; /* AF_UNIX, AF_INET, ... */
char sa_data[14];
};
struct sokaddr_in addr; /* 인터넷 주소 체계 */
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = htonl(INADDR_ANY); /* IP 주소 */
addr.sin_port = htons(5010); /* 포트 번호 */
bind(socket, (struct sockaddr *)&addr, sizeof(addr));
/* bind는 통신 프로토콜의 종류와 관계없이 공통으로 사용,
두 번째 매개변수(주소값) 특정 주소 체계로 고정하여 정의할 수 없음
sockaddr과 같은 공통 주소 체계로 형 변환하여 문법의 통일성 유지 */
2. 소켓 서비스
제공하는 서비스에 따른 소켓 유형도 다양함
- SOCK_STREAM: 연결형 서비스, TCP 프로토콜에 대응
- SOCK_DGRAM: 비연결형 서비스, UDP 프로토콜에 대응
- SOCK_RAW: IP 프로토콜을 직접 사용
3. 소켓의 기본 함수
- socket: 소켓 생성
- bind: socket() 함수로 생성된 소켓에 주소 부여
- listen: 소켓 활성화, 서버 프로세스에서 실행
- accept: 소켓에서 클라이언트의 연결 요구가 들어올 때까지 대기, 연결 설정 요구가 발생하면 연결 생성
- connnect: 클라이언트 프로세스에서 사용, 서버와 연결 설정 시도
- send: 연결형 서비스를 제공하는 환경에서 데이터 전송
- recv: 연결형 서비스에서 데이터 수신
02 소켓 시스템 콜
TCP, UDP 사용을 위해 소켓 시스템 콜이라는 라이브러리 함수 이용
소켓: 통신을 원하는 프로세스에 할당되는 자원, 고유의 소켓 주소 부여
소켓=포트라고도 부름
1. socket() 함수
#include <sys/types.h>
#include <sys/socket.h>
int socket(int domain, int type, int protocol);
domain: 사용할 프로토콜의 도메인
type: 서비스 유형 (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_STREAM, 0); /* 인터넷 주소 연결형 서비스 */
sd = socket(AF_INET, SOCK_DGRAM, 0); /* 인터넷 주소 비연결형 서비스 */
인터넷 연결형 서비스: TCP 프로토콜용 소켓 할당, 비연결형 서비스: UDP 프로토콜용 소켓 할당
2. bind() 함수
socket() → 소켓 생성, 소켓 디스크립터가 반환됨
bind(): 디스크립터를 이용해 상대 프로세스와 통신하려면 생성된 소켓에 주소 부여
ex. 스마트폰 자체를 소켓으로 본다면 스마트폰을 통신 회사에 등록하여 전화번호를 부여하는 기능
#include <sys/types.h>
#include <sys/socket.h>
int bind(int s, const struct socakddr *name, socklen_t *namelen);
s: socket() 함수 반환 값, 소켓 디스크립터 번호
name: 바인딩할 소켓 주소
namelen: name에 보관된 주소의 크기
유닉스 도메인의 예
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)) == -) {
perror("bind");
exit(1);
}
데이터 전송에 사용할 소켓 생성, 반환값을 sd에 보관
유닉스 도메인 연결형 서비스를 지원하는 소켓 생성
sockaddr_un 구조체를 이용해 addr 변수 선언 → 주소 값 보관
bind함수: 생성된 소켓의 주소를 /tmp/sock_addr로 지정
인터넷 도메인의 예
AF_INET: 호스트의 IP 주소와 포트 번호를 이용해 주소 표기
bind() 함수에서 사용하는 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)) == -) {
perror("bind");
exit(1);
}
INADDR_ANY라는 호스트 주소 표기 방법: 프로세스가 실행되는 로컬 호스트 자체 의미 → 호스트의 IP 주소로 자동 대체됨
htonl(), htons() 함수: 컴퓨터 사이의 바이트 저장 순서 차이에 따른 문제점 해결
3. listen() 함수
서버 프로세스에서 실행
s가 가리키는 소켓에서 대기할 수 있는 클라이언트의 연결 요청 개수 지정
일반적으로 연결형 서비스에서 사용
#include <sys/types.h>
#include <sys/socket.h>
int listen(int s, int backlog);
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)) == -) {
perror("bind");
exit(1);
}
if (listen(sd, 5) == -1) {
perror("listen");
exit(1);
}
4. accept() 함수
연결형 서비스를 지원하는 서버 프로세스가 클라이언트의 연결 요청을 받으려면 accept() 함수에서 대기
서버 프로세스: accept() 함수를 시행해 클라이언트의 요청을 기다림
클라이언트 프로세스의 connect() 요청이 발생하면 연결이 설정됨
#include <sys/types.h>
#include <sys/socket.h>
int accept(int s, struct sockaddr *addr, socklen_t *addrlen);
addr 변수에 연결을 요청한 클라이언트의 소켓 주소 반환
성공적으로 연결되면 별도의 소켓이 만들어짐 → 클라이언트와 통신할 때 사용
socket() 함수에 의해 생성된 소켓: 연결 요청을 받는 목적
int sd, new;
struct sockaddr_in addr;
struct sockaddr_in client;
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)) == -) {
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);
close(new);
exit(0);
}
/* 부모 프로세스 */
close(new);
}
클라이언트 프로세스와 연결이 이루어지면 하위의 자식 프로세스를 생성해 클라이언트와 통신
자신은 accept()에서 다시 대기함
5. connect() 함수
연결형 서비스에서 클라이언트 프로세스가 서버 프로스에 연결 요청을 할 때
서버 프로세스는 자신의 바인드된 주소를 공개해야 클라이언트가 서버에 연결을 시도할 수 있음
클라이언트는 자신의 주소를 공개하지 않아도 됨 → bind() 함수 실행x, 대신 임의의 주소가 자동 할당
#include <sys/types.h>
#include <sys/socket.h>
int connect(int s, const struct sockaddr *name, socklen_t namelen);
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() 함수가 수행 (십진수와 이진수 표기 변환)
- inet_addr(): 십진수 → 32비트
- inet_ntoa(): 32비트 → 십진수
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);
s: 클라이언트와 서버 사이의 연결 설정이 완료된 상태의 소켓 (accept, connect 함수가 성공적으로 실행)
msg: 전송되는 데이터
to, tolen: 비연결형 서비스에서 수신 프로세스의 소켓 주소 표기
if (send(sd, data, length, 0) == -1) {
perror("send");
exit(1);
}
7. recv() 함수
recv(), recvform(): 상대 프로세스가 전송한 데이터를 소켓을 통해 읽음
recv(): 연결형, recvform(): 비연결형 서비스
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/uio.h>
ssize_t recv(int s, void *buf, size_t len, int flags);
ssize_t recvform(int s, void *buf, size_t len, int flags
,struct sockaddr *from, socklen_t *fromlen);
buf: 소켓을 통해 읽은 데이터 저장, 크기 반환
비연결형 서비스: from에 송신 프로세스의 소켓 주소 기록
if (recv(sd, data, length, 0) == -1) {
perror("recv");
exit(1);
}
03 클라이언트 서버 프로그래밍
클라이언트 서버 모델: 네트워크 서비스를 제공하는 서비스 제공자와 서비스 이용자 사이의 관계
서버 프로세스: 서비스를 제공하는 프로그램
클라이언트 프로세스: 서버와 연결 시도, 서비스를 제공받는 프로그램
1. 연결형 서비스
소켓: 네트워크 통신을 위한 소프트웨어 교신점
2개의 독립 프로세스가 네트워크를 통해 통신하려면 소켓이 필요
클라이언트와 서버의 동작
서버의 동작 과정
- 서비스 교신점(호스트 IP 주소, 포트 번호) 공개
- 클라이언트로부터 발생하는 서비스 요구 대기
- 클라이언트에 서비스 제공
- 서비스 시간이 짧다면 직접 제공
- 시간이 길어지면 하위 서버 프로세스 생성
- 해당 클라이언트에 서비스 제공 완료
- 2로 이동
클라이언트의 동작 과정
- 원하는 서비스를 제공하는 서버 확인
- 해당 서버와 연결 시도
- 서버에 서비스 요청
- 서비스 이용 완료
프로그램 컴파일 방법
클라이언트와 서버 프로그램을 따로 컴파일한 후 서버, 클라이언트 순으로 실행
& cc -o time_client time_client.c -lsoket -lnsl
& cc -o time_server time_server.c -lsoket -lnsl
컴파일 → time_client, time_server 실행 파일이 생성됨
2. 비연결형 서비스
connect, accept 함수로 연결을 설정하는 과정이 생략
sendto, recvform 함수 사용
전송 데이터마다 수신자의 소켓 주소를 함께 전송
연결 절차가 생략 → 클라이언트의 sendto() 요청으로 서버가 클라이언트 인지
'CS > Network' 카테고리의 다른 글
[HTTP 웹 기본지식] 2. URI와 웹 브라우저 요청 흐름 (0) | 2022.11.14 |
---|---|
[HTTP 웹 기본 지식] 1. 인터넷 네트워크 (0) | 2022.11.14 |
[네트워크] 11. 상위 계층 (0) | 2022.10.23 |
[네트워크] 10. 전송 계층 (0) | 2022.10.21 |
[네트워크] 9. TCP 프로토콜 (0) | 2022.10.19 |