반응형

리눅스 C : TCP 소켓 프로그램

알게된 배경

 라즈베리 파이를 위한 소캣 프로그램을 만들다 보니 어쩔 수 없이 TCP와 같은 소켓 프로그램을 제작하게 되었다. 소켓 프로그램의 경우 대부분 표준화가 되어 있으며, 구현하는 절차가 거의 정해져 있다. 때문에 이해를 했다 하더라도 외우고 있는건 무리기 때문에 기록을 남긴다.


사전에 염두 해야 할 점

 C와 C++이 일반적으로 빠른 이유는 운영체제와 거의 직접적으로 맞닿아서 작동이 된다는 점이다. 심지어 운영체제가 없는 마이크로프로세서 분야에서도 C가 사용되고 있다. 즉, 하드웨어나 운영체제의 영향을 많이 받기 때문에 당연히 윈도우와 리눅스간 코드는 차이가 있다.

 하나 더 알아야 할 것은 리눅스, OS X, FreeBSD 등 윈도우를 제외하고는 대부분은 유닉스 기반이라 소켓 코드가 거의 같다(게다가 컴파일러도 거의 비슷하다).


소스코드

 일반적으로 에코서버 코드가 기본이지만, 여기서는 스트림을 전송하거나 수신하는 코드는 제외한다.


서버

#include <sys/socket.h>
#include <arpa/inet.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>

void errorHandl(char* msg)
{
    puts(msg);
    exit(1);
}

int main(int argc, char* argv[])
{
    int serv_sock;
    int clnt_sock;
    struct sockaddr_in serv_addr;
    struct sockaddr_in clnt_addr;
    socklen_t clnt_addr_sz;

    if(argc != 2)
    {
        printf("Usage : %s <port>\n", argv[0]);
        exit(1);
    }
    // 서버 TCP 소켓 생성
    serv_sock = socket(PF_INET, SOCK_STREAM, 0);
    if(serv_sock == -1)
         errorHandl("socket() error");

    memset(&serv_addr, 0, sizeof(serv_addr));
    serv_addr.sin_family = AF_INET;
    serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
    serv_addr.sin_port = htons(atoi(argv[1]));

    if(bind(serv_sock, (struct sockaddr*) &serv_addr, sizeof(serv_addr)) == -1)
        errorHandl("bind() error");

    // serv_sock 다음 인수는 최대 대기중인 클라이언트 수
    if(listen(serv_sock, 5) == -1)
        errorHandl("listen() error");

    clnt_addr_sz = sizeof(clnt_addr);
    clnt_sock = accept(serv_sock, (struct sockaddr*) &clnt_addr, &clnt_addr_sz);
    if(clnt_sock == -1)
        errorHandl("accept() error");

    // 이제 소켓을 통해서 전송할 작업을 작성하면 된다.
    // 생략

    // 클라이언트/서버 순서대로 스트림을 닫느다.
    close(clnt_sock);
    close(serv_sock);

    return 0;
}


클라이언트

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>

void errorHandl(char* msg)
{
    puts(msg);
    exit(1);
}

int main(int argc, char* argv[])
{
    int sock;
    struct sockaddr_in serv_addr;
    if(argc != 3)
    {
        printf("Usage : %s <IP> <port>\n", argv[0]);
        exit(1);
    }
    // TCP 소켓 설정
    sock = socket(PF_INET, SOCK_STREAM, 0);
    if(sock == -1)
         errorHandl("socket() error");

    memset(&serv_addr, 0, sizeof(serv_addr));
    serv_addr.sin_family = AF_INET;
    serv_addr.sin_addr.s_addr = inet_addr(argv[1]);
    serv_addr.sin_port = htons(atoi(argv[2]));

    if(connect(sock, (struct sockaddr*) &serv_addr, sizeof(serv_addr)) == -1)
        errorHandl("connect() error");

    // 여기서 전송이나 수신하는 내용을 작성함
    // 생략

    // 소켓 스트림 닫음
    close(sock);

    return 0;
}



반응형
반응형

리눅스/윈도우 C : 현재날짜와 시간 가져오기

알게된 배경

 서버애서 생성하는 파일명에 날짜와 시간요소를 넣어서 생성하기 위해서 시스템의 시간을 알아야 할 필요가 있었다.


리눅스 계열(유닉스)과 윈도우

 윈도우 엄밀하게 말하면, MVC(VS 20xx)에서 사용하는 컴파일러는 C++에 대해서는 지원을 잘하지만, C에대해서는 지원이 잘 안되고 있다. 따라서 본의 아니게 소스코드를 실행할 때 차이점이 발생한다. 물론 이를 해결하기 위해서는 새로 소스코드를 작성하거나 minGW, Cygwin 처럼 리눅스 함수를 윈도우에 맞게 포팅해주는 컴파일러를 사용하면된다(하지만 socket 계열은 지원이 안되는 걸로 기억한다).

 시간에 대해서 더 자세히 알기 위해서는 struct tm에 대해서 검색을 해보도록 하자.



리눅스 소스코드

 C++에서는 헤더파일을 ctime으로 호출이 가능하며, 동일하게 사용이 가능하다.

// clock.c
#include <stdio.h>
#include <time.h>

void printTime(void)
{
    time_t timer;
    struct tm* t;

    timer = time(NULL);
    t = localtime(&timer);

    printf("UNIX Time : %d [sec]\n\n", timer);
    printf("year : %d\n", t->tm_year + 1900);
    printf("month : %d\n", t->tm_mon + 1);
    printf("days : %d\n", t->tm_mday);
    printf("hour : %d\n", t->tm_hour);
    printf("min : %d\n", t->tm_min);
    printf("sec : %d\n", t->tm_sec);
}


윈도우 소스코드

 리눅스 소스코드와 차이점은 struct tm 포인터 객체 대신 객체 자체를 선언하면 되며, localtime_s()를 사용한다.

// clock.c
#include <stdio.h>
#include <time.h>

void printTime(void)
{
    time_t timer;
    struct tm t;

    timer = time(NULL);
    localtime_s($t, &timer);

    printf("WINDOWS Time : %d [sec]\n\n", timer);
    printf("year : %d\n", t->tm_year + 1900);
    printf("month : %d\n", t->tm_mon + 1);
    printf("days : %d\n", t->tm_mday);
    printf("hour : %d\n", t->tm_hour);
    printf("min : %d\n", t->tm_min);
    printf("sec : %d\n", t->tm_sec);
}


참고자료

블로그: 유닉스 현재시간 아는 방법 소개


반응형

'C' 카테고리의 다른 글

리눅스 C : TCP 소켓 프로그램  (0) 2017.05.24
Linux C : 파일이나 폴더 조회하기  (0) 2017.04.24
C : 구조체를 네임스페이스처럼 사용하기  (0) 2017.04.22
C : 문자열 숫자 변환  (0) 2017.04.21
C : extern 키워드  (0) 2016.11.28
반응형

Linux C : 파일이나 폴더 조회하기

알게된 배경

 라즈베리 파이의 라즈비안에서 프로그램으로 파일관리를 하기 위해서 사용하였다. 물론 소스코드 작성은 우분투에서 작성하여 먼저 테스트를 진행 했었다.


소스 코드

// Ls.h
#include <stdio.h>
#include <dirent.h>

typedef enum {false, true}bool;
bool isFile(const unsigned char  type);
bool isFolder(const unsigned char type);
void ls(const char* path, const bool isFile);
// Ls.c
#include "Ls.h"

bool isFile(const unsigned char type)
{
    return type == 0x08;
}
bool isFolder(const unsigned char type)
{
    return type == 0x04;
}
// 실제 파일이나 폴더리스트를 가져오는 함수
// 인자는 1:초기 경로, 2:true일경우 파일명, false일경우 폴더명
void  ls(const char* path, const bool isFile)
{
    DIR* dir;
    struct dirent* ent;
    if(NULL != (dir = opendir(path)) )
    {
        while(NULL != (ent = readdir(dir)) )
        {
            if(isFile? isFile(ent->d_type) : isFolder(ent->d_type) )
            {
                printf("%s\n", ent->d_name);
            }
        }
        closedir(dir);
    }
    else
    {
        perror("could not open directory");
    }
}


추가 참조 사항

 더 궁금한 점이 있다면, dirent 구조체에 대해서 조사하도록 하자. dirent.h 파일은 디렉토리나 같은 파일 시스템과 관련된 함수들이 있다.

 C++에서는 cdirent가 없기 때문에 C의 함수를 가져와서 사용해야 한다. 다른 대체 방법이 있는 걸 아시는 분은 댓글로 피드백을 준다면 환영한다.


참고자료

커뮤니티: 파일 및 폴더 명 받기

블로그: dirent 구조체에 대한 설명

반응형
반응형

C : 구조체를 네임스페이스처럼 사용하기

알게된 배경

리눅스 시스템에서 네트워크 프로그래밍을 공부할 때 참조한 책이 하필 C로 설명이 되어 있는 책이었다. 덕분에 C++과 달리 요즘에는 자료를 찾기 힘들기 때문에 필요한 정보를 처리하기 위해서 직접 함수를 만들어야 하는 경우가 많았다.

 함수가 많아지면 발생되는 문제는 역시 작명문제이다. 그러나 C는 네임스페이스나 클래스 기능이 제공되지 않기 때문에 구조체(struct)와 포인터의 조합으로 비슷하게 흉내내서 구현할 수 있다.

 그러나 타이핑 해야할 양이 급증하게 되므로 가능하면, C로 코딩해야 하는 상황을 최대한 벗어나길 바란다.


간략한 원리

구조체가 전역으로 선언이 되어 있어야 하며, 함수들은 포인터로 연결되어 있어야 한다. 그리고 이렇게 선언된 구조체는 외부 참조가 가능하도록 선언을 해야 네임스페이스 혹은 클래스처럼 사용할 수 있다.


구현 예시

예시로 문자열의 뒤쪽의 단어가 일치하는지 검사하는 함수를 구조체내의 함수로 구현한다. 확장자가 포함된 파일이름의 확장자 검사하는 용도로 사용될 수 있다.

헤더파일)

// ns_util.h
#pragma once

typedef enum{ false, true } bool;

struct ns_util_tools
{
    bool (*isHasBackword(const char* str, const char* backword);
};
// 아래의 struct가 존재하니 이 헤더파일을 인클루드 하면 사용할 수 있음
extern const struct ns_util_tools ns_util;

C파일)

// ns_util.c
#include <string.h>
#include "ns_util.h"
bool ns_util__isHasBackword(const char* str, const char* backword)
{
    int str_len, backword_len;
    int i;
    str_len = strlen(str);
    backword_len = strlen(str);
    for(i=0; i<backword_len; ++i)
    {
        if(str[str_len -i] != backword[backword_len -i])
            return false;
    }
    return true;
}
// 실제로 네임스페이스 처럼 사용될 구조체
const struct ns_util_tools ns_util = {
    .isHasBackword = ns_util__isHasBackword
};


다른파일에서 호출)

// main.c
#include <stdio.h>
#include "ns_util.h"

int main(int argc, char* argv[])
{
    char* str = "main.exe";
    if(ns_util.isHasBackword(str, ".exe"))
        printf("%s is exe file!!", str);
    else
        printf("%s is What is?", str);

    return 0;
}



 이로써 C로써도 충분히 객체지향으로 코딩이 가능하다는 것을 알 수 있다. 그렇지만, C로 프로젝트 진행하는 것은 가능하면 피하고 싶다.

반응형
반응형

C : 문자열 숫자 변환

작성계기

이전부터 정리를 하려 했으나 게으름으로 이제야 정리를 하게 되었다. 문자열을 숫자로 바꿔야 하는 경우는 굉장히 많기 때문에 유용하다. 한편으로는 간단하게 직접 똑같은 기능을 하는 함수를 간단히 만들 수 있다(본인은 atoi()를 늦게 알게 되어서 필요할때 직접 구현을 하였었다.


atoi()

헤더 파일: stdlib.h

선언 구조: int atoi(const char* str);

리턴: 정상적으로 작동될 경우 정수를 반환하고, 실패할 경우 아무것도 반환하지 않는다.

부가 설명: 함수 이름은 a는 문자열, i는 int의 첫 글자를 따서 문자열을 정수로 바꾼다는 의미로 지어진 이름이다. 따라서 atoi() 역이 되는 함수는 itoa()가 되어야 한다.


직접 구현해보기

atoi()와 같은 기능을 하는 함수를 간단히(?) 다음과 같이 구현할 수 있다.


// 한문자를 정수 숫자로 반환하는 함수
#include <string.h>

int char2int(const char ch)
{
    char index[10] = "0123456789";
    int t;
    for(t=0; t<10; ++t)
    {
        if(index[t] == ch)
            return t;
    }
    return -1;
}

// 10의 제곱수를 반환하는 함수
int pow10(const int n)
{
    int tmp;
    int num = 1;
    for(tmp = n; tmp>0; --tmp)
        num *= 10;
    return num;
}

// atoi()와 같은 역할을 하는 함수
int str2int(const char* str)
{
    int strLen = strlen(str);
    int op = 1;          // 부호
    int n = strLen;    // 자리수
    int num = 0;       // 결과값
    int t = 0;
    for(t=0; t<strlen; ++t)
    {
        int tmp = 0;
        if(0== t && '-' == str[0])
        {
            op = -1;
            --n;
            continue;
        }
        if(-1 == tmp)
            return;
        num += tmp*pow10(n);
    }
    return num*op;
}

조금 차이지만 여전히 C++보다 C가 더 불편하니 가능하면, 표준함수에서 제공하는 것을 소중히 여기도록 하자.

반응형
반응형

C : extern 키워드

작성계기

실제로 실수를 했었고, 찾아보니 의외로 다른사람도 잦은 실수를 하는 것 같아서 작성함.


키워드의 의미

다른 파일에 있는 변수를 현재 파일에서 사용하기 위해서 사용하는 키워드이다. 원리상 다른 파일 어딘가(메모리의 어딘가)에 해당변수가 있으니 그걸 참조하라는 의미가 된다.


예시)

a.c (변수가 정의 되어 있는 파일)

int a = 0;


b.c (a.c 파일에 있는 변수를 호출해서 사용하는 파일)

extern int a;
a = 20;


활용 및 응용

매번 새로운 파일에서 참조를 할 때마다 앞단에 extern 키워드로 사용가능하게 하는 것은 상당히 번거롭기 때문에 해당되는 헤더파일에 작성하여 여러번 작성을 하지 않게 할 수 있다.


예시)

a.c (변수가 정의 되어 있는 파일)

#include "a.h"
int a = 0;


a.h (변수가 정의 되어 있는 파일의 헤더파일)

#pragma once
extern int a;


b.c (a.c 파일에 있는 변수를 사용하는 파일)

#include "a.h"
a = 20;


당연한 말이지만, 해당 선언을 중복으로 선언하게 되면, 오류가 된다. 따라서 #pragma once 혹은 #ifndef를 활용하여 중복선언을 피하도록 하자.


실수하는 예

C의 전형적인 함정은 주로 배열에서 발생된다. 배열로 선언/정의 한것과 포인터로 선언/정의 된것은 다른 것으로 컴파일러가 구분한다. 의미상 배열선언의 키워드도 포인터로 같은 종류로 (사람은) 인식하지만, 컴파일러는 다른 것으로 인식을 한다.


예시)

a.c (정의된 원본 파일)

char a[] = "abc";


b1.c (오류/에러로 인식 "그런거 없다고 한다")

extern *a;
char b = a[0];


b2.c (정상 호출)

extern a[];
char b = a[0];



반응형
반응형

리눅스/윈도우 C : 헤더파일 중복 참조 방지

알게된 배경

 참여하는 프로젝트에서 소켓 네트워크 프로그램을 만들어야 하는데, 가장 먼저 잡은 책이 하필 C로 실습하는 책이었다. 문제는 C는 운영체제를 많이 타는 편이라서 리눅스와 윈도우를 같이 번갈아가면서 사용해야 했다.


들어가기에 앞서

 현재 C도 C++처럼 표준이 존재하며, 리눅스는 C90 ~ C99를 지원하고, 윈도우는 C80만 지원한다. 따라서 지속적으로 계속 차이가 날 수 밖에 없다. 그리고 대부분의 C 학습 서적은 대부분 윈도우 환경의 Microsoft Visual(MV) C++ 컴파일러를 기준으로 되어 있다.


매크로변수로 구분하는 방법(#ifndef)

예시)

#ifndef _HEADER_H_
#define _HEADER_H_

int func1(void);

#endif //_HEADER_H_

초기 C에서 사용되는 방법으로 매크로 변수 선언여부에 따라서 컴파일러는 정해진 구간의 코드를 컴파일하지 않는다. 매크로는 컴파일 하기전에 실행되는 것이기 때문에 이를 통해서 매번 다른 C파일에서 중복으로 헤더파일을 인클루드 해도 컴파일러는 여러번 참조하지 않는다. 헤더파일 중복 참조를 막기 위한 변수명은 위의 코드처럼 _(언더바)로 시작하고 대문자로 작성하며, 마지막에 _H_로 표기하도록 권장한다.


 다만, 기기 입장에서 컴파일을 할때 매크로에 의해서 컴파일전에 헤더파일의 내용을 건너뛰기는 하지만, 파일 전체가 아닌 구간을 넘기는 거라서 원리상 파일을 중복 참조하기는 한다. 그렇지만, 이게 워낙 빨라서 사람이 체감하기는 어렵다.


한번만 읽는 매크로를 실행하는 방법(#pragma once)

#pragma once

int func1(void);

MV C++ 에서 사용하기 시작한 방법이다. 매크로는 컴파일러가 컴파일전에 작업한다는 것을 다시 한번 상기하자. 매크로 명령어로 컴파일러에게 컴파일전에 이 파일은 한번만 읽으라고 하는 의미이다. 결국 앞의 매크로변수 유무에 따라서 컴파일하는 것과 같은 결과를 갖게 되는데, 원리상 차이가 있다.


 파일을 한번만 읽으라고 했기 때문에 아무래도 매크로 변수 존재 유무를 확인하고 구간을 건너뛰는 것보다는 매크로실행과정이 짧아진다. 물론 속도 차이는 체감이 거의 안된다.


중복참조 선택방법

 이러한 매크로(#pragma once)는 처음에 MV C++에서만 지원이 되었고 비표준이었다. 하지만, 요즘에는 표준화가 되었다. 그러나 아직 표준화 된지 오래 안되어서 인지 지원이 안되는 컴파일러가 있을 수 있기에 아직도 매크로 변수를 이용한 중복 참조를 피하는 방법을 리눅스 프로그래밍(OS X 에서도) 쪽에서는 아직도 많이 사용하고 있다.


 자신의 개발환경이 오래된 혹은 가벼운 컴파일러를 사용해야 할 경우에는 어쩔 수 없이 매크로변수를 이용해야 하고 그게 아니라면 #pragma once를 사용하자.


위키피디아에서 #pragma once 매크로가 지원되는 컴파일러 목록이 정리되어 있다

[참조링크]


반응형

+ Recent posts