소켓 인터페이스는 Unix I/O 함수와 함께 사용되어 네트워크 애플리케이션을 구축하는 함수들의 집합

서버 측 순서
🔁 이후에는 다시 accept()로 돌아가 다음 클라이언트를 기다림
클라이언트 측 순서
/* IP socket address structure */
struct sockaddr_in {
uint16_t sin_family; /* Protocol family (always AF_INET) */
uint16_t sin_port; /* Port number in network byte order */
struct in_addr sin_addr; /* IP address in network byte order */
unsigned char sin_zero[8]; /* Pad to sizeof(struct sockaddr) */
};
/* Generic socket address structure */
struct sockaddr {
uint16_t sa_family; /* Protocol family */
char sa_data[14]; /* Address data */
};
_in 접미사: "internet"의 줄임말 (input이 아님)#include <sys/types.h>
#include <sys/socket.h>
int socket(int domain, int type, int protocol);
// Returns: nonnegative descriptor if OK, -1 on error
사용 예시:
clientfd = socket(AF_INET, SOCK_STREAM, 0);
AF_INET (32비트 IP 주소)SOCK_STREAM (TCP/UDP 인지)0 (기본값)#include <sys/socket.h>
int connect(int clientfd, const struct sockaddr *addr, socklen_t addrlen);
// Returns: 0 if OK, -1 on error
특징:
(x:y, addr.sin_addr:addr.sin_port)#include <sys/socket.h>
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
// Returns: 0 if OK, -1 on error
역할:
#include <sys/socket.h>
int listen(int sockfd, int backlog);
// Returns: 0 if OK, -1 on error
특징:
backlog: 대기 큐 크기 (보통 1,024로 설정)#include <sys/socket.h>
int accept(int listenfd, struct sockaddr *addr, int *addrlen);
// Returns: nonnegative connected descriptor if OK, -1 on error
역할:
| 수신 대기 디스크립터 | 연결 디스크립터 |
|---|---|
| 클라이언트 연결 요청의 끝점 | 클라이언트-서버 연결의 끝점 |
| 서버 생존 기간 동안 존재 | 클라이언트 서비스 기간 동안만 존재 |
| 한 번 생성되어 계속 사용 | 각 연결마다 새로 생성 |
장점: 동시 다중 클라이언트 처리 가능 (각 클라이언트마다 별도의 연결 디스크립터)
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
int getaddrinfo(const char *host, const char *service,
const struct addrinfo *hints,
struct addrinfo **result);
// Returns: 0 if OK, nonzero error code on error
void freeaddrinfo(struct addrinfo *result);
const char *gai_strerror(int errcode);
특징:
struct addrinfo {
int ai_flags; /* Hints argument flags */
int ai_family; /* First arg to socket function */
int ai_socktype; /* Second arg to socket function */
int ai_protocol; /* Third arg to socket function */
char *ai_canonname; /* Canonical hostname */
size_t ai_addrlen; /* Size of ai_addr struct */
struct sockaddr *ai_addr; /* Ptr to socket address structure */
struct addrinfo *ai_next; /* Ptr to next item in linked list */
};
AF_INET: IPv4만AF_INET6: IPv6만AF_UNSPEC: 둘 다SOCK_STREAM: TCP 연결AI_ADDRCONFIG: 로컬 호스트 설정에 맞는 주소만 반환AI_CANONNAME: 정식 호스트명 반환AI_NUMERICSERV: 서비스를 포트 번호로 강제AI_PASSIVE: 서버용 (와일드카드 주소 사용)#include <sys/socket.h>
#include <netdb.h>
int getnameinfo(const struct sockaddr *sa, socklen_t salen,
char *host, size_t hostlen,
char *service, size_t servlen, int flags);
// Returns: 0 if OK, nonzero error code on error
특징:
#include "csapp.h"
int main(int argc, char **argv) {
struct addrinfo *p, *listp, hints;
char buf[MAXLINE];
int rc, flags;
/* Get a list of addrinfo records */
memset(&hints, 0, sizeof(struct addrinfo));
hints.ai_family = AF_INET; /* IPv4 only */
hints.ai_socktype = SOCK_STREAM; /* Connections only */
if ((rc = getaddrinfo(argv[1], NULL, &hints, &listp)) != 0) {
fprintf(stderr, "getaddrinfo error: %s\n", gai_strerror(rc));
exit(1);
}
/* Walk the list and display each IP address */
flags = NI_NUMERICHOST;
for (p = listp; p; p = p->ai_next) {
getnameinfo(p->ai_addr, p->ai_addrlen, buf, MAXLINE, NULL, 0, flags);
printf("%s\n", buf);
}
/* Clean up */
freeaddrinfo(listp);
exit(0);
}
#include "csapp.h"
int open_clientfd(char *hostname, char *port);
// Returns: descriptor if OK, -1 on error
구현:
int open_clientfd(char *hostname, char *port) {
int clientfd;
struct addrinfo hints, *listp, *p;
// 1. 서버 주소 정보를 얻기 위한 힌트 설정
memset(&hints, 0, sizeof(struct addrinfo));
hints.ai_socktype = SOCK_STREAM; // TCP 연결
hints.ai_flags = AI_NUMERICSERV; // 포트는 숫자로 처리
hints.ai_flags |= AI_ADDRCONFIG; // 환경에 맞는 주소 체계 사용
// 2. 호스트 이름과 포트를 기반으로 주소 정보 가져오기
Getaddrinfo(hostname, port, &hints, &listp);
// 3. 주소 리스트를 순회하며 연결 시도
for (p = listp; p; p = p->ai_next) {
// 소켓 생성
if ((clientfd = socket(p->ai_family, p->ai_socktype, p->ai_protocol)) < 0)
continue; // 소켓 생성 실패 시 다음 주소로
// 서버에 연결 시도
if (connect(clientfd, p->ai_addr, p->ai_addrlen) != -1)
break; // 연결 성공 시 루프 종료
// 연결 실패 시 소켓 닫기
Close(clientfd);
}
// 4. 주소 정보 메모리 해제
Freeaddrinfo(listp);
// 5. 연결 성공 여부 확인 후 결과 반환
if (!p) // 모든 연결 시도 실패
return -1;
else // 연결 성공
return clientfd;
}
int open_listenfd(char *port)
{
struct addrinfo hints, *listp, *p;
int listenfd, optval = 1;
// 1. 주소 정보를 위한 힌트 설정
memset(&hints, 0, sizeof(struct addrinfo));
hints.ai_socktype = SOCK_STREAM; // TCP 소켓
hints.ai_flags = AI_PASSIVE | AI_ADDRCONFIG; // 모든 IP에서 수신
hints.ai_flags |= AI_NUMERICSERV; // 포트 번호는 숫자
// 2. 포트에 대해 가능한 주소 목록 획득
Getaddrinfo(NULL, port, &hints, &listp);
// 3. 가능한 주소 중 하나에 바인딩 시도
for (p = listp; p; p = p->ai_next) {
// 소켓 생성
if ((listenfd = socket(p->ai_family, p->ai_socktype, p->ai_protocol)) < 0)
continue;
// 주소 재사용 옵션 설정 (bind error 방지)
Setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR,
(const void *)&optval, sizeof(int));
// 바인딩 성공하면 루프 종료
if (bind(listenfd, p->ai_addr, p->ai_addrlen) == 0)
break;
// 바인딩 실패한 경우 소켓 닫기
Close(listenfd);
}
// 4. 주소 정보 메모리 해제
Freeaddrinfo(listp);
if (!p) // 바인딩 성공한 주소가 없으면 실패
return -1;
// 5. 리스닝 상태로 전환
if (listen(listenfd, LISTENQ) < 0) {
Close(listenfd);
return -1;
}
return listenfd; // 준비된 리스닝 소켓 반환
}
AI_PASSIVE: 서버용 소켓 (와일드카드 주소 사용)AI_ADDRCONFIG: 로컬 호스트 설정에 맞는 주소만 반환AI_NUMERICSERV: 포트 번호 사용Setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR,
(const void *)&optval, sizeof(int));
특징:
AI_PASSIVE 플래그 사용 (와일드카드 주소)setsockopt로 즉시 재시작 가능하도록 설정#include "csapp.h"
int main(int argc, char **argv)
{
int clientfd;
char *host, *port, buf[MAXLINE];
rio_t rio;
if (argc != 3) {
fprintf(stderr, "usage: %s <host> <port>\n", argv[0]);
exit(0);
}
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);
}
clientfd = Open_clientfd(host, port);
Rio_readinitb(&rio, clientfd);
Open_clientfd(): 서버와 연결 수립Rio_readinitb(): 버퍼링된 읽기를 위한 초기화while (Fgets(buf, MAXLINE, stdin) != NULL) {
Rio_writen(clientfd, buf, strlen(buf)); // 서버로 전송
Rio_readlineb(&rio, buf, MAXLINE); // 서버로부터 수신
Fputs(buf, stdout); // 표준 출력에 출력
}
Fgets()가 NULL 반환 (EOF 만남)Close(clientfd);
#include "csapp.h"
void echo(int connfd);
int main(int argc, char **argv)
{
int listenfd, connfd;
socklen_t clientlen;
struct sockaddr_storage clientaddr; /* Enough space for any address */
char client_hostname[MAXLINE], client_port[MAXLINE];
if (argc != 2) {
fprintf(stderr, "usage: %s <port>\n", argv[0]);
exit(0);
}
listenfd = Open_listenfd(argv[1]);
while (1) {
clientlen = sizeof(struct sockaddr_storage);
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);
}
struct sockaddr_storage clientaddr;
struct sockaddr_in은 IPv4만 지원Getnameinfo((SA *) &clientaddr, clientlen, client_hostname, MAXLINE,
client_port, MAXLINE, 0);
printf("Connected to (%s, %s)\n", client_hostname, client_port);
while (1) {
connfd = Accept(listenfd, ...);
echo(connfd); // 한 번에 하나의 클라이언트만 처리
Close(connfd);
}
#include "csapp.h"
void echo(int connfd)
{
size_t n;
char buf[MAXLINE];
rio_t rio;
Rio_readinitb(&rio, connfd);
while((n = Rio_readlineb(&rio, buf, MAXLINE)) != 0) {
printf("server received %d bytes\n", (int)n);
Rio_writen(connfd, buf, n);
}
}
Rio_readinitb(&rio, connfd);
while((n = Rio_readlineb(&rio, buf, MAXLINE)) != 0) {
printf("server received %d bytes\n", (int)n);
Rio_writen(connfd, buf, n);
}
Rio_readlineb()가 0 반환 (EOF 감지)
서버 실행
클라이언트 실행(telnet)
tcpdump 패킷 확인
위 hello를 입력하면 서버에서 7 Byte를 받은 이유는
hello(문자열 5) + \r\n(캐리지 리턴 + 개행 문자 = 2) = 7 Byte 이기 때문이다.