기본적인 서버 프로그램을 만들어 보자.
서버가 클라이언트의 요청을 처리하는 과정은 이러하다.
socket()
bind()
listen()
accept()
read()
/write()
#include <sys/socket.h>
int socket(int domain, int type, int protocol);
소켓을 생성해주는 함수이다.
domain
- 주소 패밀리
AF_UNIX: 로컬에서 프로세스끼리 통신할 때
AF_INET: IPv4 주소체계를 이용
AF_INET6: IPv6 주소체계를 이용
type
- 소켓의 형식
SOCK_STREAM: TCP 이용
SOCK_DGRAM: UDP 이용
SOCK_RAW: 네트워크와 전송 계층의 헤더를 사용자 임의로 조작
protocol
- 프로토콜
IPPROTO_TCP: TCP 이용
IPPROTO_UDP: UDP 이요
0: type에서 지정한 값 이용
성공 시 0 실패 시 -1 반환
#include <sys/socket.h>
int bind(int sockfd, struct sockaddr *myaddr, socklen_t addrlen);
생성된 소켓과 서버의 정보를 묶어주는 함수이다.
클라이언트가 서버에서 연결을 할 때 IP주소와 port등의 '주소 정보'를 이용하여 연결을 요청하므로 이 주소 정보와 생성한 소켓을 묶어줄 필요가 있다.
sockfd
- 소켓의 fdmyaddr
- 서버의 주소정보addrlen
- myaddr의 길이성공 시 0 실패 시 -1 반환
#include <sys/socket.h>
int listen(int sockfd, int backlog);
소켓에 연결 요청이 들어오는지 기다리는 함수이다.
sockfd
- 소켓의 fdbacklog
- 연결 요청 대기열(queue)의 크기성공 시 0 실패 시 -1 반환
#include <sys/socket.h>
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
연결 요청을 수락하는 함수이다.
sockfd
- listen하고 있는 소켓의 fdaddr
- 클라이언트의 주소 정보를 담을 객체addrlen
- 클라이언트 주소 정보의 길이성공 시 0 실패 시 -1 반환
클라이언트의 입력을 그대로 echo해주는 서버이다.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#define BUF_SIZE 512
void error_handling(char* message)
{
fputs(message, stderr);
fputc('\n', stderr);
exit(1);
}
int main(int argc, char* argv[])
{
int serv_sock;
int clnt_sock;
// sockaddr은 주소정보를 담는 기본형태이다.
// 주소체계에 따라 sockaddr_in, sockaddr_un, sockaddr_in6 등을 sockaddr로 형변환해서 사용하면 편리하다.
struct sockaddr_in serv_addr;
struct sockaddr_in clnt_addr;
socklen_t clnt_addr_size;
// TCP연결, IPv4 도메인을 위한 소켓 생성
serv_sock = socket(AF_INET, SOCK_STREAM, 0);
if (serv_sock == -1)
error_handling("socket error");
//서버의 주소 정보 설정
memset(&serv_addr, 0, sizeof(serv_addr));
serv_addr.sin_family = AF_INET; // 주소패밀리
serv_addr.sin_addr.s_addr = htonl(INADDR_ANY); // 서버의 ip주소
serv_addr.sin_port = htons(atoi(argv[1])); // 서버 프로그램의 포트
//htonl, htos - 빅엔디안 타입으로 변환
// 소켓과 주소정보를 결합
if (bind(serv_sock, (struct sockaddr*)&serv_addr, sizeof(serv_addr)) == -1)
error_handling("bind error");
// 연결요청 대기
if (listen(serv_sock, 5) == -1)
error_handling("listen error");
// 연결 수락
clnt_addr_size = sizeof(clnt_addr);
clnt_sock = accept(serv_sock, (struct sockaddr*)&clnt_addr, &clnt_addr_size);
if (clnt_sock == -1)
error_handling("accept error");
while (1)
{
char* buf[BUF_SIZE];
// 클라이언트 데이터 읽기
if (read(clnt_sock, buf, BUF_SIZE) <= 0)
{
close(clnt_sock);
printf("client disconnected\n");
break;
}
// 클라이언트에게 데이터 전송
else
{
write(clnt_sock, buf, strlen((const char*)buf));
}
memset(buf, 0, BUF_SIZE);
}
close(serv_sock);
return 0;
}
컴파일 및 실행
> gcc echoserver.c -o echoserver
> ./echoserver 5150
netcat을 이용해 에코서버 접속
> nc localhost 5150
hello
hello
hi
hi