저 수준 파일 입출력과 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);
}