[Nework][TCP/IP] Hand made Ping

Ping server & Ping clinet

이전에 공부한 socket, bind, listen, accept, connect 등의 함수를 이용해 간단한 echo 서버를 만들었다.

기본적인 골격은 아래와 같다.

socket_flow

서버에서 연결 준비를 거친 뒤 클라이언트에서 연결한다.
이후 클라이언트에서 write 함수를 통해 입력 받은 값을 서버에게 전달한다.
서버는 전달 받은 데이터를 read 함수를 통해 한번 읽어 저장한 뒤 write 함수로 클라이언트에게 전달한다.
서버로부터 데이터를 전달 받은 클라이언트는 read 함수를 통해 정보를 저장한 뒤 다시 데이터를 입력 받을 준비를 한다.

지속적으로 데이터를 읽고 쓰는 방식은 read 와 write 함수 부분에 while 문을 넣는 아주 간단한 방식을 사용했다.


Server Side Code

단계 (해당 함수 혹은 구조체 명)

소켓을 연다 (socket)
    > 서버에 주소를 할당한다(sturct sockaddr_in)
       > 서버를 연다 (bind)
           > 연결을 기다린다.(listen)
               > 연결이 오면 허가한다.(accept)
                   > 데이터를 주고 받는다.(read/write)
> 연결을 종료한다. (close)

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

enum{CMD, PORT};

int main(int argc, char * argv[])
{
    int sock, sock_clinet;
    int sock_client_len;
    struct sockaddr_in sock_addr, sock_clinet_addr;
    char buf[1024];

    if(argc != 2){
        printf("Usage %s <PORT>\n",argv[CMD]);
        exit(1);
    }

// 소켓을 {{
    sock=socket(AF_INET,SOCK_STREAM,0);
    if(sock < 0){
        perror("socket ");
        exit(1);
    }
// }} 여는 부분

// 서버의 주소를{{ 
    memset(&sock_addr,0x00,sizeof(sock_addr));
    sock_addr.sin_family = AF_INET;
    sock_addr.sin_addr.s_addr = htons(INADDR_ANY);    //INADDR_ANY는 실행 중인 컴퓨터에 IP 주소를 의미한다.
    sock_addr.sin_port=htons(atoi(argv[PORT]));
// }} 설정하는 부분

// 설정된 주소로 {{
    if(bind(sock,(struct sockaddr *)&sock_addr, sizeof(sock_addr)) < 0){
        perror("bind ");
        exit(1);
    }
// }} 서버를 여는 부분

// 연결을 {{
    if(listen(sock,5) < 0){
        perror("listen ");
        exit(1);
    }
// }} 기다리는 부분

// 클라이언트의 연결을 {{
    sock_client_len = sizeof(sock_clinet_addr);
    sock_clinet = accept(sock,(struct sockaddr *)&sock_clinet_addr, &sock_client_len);
    if(sock_clinet < 0){
        perror("accept ");
        exit(1);
    }
// 받는 부분

// 데이터를 {{
    while(1){
        if(read(sock_clinet,buf,sizeof(buf)) < 1){
            perror("read ");
            exit(1);
        }

        printf("====Ping data====\n%s\n====================\n", buf);

        if(write(sock_clinet,buf,sizeof(buf))<0){
            perror("write ");
            exit(1);
        }
    }
// }} 주고 받는 부분

    close(sock_clinet);
    close(sock);
    return 0;
}

Client Side Code

소켓을 연다 (socket)
    > 서버 주소를 삽입한다(sturct sockaddr_in)
       > 서버에게 요청한다. (connect)
           > 데이터를 주고 받는다.(read/write)
> 연결을 종료한다. (close)

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

enum{CMD, IP, PORT};

int main(int argc, char * argv[])
{
    int sock;
    struct sockaddr_in sock_addr;
    char buf[1024];

    if(argc != 3){
        printf("Useage: %s <IP> <PORT>\n", argv[CMD]);
        exit(1);
    }

// 소켓을 {{
    sock=socket(AF_INET,SOCK_STREAM,0);
    if(sock < 0){
        perror("socket ");
        exit(1);
    }
// }} 여는 부분

// 주소를 {{
    memset(&sock_addr,0x00,sizeof(sock_addr));
    sock_addr.sin_family = AF_INET;
    sock_addr.sin_addr.s_addr = inet_addr(argv[IP]);
    sock_addr.sin_port=htons(atoi(argv[PORT]));
// }} 설정하는 부분

// 설정된 주소로 {{
    if(connect(sock,(struct sockaddr *)&sock_addr, sizeof(sock_addr)) < 0){
        perror("connect ");
        exit(1);
    }
// }} 연결하는 부분

// 데이터를 {{
    while(1){
        printf("Input string :");
        scanf("%s",buf);

        if(write(sock,buf,sizeof(buf))<0){
            perror("write ");
            exit(1);
        }

        if(read(sock,buf,sizeof(buf))<0){
            perror("read ");
            exit(1);
        }
        printf("====Pong data====\n%s\n====================\n", buf);
    }
// }} 주고 받는 부분

    close(sock);
    return 0;
}

 

[Network Term] Header, Palyload, Trailer

Network 용어
> Payload (페이로드)

네트워크의 패킷의 구조는 아래와 같이 생겼다.

Packet에 구조

앞에 패킷의 대한 설명을 담고 있는 Header뒤에오는 데이터 Payload라고한다.

또한 뒤에 오는 Trailer(트레일러)는 패킷의 오류 등을 검출하기 위한 정보 등이 담겨있다.

[Network Basic] Socket 연결 과정

TCP SERVER SIDE

socket_server_flow
[출처] 윤성우의 열혈 강의

소캣을 생성한 후 bind함수까지 호출이 완료 되면 소캣은 주소가 할당 된다.

listen( )함수를 통해 연결이 들어올 때 까지 대기한다.

클라이언트 측에서 connect 시도가 오면 accept( )함수를 통해 받아들인다

이후 read( ) 와 write( )함수를 통해 데이터를 송수신 하고 close( )함수를 통해 연결을 종료한다.

TCP CLIENT SIDE

socket_client_flow
[출처] 윤성우의 열혈 강의

소캣을 생성한 후 connect( )함수를 이용해 서버에 접속을 요청한다.

read( ) / write( )함수를 통해 데이터를 송수신 하며, close( )함수를 통해 연결을 종료한다.

 

 

 

 

위 내용을 한장의 사진으로 정리하면 아래와 같은 이미지처럼 통신하게 된다.

socket_flow
[출처] 윤성우의 열혈 강의

서버와 클라이언트의 소캣을 생성한 뒤 
서버는 bind를 통해 서버를 열고 listen함수를 이용해
클라이언트가 connect함수를 이용해 접근을 요청해 오길 기다리며
클라이언트가 접근하면 서버는 accept을 통해 접근을 허용하고
read/write를 통해 데이터를 송수신한뒤
close를 통해 소켓 통신을 종료한다.


Echo 서버의 구현

 

Echo_server_flow
[출처] 윤성우의 열혈 강의

Echo 서버는 클라이언트가 connect함수를 통해 접속한 뒤 보낸 내용을 다시 클라이언트에게 보낸 뒤 접속을 끊는다.

이때 옆 이미지와 같은 과정을 거치게 된다.

다만 주의 할 점은 옆 Echo 서버 모델은 동시에 둘 이상의 클라이언트에게 서버스를 제공할 수 있는 모델이 아님을 주의해야 한다.

 

 

 

 

 

 

 

 

[Network Basic] sockaddr_in 구조체 for IPv4

struct sockaddr_in 구조체

sockaddr_in 구조체는 IPPORT 정보를 담는 구조체이다.


Define of sockaddr_in struct

struct sockaddr_in
{					/** 담길 정보 **/
	sa_family_t	sin_family;	//  주소체계 (AF_INET:IPv4)
	uint16_t	sin_port;	//  port번호 > uint16_t로도 정의되어있음
	struct in_addr	sin_addr;	// 32비트 IP주소
	char		sin_zero[8];	// 사용되지 않음
}

  • Define of in_addr struct

struct in_addr
{				/** 담길 정보 **/
	in_addr_t s_addr;	// 32비트 IPv4 인터넷 주소
				// > uint32_t로 정의되어있음
}

sockaddr_in 구조체 멤버 분석

멤버 정보
sin_family º 주소체계 정보 저장

주소체계(Address Family) 의 미
AF_INET IPv4 인터넷 프로토콜에 적용하는 주소체계
AF_INET6 IPv6 인터넷 프로토콜에 적용하는 주소체계
AF_LOCAL 로컬 통신을 위한 유닉스 프로토콜 주소체계 
sin_port º  16비트 PORT정보 저장
º 네트워크 바이트 순서로 저장 ( using htonl(), htons() etc…)
sin_addr º 32비트 IP주소 정보 저장
º 네트워크 바이트 순서로 저장 (using inet_addr() )
º sin_addr의 구조체 자료형 in_addr 사싱상 32비트 정수 자료형
sin_zero º 특별한 의미를 지니지 않은 맴버
º 0으로 채워야 하는데 memset()으로 0을로 채워짐 

Example sockaddr_in

IP와 PORT할당을 위해 bind() 함수의 인자로 사용됨

struct sock_addr

if(bind(sock, (struct sockaddr *) &sock_addr, sizeof(sock_addr)) == -1 )
{ … 

bind함수에 대한 포스트


what is difference sockaddr?

sockaddr의 구조체는 아래와 같다.

struct sockaddr
{
      sa_family_t sa_family;
      char sa_data[14];
}

위에 각각의 멤버는 주소체계를 저장하는 변수(sa_family) IPv4를 저장하는 변수(sa_data)가 존재하지만 IP통신에서 Port정보를 표현하기 힘들어 sockaddr_in을 주로 사용한다.


구현 예제

>> IP가 112.170.203.147이고 Port가 80인 곳으로 연결 혹은 데이터를 보내라. 

...
struct sockaddr_in addr;		
char *sock_ip = "112.170.203.147"	// IP주소 문자열 선언
char *sock_prot ="80"			// PORT번호 문자열 선언
memset(&addr, 0, sizeof(addr));		// 구조체 변수 addr의 모든 멤버 0으로 초기화

addr.sin_family=AF_INET;		// 주소체계 지정
addr.sin_addr.s_addr=inet_addr(sock_ip);// 문자열 기반 IP주소 초기화
addr.sin_port=htons(atoi(sock_port));	// 문자열 기반 PORT주소 초기화
...

+ INADDR_ANY

...
struct sockaddr_in addr;		
char *sock_prot ="80"				// PORT번호 문자열 선언
memset(&addr, 0, sizeof(addr));			// 구조체 변수 addr의 모든 멤버 0으로 초기화

addr.sin_family=AF_INET;			// 주소체계 지정
addr.sin_addr.s_addr=inet_addr(INADDR_ANY);	// 현재 실행중인 컴퓨터의 IP입력
addr.sin_port=htons(atoi(sock_port));		// 문자열 기반 PORT주소 초기화
...

서버 프로그램에서 주로 사용되는 INADDR_ANY는 현재 실행 중인 컴퓨터의 IP를 소켓에 부여 할 때 사용된다.

[Network Basic] ioctl()함수와 ifreq구조체

I/O 관련 하드웨어 제어 함수

header : #include <sys/ioctl.h>
int getsockopt(int sockfd, int request, …)
         > setsockopt()함수 보다 더 낮은 레벨에서 소켓 디스크립터를 제어 가능한 함수

인자 설명

  1. int sockfd : 소켓 디스크립터
  2. int request : 요청할 정보
  3. … : 요청한 정보에 따라 달라진다.

∗ 함수 실행에 문제 생길 경우 0보다 작은 값을 반환한다.


ioctl()를 이용해 Ethernet과 관련된 정보를 요청

Ethernet과 관련된 정보가 필요하다면 ifreq 구조체를 사용하면 된다.
ifreq의 형식은 아래와 같다.

struct ifreq
  {
# define IFHWADDRLEN	6
# define IFNAMSIZ	IF_NAMESIZE
    union
      {
	char ifrn_name[IFNAMSIZ];	/* Interface name, e.g. "en0".  */
      } ifr_ifrn;

    union
      {
	struct sockaddr ifru_addr;
	struct sockaddr ifru_dstaddr;
	struct sockaddr ifru_broadaddr;
	struct sockaddr ifru_netmask;
	struct sockaddr ifru_hwaddr;
	short int ifru_flags;
	int ifru_ivalue;
	int ifru_mtu;
	struct ifmap ifru_map;
	char ifru_slave[IFNAMSIZ];	/* Just fits the size */
	char ifru_newname[IFNAMSIZ];
	__caddr_t ifru_data;
      } ifr_ifru;
  };
# define ifr_name	ifr_ifrn.ifrn_name	/* interface name 	*/
# define ifr_hwaddr	ifr_ifru.ifru_hwaddr	/* MAC address 		*/
# define ifr_addr	ifr_ifru.ifru_addr	/* address		*/
# define ifr_dstaddr	ifr_ifru.ifru_dstaddr	/* other end of p-p lnk	*/
# define ifr_broadaddr	ifr_ifru.ifru_broadaddr	/* broadcast address	*/
# define ifr_netmask	ifr_ifru.ifru_netmask	/* interface net mask	*/
# define ifr_flags	ifr_ifru.ifru_flags	/* flags		*/
# define ifr_metric	ifr_ifru.ifru_ivalue	/* metric		*/
# define ifr_mtu	ifr_ifru.ifru_mtu	/* mtu			*/
# define ifr_map	ifr_ifru.ifru_map	/* device map		*/
# define ifr_slave	ifr_ifru.ifru_slave	/* slave device		*/
# define ifr_data	ifr_ifru.ifru_data	/* for use by interface	*/
# define ifr_ifindex	ifr_ifru.ifru_ivalue    /* interface index      */
# define ifr_bandwidth	ifr_ifru.ifru_ivalue	/* link bandwidth	*/
# define ifr_qlen	ifr_ifru.ifru_ivalue	/* queue length		*/
# define ifr_newname	ifr_ifru.ifru_newname	/* New name		*/
# define _IOT_ifreq	_IOT(_IOTS(char),IFNAMSIZ,_IOTS(char),16,0,0)
# define _IOT_ifreq_short _IOT(_IOTS(char),IFNAMSIZ,_IOTS(short),1,0,0)
# define _IOT_ifreq_int	_IOT(_IOTS(char),IFNAMSIZ,_IOTS(int),1,0,0)

Ethernet 카드의 MAC 주소를 가져오는 예제

#include <stdio.h>
#include <string.h>
#include <sys/ioctl.h>
#include <net/if.h>

enum { ARGV_CMD, ARGV_INTERFACE };

int main(int argc, char * argv[])
{
    int sock;
    struct ifreq ifr;
    unsigned char *mac = NULL;

    if(argc != 2){
        printf("%s [Interface name]\n", argv[ARGV_CMD]);
        return 1;
    }

    memset(&ifr, 0x00, sizeof(ifr));
    strcpy(ifr.ifr_name, argv[ARGV_INTERFACE]);

    int fd=socket(AF_UNIX, SOCK_DGRAM, 0);

    if((sock=socket(AF_UNIX, SOCK_DGRAM, 0))<0){
        perror("socket ");
        return 1;
    }

    if(ioctl(fd,SIOCGIFHWADDR,&ifr)<0){
        perror("ioctl ");
        return 1;
    }

    mac = ifr.ifr_hwaddr.sa_data;

    printf("%s: %02x:%02x:%02x:%02x:%02x:%02x\n", ifr.ifr_name, mac[0],mac[1],mac[2],mac[3],mac[4],mac[5]);

    close(sock);
    return 0;
}

출력

[email protected]:~$ ./Ex_13-09 eth0
eth0: 08:00:27:40:0c:3b

코드 리뷰

19 ~ 20 행 : ifreq 구조체 변수 'ifr'을 초기화 하고 ifr의 멤버인 'ifr.ifr_name'에 인터페이스 이름을 작성한다.
29 ~ 32 행 : ioctl()함수를 이용하여 하드웨어 주소를 얻고 있다.

[Network Basic] Socket option function

소켓 옵션 관련 함수

이번 포스트에선 소켓의 옵션을 변경하거나 현재 소켓의 설정 정보를 가져오는 방법에 대해서 알아보자.

header : #include <sys/types.h>
              #include <sys/socket.h>
int getsockopt(int sockfd, int level, int optname, void *optval, socklen_t *potlen)
         > 소켓의 정보를 가져오는 함수

인자 설명

  1. int sockfd : 소켓의 디스크립터
  2. int level : 검사할 프로토콜 레벨
  3. int optname : 옵션 정보를 가져 올 옵션의 이름
  4. void *optval : 옵션 값이 저장될 버퍼를 가르키는 주소
  5. socklen_t *optlen : 입력된 버퍼의 길이 전달

∗ 많이 사용되는 프로토콜 레벨과 옵션의 이름

프로토콜 레벨 옵션 이름 의미
SOL_SOCKET SO_REUSEADDR 이미 사용되고 있는 주소에 소켓을 연결 할 수 있도록 설정
SOL_SOCKET SO_KEEPALIVE 소켓 연결에 연결을 유지하는 패킷을 전송하도록 설정
SOL_SOCKET SO_SNDBUF 송신 버퍼의 크기
SOL_SOCKET SO_RCVBUF 수신 버퍼의 크기
SOL_SOCKET SO_TYPE 소켓의 형태

getsockopt()를 이용한 송수신 버퍼의 크기를 가져오는 소스 코드

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

int main(void)
{
    int sock;
    unsigned int sendsize = 0;
    unsigned int recvsize = 0;
    unsigned int optlen;

    if ((sock = socket(AF_INET,SOCK_STREAM,0)) < 0){
            perror("socket ");
            return 1;
    }

    optlen = sizeof(sendsize);
    getsockopt(sock, SOL_SOCKET,SO_SNDBUF,(char*)&sendsize, &optlen);

    optlen = sizeof(recvsize);
    getsockopt(sock, SOL_SOCKET,SO_RCVBUF,(char*)&recvsize, &optlen);

    printf("sendsize: %u\n",sendsize);
    printf("recive: %u\n",recvsize);

    close(sock);

    return 0;
}

출력

sendsize: 16384
recive: 87380
Press <RETURN> to close this window...

∗ 송수신 버퍼의 크기를 가져오기 위해 옵션 이름은 SO_SENDBUF, SO_RCVBUF를 사용했으며,
      프로토콜 레벨 명은 SOL_SOCKET을 사용하였다.


header : #include <sys/types.h>
              #include <sys/socket.h>
int setsockopt(int sockfd, int level, int optname, const void *optval, socklen_t *potlen)
         > 소켓의 설정을 변경하는 함수

인자 설명

  1. int sockfd : 소켓의 디스크립터
  2. int level : 검사할 프로토콜 레벨
  3. int optname : 옵션 정보를 가져 올 옵션의 이름
  4. const void *optval : 변경할 옵션이 저장된 위치의 주소 값
  5. socklen_t *optlen : 변경할 옵션의 길이

setsockopt()의 사용 소스 코드

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

int main(void)
{
    int sock;
    unsigned int sendsize = 0;
    unsigned int optlen;

    if((sock=socket(AF_INET, SOCK_STREAM, 0)) < 0){
        perror("socket ");
        return 1;
    }

    optlen = sizeof(sendsize);
    getsockopt(sock, SOL_SOCKET, SO_SNDBUF, (char *)&sendsize, &optlen);
    printf("sendsize: %u\n",sendsize);

    sendsize = 100;
    optlen = sizeof(sendsize);
    setsockopt(sock, SOL_SOCKET, SO_SNDBUF, (char *)&sendsize, optlen);

    getsockopt(sock, SOL_SOCKET, SO_SNDBUF, (char *)&sendsize, &optlen);
    printf("sendsize: %u\n",sendsize);

    close(sock);

    return 0;
}

출력

sendsize: 16384
sendsize: 4608
Press <RETURN> to close this window...

setsockopt()함수를 통해 송신 버퍼의 크기를 변경하고,
변경 전과 후의 getsockopt()함수를 이용해 송신 버퍼의 크기를 출력하고 있다.

[Network Basic] Byte Order Function

바이트 순서 관련 함수

네트워크 프로그래밍을 위해 추가로 신경 써야 하는 것이 바로 데이터의 바이트 순서이다.
 바이트 순서는 리틀 엔디언과 빅 엔디언의 두 가지가 있다. Intel 의 경우 리틀 엔디언을 사용하고 모토로라 등은 빅 엔디언을 사용하는데 이때 각각의 엔디언에 맞게 전송해주어야 한다.

종류 0x12345678의 표현
리틀 엔디언(Little Endian) 78 56 34 12
빅 엔디언(Big Endian) 12 34 56 78

대부분의 호스트 시스템은 리틀 엔디언을 사용하고 대부분의 네트워크 장비는 빅 엔디언 방식을 사용하기 때문에 네트워크 프로그래밍을 할 때에는 이를 맞춰 변환 해주어야 한다.

이에 C 언어는 효율적으로 바이트 순서를 변환할 수 있다록 지원한다.
이번 포스트에서는 그 함수들을 알아보자.

header : #include <arpa/inet.h>
uint32_t htonl(uint32_t hostlong)
         > 4바이트 값의 호스트 바이트 순서를 네트워크 바이트 순서로 변환한다.

uint32_t htons(uint32_t hostshort)
         > 2바이트 값의 호스트 바이트 순서를 네트워크 바이트 순서로 변환한다.

uint32_t ntohl(uint32_t netlong)
         > 4바이트 값의 네트워크 바이트 순서를 호스트 바이트 순서로 변환한다.

uint32_t ntohs(uint32_t netshort)
         > 2바이트 값의 네트워크 바이트 순서를 호스트 바이트 순서로 변환한다.


네트워크 바이트 순서와 호스트 바이트 순서 비교 예제

#include <stdio.h>
#include <arpa/inet.h>

void print2hex(const char *msg, void *p, size_t len);

int main(void)
{
    int a = 0x12345678;
    int b = htonl(0x12345678);
    int c = htonl(b);

    print2hex("a", &a, sizeof(a));
    print2hex("b", &b, sizeof(b));
    print2hex("c", &c, sizeof(c));
}

void print2hex(const char *msg, void *p, size_t len){
    size_t i;

    printf("%s :\n",msg);
    for(i=0;i<len;i++){
        printf("[%p]%x ",((char*)p+i), *((char*)p+i));
    }
    printf("\n");
}

출력

a :
[0x7ffcaa5c21e4]78 [0x7ffcaa5c21e5]56 [0x7ffcaa5c21e6]34 [0x7ffcaa5c21e7]12 
b :
[0x7ffcaa5c21e8]12 [0x7ffcaa5c21e9]34 [0x7ffcaa5c21ea]56 [0x7ffcaa5c21eb]78 
c :
[0x7ffcaa5c21ec]78 [0x7ffcaa5c21ed]56 [0x7ffcaa5c21ee]34 [0x7ffcaa5c21ef]12

위 출력에서 a는 호스트에서 저장될 때 리틀 엔디언으로 저장되는 것을 보여준다.
b와 같은 경우는 htonl()함수를 동해 리틀 엔디언을 빅 엔디언으로 변환 한 것을 보여준다.
c와 같은 경우는 htonl()함수를 다시 사용해 빅 엔디언을 리틀 엔디언으로 변환 한 것 이다.

위와 같이 htonl()과 ntohl()을 크게 구분하지 않아도 되지만,
프로그래밍 함에 있어서 버그를 발생 시키지 않도록 구분해서 사용하는 것이 좋다.


네트워크 주소와 관련된 함수

header : #include <sys/socket.h>
              #include <netinet/in.h>
              #include <arpa/inet.h>

in_addr_t inet_addr(const char *cp)
         > 일반적으로 표기하는 IP 주소 형태(0.0.0.0)를 4 Byte의 네트워크 바이트 순서로 변환하는 함수

char *inet_ntoa(strcut in_addr in)
         > 4 Byte의 네트워크 바이트 순서를 일반적으로 표기하는 IP 주소 형태(0.0.0.0)로 변환하는 함수


inet_addr 및 inet_ntoa 사용 예제

#include <stdio.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

void print2hex(void *p, size_t len);

int main(void)
{
    struct in_addr addr;

    addr.s_addr = inet_addr("127.0.0.1");

    printf("inet_addr : %08x\n", addr.s_addr);
    print2hex(&addr.s_addr, sizeof(addr.s_addr));
    printf("inet_ntoa : %s\n", inet_ntoa(addr));

    return 0;
}


void print2hex(void *p, size_t len){
    size_t i;

    for(i=0;i<len;i++){
        printf("[%p]%x ",((char*)p+i),*((char *)p+i));
    }
    printf("\n");
}

출력

inet_addr : 0100007f
[0x7ffe0f730c30]7f [0x7ffe0f730c31]0 [0x7ffe0f730c32]0 [0x7ffe0f730c33]1 
inet_ntoa : 127.0.0.1
Press <RETURN> to close this window...

 

[Network Baisc] Socket Function

Socket이란?

네트워크 프로그램을 개발할 수 있도록 운영체제에서 제공하는 인터페이스라고 표현할 수 있다.
즉, 네트워크를 통해 데이터 Pecket을 주고 받는 개체인 통신 종점이다.


스트림(Stream) 이란?

  • Stream Sockets − Delivery in a networked environment is guaranteed. If you send through the stream socket three items “A, B, C”, they will arrive in the same order − “A, B, C”. These sockets use TCP (Transmission Control Protocol) for data transmission. If delivery is impossible, the sender receives an error indicator. Data records do not have any boundaries.
    >> 서로 연결 됨을 확인 후 데이터를 전송하는 TCP를 의미한다.

  • Datagram Sockets − Delivery in a networked environment is not guaranteed. They’re connectionless because you don’t need to have an open connection as in Stream Sockets − you build a packet with the destination information and send it out. They use UDP (User Datagram Protocol).
    >> 연결 확인 없이 무작정 데이터를 전송하는 UDP를 의미한다.
  • Raw Sockets − These provide users access to the underlying communication protocols, which support socket abstractions. These sockets are normally datagram oriented, though their exact characteristics are dependent on the interface provided by the protocol. Raw sockets are not intended for the general user; they have been provided mainly for those interested in developing new communication protocols, or for gaining access to some of the more cryptic facilities of an existing protocol.
    >> 프로토콜의 기본적인 stream을 의미한다.

에러 처리 관련 함수

에러 메시지를 출력하는 예제

#include <stdio.h>
#include <string.h>
#include <errno.h>

int main(void)
{
    printf("errno\t: %d\n",errno);
    printf("malloc\t: %s\n",strerror(errno));

    return 0;
}

출력

errno	: 0
malloc	: Success
Press <RETURN> to close this window...

Socket 관련 함수

리눅스와 윈도우 공통으로 사용이 가능한 함수와 리눅스 전용 함수 만을 정리 되어있으며,
header 정보는 리눅스에서 사용하기 위한 header이다. 윈도우는 해당 함수를 사용하기 위한 헤더를 따로 사용해야 한다.
(대부분의 경우 윈도우는 <winsock2.h>를 사용 하면 된다.)

header : #include <sys/types.h>
              #include <sys/socket.h>
int socket(int domain, int type, int protocol)
         > 소켓을 초기화 하는 함수

인자 설명

  1. int domain : 통신 domain 지정하는 인자로 어떤 네트워크에서 사용될 socket인지 정한다.
  2. int type : socket의 형태를 지정하는 것으로 스트림을 의미하는 SOCK_STREAM이 있으며, 데이터 그램을 의미하는 SOCK_DGRAM이 있다.
    또한 SOCK_RAW 형태로 소켓을 생성하여 직접 프로토콜 헤더를 만들 수도 있다.
  3. int protocol : 어떤 통신 프로토콜을 사용할지 지정하는 것이다.

소켓이 정상적으로 생성되면 소켓을 구분 할 수 있는 소켓 디스크립터(socket descriptor)가 리턴 되며, 소켓을 생성하는 과정 중에서 오류가 발생하면 0보다 작은 값이 반환 된다.


header : #include <sys/types.h>
              #include <sys/socket.h>
int connect(int sockfd, const strcut sockadd *addr, socklen_t addrlen)
         > 연결 대기 중인 서버로 실제 연결을 맺는 함수

인자 설명

  1. int sockfd : int socket()로 생성된 소켓 디스크립터가 들어간다.
  2. const strcut sockaddr * addr : 접속하고자 하는 아이피 및 서버 포트 정보가 있는 sockaddr 구조체의 주소 값을 지정 한다.
    해당 구조체는 아래와 같다.
    struct sockaddr{
        sa_family_t sa_family;
        char sa_data[14];
    }
  3. socklen_t addrlen : 전달 하고자 하는 구조체의 주소 값, 즉 sockaddr 구조체의 길이를 지정하면 된다.

∗ sockaddr 구조체는 소켓 관련 함수를 사용하기 위해서 최종적으로 반환 되는 구조체로 TCP/IP의 실제 주소 정보를 저장하기 위해서는 sockaddr과 같은 크기의 sockaddr_in 구조체가 사용된다. sockaddr_in 구조체는 아래와 같다.
struct sockaddr_in{
    sin_family_t sin_family;
    uint16_t sin_port;
    struct in_addr sin_addr;
    char sin_zero[8];
}

여기서 sin_addr은 ip주소 값을 저장하기 위한 멤버 변수로 주소를 저장할 수 있는 구조체 in_addr을 사용하며 아래와 같다
struct in_addr{
    in_addr_t s_addr;
}


header : #include <sys/types.h>
              #include <sys/socket.h>
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen)
         > 서버에서 소켓 연결에 사용되는 주소를 소켓에 할당하기 위한 함수

인자 설명

  1. int sockfd : int socket()로 생성된 소켓 디스크립터가 들어간다.
  2. const strcut sockaddr * addr : 접속하고자 하는 아이피 및 서버 포트 정보가 있는 sockaddr 구조체의 주소 값을 지정 한다.
  3. socklen_t addrlen : 전달 하고자 하는 구조체의 주소 값, 즉 sockaddr 구조체의 길이를 지정하면 된다.

∗ connet()함수에서 sockaddr 구조체의 주소 값은 접속하려는 서버의 정보지만 bind()에서의 sockaddr는 소켓 디스크립터가 연결 받고자 하는 주소값이 저장된 sockaddr 구조체의 주소 값이다.


header : #include <sys/types.h>
              #include <sys/socket.h>
int listen(int sockfd, int backlog)
         > 해당 소켓에서 연결을 기다리는 함수

인자 설명

  1. int sockfd : int socket()로 생성된 소켓 디스크립터가 들어간다.
  2. int backlog : 연결을 기다리는 대기열 큐의 사이즈이다.

header : #include <sys/types.h>
              #include <sys/socket.h>
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen)
         > 해당 소켓에 연결 요청이 왔을 때 연결을 받아들이는 함수

인자 설명

  1. int sockfd : int socket()로 생성된 소켓 디스크립터가 들어간다.
  2. const strcut sockaddr * addr : 클라이언트의 아이피 및 서버 포트 정보가 있는 sockaddr 구조체의 주소 값이다.
  3. socklen_t *addrlen : sockaddr 구조체의 길이가 저장된 변수의 주소 값이다.

∗ 연결에 실패하면 0보다 작은 값을 반환한다. 또한 해당 소켓으로 연결 요청이 없는 경우, 클라이언트가 연결을 요청할 때 까지 소켓을 계속 감시하며 대기 상태를 유지한다.켓


header : #include <sys/types.h>
              #include <sys/socket.h>
ssize_t send(int sockfd, const void *buf, size_t len,int flags)
         > 스트림 기반(SOCK_STREAM)으로 생성된 소켓에서 데이터를 전송하는 함수

인자 설명

  1. int sockfd : int socket()로 생성된 소켓 디스크립터가 들어간다.
  2. const void *buf : 보내고자 하는 데이터의 시작 주소 값
  3. size_t len : 데이터의 시작 주소부터 얼마만큼 데이터를 전송할지 데이터의 길이를 나타낸다.
  4. int flags : send() 함수를 호출할 때 사용되는 옵션 플래그 (일반적으로 0을 사용)

header : #include <sys/types.h>
              #include <sys/socket.h>
int recv(int sockfd,void *buf, size_t len,int flags)
         > 스트림 기반으로 생성된 소켓에서 데이터를 수신 받기 위한 함수

인자 설명

  1. int sockfd : int socket()로 생성된 소켓 디스크립터가 들어간다.
  2. const void *buf : 수신 받은 데이터가 저장될 버퍼의 주소 값
  3. size_t len :버퍼의 최대 길이를 나타낸다.
  4. int flags : send() 함수를 호출할 때 사용되는 옵션 플래그 (일반적으로 0을 사용)

header : #include <sys/types.h>
              #include <sys/socket.h>
int sendto(int sockfd,const void *buf, size_t len, int flags, const struct sockaddr *dest_addr, socklen_t addrlen)
         > send()와는 다르게 데이터그램(SOCK_DGRAM)으로 생성된 소켓 연결에서 데이터를 전송하는 함수

인자 설명

 

  1. int sockfd : int socket()로 생성된 소켓 디스크립터가 들어간다.
  2. const void *buf : 보내고자 하는 데이터의 시작 주소 값
  3. size_t len : 데이터의 시작 주소부터 얼마만큼 데이터를 전송할지 데이터의 길이를 나타낸다.
  4. int flags : send() 함수를 호출할 때 사용되는 옵션 플래그 (일반적으로 0을 사용)
  5. const strcut sockaddr * dest_addr : 전송하려는 대상의 주소 정보가 저장된 sockaddr 구조체
  6. socklen_t addrlen : sockaddr 구조체의 길이를 나타낸다.

 


header : #include <sys/types.h>
              #include <sys/socket.h>
int recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr *src_addr, socklen_t *addrlen)
         > recv()와는 다르게 데이터그램(SOCK_DGRAM)으로 생성된 소켓 연결에서 데이터를 수신하는 함수

 

인자 설명

  1. int sockfd : int socket()로 생성된 소켓 디스크립터가 들어간다.
  2. const void *buf : 수신 받은 데이터가 저장될 버퍼의 주소 값
  3. size_t len :버퍼의 최대 길이를 나타낸다.
  4. int flags : send() 함수를 호출할 때 사용되는 옵션 플래그 (일반적으로 0을 사용)
  5. strcut sockaddr * src_addr : 수신지의 주소 정보가 저장된 sockaddr 구조체
  6. socklen_t *addrlen : sockaddr 구조체의 길이의 주소 값을 나타낸다.

== 리눅스 전용 함수 ==
header : #include <unistd.h>
int close(int fd)
         > 파일의 디스크립터를 닫는데 사용되는 함수 + 소켓 디스크립터를 닫는 데도 사용되는 함수는

인자 설명

  1. int fd : 파일이나 소켓의 디스크립터

윈도우의 경우 소켓 디스크립터를 닫기 위한 함수가 따로 존재하니 참고 바란다.