소켓
ㅡ> 소켓 = 클라이언트 프로세스와 서버 프로세스간의 통신을 위해, OS가 제공하는 일종의 API
ㅡ> OS 내부에는 app계층 밑의 계층들에 있는 것들이 구현되어 있음
ㅡ> OS가 제공하는 전송계층이 TCP/UDP 2가지라서 둘 중 하나를 사용해야 함
ㅡ> TCP를 사용하고 싶으면 TCP와 연결된 소켓을 사용, UDP를 사용하고 싶으면 UDP와 연결된 소켓을 사용
(소켓 = app계층과 OS사이의 인터페이스)
TCP
ㅡ> SOCK_STREAM
- Reliable delivery (신뢰성 있는 전송): 데이터를 신뢰성 있게 목적지에 전송합니다. 패킷이 손실되면 재전송됩니다.
- In-order guaranteed (순서 보장): 데이터가 전송된 순서 그대로 도착합니다.
- Connection-oriented (연결 지향적): 데이터를 전송하기 전에 클라이언트와 서버 간의 연결이 설정됩니다.
- Bidirectional (양방향 통신 가능): 클라이언트와 서버는 양방향으로 데이터를 주고받을 수 있습니다.
UDP
ㅡ> SOCK_DGRAM
- Unreliable delivery (신뢰성 없는 전송): UDP는 패킷 손실에 신경 쓰지 않으며, 손실된 데이터에 대한 재전송이 이루어지지 않습니다.
- No order guarantees (순서 보장 없음): 패킷이 네트워크에서 목적지에 도착하는 순서를 보장하지 않습니다.
- No connection (비연결형): UDP는 연결 설정 과정이 없으며, 데이터를 독립적으로 전송합니다.
- Can send or receive (전송 또는 수신 가능): 송신자는 데이터를 빠르게 전송할 수 있지만, 신뢰성과 순서 보장이 없습니다.
TCP 소켓 함수
이 과정이 끝나면
두 소켓 사이에 단단한 연결고리가 생성
read&write 관계
socket()
int socket(int domain, int type, int protocol);
동작
ㅡ> 소켓 생성
ㅡ> 소켓의 아이디or인덱스 반환 (file discriptor)
소켓 생성 실패
ㅡ> -1 반환
ㅡ> errno에 실패원인 설정
<파라미터>
domain (프로토콜 패밀리):
ㅡ> PF_INET: IPv4 주소 체계를 사용합니다. 일반적으로 많이 사용됩니다.
ㅡ> PF_INET6: IPv6 주소 체계를 사용합니다.
ㅡ> PF_UNIX 또는 PF_LOCAL: 유닉스 소켓을 사용할 때 사용합니다. 같은 머신 내에서 프로세스 간 통신에 사용됩니다.
ㅡ> PF_ROUTE: 라우팅 테이블과 관련된 작업에 사용되는 소켓 패밀리입니다.
type (통신 방식):
ㅡ> SOCK_STREAM: TCP를 사용한 신뢰성 있는 양방향 연결을 제공합니다. 데이터가 순서대로 전달되며 손실이 발생하면 재전송됩니다.
ㅡ> SOCK_DGRAM: UDP를 사용한 비연결형 통신을 제공합니다. 데이터 순서나 신뢰성을 보장하지 않지만 빠른 전송이 가능합니다.
protocol (프로토콜):
ㅡ> 이 인자는 주로 0으로 설정되어 기본 프로토콜(TCP 또는 UDP)이 선택되도록 합니다.
ㅡ> 특정 프로토콜을 지정하고 싶을 때는 getprotobyname() 함수나 /etc/protocols 파일을 참조하여 사용할 수 있습니다.
bind()
int bind(int sockfd, struct sockaddr* myaddr, int addrlen);
동작
ㅡ> 소켓ID를 이용해서, 소켓을 특정 주소에 바인딩 (IP, 포트넘버)
<파라미터>
sockfd (소켓 파일 디스크립터):
ㅡ> socket() 함수가 반환한 소켓의 파일 디스크립터입니다. 이 소켓을 로컬 IP 주소 및 포트에 바인딩하게 됩니다.
myaddr (소켓 주소 구조체):
ㅡ> IP 주소와 포트 번호를 포함하는 구조체로, 이 정보를 소켓에 할당합니다.
ㅡ> 이 구조체는 struct sockaddr_in 형태로 주어지며, IP 주소와 포트 번호가 설정됩니다.
ㅡ> IP 주소 설정: 만약 IP 주소가 **INADDR_ANY**로 설정되면, 커널이 사용 가능한 로컬 IP 주소를 자동으로 할당합니다.
ㅡ> 포트 번호 설정: 포트 번호가 0으로 설정된 경우, 커널이 사용 가능한 포트 번호를 자동으로 할당합니다. 그렇지 않으면, 사용자가 지정한 포트 번호로 바인딩됩니다.
addrlen (주소 구조체의 크기):
ㅡ> 바인딩할 주소 구조체의 크기를 나타냅니다. 일반적으로 **sizeof(struct sockaddr_in)**로 설정됩니다.
listen()
int listen(int sockfd, int backlog);
동작
ㅡ> 소켓을 수동상태로 만든다
ㅡ> 수동상태 : 서버가 클라이언트의 연결을 대기하는 상태
ㅡ> 성공 : 0반환
ㅡ> 실패 : -1반환, errno설정
non-blocking
<파라미터>
sockfd
ㅡ> socket() 함수가 반환한 소켓 파일 디스크립터로, 수신 대기 상태로 만들 소켓을 지정합니다.
backlog
ㅡ> 연결 대기열의 크기를 설정하는 값으로, 클라이언트가 연결을 요청했을 때 아직 처리되지 않은 연결 요청의 수를 의미합니다. 커널이 관리하는 대기열의 크기를 설정하며, 클라이언트의 연결 요청을 처리할 때까지 기다리는 대기열이 꽉 차면, 추가적인 연결 요청은 거부될 수 있습니다.
ㅡ> (동시에 리퀘스트가 들어왔을 때, 최대 몇개까지 큐에 잡아둘 것이냐를 설정)
accept()
int accept(int sockfd, struct sockaddr* cliaddr, int* addrlen);
동작
ㅡ> 클라이언트의 연결요청을 수락
ㅡ> 그 연결을 처리할 새로운 소켓 생성
ㅡ> 성공 : 새로운 소켓 file discriptor 반환
ㅡ> 실패 : -1반환, errno설정
blocking
<파라미터>
sockfd
ㅡ> socket() 함수가 반환한 소켓 파일 디스크립터로, 연결을 수락할 소켓을 지정합니다.
cliaddr
ㅡ> 클라이언트의 IP 주소와 포트 번호가 저장되는 구조체입니다. 클라이언트의 주소 정보를 담고 있습니다.
addrlen
ㅡ> 주소 구조체의 길이를 나타내며, 함수 호출 전에는 **sizeof(struct sockaddr_in)**으로 설정하고, 함수 호출 후에는 클라이언트의 주소 크기로 갱신됩니다.
(sockfd는 서버의 소켓이고, cliaddr는 클라이언트의 주소다.)
connect()
int connect(int sockfd, struct sockaddr* servaddr, int addrlen);
동작
ㅡ> 서버에 연결요청
ㅡ> 성공 : 0 반
ㅡ> 실패 : -1 반환, errno 설정
ㅡ> blocking 함수
파라미터
sockfd
ㅡ> 클라이언트 측에서 생성된 소켓 파일 디스크립터입니다. 이 소켓을 통해 서버에 연결을 요청하게 됩니다.
servaddr
ㅡ> 연결하려는 서버의 IP 주소와 포트 번호를 저장하는 구조체입니다. 이 구조체를 사용하여 클라이언트는 어느 서버에 연결할지를 지정합니다.
addrlen
ㅡ> 서버 주소 구조체의 크기를 지정하는 값으로, 일반적으로 **sizeof(struct sockaddr_in)**으로 설정됩니다.
write()
int write(int sockfd, char* buf, size_t nbytes);
동작
ㅡ> 소켓을 통해 데이터를 보냄
ㅡ> 성공 : 전송된 바이트 수 반환
ㅡ> 실패 : -1반환, errno설정
ㅡ> Blocking 함수
블로킹 특성
ㅡ> 데이터를 다 전송할 때까지 함수가 반환되지 않고 대기상태
ㅡ> 따라서, 네트워크 지연이나 대기 상태가 발생할 수 있다
<파라미터>
sockfd:
ㅡ> 소켓 파일 디스크립터
buf:
ㅡ> 전송할 데이터가 저장된 버퍼
nbytes:
ㅡ> 전송할 데이터의 크기를 바이트 단위로 지정
read()
int read(int sockfd, char* buf, size_t nbytes);
동작
ㅡ> 소켓을 통해 데이터를 읽는 함수
ㅡ> 성공 : 실제로 읽은 바이트 수 반환
ㅡ> 실패 : -1반환, errno 설정
ㅡ> Blocking 함수
<파라미터>
- sockfd: 소켓 파일 디스크립터입니다.
- buf: 데이터를 저장할 버퍼입니다.
- nbytes: 읽고자 하는 바이트 수를 지정합니다.
close()
int close(int sockfd);
동작
ㅡ> 소켓을 사용한 후에는 이를 닫아야 한다.
ㅡ> close 함수는, 소켓을 닫고, 관련된 리소스들을 해제시킨다.
파라미터
sockfd
ㅡ> 닫을 소켓의 파일 디스크립터
Tip) 포트 해제
컨트롤 C 같은 것으로, 비정상 종료 됐을 떄
ㅡ> 프로세스는 죽는다
ㅡ> 프로세스에 바인딩 된 포트는 한동안 풀리지 않는다
ㅡ> 이럴때 signal 라이브러리를 사용해서, 프로세스에 바인딩된 자료구조들을 release 시켜준다.
TCP 소켓 연결 setup단계
참고 : 웹서버는 포트넘버를 보통 80으로 지정해줘야 돼서 bind()를 쓰지만,
클라이언트는 포트넘버 아무거나 상관없어서 bind()를 쓰지 않는다.
connect()를 할때 자동으로 ip와 포트넘버가 할당되게 된다.
코드
헤더 파일과 상수 정의
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <sys/wait.h>
#define PORT 3490 /* 서버가 사용할 포트 번호 */
#define BACKLOG 10 /* 연결 대기 중인 클라이언트 수를 제한하는 백로그 */
서버의 main() 함수
main()
{
int sockfd, new_fd; /* 소켓 파일 디스크립터 */
struct sockaddr_in my_addr; /* 서버의 주소 정보 */
struct sockaddr_in their_addr; /* 클라이언트의 주소 정보 */
int sin_size;
if ((sockfd = socket(PF_INET, SOCK_STREAM, 0)) == -1) {
perror("socket");
exit(1);
}
서버의 주소 설정 및 bind() 함수
my_addr.sin_family = AF_INET; /* IPv4 */
my_addr.sin_port = htons(MYPORT); /* 포트 번호 */
my_addr.sin_addr.s_addr = htonl(INADDR_ANY); /* 모든 IP로부터 연결 허용 */
if (bind(sockfd, (struct sockaddr *)&my_addr, sizeof(struct sockaddr)) == -1) {
perror("bind");
exit(1);
}
서버가 연결을 대기하고, 클라이언트와 연결하는 부분
if (listen(sockfd, BACKLOG) == -1) {
perror("listen");
exit(1);
}
while (1) { /* 클라이언트 연결을 대기하는 루프 */
sin_size = sizeof(struct sockaddr_in);
if ((new_fd = accept(sockfd, (struct sockaddr *)&their_addr, &sin_size)) == -1) {
perror("accept");
continue;
}
printf("server: got connection from %s\n", inet_ntoa(their_addr.sin_addr));
}
}
클라이언트의 main() 함수
if ((sockfd = socket(PF_INET, SOCK_STREAM, 0)) == -1) {
perror ("socket");
exit (1);
}
their_addr.sin_family = AF_INET;
their_addr.sin_port = htons(Server_Portnumber);
their_addr.sin_addr = htonl(Server_IP_address);
if (connect(sockfd, (struct sockaddr*)&their_addr, sizeof(struct sockaddr)) == -1) {
perror ("connect");
exit (1);
}
UDP 소켓함수 큰그림
Multiplexing/demultiplexing
- Multiplexing은 여러 소켓에서 데이터를 모아서 네트워크로 보내는 과정
- Demultiplexing은 수신된 데이터를 적절한 소켓으로 전달하는 과정
- 이러한 과정을 통해 네트워크 상에서 다수의 애플리케이션이 동시에 통신할 수 있다.
Multiplexing (다중화)
ㅡ> 여러 소켓에서 데이터를 수집
ㅡ> 데이터를 헤더와 묶어서 segment로 만듬
Demultiplexing
ㅡ> 수신된 segment를 적절한 소켓으로 전달
ㅡ> (헤더 정보를 활용하여 적절한 소켓을 찾음)
Segment
Source port :보내는 소켓의 포트넘버
Dest port : 받는 소켓의 포트넘버
UDP의 경우
- dest IP
- dest port
이 2가지만으로 목적지 소켓을 찾음
TCP의 경우
- src IP
- src port
- dest IP
- dest port
이 4가지로 목적지 소켓 찾음
하나라도 다르면 다른 소켓으로 감
ㅡ> 그래서 Connection-oriented이다.
UDP에서는 목적지 소켓에 도착하는 segment들이
다양한 source로부터 오지만
TCP에서는 목적지 소켓에 도착하는 segment들이
하나의 srource로부터만 온다 (connected)
ex)
네이버에 접속한다 치면 (TCP)
네이버에 사용자가 하나 접속할 때마다, 소켓을 하나 생성하는 것이다.
우리가 UDP, TCP, IP의 헤더 정보는 잘 알고 있어야 한다
UDP Segment
포트 한 필드가 16비트
ㅡ> 포트넘버 개수 = 65536개
checksum
ㅡ> 전송도중 에러가 있었는지 판단
ㅡ> 에러있으면 data를 올리지 않고 드랍시킴
ㅡ> UDP가 아무것도 안해주는 것 같지만, 사실 에러체킹정도는 해주는 것
UDP 하는일
ㅡ> 멀티플렉싱/디펄티플렉싱, 에러체킹
'CS > 네트워크' 카테고리의 다른 글
전송계층2 - TCP, segment구조, RTT, 타임아웃 (0) | 2024.09.20 |
---|---|
전송계층 1 - RDT효율 : Go-Back-N, Seletive Repeat (0) | 2024.09.20 |
2. 어플리케이션 계층2 (0) | 2024.09.15 |
컴퓨터네트워크 기본2 (1) | 2024.09.04 |
컴퓨터 네트워크 기본1 (0) | 2024.08.28 |