Home socket programming in c
Post
Cancel

socket programming in c

저 수준 파일 입출력과 FD


  • 저 수준 파일 입출력
    • ANSI 표준 함수가 아닌 OS가 제공하는 함수 기반의 파일 입출력
    • 표준이 아니기에 운영체제 호환성이 없다.
    • 리눅스는 소켓도 파일로 간주한다.
      • 고로 저 수준 파일 입출력 함수로 소켓 기반 데이터 통신 가능
  • 파일 디스크립터 (FD)
    • 열어둔 파일을 구분하기 위한 숫자
    • 저 수준 파일 입출력 함수는 fd를 이용해 파일을 지정함
  • 0, 1, 2는 default로 지정
    • 표준 입력, 표준 출력, 표준 에러
    • 이후 파일이 열릴 때마다 +1 생성
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include <stdio.h>
#include <sys/socket.h>
#include <stdlib.h>
#include<fcntl.h>

// socket 1 2 3 default
// standard in out error
// 파일과 소켓을 동일하게 취급 in linux

int main(){

    int fd1, fd2, fd3;
    fd1 = socket(PF_INET, SOCK_STREAM, 0);
    fd2 = open("test.dat",O_CREAT|O_WRONLY|O_TRUNC);
    fd3 = socket(PF_INET, SOCK_DGRAM, 0);

    printf("%d", fd1);
    printf("%d", fd2);
    printf("%d", fd3);
  
    close(fd1);
    close(fd2);
    close(fd3);
}

소켓의 생성


1
2
3
4
5
6
7
8
9
#include <sys/socket.h>
int socket(int domain, int type, int protocol);

sock=socket(PF_INET, SOCK_STREAM, 0);
// PF_INET -> IPv4 프로토콜
	// protocol family
// SOCK_STREAM 연결 지향형 TCP
// SOCK_DGRAM 비 연결 지향형 UDP
// IPPROTO_TCP or UDP -> 굳이 지정 안해도 앞의 정보로 결정됨 고로 0보냄
  • 소켓을 생성함
    • 성공 시 fd, 실패 시 -1
  • domain : 프로토콜 체계(protocol family) 정보
    • PF_INET : IPv4
    • PF_INET6 : IPv6
    • 등등
  • type : 데이터 전송 방식
    • IPv4는 두 가지 방식 존재
    • SOCK_STREAM // tcp
      • 데이터 소멸이 안되는 것처럼 만듬
      • 받을 때 순서 섞이나 순서대로 올려줌
      • 데이터 경계 존재하지 않는다.
      • 소켓은 1대 1의 구조 ( 지금은 이렇게 배움 )
    • SOCK_DGRAM // udp
      • 데이터 손실 및 파손의 우려가 있다.
      • 데이터의 경계가 존재한다.
  • protocol : 통신에 이용되는 프로토콜 정보 전달
    • 이미 위의 정보를 통해 정해짐
    • 그래서 그냥 0으로 던짐
    • tcp, udp 써주는 거임

sockaddr_in으로 주소 전달하기


1
2
3
4
5
6
7
// 해당 주소의 서버로 연결
if(connect(sock, (struct sockaddr*)&serv_addr, sizeof(serv_addr))==-1)
        error_handling("connect() error!");
        
// 특정 ip 와 port로 들어오는 정보는 나에게 보내라
if(bind(serv_sock, (struct sockaddr*) &serv_addr, sizeof(serv_addr))==-1 )
	error_handling("bind() error");
  • 두 함수 모두 sockaddr_in 이용함
    • 이는 IPv4 전용으로 범용적인 sockaddr로 형변환해서 사용
1
2
3
4
5
6
7
8
9
10
11
struct sockaddr_in

{
    sa_family_t sin_family; // 주소 체계
    uint16_t sin_port;      // 포트 주소
    struct in_addr          
    {
        in_addr_t s_addr; // in_addr_t = uint32_t
    } sin_addr;           // 32비트 IP주소
    char sin_zero[8];     // 형변환을 위한 값
};
1
2
3
4
5
struct sockaddr
{
    sa_family_t sin_family; // 주소 체계 (Adress Family)
    char        sa_data[14]; // 주소 정보
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
    memset(&serv_addr, 0, sizeof(serv_addr));
    serv_addr.sin_family=AF_INET; // address family
    // AF_INET, AF_INET | IPv4, 6
    // AF_LOCAL | 로컬 통신을 위한 unix 주소체계
    
    serv_addr.sin_addr.s_addr=inet_addr(argv[1]);
    // 32bit IP 주소
    // 네트워크 바이트 순서로 저장 / Big endian
    // sin_addr는 구조체 자료형 in_addr, 사실상 u32비트 정수

    // struct in_addr
    // {
    //  in_addr_t s_addr; // in_addr_t = uint32_t
    // };

    // inet_addr은 "211.214.142.09" 같이 10진수로 표현된 문자열을 u32비트 정수형으로 반환
    
    // in_addr_t inet_addr(const char* string);
    // int inet_aton(const char* string, struct in_addr* addr)
    // inet_addr과 동일, 다만 구조체에 저장해줌 그리고 성공 결과(t f) 반환  
    // inet_aton(addr, &addr_inet.sin_addr)
    
    // char* inet_ntoa(struct in_addr adr); 반대로 정수형으로 주소를 보여줌
    
    serv_addr.sin_port=htons(atoi(argv[2]));
    // uint16_t , 16비트 포트 번호 저장
    // 네트워크 바이트로 저장해야함 -> htons(host to networks) 이용
    // hostns는 바이트 변환 함수이다. s는 short를 의미, l(long)도 있다.
    
    // sin_zero는 sockadd_in 을 sockaddr로 바꾸기 위한 패딩으로 이용됨
    // 반드시 0으로 초기화
    // sockadd_in은 ipv4만을 위함
    // sockadd는 다양한 주소체계의 주소 정보를 담게 정의됨

    // 클라이언트는 연결할 서버의 정보를 저장
    if(connect(sock, (struct sockaddr*)&serv_addr, sizeof(serv_addr))==-1)
        error_handling("connect() error!");
    str_len=read(sock, message, sizeof(message)-1); // fd, void*buf, 수신할 최대 바이트 수
    if(str_len==-1) // 읽은 바이트수 반환, 파일의 끝 = 0 , 실패 시 -1
        error_handling("read() error!");
    printf("Message from server: %s \n", message);  
    close(sock);
    return 0;

}
1
2
3
4
5
serv_addr.sin_addr.s_addr=htonl(INADDR_ANY);
// any는 모든 ip를 의미한다. 127.0.0.1 or 213.133.412.13
// 현재 실행중인 pc의 ip
// 127.0.0.1 루프백 주소
// 내려온 패킷을 받은 것 처럼 다시 올려줌
  • 서버는 ip주소를 적을 때 INADDR_ANY 이용한다.

빅엔디안, 리틀엔디안


  • 네트워크 통신은 빅엔디안이 기준이다.
    • 높은 주소로 마지막 바이트가 저장된다.
    • 즉 주소에 바이트가 순서대로 저장된다.
  • 인텔은 리틀엔디안을 이용한다.
    • 고로 ip, port 주소를 적어주려면 체계를 바꿔야 한다.

inet_addr


  • 문자열로 전달된 ip 주소를 32비트로 변환해준다.
  • 네트워크 기준인 빅엔디안으로 바꾸어 반환한다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include <arpa/inet.h>
#include <stdio.h>

int main(){
    char*addr1="1.2.3.4";
    char*addr2 = "1.2.3.256"; // 256이라서 에러
    unsigned long conv_addr = inet_addr(addr1);
    if(conv_addr==INADDR_NONE){
        printf("Error occured!\n");
    }
    else{
        printf("%#lx\n", conv_addr);
    }
    conv_addr = inet_addr(addr2);
    if(conv_addr==INADDR_NONE){
        printf("Error occured!\n");
    }
    else{
        printf("%#lx\n", conv_addr);
    }
	struct sockaddr_in addr;
    addr.sin_addr.s_addr = inet_addr(addr1);  // 구조체를 받음
    printf("%s", inet_ntoa(addr.sin_addr)); // 역변환 
}

  • 역순으로 출력되는 이유는 인텔에서 돌려서 그렇다.
    • 리틀엔디안으로 저장된 것을 보기 좋게 출력해주는데, 빅엔디안으로 저장되어 뒤집혀 보이는 것이다.
    • 즉 출력 시 역순으로 읽어서 보여주는 것 (인텔은 리틀 엔디안이 기준이니까)

htons


  • host to network short
    • 호스트의 바이트 순서를 네트워크 기준으로 바꿔줌
    • ntohs도 존재
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include <arpa/inet.h>
#include <stdio.h>

int main(){
    unsigned short host_port = 0x1234;
    unsigned short net_port;
    unsigned long host_addr = 0x12345678;
    unsigned long net_addr;
    
    net_port = htons(host_port);
    net_addr = htonl(host_addr);
    
    printf("host_port : %#x\n", host_port);
    printf("host_addr : %#lx\n", host_addr);
    printf("net_port : %#x\n", net_port);
    printf("net_addr : %#lx\n", net_addr);
}

This post is licensed under CC BY 4.0 by the author.