본문 바로가기

별걸다하는 IT/네트워크_소켓_통신

[소켓 프로그래밍 C언어] 기본적인 서버 프로그램 만들기 (리눅스, 유닉스 편) server 관련 함수 및 소스코드

안녕하세요~! 

오늘은 기본적인 TCP 서버 프로그램을 작성해볼게요.

소켓 프로그래밍으로 간단한 채팅 서버를 만들어볼 생각인데, 찬찬히 진행해보도록 합시다.

소켓 프로그래밍이란?

오늘날 모든 컴퓨터는 소통을 하죠! 

보통 내가 먼저 다른 노드(컴퓨터)에 요청을 하면 나는 고객(?)이니까 클라이언트가 됩니다.

그리고 반면에 다른 컴퓨터가 똑똑 두드리면, 신호를 받아서 데이터를 내려주는 역할을 서버라고 간략하게 정의해볼 수 있어요. 네트워크로 연결되어 있는 서로 다른 두 컴퓨터가 데이터를 주고받을 수 있도록 하는 것이 네트워크 프로그래밍, 즉 소켓 프로그래밍입니다.

소켓 socket?

소프트웨어적인 데이터 송수신 방법을 이미 운영체제에서 제공해주고 있는데 이게 '소켓(Socket)'입니다. 모든 운영체제에서 지원해줘요.

이는 물리적으로 연결된 네트워크상에서의 데이터 송수신에 사용할 수 있는 소프트웨어적인 장치를 의미합니다. 

그냥 데이터를 주고 받기 위해서는 소켓 디스크립터라는 파일시스템을 이용해야하는데, 그게 소켓이라 생각하면 돼요!

그리고 이미 운영체제에서 제공해주고 있는 함수로 우리는 원하는 통신 프로그램을 소스로 짜서 실현시킬 수 있다는거~

서버를 만들기 위한 절차

1. socket() 소켓 생성

일단 다른 노드(컴퓨터)들과 통신하려면, 통신 개찰구가 필요하잖아요. 소켓을 생성해줍니다.

 

2. bind()

그 다음 bind라는 걸 해야하는데요. 

내가 만드려는 서버의 ip주소와 포트를 소켓에 할당하는 역할을 수행해요.

 

3. listen()

그럼 이제 다른 컴퓨터에서 노크를 두드려도 응답해줄 수 있도록 대기상태로 만들기

 

4. accept()

만약 어떤 클라이언트로부터 연결 요청이 왔으면 수락해주는 함수

 

5. read() write()

이제 두 컴퓨터가 연결이 되었으니 필요한 데이터를 전송하고 받고~

 

6. close()

끝났으면 연결을 끊어줍시다.

서버 만드는데 필요한 함수

이제 해당 순서에 필요한 함수들을 살펴봅시다.

 

1. socket() 소켓 생성

소켓 생성하는 함수입니다. 

#include <sys/socket.h>
int socket(int domain, int type, int protocol);

인자값:

int domain:

어떤 영역에서 통신할 것인지에 대한 영역을 지정합니다 (protocol family지원)

올 수 있는 값 : AF_UNIX, AF_INET, AF_INET6 등

AF_UNIX는 프로세스끼리 통신할 때,

AF_INET은 IPv4, AF_INET6는 IPv6를 의미합니다.

우린 IPv4를 사용하니까 포스팅에선 AF_INET을 사용해줄거예요.

 

int type:

어떤 서비스 타입의 소켓을 생성할건지 적는건데요, 값 내용은 아래와 같습니다.

SOCK_STREAM(TCP), SOCK_DGRAM(UDP), SOCK_RAW(Raw 방식 TCP나 UDP를 거치지 않고 바로 IP계층 사용시)

우리는 TCP연결 지향형 통신을 생성하고자 하니 SOCK_STREAM을 적어주겠네요.

 

int protocol:

소켓에서 사용할 프로토콜.

IPPROTO_TCP: TCP방식

IPPROTO_UDP: UDP방식

0: type에서 미리 정해진 경우.

 

리턴값:

소켓을 가리키는 소켓 디스크립터를 반환합니다.

-1 소켓 생성 실패

0 이상의 값 : 소켓 디스크립터 

 

코드 예시:

//TCP연결지향형이고 ipv4 도메인을 위한 소켓을 생성
serv_sock=socket(PF_INET, SOCK_STREAM, 0); 
if(serv_sock == -1)
     printf("socket error\n");

 

2. bind()

#include <sys/socket.h>
int bind(int sockfd, struct sockaddr *myaddr, socklen_t addrlen);

그 다음 소켓이랑 서버의 정보를 묶어주는 함수입니다.

이걸 왜하냐, 다른 외부의 컴퓨터가 서버에 연결하려고 요청을 했어요, 물론 IP주소를 기반으로 찾았겠죠. 근데 통신을 위해선 소켓디스크립터 번호를 알아야하는데 IP주소를 안다고 얘를 알 수 있는게 아니잖아요. 그래서 두개를 묶어주는 작업을 하는겁니다.

 

인자값:

int sockfd:

fd가 파일디스크립터의 약자예요. 즉 1번 함수 리턴값으로 받은 소켓 디스크립터를 여기 넣어주면 됩니다.

 

struct sockaddr *myaddr:

서버의 IP주소를 넣어줘요.

 

scoklen_t addrlen:

주소 길이를 넣어줍니다.

 

리턴값:

성공시 0, 실패시 -1.

 

코드 예시:

//소켓과 서버 주소를 바인딩
if(bind(serv_sock, (struct sockaddr*) &serv_addr, sizeof(serv_addr))==-1)
  	printf("bind error");

 

3. listen()

이제 어떤 컴퓨터로부터 요청이 와도 수락할 수 있게 대기상태에 들어가는 함수입니다.

#include <sys/socket.h>
int listen(int sockfd, int backlog);

인자값:

int sockfd:

2번 bind함수의 첫 인자와 같습니다. 소켓 디스크립터를 여기 넣어줍시다.

 

int backlog:

연결 대기열의 크기를 지정합니다. listen이라는 게 결국 연결요청 소켓이 대기하는 연결 대기열을 생성하는 거거든요.

네트워크 상태와 서비스 종류에 따라서 달라집니다.

 

리턴값:

성공시 0, 실패시 -1.

 

4. accept()

#include <sys/socket.h>
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);

서버 소켓에다가 이제 클라이언트를 연결하는 함수입니다.

그래서 두 번째와 세 번째 주소 관련 인자에는 클라이언트 정보가 들어가게 돼요.

 

인자값:

int sockfd:

2번 bind함수의 첫 인자와 같습니다. 서버 소켓 디스크립터를 여기 넣어줍시다.

 

struct sockaddr *addr:

클라이언트 주소 정보를 담고 있는 구조체

 

socklen_t *addrlen:

2번째 인자 값의 길이

C언어 서버 프로그램

전체 흐름과 필요한 함수들을 모두 살펴봤으니 아주아주 기본적인 서버 프로그램을 작성해봅시다.

가장 베이직한 거니까, 서버 IP는 임의적으로 사용 가능한걸 할당하게 하고, port만 실행시 인자로 받을 거예요.

 

이런식으로 명령어를 치게 되면 3550포트에 서버 프로세스를 켜놓는거죠.

$server 3550

<server.c 소스코드> 

#include <stdio.h>
#include <stdlib.h> //atoi를 사용하려면 있어야함
#include <string.h> // memset 등
#include <unistd.h> //sockaddr_in, read, write 등
#include <arpa/inet.h>  //htnol, htons, INADDR_ANY, sockaddr_in 등
#include <sys/socket.h>
void error_handling(char * message);

int main(int argc, char* argv[])
{    
    int serv_sock;
    int clnt_sock;
   
    //sockaddr_in은 소켓 주소의 틀을 형셩해주는 구조체로 AF_INET일 경우 사용
    struct sockaddr_in serv_addr;
    struct sockaddr_in clnt_addr; //accept함수에서 사용됨.
    socklen_t clnt_addr_size;
   
    //TCP연결지향형이고 ipv4 도메인을 위한 소켓을 생성
    serv_sock=socket(PF_INET, SOCK_STREAM, 0); 
    if(serv_sock == -1)
         error_handling("socket error");
    
    //주소를 초기화한 후 IP주소와 포트 지정
    memset(&serv_addr, 0, sizeof(serv_addr)); 
    serv_addr.sin_family=AF_INET;                //타입: ipv4
    serv_addr.sin_addr.s_addr=htonl(INADDR_ANY); //ip주소
    serv_addr.sin_port=htons(atoi(argv[1]));     //port
    
    //소켓과 서버 주소를 바인딩
    if(bind(serv_sock, (struct sockaddr*) &serv_addr, sizeof(serv_addr))==-1)
    	error_handling("bind error");
    
    //연결 대기열 5개 생성 
    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");
        
    /*-----데이터 전송-----*/
    char msg[] = "Hello this is server!\n";
    write(clnt_sock, msg, sizeof(msg));
    
    //소켓들 닫기
    close(clnt_sock);
    close(serv_sock);
    
    return 0;
}
void error_handling(char *message)
{
    fputs(message,stderr);
    fputc('\n', stderr);
    exit(1);
}

위 IP주소 설정해주는 부분에서

INADDR_ANY라는게 있는데 '컴퓨터에 존재하는 랜카드 중 사용 가능한 랜카드의 IP주소를 사용하라'라는 의미입니다.

위 소스같은 경우에는 일단 하나의 클라이언트와 연결이 되면, Hello this is server라는 메세지를 클라이언트에 내려주고 통신을 종료해버려요. 정말 기본요소들만 갖고 있는셈이죠. 

 

▼혹시! 해당 소스코드 중 sockaddr 등, 주소체계를 저장하는 구조체에 대한 내용이 궁금하신 분은 아래 포스팅을 참조해주세요!

 

[네트워크/소켓 C언어 프로그래밍] 주소 체계 저장 방법 sockaddr, sockaddr_in, sockaddr_un 구조체 알아보기, 설명과 사용예제.

안녕하세요!! 저번시간에는 가장 기초적인 서버/클라이언트 프로그램을 작성해보았어요. 전체적인 흐름을 잡는데는 이해가 되었을 거 같으나, 내부적으로 코드 한줄 한줄이 궁금한 사람이 있을 것 같아서~~ 네트워..

jhnyang.tistory.com

컴파일

gcc server.c -o server //gcc컴파일러일 경우
cc server.c -o server //gcc가 아닐 경우 기본

다 작성 하였으면 위 명령어로 컴파일을 해줍시다. server.c를 컴파일해서 server라는 실행파일을 만들라는 명령어예요.

gcc가 없을 경우 설치

컴파일러가 없을 경우에는 sudo apt install gcc 명령어를 통해 설치해주면 됩니다.

컴파일 후 

그리고 컴파일 하면 .c 소스파일 외에 server 실행파일이 생깁니다.

서버 실행후 클라이언트 접속해보기

서버를 실행한 후에 telnet으로 접속 시도로 결과를 확인해볼게요.

저의 경우 리눅스 가상머신에다가 server를 포트 3550에 실행시켜줬습니다. 3550포트는 임의의 포트입니다. telnet으로 서버에 연결할 때 동일한 포트를 써주면 돼요.

서버 실행화면

그 다음 컴퓨터의 cmd창이나 파워셸에다가 telnet 연결을 시도했어요.

즉 리눅스 가상머신은 서버가! 내가 실행하고 있는 윈도우 데스크탑은 클라이언트가되는거죠!

서버 접속 시도

참고로 서버의 ip주소는 ifconfig라는 명령어로 확인해볼 수 있습니다.

hostname

또는 I옵션과 함께 hostname 명령어로 IP주소를 확인할 수 있어요. 

실행 결과,

'Hello this is server!'라는 응답을 정상적으로 수신한 후 통신이 종료되는 것을 확인할 수 있어요.

오늘은 간단한 서버 프로그램을 짜봤어요. ㅎㅎ 다음 포스팅에는 기본적인 클라이언트 프로그램을 작성해보도록 할게요 ㅎㅎ 지금은 클라이언트 프로그램이 없어서 telnet을 이용해 테스트 해봤지만, 다음 시간엔 가상머신 두 대를 켜놓고 서버와 클라이언트 간의 통신을 확인해보도록 합시다.

▼다음 포스팅 바로가기!

 

[소켓 프로그래밍 C언어] 기본적인 클라이언트 프로그램 만들기 (리눅스, 유닉스 편 client) 관련 함수 및 소스코드

안녕하세요! 저번 시간에는 기초적인 서버를 만들어서 제대로 동작하는지 테스트 하는 시간을 가졌었어요.ㅎㅎ 이제 서버가 있으니 오늘은 클라이언트를 만들어서 상호간 통신을 시켜보도록 합시다. 지난 포스팅이..

jhnyang.tistory.com

도움이 되셨다면 좋아요, 댓글, 광고보답은 어떤가요?! 정보 공유에 큰 활력이 됩니다. 감사합니다. 

  • 뚱이 2020.06.21 18:23

    #include <arpa/inet.h>의 해당파일 또는 디렉토리가 없다고 에러가 떠요

  • 기본 2020.09.20 11:08

    int domain 설명해 주신 곳에 IF_OOOO 을 쓰셨는데 실제 코드에 쓰는 AF_OOOO나 PF_OOOO랑 다른 건가요??
    아니면 그냥 언더바 앞에는 상관이 없는 건가요??

    • IT 양햄찌(jhnyang) 2020.09.22 00:49 신고

      IF_INET이 아니라 AF_INET입니다. 오타가 있었네요 ㅎㅎ 알려주셔서 감사합니다. AF_INET과 PF_INET의 경우에는 동일합니다. 초기에는 확장성을 생각해 이와 같이 정의되었으나, 하나의 주소체계가 여러 프로토콜을 지원하는 경우는 현재까지도 없어 결국 똑같아요. 다만, AF는 어드레스 패밀리의 약어고, PF는 프로토콜 패밀리의 약자로 문맥에 맞게 사용해주는게 좋습니다.

  • 2020.10.13 21:03

    비밀댓글입니다

  • 개구리 2021.02.21 17:17

    리눅스 전용 코드인가요? ㅠㅠ
    환경 : Microsoft Visual Studio Community 2019 버전 16.8.3 // Windows 10 Home 20H2

    심각도 코드 설명 프로젝트 줄
    오류(활성) E1696 파일 소스을(를) 열 수 없습니다. "arpa/inet.h" SocketChatting 5
    오류(활성) E1696 파일 소스을(를) 열 수 없습니다. "unistd.h" SocketChatting 4
    오류(활성) E1696 파일 소스을(를) 열 수 없습니다. "sys/socket.h" SocketChatting 6
    오류(활성) E0070 불완전한 형식은 사용할 수 없습니다. SocketChatting 15
    오류(활성) E0070 불완전한 형식은 사용할 수 없습니다. SocketChatting 16
    오류(활성) E0020 식별자 "socklen_t"이(가) 정의되어 있지 않습니다. SocketChatting 17
    오류(활성) E0020 식별자 "socket"이(가) 정의되어 있지 않습니다. SocketChatting 20
    오류(활성) E0020 식별자 "PF_INET"이(가) 정의되어 있지 않습니다. SocketChatting 20
    오류(활성) E0020 식별자 "SOCK_STREAM"이(가) 정의되어 있지 않습니다. SocketChatting 20
    오류(활성) E0167 const char * 형식의 인수가 "char *" 형식의 매개 변수와 호환되지 않습니다. SocketChatting 22
    오류(활성) E0070 불완전한 형식은 사용할 수 없습니다. SocketChatting 25
    오류(활성) E0070 불완전한 형식은 사용할 수 없습니다. SocketChatting 26
    오류(활성) E0020 식별자 "AF_INET"이(가) 정의되어 있지 않습니다. SocketChatting 26
    오류(활성) E0070 불완전한 형식은 사용할 수 없습니다. SocketChatting 27
    오류(활성) E0020 식별자 "htonl"이(가) 정의되어 있지 않습니다. SocketChatting 27
    오류(활성) E0020 식별자 "INADDR_ANY"이(가) 정의되어 있지 않습니다. SocketChatting 27
    오류(활성) E0070 불완전한 형식은 사용할 수 없습니다. SocketChatting 28
    오류(활성) E0020 식별자 "htons"이(가) 정의되어 있지 않습니다. SocketChatting 28
    오류(활성) E0020 식별자 "bind"이(가) 정의되어 있지 않습니다. SocketChatting 31
    오류(활성) E0070 불완전한 형식은 사용할 수 없습니다. SocketChatting 31
    오류(활성) E0167 const char * 형식의 인수가 "char *" 형식의 매개 변수와 호환되지 않습니다. SocketChatting 32
    오류(활성) E0020 식별자 "listen"이(가) 정의되어 있지 않습니다. SocketChatting 35
    오류(활성) E0167 const char * 형식의 인수가 "char *" 형식의 매개 변수와 호환되지 않습니다. SocketChatting 36
    오류(활성) E0070 불완전한 형식은 사용할 수 없습니다. SocketChatting 39
    오류(활성) E0020 식별자 "accept"이(가) 정의되어 있지 않습니다. SocketChatting 40
    오류(활성) E0167 const char * 형식의 인수가 "char *" 형식의 매개 변수와 호환되지 않습니다. SocketChatting 42
    오류(활성) E0020 식별자 "write"이(가) 정의되어 있지 않습니다. SocketChatting 46
    오류(활성) E0020 식별자 "close"이(가) 정의되어 있지 않습니다. SocketChatting 49
    오류 C1083 포함 파일을 열 수 없습니다. 'unistd.h': No such file or directory SocketChatting 4

    • IT 양햄찌(jhnyang) 2021.02.21 17:37 신고

      넵 해당 소스코드는 리눅스 운영체제에서 지원하는 라이브러리로 작성한 코드로, 윈도우 소켓 프로그래밍을 할 경우에는 winsocket과 같은 다른 라이브러리를 인클루드해주셔야 합니다 ㅠ_ㅠ 하지만 라이브러리명만 조금 차이가 있을뿐 윈도우나 리눅스나 유사합니다 ㅎㅎ