<Socket Interface>
1. socket
(1) 클라이언트와 서버는 socket descriptor를 사용하기 위해 socket 함수를 사용
int socket(int domain, int type, int protocol);
ex)
int clientfd = socket(AF_INET, SOCK_STREAM, 0);
AF_INET : 32비트의 IPv4 주소를 사용
SOCK_STREAM : 소켓은 connection의 endpoint
- getaddrinfo를 사용하여 파라미터를 자동으로 생성하고, 코드는 protocol independent
- getaddrinfo로부터 IP 주소와 포트 넘버를 받아와서 소켓 생성
2. bind
(1) 서버는 bind 함수를 통해 커널에 서버의 소켓 주소(IP 주소, 포트 넘버)와 소켓의 descriptor를 결합한다고 요청
int bind(int sockfd, SA *addr, socklen_t addrlen);
-> binding success / fail 값을 return
- 프로세스는 descriptor인 sockfd에서 읽음으로써 endpoint가 addr인 connection에 도착한 바이트를 읽을 수 있음
- 비슷하게, sockfd에 쓴 것은 endpoint가 addr인 connection을 통해 전송
- getaddrinfo 함수를 사용하여 addr, addrlen 인자를 제공받음
3. listen
(1) 커널은 소켓 함수로부터의 descriptor가 connection의 클라이언트 end에 있는 active socket이라고 가정
(2) 서버는 listen 함수를 호출하여 커널에게 descriptor가 서버에 의해 사용될 거라고 알려줌
- 클라이언트로부터 오는 connection 요청을 들음
int listen(int sockfd, int backlog);
(3) active socket으로부터의 sockfd -> 클라이언트로부터의 connection 요청을 수락할 수 있는 listening socket으로 변경
(4) backlog : connection 요청의 수에 대한 힌트로, 이 요청은 커널이 요청을 거절하기 전에 queuing 시킴
** 이미 클라이언트와 connection을 해서 데이터를 주고받고 있는데 또 connection이 오면 이걸 받아서 queuing
-> 현재 connection이 끝나면 queuing하고 있던 connection을 하나씩 꺼내서 connection establish
4. accept
(1) 서버는 클라이언트로부터 accept 함수를 호출함으로써 connetion 요청을 기다림
int accept(int listenfd, SA *addr, int *addrlen);
(2) listenfd로 바인딩된 connection에 도착한 connection 요청을 기다림 -> addr에 클라이언트의 소켓 주소를 채우고, addrlen에 소켓 주소의 사이즈를 채움
(3) Unix I/O 루틴을 통해 클라이언트와 통신할 수 있는 connected descriptor를 리턴
5. connect
(1) 클라이언트는 connect 함수를 호출함으로써 서버와의 connection을 설립
int connect(int clientfd, SA *addr, socklen_t addrlen);
(2) 소켓 주소에서 서버와의 connection 설립을 시도
- 만약 connection을 수락하면, clientfd는 읽고 쓸 준비가 됨
- connection의 결과는 소켓 쌍이 특징
ex. x:y, addr.sin_addr:addr.sin_port
-> x - 클라이언트 주소
-> y - ephemeral 포트인데 클라이언트 호스트에서 클라이언트 프로세스를 고유하게 식별함
- getaddrinfo를 사용하여 addr과 addrlen 인자를 제공받음
(3) accept 과정

1. 서버는 listenfd descriptor를 listen하면서 connection 요청을 기다리면서 accept를 blocking

2. 클라이언트는 connect 함수를 호출하고 blocking하면서 서버에 connection 요청을 함

3. 서버는 accept함수로부터 connfd를 리턴하면서 read, write할 준비가 됨
클라이언트는 connect함수로부터 clientfd 리턴
connection은 clientfd와 connfd 사이에서 생성됨
현재 connection을 수락하여 데이터를 주고 받는 동안 새로운 connection 요청이 들어오면 커널의 TCP manager는 이 요청을 받아서 queuing
6. Connected vs. Listening Descriptor
(1) Listening Descriptor
- 클라이언트 connection 요청에 대한 end point
- 한번 생성되면 서버가 실행되는 동안 계속 존재
(2) Connected Descriptor
- 클라이언트와 서버 사이의 connection의 endpoint
- 새로운 descriptor는 서버가 클라이언트로부터 connection 요청을 수락할 때마다 생성됨
- 클라이언트를 수행하는 동안에만 존재
(3) 구별짓는 이유
- 많은 클라이언트 connection을 통해 동시에 통신할 수 있는 concurrent한 서버를 허용하기 위해
: 새로운 요청을 받을 때마다, 요청을 처리하기 위해 자식을 fork함

클라이언트에서 서버로 connection request를 보낼 때마다 서버에선 listenfd가 이를 보고 fork를 띄워 요청을 accept하여 connectd fd를 생성
connection이 되면서 client fd와 connected fd가 연결
7. Socket Helper
(1) open_clientfd : 서버와의 connection을 establish
int open_clientfd(char *hostname, char *port) {
int clientfd;
struct addrinfo hints, *listp, *p;
/* Get a list of potential server addresses */
memset(&hints, 0, sizeof(struct addrinfo));
hints.ai_socktype = SOCK_STREAM; /* Open a connection */
hints.ai_flags = AI_NUMERICSERV; /* …using numeric port arg. */
hints.ai_flags |= AI_ADDRCONFIG; /* Recommended for connections */
Getaddrinfo(hostname, port, &hints, &listp);
// 리스트 안에는 addrinfo가 들어있음
/* Walk the list for one that we can successfully connect to */
for (p = listp; p; p = p->ai_next) {
/* Create a socket descriptor */
if ((clientfd = socket(p->ai_family, p->ai_socktype, p->ai_protocol)) < 0)
continue; /* Socket failed, try the next */
/* Connect to the server */
if (connect(clientfd, p->ai_addr, p->ai_addrlen) != -1)
break; /* Success */
Close(clientfd); /* Connect failed, try another */
}
/* Clean up */
Freeaddrinfo(listp);
if (!p) /* All connects failed */
return -1;
else /* The last connect succeeded */
return clientfd;
}
for문으로 리스트 안을 도는데, socket 함수를 이용하여 소켓 descriptor를 생성하고 현재 가리키는 포인터를 통해 연결이 잘 됐는지 확인
연결이 잘 됐으면 반복문을 빠져나옴
리스트를 free하고 모든 연결이 잘 됐으면 clientfd를 리턴
(2) open_listenfd : listening descriptor를 생성하여 클라이언트로부터의 connect 요청을 accept
int open_listenfd(char *port)
{
struct addrinfo hints, *listp, *p;
int listenfd, optval=1;
/* Get a list of potential server addresses */
memset(&hints, 0, sizeof(struct addrinfo));
hints.ai_socktype = SOCK_STREAM; /* Accept connect. */
hints.ai_flags = AI_PASSIVE | AI_ADDRCONFIG; /* …on any IP addr */
hints.ai_flags |= AI_NUMERICSERV; /* …using port no. */
Getaddrinfo(NULL, port, &hints, &listp);
/* Walk the list for one that we can bind to */
for (p = listp; p; p = p->ai_next) {
/* Create a socket descriptor */
if ((listenfd = socket(p->ai_family, p->ai_socktype, p->ai_protocol)) < 0)
continue; /* Socket failed, try the next */
/* Eliminates "Address already in use" error from bind */
Setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, (const void *)&optval , sizeof(int));
/* Bind the descriptor to the address */
if (bind(listenfd, p->ai_addr, p->ai_addrlen) == 0)
break; /* Success */
Close(listenfd); /* Bind failed, try the next */
}
/* Clean up */
Freeaddrinfo(listp);
if (!p) /* No address worked */
return -1;
/* Make it a listening socket ready to accept conn. requests */
if (listen(listenfd, LISTENQ) < 0) {
Close(listenfd);
return -1;
}
return listenfd;
}
connect를 accept하여 for문으로 리스트 전체를 돎
socket 함수를 이용하여 socket descriptor를 생성하여 listenfd에 할당하고 socket descriptor를 생성
생성에 성공하면 Setsockopt를 이용하여 bind 함수로부터의 주소가 이미 사용중이라는 에러 메세지를 제거
주소와 descriptor의 바인딩을 성공했으면 반복문을 빠져나옴
리스트를 free해주고 connection 요청을 accept하기 위해 listening socket 생성
=> open_clientfd와 open_listenfd는 IP 버전에 독립적
8. Echo
(1) Echo client : Main routine
#include "csapp.h"
int main(int argc, char **argv)
{
int clientfd;
char *host, *port, buf[MAXLINE];
rio_t rio;
host = argv[1];
port = argv[2];
clientfd = Open_clientfd(host, port);
Rio_readinitb(&rio, clientfd);
while (Fgets(buf, MAXLINE, stdin) != NULL) {
Rio_writen(clientfd, buf, strlen(buf)); //버프는 서버에서 응답이 오기 전까지 블럭킹 (???)
Rio_readlineb(&rio, buf, MAXLINE);
Fputs(buf, stdout);
}
Close(clientfd);
exit(0);
}
(2) Iterative Echo Server : Main Routine
#include "csapp.h”
void echo(int connfd);
int main(int argc, char **argv) {
int listenfd, connfd;
socklen_t clientlen;
struct sockaddr_storage clientaddr; /* Enough room for any addr */
char client_hostname[MAXLINE], client_port[MAXLINE];
listenfd = Open_listenfd(argv[1]);
while (1) {
clientlen = sizeof(struct sockaddr_storage); /* Important! */
connfd = Accept(listenfd, (SA *)&clientaddr, &clientlen);
Getnameinfo((SA *) &clientaddr, clientlen, client_hostname, MAXLINE, client_port, MAXLINE, 0);
printf("Connected to (%s, %s)\n", client_hostname, client_port);
echo(connfd); // 클라이언트와 메세지 주고받음
Close(connfd);
}
exit(0);
}
(3) Echo Server : echo func
- 서버는 RIO를 사용하여 EOF를 받을 때까지 텍스트 라인을 읽고 echo함
: EOF는 클라이언트에 의해 close를 호출(clientfd)하면서 발생
void echo(int connfd)
{
size_t n;
char buf[MAXLINE];
rio_t rio;
Rio_readinitb(&rio, connfd); // read 초기화
while((n = Rio_readlineb(&rio, buf, MAXLINE)) != 0) { //EOF를 받기 전까지, 버퍼에 write
printf("server received %d bytes\n", (int)n);
Rio_writen(connfd, buf, n);
}
}
'SP' 카테고리의 다른 글
| [sp] Concurrent Programming (2) (0) | 2022.04.15 |
|---|---|
| [sp] Concurrent Programming(1) (0) | 2022.04.11 |
| [sp] Network Programming (2) (0) | 2022.04.06 |
| [sp] Network Programming (1) (0) | 2022.04.05 |
| [sp] i/o (2) (0) | 2022.04.05 |