3,4,5주차 정리
소켓이란
네트워크 연결 모델
인터넷 서비스 포트
- 소켓
- TCP/IP 4계층에서 전송계층 위에 놓인 것
- 전송 계층에서 전송계층의 프로토콜 제어를 위한 코드를 제공
- 전송 계층 아래를 모두 추상화하는 고수준 네트워크 프로그래밍 인터페이스를 제공
- 소켓의 역할
- 소프트웨어와 소프트웨어를 연결, 소프트웨어간 데이터 통신
- 소켓의 역할(www 를 예로)
- Apache와 Chrome 브라우저가 인터넷을 사이에 두고 떨어져 있다
- Apache
- 웹에서 HTML 문서 서비스를 위해서 사용되는 인터넷 프로그램 이라고 하며, 웹 서버라고도 부른다
- Chrome
- 웹 브라우저로 웹 서버에 문서를 요청하고 이를 Rendering 한다
- 기다리는 측은 accept 함수를 이용해 기다림
- 연결하는 측은 connect 함수를 이용해 연결 시도
- coneect 함수를 호출하면, 인터넷의 경로시스템을 이용해서 apache 소켓과 연결하고 연결 통로가 만들어 진다
- 네트워크 연결 모델
- 인터넷은 프로그램과 프로그램의 연결
- 다양한 연결 방식
- 서버 & 클라이언트 모델
- 매니저 & 에이전트 모델
- p2p 모델
- 서버 & 클라이언트 모델
- 고객 & 서비스 제공자 모델이라고 함
- 서버
- 데이터를 관리하고 서비스를 제공하는 네트워크 프로그램
- 클라이언트
- 서버에 정보를 요청하는 프로그램
- 상업적으로 정보를 판매하는데 유리한 모델이기 때문에 초기부터 지금까지 널리 사용됨
- 서버에서 정보를 집중 관리하기 때문에, 서버 성능이 중요함
- 정보가 집중되기 때문에 악의적인 공격과 보안에 취약한 측면이 있음
- 서버 성능을 높이는데 한계가 있기 때문에 대량의 데이터를 처리해야할 경우, 서버 성능 확장에 많은 비용이 소비됨 -> 클라우드로 이동하는 추세
- 매니저 & 에이전트 모델
- 매니저 프로그램과 에이전트 프로그램으로 구성
- 매니저 프로그램이 에이전트 프로그램으로 정보 요청
- 서버 & 클라이언트 모델 -> N(클라이언트) : 1(서버)
- 매니저 & 에이전트 모델 -> 1(매니저) : N(에이전트) - 서버 & 클라이언트 모델과 반대
- 다수의 컴퓨터로 명령을 내리거나 정보 수집에 적합
- p2p (peer to peer)
- 동등 계층 모델
- 모든 노드가 동등한 자격으로 네트워크에 참여 (데이터의 전송이 빠르다는 장점 - ex 토렌토)
- 유저간 동등한 정보 교환으로 인터넷의 철학을 구현한 모델로 평가
- 정보 산업화와 맞지 않는 측면으로 사장됨
- 정보의 불균질에서 이윤이 발생
- 정보가 균질해지는 p2p에서는 이윤 만들기가 어려움
- 모델의 선택
- 가장 많이 사용되는 모델
- 서버 & 클라이언트 모델
- 상업적인 목적에 적당함
- 잘 알려진 모델이라서 구현이 수월함
- 서버 & 클라이언트 모델
- 분산 시스템 관리에서 사용되는 모델
- 매니저 & 에이전트 모델
- 시스템 관리의 대부분은 이 모델을 따름
- 매니저 & 에이전트 모델
- p2p
- 일부 서비스를 위해 사용하고 있기는 하지만 주로 부하를 클라이언트에 분산하기 위해 사용
- 가장 많이 사용되는 모델
서비스 포트
- 인터넷은 프로그램과 프로그램의 연결
- 어떻게 프로그램이 프로그램을 찾을 수 있을까?
- 컴퓨터의 위치는 IP 주소로 찾음
- 컴퓨터내에서 프로그램의 위치는 port 번호로 찾음
- 포트
- 단일 플랫폼에서 성격이 다른 정보를 서비스 하기 위한 구조
- 네트워크 프로그램은 고유의 포트번호와 연결(bind)
- 원격 프로그램은 ip 주소를 이용해서 컴퓨터를 찾고 포트번호로 원하는 프로그램을 찾음
- 표준 서비스 포트
- 운영체제는 표준적인 웹 서비스 포트를 정의
- 포트 번호 범위
- 포트번호 크기
- short int(1~65536)
- 시스템 포트 (1~1024)
- 표준인터넷 서비스 번호로 관리자 권한으로만 사용될 수 있다
- 일반 서비스 포트 (1025~)
- 일반 프로그램에서 자유롭게 사용할 수 있는 포트 번호
- 포트번호 크기
- 포트 번호의 할당
- 서버 포트 (명시적으로 할당)
- 포트번호에 서버 프로그램이 bind 되므로 클라이언트도 사전에 알고 있어야 한다
- 클라이언트 포트 (임의 할당)
- 클라이언트 포트는 1025 이상의 포트 번호 중 남는 포트 번호가 임의로 할당
- 서버 포트 (명시적으로 할당)
네트워크 프로그래밍에 대해서
소켓 다루기
서버/클라이언트 네트워크 함수
- 네트워크 프로그램의 흐름
- 송신자(클라이언트), 수신자(서버), 전령(데이터)으로 구성
- 인터넷 구성요소
- 컴퓨터 노드 : 송신자 혹은 수신자의 역할
- 인터넷 : 전령 혹은 매체의 역할
- 소켓 : 전화기 역할, 인터넷과의 접점 혹은 관문
- Node를 인터넷에 연결 : 소켓에 전체 인터넷 노드에서 유일하게 식별가능한 주소를 부여. 프로그램을 찾을 수 있도록 포트 번호를 bind
- 원격지의 Node 찾기 : 원격지의 Node IP를 통해 컴퓨터의 위치를 찾고, port 번호를 이용해서 프로그램을 찾는다
- 송신측
- 소켓 생성 -> 포트 부여 -> 상대편 ip/port 주소로연결 -> 통신 -> 종료
- 수신측
- 소켓 생성 -> 포트 부여 -> 상대편 연결 기다리기 -> 통신 -> 종료
- 소켓 생성: 인터넷과 연결하기 위한 접점 소켓(ENDPOINT SOCKET) 생성
#include <sys/types.h> //꼭 필요
#include <sys/socket.h> //꼭 필요
int socket(int domain, int type, int protocol);
//domain : 소켓의 사용영역 정의
//type : 소켓 유형을 정의
//protocol : 소켓이 사용할 프로토콜 정의 - TCP, UDP
//따로 인터넷에서 정리한거
domain - 소켓이 사용할 프로토콜 체계(Protocol Family) 정보 전달 ex) IPv4, IPv6, IPX, low level socket 등 설정 type - 소켓의 데이터 전송방식에 대한 정보 전달 ex) TCP, UDP 설정
protocol - 두 컴퓨터간 통신에 사용되는 프로토콜 정보 전달 ex) 특정 프로토콜 사용을 지정 (보통 0)
- domain : 소켓이 사용되는 네트워크의 영역을 정의
- type & protocol
- type : 통신에 사용할 패킷의 타입을 지정
- protocol : 통신에 사용할 프로토콜 지정
- type에 따라서 protocol이 정해짐
- socket 함수를 이용한 소켓 생성의 예
- tcp 소켓
- socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)
- udp 소켓
- socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP)
- tcp 소켓
- socket 함수 반환 값
- 성공적으로 소켓을 만들면 0보다 큰 int값 반환
- 소켓 지정번호, socket descriptor 라고 부른다
- 소켓을 지시하며, 이를 통해 소켓을 제어
- 소켓 연결 : connect 함수를 이용한 소켓 연결
- 연결하고자 하는 상대 Node의 ip주소와
- 연결하고자 하는 프로그램의 port 번호를 명시
#include <sys/types.h>
#include <sys/socket.h>
int connect(int sockfd, const struct sockaddr* serv_addr, socklen_t addrlen);
//sockfd : 파일 지정 번호(핸들러)
//serv_addr : 연결할인터넷 주소와 port 번호를 포함한 구조체
//addrlen : 두 번째 매개 변수로 넘길 데이터의 크기
- connect 함수의 사용 예
//internet tcp/ip 통신 ,
//218.234.19.87 주소로 연결 요청
//8080 포트에 연결된 프로그램을 요청
struct sockaddr_in serveraddr;
server_sockfd = socket(AF_INET, SOCK_STREAM, 0); //소켓의 지정번호
serveraddr.sin_family = AF_INET;
Serveraddr.sin_addr.s_addr = inet_addr("218.234.19.87");
Serveraddr.sin_port = htons(8080);
client_len = sizeof (serveraddr);
connect(server_sockfd, (struct sockaddr * )&serveraddr, client_len);
- 데이터 통신
- 데이터 입출력
- 소켓 함수 : send, recv, sendto, recvfrom
- 파일 함수 : read, write(리턴값을 정확히 예외처리 해야함)(서버가 클라이언트에게 전달할때)
- 입력 함수
- send, recv, read
- 출력함수
- recv, sendto, write
- 유닉스는 소켓을 포함한 모든 자원을 파일로 본다
- 파일 입출력에 사용하는 함수를 소켓에 사용 가능
- 데이터 쓰기
- ssize_t write(int fd, const void *buf, size_t count);
- int send(int fd, const void *msg, size_t len, int flags);
- 데이터 읽기
- ssize_t read(int fd, const void *buf, size_t count);
- int recv(int fd, const void *buf, size_t len, int flags);
- 데이터 입출력
fd : 소켓 지정 번호
buf : 통신에 사용할 데이터를 가리키는 포인터
count : 통신에 사용할 데이터의 크기
- 연결 종료
- 데이터 통신이 끝났다면, close 함수를 이용해서 소켓을 닫음
- 소켓을 닫지 않을 경우 자원 누수 발생
close(int sockfd);
closesocket(SOCKET sockfd);
클라이언트 함수
- 클라이언트 프로그램의 흐름
- socket() : 소켓 생성
- connect() : 연결 요청
- read() / write() : 데이터 통신
- close() : 소켓 닫기
socket() -> connect() -> read() / write() -> close()
서버 네트워크 프로그램 함수
- 서버 프로그램의 흐름
- socket() : 소켓 생성
- bind() : 소켓을 인터넷 주소와 포트번호로 묶음
- listen() : 수신 대기열 생성 (listen queue)
- accept() : 연결 대기
- read() / write() : 데이터 통신
- close() : 소켓 닫기
socket() -> bind() -> listen() -> accept() -> read() / write() -> close()
- bind 함수
- 소켓을 인터넷 주소에 묶어준다. (ip주소, port 번호)
int bind(int sockfd, struct sockaddr *my_addr, socklen_t addrlen);
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = htonl(NADDR_ANY);
addr.sin_port = htons(8080);
state = bind(sockfd, (struct sockaddr *) & addr, sizeof(addr));
- listen 함수
- 수신 대기열 생성 - 클라이언트의 요청은 먼저 수신 대기열에 들어간다
int listen(int queue_size);
- accept 함수
- 수신 대기열의 맨 앞에 있는 클라이언트 요청을 읽는다
- 클라이언트 요청이 있다면 클라이언트와 통신을 담당할 소켓 지정 번호를 반환
int accept(int s, struct sockeaddr * addr, socklen_T * addrlen);
// s : 듣기 소켓의 소켓 지정 번호
// addr : 클라이언트의 주소 정보
//addrlen : 두 번째 매개 변수의 데이터 크기
(소켓 서버 및 클라이언트 프로그램 실습)
서버 프로그램
- socket 생성 함수
int server_socket;
server_socket = socket(PF_INET, SOCK_STREAM, 0);
if (-1 == server_socket){//예외처리 -> 반대로 쓴 이유 : 오타 실수 없애기 위해
printf("server socket : 생성 실패");
exit(1);
}
- bind 함수
struct sockaddr_in server_addr;
memset(&server_addr, 0x00, sizeof(server_addr)); //초기화 (server_addr 를 0x00 으로 세팅한다)
server_addr.sin_family = AF_INET; //IPv4 인터넷 프로토콜
server_addr.sin_port = htons(4000); //사용할 port 번호는 4000
server_addr.sin_addr = htonl(INADDR_ANY); //32bit IPv4 주소
if (-1 == bind(server_socket, (struct sockaddr *) & server_addr, sizeof(server_addr)))
{
printf("bind() 실행 에러\n");
exit(1);
}
/*
인자로 받은 값을 빅 엔디안 형식으로 바꿔주며
CPU가 빅 엔디안을 사용하고 있다면 아무런 변화를 주지 않고 값을 반환합니다.
hton은 host to network의 약자인듯 한데 호스트 값으로 저장된 값을 네트워크에서
사용하기 위한 용도로 값을 변환하다고 보심 되겠습니다.
뒤쪽의 s와 l은 각각 unsigned short, unsigned long을 뜻합니다.
*/
- listen 함수
if (-1 == listen(server_socket, 5))
{
printf("대기 상태 모드 설정 실패\n");
exit(1);
}
- accept 함수
int client_addr_size;
client_addr_size = sizeof(client_addr);
client_socket = accept(server_socket, (struct sockaddr*)& client_addr, &client_addr_size); //client_addr:client 정보
if (-1 == client_socket)
{
printf("클라이언트 연결 수락 실패\n");//실패 log
exit(1);
}
- read 함수
read(client_socket, buff_rcv, BUFF_SIZE); //데이터가 들어올때까지 대기 (정상 작동시 반환값 = BUFF_SIZE > 0)
- write 함수
sprintf(buff_snd, "%d : %s", strlen(buff_rcv), buff_rcv); //buff_snd에 값을 넣어줌
write(client_socket, buff_snd, strlen(buff_snd)+1); //+1 : NULL 까지 포함해서 전송
/*
strlen() 함수는 문자열의 길이를 계산할 때 \0은 제외하고 세기 때문에, strlen(buff_snd)은 실제 문자열의 길이만 반환합니다. 따라서, write() 함수를 통해 이 문자열을 전송할 때 +1을 추가하여 \0까지 함께 전송하게 되는 것입니다.
이렇게 해야 수신 측에서 이 문자열을 받을 때 어디까지가 문자열인지 알 수 있습니다. 만약 \0을 포함하지 않고 전송하면 수신 측에서 문자열의 끝을 알 수 없기 때문에, 예상치 못한 동작이 발생할 수 있습니다.*/
- close 함수
close(client_socket);
전체 서버 프로그램
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/socket.h>
#define BUFF_SIZE 1024
int main(int argc, char ** argv){
int server_socket = 0;// socket (서버 소켓을 만들 때 사용되는 헨들러)
int client_socket = 0;// accept
int client_addr_size = 0;
struct sockaddr_in server_addr;
struct sockaddr_in client_addr;
char buff[BUFF_SIZE+5]; //1029
memset(&server_addr, 0x00, sizeof(server_addr));
memset(&client_addr, 0x00, sizeof(client_addr));
memset(buff, 0x00 , sizeof(buff));
server_socket = socket(PF_INET, SOCK_STREAM, 0); //소켓 생성
if (-1== server_socket)
{
printf("server socket 생성 실패\n");
exit(1);
}
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(4000);
server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
if (-1 == bind(server_socket, (struct sockaddr *)& server_addr, sizeof(server_addr))//bind
{
printf("bind() 실행 에러\n");
exit(1);
}
if (-1 == listen(server_socket, 5)) //listen
{
printf("listen()실행 실패\n");
exit(1);
}
while(1){
client_addr_size = sizeof(client_addr);
client_socket = accept(server_socket, (struct sockaddr *)&client_addr, &client_addr_size);
if (-1 == client_socket)
{
printf("클라이언트 연결 수락 실패\n");
exit(1);
}
read(client_socket, buff_rcv, BUFF_SIZE); //1024만큼 데이터 받기
printf("receive : %s\n", buff_rcv);
sprintf(buff_snd, "%d:%s", strlen(buff_rcv), buff_rcv);
write(client_socket, buff_snd, strlen(buff_snd) + 1); // +1:NULL까지 포함해서 전송
close(client_socket);
}
}
클라이언트 프로그램
- connect 함수
struct sockaddr_int server_addr;
memset(&server_addr, 0x00, sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(4000);
server_addr.sin_addr.s_addr = inet_addr("127.0.0.1"); //서버의 주소
if (-1 == connect(client_socket, (struct sockaddr *)&server_addr, sizeof(server_addr))
{
printf("접속실패\n");
exit(1);
}
클라이언트 프로그램 전체
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/socket.h>
#define BUFF_SIZE 1024
int main(int argc, char ** argv){
int client_socket = 0;
struct sockaddr_in server_addr;
char buff[BUFF_SIZE+5]; //1029
client_socket = socket(PF_INET, SOCK_STREAM, 0); //소켓 생성
if (-1== server_socket)
{
printf("server socket 생성 실패\n");
exit(1);
}
memset(&server_addr, 0, sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(4000);
server_addr.sin_addr.s_addr = inet_addr("127.0.0.1");
if (-1 == connect(client_socket, (struct sockaddr *)& server_addr, sizeof(server_addr))//connect
{
printf("접속 실패\n");
exit(1);
}
write(client_socket, argv[1], strlen(argv[1]+1); //+1:NULL까지 포함해서 전송
read(client_socket, buff, SUFF_SIZE);
printf("%s\n", buff);
close(client_socket);
return 0;
}
실제 실행결과는 아래와 같다.
(추후 다시 복습하며 업로드예정)
'cs이론 > 네트워크 프로그래밍' 카테고리의 다른 글
[네트워크 프로그래밍] client, server 통신 (0) | 2024.10.06 |
---|---|
[네트워크 프로그래밍-윤성우 저 열혈강의 TCP/IP 소켓 프로그래밍] chap01,02,03 정리 (0) | 2024.09.13 |
[네트워크 프로그래밍-3주차 정리] 소켓이란/네트워크 연결 모델/인터넷 서비스 포트 (0) | 2024.09.13 |