TCP Socket
TCP 소켓
- TCP Client와 TCP Server간 통신의 전형적인 절차와 기본적인 소켓 함수들이 표현된 그림이다.
System Call (시스템 콜)
- System Call은 S/W Interruption이라 볼 수 있다.
- 즉, User Mode에서 Kernel Mode로 전환된다. (본 프로그램은 User Process에서 구동되는 APP임에 유의하자.)
- Control이 S/W Interrupt Handler가 있는 Procedure에게 넘어간다.
- 이 때, 하나의 User Process 마다 하나의 Kernel Thread가 Mapping된다.
- Kernel Thread는 Kernel 내에 구현되어 있는 Stack을 이용하여 작업을 수행한다.
- User Process에서 사용할 수 있는 Kernel Object를 OS에서는 File이라 하며,
이 Socket 또한 I/O가 가능한 하나의 File이다.
- Kernel은 현재 작동중인 Process가 접근하고 있는 File들에 대한 정보인 Open File Table을 통해 File들을 관리한다.
(Open File Table의 Index가 바로 Descriptor이다.)
* Open File Table의 Index(Descriptor)에는 Device File을 위해, 미리 예약되어 있는 값들이 있다.
- File Descriptor = 0 : \(\texttt{stdin}\) (키보드)
- File Descriptor = 1 : \(\texttt{stdout}\) (스크린)
- File Descriptor = 2 : \(\texttt{stderr}\) (에러)
UNIX \(\texttt{errno}\) Value
- \(\texttt{socket()}\) 함수와 같은,
UNIX 함수에서 오류가 발생하면 Global Variable : \(\texttt{errno}\)가 해당 오류를 가리키는 정수값으로 설정된다.
- 오류가 없을 때, errno의 값은 0으로 설정되어 있다.
- 오류에 해당하는 정숫값들은 \(\texttt{<sys/errno.h>}\) 헤더파일에 정의되어 있다.
\(\texttt{socket()} Function (\texttt{socket()}\) 함수)
#include <sys/socket.h>
int socket(int family, int type, int protocol);
- 사용할 소켓 정보를 작은 int형 값 형태의 Descriptor로 리턴해주는 함수이다.
- IP주소와 포트번호는 설정하지 않은 상태의 "빈 소켓"을 설정하는 함수이다.
- Network I/O를 위해 Process가 따를 프로토콜을 지정하는 함수이다.
- Kernel Memory내에 Socket File Object를 생성하고,
User Process가 용이하게 접근할 수 있도록 해당 파일의 번호(File Descriptor)를 리턴한다.
(즉, Socket API 역할을 하는 함수이다.)
Return:
Descriptor가 정상적이면, 해당 소켓의 Descriptor 번호(=\(\texttt{sockfd}\), Non-Negative Number)를 리턴한다.
에러가 발생되면, -1을 리턴한다.
Arguments:
1) \(\texttt{int family}\)
\(\texttt{int family}\) Values | Description |
\(\texttt{AF_INET}\) | IPv4 Protocol을 의미한다. |
\(\texttt{AF_INET6}\) | IPv6 Protocol을 의미한다. |
\(\texttt{AF_LOCAL}\) (in POSIX) \(\texttt{AF_UNIX}\) (in Historical UNIX) |
UNIX Domain Protocol을 의미한다. |
\(\texttt{AF_ROUTE}\) | Routing Socket임을 의미한다. |
\(\texttt{AF_KEY}\) | Key Socket임을 의미한다. |
- \(\texttt{family}\)는 domain이라 불리기도 한다.
- \(\texttt{family}\)와 \(\texttt{type}\)이 조합되어 해당 소켓의 프로토콜을 규정한다.
(\(\texttt{family}\)와 \(\texttt{type}\)의 모든 조합이 허용되지는 않는다.)
- Routing Socket은 Kernel의 Routing Table에 대한 인터페이스 역할을 하는 소켓이다.
- Key Socket은 암호화를 지원하는, 보안이 강화된 소켓이다. Key Socket은 Kernel의 Key Table에 대한 인터페이스 역할을 한다.
* \(\texttt{AF}\) (Address Family)
* \(\texttt{PF}\) (Protocol Family)
2) \(\texttt{int type}\)
\(\texttt{int type}\) Values | Description |
\(\texttt{SOCK_STREAM}\) | Stream Socket임을 의미한다. |
\(\texttt{SOCK_DGRAM}\) | Datagram Socket임을 의미한다. |
\(\texttt{SOCK_SEQPACKET}\) | Sequenced Packet Socket임을 의미한다. (SCTP) |
\(\texttt{SOCK_RAW}\) | Raw Socket*임을 의미한다. |
- Byte Stream Protocol인 TCP에서는 \(\texttt{SOCK_STREAM}\) 소켓만 지원된다.
* Raw Socket
- Transport Layer의 서비스를 Kernel에서 처리하지 않고, User-Level에서 처리할 때 사용하는 소켓이다.
- 주로, System Hacking에 이용된다.
3) \(\texttt{int protocol}\)
\(\texttt{int protocol}\) Values | Description |
\(\texttt{IPPROTO_TCP}\) | TCP Transport Protocol임을 의미한다. |
\(\texttt{IPPROTO_UDP}\) | UDP Transport Protocol임을 의미한다. |
\(\texttt{IPPROTO_SCTP}\) | SCTP Transport Protocol임을 의미한다. |
※ family - type Combination (시험 출제 예정)
\(\texttt{AF_INET}\) | \(\texttt{AF_INET6}\) | \(\texttt{AF_LOCAL}\) | \(\texttt{AF_ROUT}E}\) | \(\texttt{AF_KEY}\) | |
\(\texttt{SOCK_STREAM}\) | TCP/SCTP | TCP/SCTP | Allowed | NOT Allowed | NOT Allowed |
\(\texttt{SOCK_DGRAM}\) | UDP | UDP | Allowed | NOT Allowed | NOT Allowed |
\(\texttt{SOCK_SEQPACKET}\) | SCTP | SCTP | Allowed | NOT Allowed | NOT Allowed |
\(\texttt{SOCK_RAW}\) | IPv4 | IPv6 | NOT Allowed | Allowed | Allowed |
Allowed : 해당 프로토콜 조합이 가능하지만, 프로토콜의 이름이 규정되지 않은 경우이다.
NOT Allowed : 해당 프로토콜 조합이 허용되지 않는 경우이다.
\(\texttt{connect()}\) Function (\(\texttt{connect()}\) 함수)
#include <sys/socket.h>
int connect(int sockfd, const struct sockaddr *servaddr, socklen_t addrlen);
- TCP Client가 TCP Server와의 연결을 설정할 때 호출하는 함수이다.
- \(\texttt{sockfd}\) 소켓 파일을 이용하여, \(\texttt{servaddr}\)이 가리키는 서버의 IP주소와 포트 번호를 통해
연결을 설정하는 함수이다.
(즉, \(\texttt{sockfd}\) 소켓과 \(\texttt{servaddr}\) 서버와의 연결을 Kernel에 요청하는 함수이다.)
- \(\texttt{connect()}\) 함수가 호출되면, 클라이언트의 Kernel은 서버와의 TCP 3-Way Handshaking을 시작한다.
- 서버로부터 \(\texttt{connect()}\) 요청에 대한 응답을 받을 때 까지, Client Process는 Blocked된다.
- \(\texttt{connect()}\)가 연결에 실패하면, 반드시 해당 소켓을 닫고, \(\texttt{socket()}\)을 다시 호출해야 한다.
(해당 소켓을 재활용할 수 없다.)
※ TCP에서의 Connection에는 클라이언트의 소켓과 서버의 소켓이 1:1로 Mapping된다 정의되어 있다.
그러므로, \(\texttt{connect()}\)에 실패한 소켓은 해당 서버 소켓에 다시 연결될 수 없다.
Return:
연결이 정상적으로 설정되면 0을 리턴한다.
연결에 오류가 발생되면 -1을 리턴하고, 에러사항을 \(\texttt{errno}\)에 설정한다.
* Error Types
ETIMEOUT
- Client가 연속적으로 SYN을 Retransmission했음에도, 응답을 받지 못했을 때 errno에 설정되는 값이다.
ECONNRESET
- Server측에서는 이미 해당 Socket이 Close되어 연결이 불가능한 경우에 errno에 설정되는 값이다.
ECONNREFUSED (Hard Error)
- Client의 SYN에 대해, Server가 RST로 응답한 경우에 errno에 설정되는 값이다.
- 해당 포트에 Listening Server가 없는 상태에서 SYN이 수신되거나,
TCP가 연결을 Abort하길 원하거나,
존재하지 않는 Connection에 Segment가 수신되었을 때, Server는 RST를 송신한다.
EHOSTUNREACH 또는 ENETUNREACH (Soft Error)
- Client의 SYN에 대해 Intermediate Router로 부터의 ICMP "destination unreachable" 메시지(L3 레벨)를 수신하고,
Client가 지속적으로 SYN을 재전송했음에도 일정 시간 동안 응답을 받지 못했을 때 errno에 설정되는 값이다.
Arguments:
1) int sockfd
- socket() 함수로 부터 지정받은 Socket File Descriptor 번호이다.
2) const struct sockaddr *servaddr
- 소켓 주소 구조체를 가리키는 포인터이다.
- 소켓 주소 구조체에는 서버의 IP주소와 포트번호가 반드시 지정되어 있어야 한다.
3) socklen_t addrlen
- 해당 소켓 주소 구조체의 크기이다.
- 일반적으로, \(\texttt{addrlen}\)에는 \(\texttt{sizeof(servaddr)}\) 값을 넘겨준다.
bind() Function (bind() 함수)
#include <sys/socket.h>
int bind(int sockfd, const struct sockaddr *myaddr, socklen_t addrlen);
- \(\texttt{sockaddr}\) 구조체에 있는 소켓 정보들을 토대로 Binding(소켓에 주솟값을 부여)하는 함수이다.
- "Binding한다."라고 하면, 연결하고자 하는 소켓의 정보를 Server Process와 Kernel에게 알리는 것을 의미한다.
- 일반적으로, TCP Client는 bind()를 호출하지 않고, TCP Server가 bind()를 호출한다.
(클라이언트에서는 Kernel이 자동으로 bind()를 수행한다.)
- 클라이언트의 Kernel은 소켓이 사용될 동안에만 임시적으로 사용될 포트번호로 Ephemeral Port Number를 부여한다.
Return:
주소 할당에 성공하면 0을 리턴한다.
주소 할당에 오류가 발생되면 -1을 리턴하고 오류 사항을 \(\texttt{errno}\)를 설정한다.
* Error Types
EADDRINUSE
- "Address already in use"라는 의미이다.
Arguments:
1) int sockfd
- 주솟값을 할당할 소켓을 가리키는 File Descriptor이다.
2) const struct sockaddr *myaddr
- 프로토콜 주소를 저장하고 있는 구조체 포인터이다.
- 포트번호가 0으로 지정되면, bind()가 호출될 때 Kernel은 Ephemeral Port를 선택한다.
(Kernel이 부여한 Ephemeral Port Number을 얻기 위해서는 getsocketname()을 호출해야 한다.)
- IP주소가 Wildcard로 지정되면,
TCP에서 Kernel은 소켓이 연결될 때까지 IP 주소를 선택하지 않고,
UDP에서 는 User-Datagram이 소켓으로 보내질 때 까지 IP 주소를 선택하지 않는다.
- 일반적으로, 서버는 다수의 Interface를 가졌기 때문에, 다수의 IP address가 할당되어 있다.
클라이언트가 다수의 Interface 중 어느 곳으로 접속할지 모르기 때문에,
요청을 받게될 IP주소를 Wildcard 주솟값으로 설정하여, 어떠한 인터페이스로 들어오는 요청도 받아들여야 한다.
- POSIX에서 IPv4 Wildcard 주솟값은 INADDR_ANY 상수로 나타낸다.
Process Specifies | Result | |
IP address | port | |
Wildcard | 0 | Kernel chooses IP address and port (Client's Default) |
Wildcard | nonzero | Kernel chooses IP address, process specifies port |
Local IP address | 0 | Process specifies IP address, kernel chooses port |
Local IP address | nonzero | Process specifies IP address and port |
3) socklen_t addrlen
- *myaddr 구조체의 크기이다.
listen() Function (listen() 함수)
#include <sys/socket.h>
int listen(int sockfd, int backlog);
- listen() 함수는 오로지 TCP Server만 호출하는 System Call이다.
- listen() 함수는 연결되지 않은 소켓을 Passive Socket으로 바꾸고, Kernel이 이 소켓에 대한 Connection 요청을 받아들인다.
(즉, 소켓의 상태를 CLOSED에서 LISTEN으로 전환한다.)
- \(\texttt{listen()}\) 함수는 서버가 \(\texttt{listenfd}\) 소켓을 통해 Client의 접속 요청을 기다리도록 설정한다.
(즉, \(\texttt{listenfd}\) 소켓을 듣는 소켓으로 전환한다.)
Return:
소켓 변환에 성공하면 0을 리턴한다.
소켓 변환에 오류가 발생되면 -1을 리턴한다.
Arguments:
1) int sockfd
Listening Socket으로 만들 소켓의 File Descriptor이다.
2) int backlog
- sockfd 소켓에 대기시킬 수 있는 최대 Connection 개수이다.
- Kernel은 Listening Socket에 대해 두 가지 Queue*를 운용한다.
- backlog는 Incomplete Connection Queue의 크기와 Completed Connection Queue의 크기의 합의 최댓값이다.
(그러나, backlog로 지정한 값 만큼 정확히 Queue의 크기가 배정되는 것은 아니며, 시스템마다 다르다.)
※ backlog 값을 상수로 지정하면, 추후 값 수정 시, 서버 프로그램을 재컴파일해야 하기 때문에,
backlog 기본값을 정한 후, Command-Line Option이나 Environment Variable(환경 변수)을 허용하는 방법으로
backlog 값을 설정한다.
* Queues for Listening Socket
a) Incomplete Connection Queue
- 3-Way Handshaking이 "진행"중인 상대 Client로부터 수신한 SYN을 저장하는 큐이다.
- 큐에 삽입된 Item(SYN)은 3-Way Handshaking이 끝나거나, Time-Out이 발생하기 전까지 큐에 유지된다.
- 해당 서버 소켓은 SYN_RCVD 상태이다.
- 3-Way Handshaking이 완료된 Item(SYN)은 Completed Connection Queue의 맨 끝에 삽입된다.
- 클라이언트로부터의 SYN이 수신될 때, Queue가 Full 상태이면, TCP는 이 SYN을 무시하고, RST도 보내지 않는다.
(클라이언트는 SYN을 재전송할 것이다. 서버가 RST를 전송했다면, 그것은 "이 포트에 서버가 없음"을 의미하는 것이다.)
b) Completed Connection Queue
- 3-Way Handshaking이 "완료"된 상대 Client로부터 수신했던 SYN을 저장하는 큐이다.
- 해당 서버 소켓은 ESTABLISHED 상태이다.
- accept()가 호출되면, Completed Connection Queue의 맨 앞 원소가 프로세스에 할당된다.
accept() Function (accept() 함수)
#include <sys/socket.h>
int accept(int sockfd, struct sockaddr *addr, socklent_t *addrlen);
- \(\texttt{accept()}\) 함수는 \(\texttt{listen()}\) 함수를 통해, 접속 요청을 한 소켓과의 연결을 설정한다.
- TCP Server에 의해 호출된 accept() 함수는 Completed Connection Queue의 맨 앞에 있는 Item을 리턴한다.
- Completed Connection Queue가 비어있으면, 프로세스는 Sleep 상태에 놓인다.
- 즉, 서버 Process는 클라이언트 Process와의 TCP 3-Way Handshaking이 끝나기 전까지 \(\texttt{accept()}\) 함수에서 Block 상태에 놓인다.
- \(\texttt{accept()}\) 함수는 \(\texttt{SOCK_STREAM}\) 혹은 \(\texttt{SOCK_SEQPACKET}\) 과 같은, Connection-Based 소켓에 사용된다.
Return:
연결에 성공하면, Kernel이 자동으로 생성한 새로운 Connected Socket*의 Descriptor(Non-Negative Number)를 리턴한다.
(\(\texttt{accept()}\) 함수가 정상적으로 리턴(종료)되기 위해서는, TCP 3-Way Handshaking이 정상적으로 끝나야 한다.)
연결에 실패하면 -1을 리턴하고, 에러사항을 errno에 설정하며, addrlen을 수정하지 않는다.
* A TCP Connection : Three-Way Handshaking (URL)
* Connected Socket
- Server의 Kernel은 accept된 클라이언트마다 각각 하나의 Connected Socket을 구성하고,
단 하나의 Listening Socket만 유지한다.
- 서버가 어떤 클라이언트에 대한 서비스를 끝내면, Connected Socket은 닫힌다.
* ECONNABORTED (S/W caused connection abort)
- 서버가 accept()를 호출하기 전, 3-Way Handshaking을 마친 이후에
클라이언트가 RST를 서버에게 전송하여 연결을 이미 끊어버린 경우에
errno에 ECONNABORTED가 설정된다.
- 이 경우, 서버는 오류를 무시하고 accept()를 다시 호출한다.
- SVR4에서의 경우, ECONNABORTED 대신, EPROTO로 표현된다.
Arguments:
1) int sockfd
Listening Socket의 File Descriptor이다.
(Connected Socket과 다르다.)
2) struct sockaddr *addr
- Value-Result 타입의 매개변수이다.
- accept() 호출 후, addr에는 연결에 성공한 클라이언트의 프로세스 프로토콜 주소가 저장된다.
- 클라이언트의 프로세스 주소가 필요하지 않은 경우, addr와 addrlen은 Empty Pointer로 초기화된다.
3) socklen_t *addrlen
- Value-Result 타입의 매개변수이다.
- accept() 호출 전, addrlen이 참조하는 정수값을 cliaddr에 해당되는 소켓 주소 구조체의 크기로 설정한다.
- accept() 호출 후, addrlen에는 Kernel이 소켓 주소 구조체에 실질적으로 삽입한 데이터의 Byte수가 저장된다.
- 클라이언트의 프로세스 주소가 필요하지 않은 경우, cliaddr와 addrlen은 Empty Pointer로 초기화된다.
fork() Function (accept() 함수)
#include <unistd.h>
pid_t fork(void);
- UNIX에서 새로운 프로세스를 만드는 유일한 System Call이다.
- Parent Process에서 fork()를 호출하면, Child Process가 생성된다.
- Parent의 모든 Descriptor는 Child에게 공유된다.
Return: fork()는 한 번 Invoke(호출)되어, 두 부분에서 리턴된다. (Parent 측, Child 측)
fork()가 한 번 호출되면,
한 리턴값은 Parent 프로세스 측에서 Child 프로세스의 PID값이 리턴되고,
다른 리턴값은 Child 프로세스 측에서 0이 리턴된다.
(이 리턴값의 차이로 Parent인지, Child인지를 판단한다.)
fork() 수행 중 에러가 발생한 경우, -1을 리턴한다.
※ Parent는 Child의 Process ID를 fork()를 통해 얻을 수 있고, (fork()이외에 알 방법이 없다.)
Child는 Parent의 Process ID를 getppid()를 통해 얻을 수 있기 때문에,
fork()가 Child에게는 0을 리턴하는 것이다.
※ 모든 User Process의 PID 값은 1 이상이다.
PID가 0인 Process는 init 프로세스(systemd 프로세스)이다.
fork()의 용도
1) 프로세스가 fork()를 통해 자기 자신의 복사본을 만들어, 각기 다른 일을 처리하게 한다.
- 일반적으로, Network Server에서 이 같은 형태를 취한다.
2) fork()를 통해 Child를 생성하고, Parent나 Child 둘 중 하나가 exec를 호출하여 새 프로그램을 수행한다.
- 일반적으로, Shell Program에서 이 같은 형태를 취한다.
Principle of Fork
- Fork를 통해 Child를 생성한다.
- 이 때, Child는 Parent의 Code Segment(Source Code가 Compile된 Image), Data Segment, Stack Segment를 Inherit한다.
(동일한 Code Segment를 Parent와 Child가 동시에 가리키는 형식으로 상속된다. 복사되지 않는다.)
- 또한, Parent가 수행중 생성한 모든 File 또한, Child에 Inherit된다. (Socket, Directory 등)
- Fork 호출 이후에, Parent가 먼저 수행되는지, Child가 먼저 수행되는지에 대한 여부는 Scheduling Algorithm에 달렸다.
Example.
main() {
int childpid;
if ( (childpid = fork()) == -1 ) {
// Error
perror("can't fork");
exit(1);
} else if (childpid == 0) {
// Workspace for Child
printf("child: child pid : %d, parent pid : %d\n", getpid(), getppid());
exit(0);
} else {
// Workspace for Parent
print("parent: child pid : %d, parent pid : %d\n", childpid(), getpid());
exit(0);
}
}
exec Functions (exec 계열 함수)
* Execution의 약자이다.
#include <unistd.h>
extern char **environ;
int execl(const char *pathname, const char *arg, ... /* (char *) NULL */);
int execlp(const char *file, const char *arg, ... /* (char *) NULL */);
int execle(const char *pathname, const char *arg, ... /*, (char *) NULL, char *const envp[] */);
int execv(const char *pathname, char *const argv[]);
int execvp(const char *file, char *const argv[]);
int execvpe(const char *file, char *const argv[], char *const envp[]);
- exec는 현 프로세스를 Process ID를 바꾸지 않고, 새 프로그램 파일로 대체한다.
(새 프로그램은 일반적으로 main에서 시작된다.)
environ
- 새 프로그램으로 계승될 환경목록을 만드는 데 사용된다.
Return:
프로세스에 새 프로그램을 배정하는데 오류가 발생되면 -1을 리턴하고,
새 프로그램 배정에 성공하면 아무것도 리턴하지 않는다.
(기존에 수행하던 프로그램이 사라지므로, 아무것도 리턴하지 않는게 당연하다.)
※ exec는 오류 발생 시에만 Caller에게 돌아간다.
Arguments:
*arg, ..., (char *) NULL
- 유동적인 개수의 Arguments를 허용한다.
- Arguments의 끝임을 알리기 위해 NULL Pointer를 사용한다.
argv
- Arguments들을 담고있는 Pointer Array이다.
- argv 또한, 끝임을 알리는 NULL Pointer를 자체적으로 갖고 있다.
*file
- 새로 수행할 프로그램 파일의 이름이다.
*pathname
- 새로 수행할 프로그램 파일의 경로이다.
envp[]
- 명시적 환경 목록이다.
* exec의 분류 기준
1) 수행할 프로그램 파일을 파일명으로 규정하는가 경로명으로 규정하는가
2) 새 프로그램의 Arguments들을 하나씩 나열하는가, Pointer Array로 참조시키는가
3) 새 프로그램이 호출 프로레스 환경을 계승하는가, 새 환경이 배정되는가
- exec 계열 함수 중, execve만이 유일한 System Call이며,
나머지 5개의 함수는 모두 execve를 호출하는 Library이다.
close() Function (close() 함수)
#include <unistd.h>
int close(int sockfd);
- \(\texttt{sockfd}\) 소켓의 Reference Counter 값을 1만큼 감소시키고,
감소된 Reference Counter값이 0인 경우, TCP Connection Termination 절차를 시작한다.
(즉, 소켓을 닫는다.)
- 만일, 전송할 데이터가 Queue에 들어 있으면, 이를 마저 보낸 뒤에 소켓을 닫는다.
Return:
소켓을 정상적으로 닫으면 0을 리턴한다.
소켓을 닫는데에 오류가 발생되면 -1을 리턴하고 \(\texttt{errno}\)를 설정한다.
Arguments:
1) int sockfd
- 닫을 소켓의 File Descriptor이다.
※ close()와 shutdown()
1) close()
- 해당 소켓을 닫고, Reference Counter값을 1만큼 감소시킨다.
- Reference Counter값이 0이면, FIN Signal을 전송하여 TCP's Four-Packet Connection Termination Sequence를 시작한다.
2) shutdown()
- Reference Counter값에 관계없이, 바로 FIN Signal을 전송하여 TCP's Four-Packet Connection Termination Sequence를 시작한다.
- Idle한 소켓이 열린채로 유지되어 Descriptor가 고갈되는 것을 방지할 때 이용하기 좋다.
exit() Function (exit() 함수)
#include <stdlib.h>
void exit(int status);
- status의 LSB가 exit()를 호출한 프로세스의 Parent에게 전달된다.
- 즉, exit()에 전달된 Argument를 통해, Parent는 어떤 유형의 종료인지를 파악할 수 있다.
- 즉, 디버깅의 목적이 강한 시스템 콜이다.
ex) exit(0) : 정상적 종료, exit(1) : timeout, exit(2) : Input Error 등 프로그래머의 의도에 따라 구현하기 나름이다.
Return:
- exit()는 리턴되지 않는다.
(exit()은 호출한 프로세스의 Memory Segment들을 반납하므로 리턴될 자리가 없기 때문이다.)
Arguments:
1) int status
- 종료의 유형을 구분짓는 int형 값을 전달받을 변수이다.
Constants
- exit()에 의해 전달되는 상수들이다.
EXIT_SUCCESS
- 정상적인 Termination을 의미하는 상수이다.
EXIT_FAILURE
- 비정상적인 Termination을 의미하는 상수이다.
getsockname() Function (getsockname() 함수)
#include <sys/socket.h>
int getsockname(int sockfd, struct sockaddr *localaddr, socklen_t *addrlen);
- 소켓에 연계된 Local Protocol Address를 Value-Result 형식으로 localaddr에 리턴한다.
- bind()를 호출하지 않는 TCP Client에서 connect()가 끝난 후, getsockname()을 통해
Local IP Address와 Kernel이 부여한 Local Port Number를 알아낼 수 있다.
- Port Number 0으로 bind()를 호출한 경우, getsockname()을 통해 부여받은 Local Port Number를 알아낼 수 있다.
- Wild-Card IP Address로 bind()를 호출한 TCP Server에서 Client와의 accept()에 성공하고,
getsockname()을 통해 Connection에 부여된 Local IP Address를 알아낼 수 있다.
(이 때, sockfd값은 Connected Socket의 것이어야 하며, Listening Socket의 sockfd이어서는 안된다.)
Return:
주소를 성공적으로 얻어오면 0을 리턴한다.
주소를 얻는데 에러가 발생되면 -1을 리턴한다.
Arguments:
1) int sockfd
2) struct sockaddr *localaddr
3) socklen_t *addrlen
getpeername() Function (getpeername() 함수)
#include <sys/socket.h>
int getpeername(int sockfd, struct sockaddr *peeraddr, socklen_t *addrlen);
- 소켓에 연계된 Foreign Protocol Address를 Value-Result 형식으로 peeraddr에 리턴한다.
- accept()를 호출한 프로세스에 의해 서버가 exec를 호출할 때,
Server는 getpeername()을 호출하여 Client의 정보를 알아낼 수 있다.
(이 방법이 Client의 주소 정보를 알아낼 유일한 방법이다.)
Return:
주소를 성공적으로 얻어오면 0을 리턴한다.
주소를 얻는데 에러가 발생되면 -1을 리턴한다.
Arguments:
1) int sockfd
2) struct sockaddr *peeraddr
3) socklen_t *addrlen
Concurrent Server
- 동시에 다수의 Client를 처리하는 서버 모델이다.
(대부분의 TCP Server가 채택하는 모델이다.)
- 간단하게는, fork()를 통해 각각의 Client를 처리하는 Child 프로세스를 생성하여 구현하는 방법이 있다.
* Iterative Server
- 한 루프 주기 당, 하나의 Client를 처리하는 서버 모델이다.
(대부분의 UDP Server가 채택하는 모델이다.)
Typical Concurrent Server
1) Outline
- Child는 Parent의 Memory Segment 뿐만 아니라, Parent가 수행하면서 생성한 모든 File(Socket, Directory 등)까지 Inherit 한다.
- 즉 fork() 직후, Parent는 connfd를 필요로 하지 않고, Child는 listenfd를 필요로 하지 않는다.
2) Source Code (Outlined)
pid_t pid;
int listenfd, connfd;
listenfd = socket(...);
bind(listenfd, ...);
listen(listenfd, LISTENQ);
for ( ; ; ) {
connfd = accept(listenfd, ...);
// connfd : 서버와의 3-Way Handshaking에 성공한 클라이언트
// Complete Connection Queue에 있던 SYN을 보낸 클라이언트와의 소켓 중 하나가 connfd에 대응된다.
if( (pid = fork()) == 0 ) {
close(listenfd); // Child closes listening socket
// Child는 Listen Socket을 사용하지 않는다.
/* Services for clients */
close(connfd); // Done with this client (사실, 다음 구문이 exit()이므로, close는 필요없다.)
exit(0); // Child terminates
}
close(connfd); // Parent closes connected socket
// Parent는 Connected Socket을 사용하지 않는다.
}
- Parent는 Listening을 통해 클라이언트와의 Connection Socket을 생성하여 Child에게 넘기고,
Child는 해당 Connection Socket을 통해 실질적인 Service를 수행하는 구조이다.
3) Description
- 먼저 Parent가 listenfd 소켓을 통해 Client의 요청을 기다리고, connfd 소켓을 통해 Client와 연결된다.
- 이후 fork()를 통해 Child를 생성하고, Child는 Parent를 계승하기 때문에, connfd로 Client와 이미 연결되어 있는 상태이다.
(Child는 생성되는 즉시, Client로의 어떠한 작업을 수행한다.)
- Child가 Client를 상대로 작업을 하는 동안, Parent는 Client와의 Connection을 끊고, 다른 Client의 요청을 기다린다.
(추후, 작업을 마친 Child가 해당 Client와의 연결을 종결시킬 것이다. 즉, 그제서야 Reference Count*가 0이 될 것이다.)
* Descriptor Reference Count (Reference Counter)
- 모든 파일 혹은 소켓의 File Table Entry에 저장되어 있는 Counting 변수이다.
- 해당 파일이나 소켓을 참조하는 Descriptor의 개수를 카운팅하여 저장하는 변수이다.
- close()의 경우, Reference Counter 값을 1만큼 감소시킨다.
- Reference Count가 0이 되면, 해당 소켓은 De-Allocation된다.
TCP Client/Server
1) Client-Side
2) Server-Side
Reference: UNIX Network Programming Volume 1, 3E: The Sockets Networking API (W. Richard Stevens, Bill Fenner, Andrew M. Rudoff 저, Addison-Wesley, 2004)