2015년 8월 1일 토요일

알고리즘 문제 해결 전략(2)

선형 시간 알고리즘
다이어트 현황 파악: 이동 평균 계산하기
//실수 배열 A가 주어질 때, 각 위치에서의 M-이동 평균 값을 구한다.
vector<double> movingAverage1(const vector<double>& A, int M) {
vector<double> ret;
int N = A.size();
for(int i = M-1; i < N; ++i) {
//A[i]까지의 이동 평균값을 구하자.
double partialSum = 0;
for(int j = 0; j < M; ++j)
partialSum += A[i-j];
ret.push_back(partialSum / M);
               //push_back: vector의 메소드로 마지막에 원소 추가
}
return ret;
}

//길이가 N인 실수 배열 A가 주어질 때, 각 위치에서의 M-이동 평균값을 구한다.
vector<double> movingAverage2(const vector<double>& A, int M) {
vector<double> ret;
int N = A.size();
double partialSum = 0;
for(int i = 0; i < M - 1; ++i)
partialSum += A[i];
for(int i = M-1; i < N; ++i) {
partialSum += A[i];
ret.push_back(partialSum / M);
partialSum -= A[i-M+1];
}
return ret;
}

선형 이하 시간 알고리즘 - 이진 탐색
입력의 크기가 커지는 것보다 수행 시간이 느리게 증가하는 알고리즘들을 선형 이하 시간 알고리즘이라 부른다.

 지수 시간 알고리즘 - 다항 시간 알고리즘 - 재귀 함수
음식 메뉴 정하기
const int INF = 987654321;
// 이 메뉴로 모두가 식사할 수 있는지 여부를 반환한다.
bool canEverybodyEat(const vector<int>& menu);
// 요리할 수 있는 음식의 종류 수
int M;
//food번째 음식을 만들지 여부를 결정한다.
int selectMenu(vector<int>& menu, int food) {
//기저 사례:모든 음식에 대해 만들지 여부를 결정했을 때
if(food == M) {
if(canEverybodyEat(menu)) return menu.size();
//아무것도 못 먹는 사람이 있으면 아주 큰 값을 반환한다.
return INF;
}
//이 음식을 만들지 않는 경우의 값을 계산한다.
int ret = selectMenu(menu, food+1);
//이 음식을 만드는 경우의 답을 계산해서 더 작은 것을 취한다.
menu.push_back(food);
ret = min(ret, selctMenu(menu, food+1));
menu.pop_back();
return ret;
}

지수 시간 알고리즘

소인수 분해의 수행 시간
//자연수 n의 소인수 분해 결과를 담은 정수 배열을 반환한다.
vector<int> factor(int n) {
if(n == 1) return vector<int>(1, 1); // n = 1인 경우는 예외로 한다.
vector<int> ret;
for(int div = 2; n > 1; ++div)
while(n % div == 0) {
n /= div;
ret.push_back(div);
}
return ret;
}

void selectionSort(vector<int>& A) {
for(int i = 0; i < A.size(); ++i) {
int minIndex = i;
for(int j = i+1;  j < A.size(); ++j)
if(A[minIndex] > A[j])
minIndex = j;
swap(A[i], A[minIndex]);
}
}

void insertionSort(vector<int> A) {
for(int i = 0; i < A.size(); ++i) {
 // A[0..i-1]에 A[i]를 끼워넣는다.
 int j = i;
 while(j > 0 && A[j-1] > A[j]) {
 swap(A[j-1], A[j]);
 --j;
 }
}
}

const int MIN = numeric_limits<int>::min();
//A[]의 연속된 부분 구간의 최대 합을 구한다. 시간 복잡도: O(N^3)
int inefficientMaxSum(const vector<int>& A) {
int N = A.size(), ret = MIN;
for(int i = 0; i < N; i++)
for(int j = i; j < N; ++j) {
//구간 A[i, j]의 합을 구한다.
int sum = 0;
for(int k = i; k <= j; ++k)
sum += A[k];
ret = max(ret, sum);
}
return ret;
}


//A[]의 연속된 부분 구간의 최대 합을 구한다. 시간 복잡도: O(N^2)
int betterMaxSum(const vector<int>& A) {
int N = A.size(), ret = MIN;
for(int i = 0; i < N; ++i) {
int sum = 0;
for(int j = i; j < N; ++j) {
//구간 A[i, j]의 합을 구한다.
sum += A[j];
ret = max(ret, sum);
}
}
return ret;
}

//A[lo, hi]의 연속된 부분 구간의 최대 합을 구한다. 시간 복잡도: O(nlogn)
int fastMaxSum(const vector<int>& A, int lo, int hi) {
//기저 사례: 구간의 길이가 1일 경우
if(lo == hi) return A[lo];
//배열을 A[lo, mid], A[mid+1, hi]의 두 조각으로 나눈다.
int mid = (lo, hi) / 2;
//두 부분에 모두 걸쳐있는 최대 합 구간을 찾는다.
//이 구간은 A[i, mid], A[mid+1, j] 형태를 갖는 구간의 합으로 이루어진다.
//A[i, mid] 형태를 갖는 최대 구간을 찾는다.
int left = MIN, right = MIN, sum = 0;
for(int i = mid, i >= lo; --i){
sum += A[i];
left = max(left, sum);
}
//A[mid+1, j] 형태를 갖는 최대 구간을 찾는다.
sum = 0;
for(int j = mid+1; j <= hi; ++j) {
 sum += A[j];
 right = max(right, sum);
}
//최대 구간이 두 조각 중 하나에만 속해 있는 경우의 답을 재귀 호출로 찾는다.
int single = max(fastMaxSum(A, lo, mid), fastMaxSum(A, mid+1, hi));
//두 경우 중 최대치를 반환한다.
return max(left + right, single);
}

//A[]의 연속된 부분 구간의 최대 합을 구한다. 시간 복잡도: O(N)
int fastestMaxSum(const vector<int>& A) {
int N = A.size(), ret = MIN, psum = 0;
for(int i = 0; i < N; ++i) {
psum = max(psum, 0) + A[i];
ret = max(psum, ret);
}
return ret;

}

계산 복잡도 클래스: P, NP, NP-완비
P : 다항 시간 내에 풀 수 있는 문제
NP : 다한 시간 내에 풀 수 없는 문제

알고리즘의 정당성 증명 - 수학적으로 증명
수학적 귀납법
단계 나누기 : 증명하고 싶은 사실을 여러 단계로 나눈다.
첫 단계 증명 : 첫 단계에서 증명하고 싶은 내용이 성립한다.
귀납 증명 : 한 단계에서 증명하고 싶은 내용이 성립한다면 다음 단계에서도 성립함을 보임

반복문 불변식
//(*) 불변식은 여기에서 성립해야 한다.
while(condition) {
    //반복문 내용의 시작
    ..
    //반복문 내용의 끝
    //(**) 불변식은 여기에서도 성립해야 한다.
}

이진 탐색과 반복문 불변식
// 필수 조건: A는 오름차순으로 정렬되어 있다.
// 결과: A[i-1] < x <= A[i]인 i를 반환한다.
// 이때 A[-1] = 음의 무한대, A[n] = 양의 무한대라고 가정한다.
int binsearch(const vector<int>& A, int x) {
int n = A.size();
int lo = -1, hi = n;
//반복문 불변식 1: lo < hidden
//반복문 불변식 2: A[lo] < x < A[hi]
//(*) 불변식은 여기서 성립해야 한다.
while(lo + 1 < hi) {
int mid = (lo + hi) / 2;
if(A[mid] < x)
lo = mid;
else
hi = mid;
//(**) 불변식은 여기서 성립해야 한다.
}
}

삽입 정렬과 반복문 불변식
void insertionSort(vector<int> A) {
for(int i = 0; i < A.size(); ++i) {
// 불변식 a:A[0..i-1]은 이미 정렬되어 있다.
  // A[0..i-1]에 A[i]를 끼워넣는다.
  int j = i;
  while(j > 0 && A[j-1] > A[j]) {
   // 불변식 b: A[j + 1 .. i]의 모든 원소는 A[j]보다 크다.
   // 불변식 c: A[0 .. i]  구간은 A[j]를 제외하면 정렬되어 있다.
  swap(A[j-1], A[j]);
  --j;
  }
}
}

귀류법
내가 원하는 바와 반대되는 상황을 가정하고 논리를 전개해서 결론이 잘못됐음을 찾아내는 증명 기법

비둘기집의 원리

동전 뒤집기

순환 소수 찾기

구성적 증명

안정적 결혼 문제

2015년 7월 31일 금요일

알고리즘 문제 해결 전략(1)

문제 해결 과정
1. 문제를 읽고 이해한다.
2. 문제를 익숙한 용어로 재정의한다.
3. 어떻게 해결할지 계획을 세운다.
4. 계획을 검증한다.
5. 프로그램으로 구현한다.
6. 어떻게 풀었는지 돌아보고, 개선할 방법이 있는지 찾아본다.

체계적인 접근을 위한 질문들
1. 비슷한 문제를 풀어본 적이 있던가?
2. 단순한 방법에서 시작할 수 있을까? == 무식하게 풀 수 있을까?
3. 내가 문제를 푸는 과정을 수식화할 수 있을까?
4. 문제를 단순화할 수 없을까? == 문제를 좀 더 쉽게 만들어 볼 순 없을까?
5. 그림으로 그려볼 수 있을까?
6. 수식으로 표현할 수 있을까?
7. 문제를 분해할 수 있을까?
8. 뒤에서부터 생각해서 문제를 풀 수 있을까?
9. 순서를 강제할 수 있을까?
10. 특정 형태의 답만을 고려할 수 있을까? == 답 후보의 유한한 갯수만을 고려

코딩과 디버깅에 관하여
1. 간결한 코드를 작성하기
2. 적극적으로 코드 재사용하기 == 코드 모듈화
3. 표준 라이브러리 공부하기
4. 항상 같은 형태로 프로그램을 작성하기 == 반복문의 일관된 사용, 배열의 전달 방법
5. 일관적이고 명료한 명명법 사용하기
6. 모든 자료를 정규화해서 저장하기 == 모든 분수는 기약분수로, 각도를 양수로만 표현
7. 코드와 데이터를 분리하기 == 데이터는 배열로 만들어서 정리

자주 하는 실수
1. 산술 오버플로
2. 배열 범위 밖 원소에 접근 == array[n]이면 0~n-1
3. 일관되지 않은 범위 표현 방식 사용하기
4. 계산의 큰 줄기는 맞지만 하나가 모자라거나 하나가 많아서 틀리는 코드의 오류
5. 컴파일러가 잡아주지 못하는 상수 오타
6. 스택 오버플로
7. 다차원 배열 인덱스 순서 바꿔 쓰기
8. 잘못된 비교 함수 작성 == <, >
9. 최소, 최대 예외 잘못 다루기
10. 연산자 우선순위 잘못 쓰기
11. 너무 느린 입출력 방식 선택
12. 변수 초기화 문제 == 이전 입력에서 사용한 전역 변수 값을 초기화하지 않고 사용

스캐폴딩 코드
//우리가 테스트하고 싶은 정렬 함수
void mySort(vector<int>& array);
//주어진 배열을 문자열로 바꾼다
string toString(const vector<int>& array);
int main() {
//무한히 반복한다.
 while(true) {
 //임의의 입력을 만든다.
 int n = rand() % 100 + 1;
 vector<int> input(n);
 for(int i = 0; i < n; ++i)
 input[i] = rand();
 //두 개의 복제를 만들어서 하나는 우리의 정렬 함수로, 하나의 표준 라이브러리로 
 //정렬한다.
 vector<int> mySorted = input;
 mySort(mySorted);
 vector<int> reference = input;
 sort(reference.begin(), reference.end());
 if(mySorted != reference) {
 cout << "Mismatch!" << endl;
 cout << "Input: " << toString(input) << endl;
 cout << "Expected: " << toString(reference) << endl;
 cout << "Got: " << toString(mySorted) << endl;
 break;
  }
 }
}

산술 오버플로
어떤 식의 계산 값이 반환되는 자료형의 표현 가능한 범위를 벗어나는 경우

너무 큰 결과
프로그램이 출력해야 할 결과가 우리가 흔히 사용하는 32비트 자료형의 범위를 넘어가면 64비트 정수를 사용하거나 큰 정수 구현을 이용해야 한다.

너무 큰 중간 값
프로그램의 출력값의 범위는 작지만 중간과정에서 큰 값을 일시적으로 계산해야 하는 경우

너무 큰 '무한대' 값
무한대의 값을 어떤 큰 유한한 값으로 대체할 때 너무 큰 값으로 잡으면 안 됨

오버플로 피해가기
1. 캐스팅을 이용
2. 연산의 순서를 바꾼다. - 먼저 빼거나 나눈다.
3. 계산의 순서를 바꾼다.

실수 자료형의 이해
실수 == 근사값
=> 두 개의 실수값을 비교할 때에는 어느 정도의 오차는 염두해두어야 한다.
1. 오차의 한도를 상황에 맞게 설정한다.
2. 상대 오차를 이용한다.
  relativeError(a, b) = |a-b| / max( |a|, |b| ) <= 큰 수를 비교할 때 유용
//절대 오차와 상대 오차를 모두 이용해서 두 수가 같은지 판정한다.
bool doubleEqual(double a , double b) {
double diff = fabs(a - b);
//절대 오차가 허용 범위 안일 경우 무조건 true를 반환한다.
if(diff < 1e-10) return true;
//이 외의 경우레는 상대 오차를 사용한다.
return diff <= 1e-8 * max(fabs(a), fabs(b));
}

대소 비교
연산 오차에 의해서 거짓이 될 값이 참이 될 수 있다.

코드의 수치적 안정성 파악하기
프로그램의 실행 과정에서 발생하는 오차가 더 커지지 않는다.


2015년 6월 29일 월요일

Xctu로 지그비 연결 테스트 간단히

1. Xctu 실행

2. 라디오 모듈 추가(USB & RUN)
안되면 모듈을 뺐다 다시 낀다.

3. 모듈 핀 설정
(서로의 CH, ID는 같아야하고 1의 DL = 2의 MY, 1의 MY = 2의 DL)

4. 다른 모듈 탐색

5. 중요!!! 핀을 USB로 바꾼다.

6. 통신!!!!

테트리스 예제 만들기 (1월 26일 ~ 2월 5일) 80~90% 성공한 듯...

일반 테트리스 만들기

public class Blocks {

static final int BLOCKNUMBER = 7;
int [][] block = new int[4][4];  //블록을 위한 공간
int top = 0;
int bottom = 0;
int left = 0;
int right = 0;
int blockSize = 0;
int idx = 0;
public void makingBlock(){  //블록을 만들고 무작위로 리턴하는 메서드
idx =  (int)(Math.random()*(BLOCKNUMBER));

switch (idx){
case 0:           
/*
* 1 1
* 1 1
*/
block[1][1]=1;    
block[1][2]=1; 
block[2][1]=1;
block[2][2]=1;
top = 1;
bottom = 2;
left = 1;
right = 2;
blockSize =3;
break;
case 1:
/*
    *   1 1
    * 1 1
*/
block[2][1]=1;
block[2][2]=1;
block[1][2]=1;
block[1][3]=1;
top = 1;
bottom = 2;
left = 1;
right = 3;
blockSize = 3;
break;
case 2:
/*
* 1 1
*    1 1
*/
block[1][1]=1;
block[1][2]=1;
block[2][2]=1;
block[2][3]=1;
top = 1;
bottom = 2;
left = 1;
right = 3;
blockSize = 3;
break;
case 3:
/*
*  1 1 1
*  1
*/
block[1][1]=1;
block[1][2]=1;
block[1][3]=1;
block[2][1]=1;
top = 1;
bottom = 2;
left = 1;
right = 3;
blockSize = 3;
break;
case 4:
/*
* 1 1 1
*      1
*/
block[1][1]=1;
block[1][2]=1;
block[1][3]=1;
block[2][3]=1;
top = 1;
bottom = 2;
left = 1;
right = 3;
blockSize = 3;
break;
case 5:
/*    
*    1 1 1
*       1
*/
block[1][1]=1;
block[1][2]=1;
block[1][3]=1;
block[2][2]=1;
top = 1;
bottom = 2;
left = 1;
right = 3;
blockSize = 3; 
break;
case 6:
/*
* 1 1 1 1 
*/
block[1][0]=1;  
block[1][1]=1;
block[1][2]=1;
block[1][3]=1;
top = 1;
bottom = 1;
left= 0;
right = 3;
blockSize = 4;
break;
}
}
public int [][] turnBlock(int [][] blocks){
int [][]blockArray = new int[blocks.length][blocks[0].length];
for(int i = 0; i < blocks.length;i++){
for(int j = 0; j < blocks[0].length;j++){
blockArray[j][blocks.length -1 - i] = blocks[i][j];
}
}
return blockArray;
}
}

2015년 5월 18일 월요일

네트워크 (2)

소켓의 생성방법을 이해해보자!

프로토콜: "컴퓨터 상호간의 대화에 필요한 통신 규약"

소켓의 생성: 호스트가 통신을 하기 위해 필요한 리소스를 할당하는 것

필요한 인자
1) domain : 프로토콜 체계
   ex) PF_INET, PF_INET6, PF_LOCAL, PF_PACKET, PF_IPX
2) type : 전송 타입 설정
   // 하나의 프로토콜 체계 안에서도 데이터 전송 방법이 여러 가지 있을 수 있다.
  1. SOCK_STREAM(연결 지향형)
     에러나 데이터의 손실 없이 무사히 전달
     전송하는 순서대로 데이터가 전달
     전송되는 데이터의 경계가 존재하지 않는다.
   2. SOCK_DGRAM(비연결 지향형)
      전송되는 순서에 상관없이 가장 빠른 전송을 지향한다.
      전송되는 데이터는 손실될 수도 있고 에러가 발생할 수도 있다.
      전송되는 데이터의 경계가 존재
      한번에 전송되는 데이터의 크기는 제한된다.
3) protocol : 특정 프로토콜 지정

주소 체계와 데이터 결정(주소 할당하는 방법)

IP 주소: 인터넷상에 존재하는 호스트들을 구분하기 위한 32비트 주소 체계

네트워크 주소: 네트워크를 구분해 주는 ID
ex)
                                    특정 호스트를 찾아가는 순서
      1) 4바이트 IP주소 중에서 네트워크 주소만을 참조해서 찾아간다.(203.211.217)
      2) 호스트 주소를 참조한다.

Port란??
*IP주소로는 인터넷에 연결되어 있는 컴퓨터들을 구분하여 줄 수는 있어도 컴퓨터 안에서 실행되고 있는 프로그램까지 구분하지는 못한다.
Port는 실행되는 프로그램을 구분해줌
범위는 0에서 65525(2바이트) <= 0부터 1023은 예약되어 있는 Port이므로 사용이 제한됨

주소 정보의 표현(IP 주소, Port)
1) 1Pv4의 주소 체계를 나타내는 구조체
struct sockaddr_in{
sa_family_t               sin_family       // 주소 체계
uint16_t                  sin_port         // 16비트 TCP/UDP Port
struct in_addr           sin_addr;       // 32비트 IPv4 주소
char                       sin_zero[8];    // 사용되지 않음
};
struct in_addr {
        sint32_t                   s_addr;        // 32비트 IPv4 인터넷 주소
};
2) sockaddr_in 구조체 정보
- sin_family : 프로토콜 체계마다 주소체계가 다르다.
     ex) AF_INET, AF_INET6, AF_LOCAL
- sin_port : 16 port 정보를 대입해준다.(네트워크 바이트 순서)
- sin_addr: 32비트 IP 주소 정보를 대입해준다.(네트워크 바이트 순서)

네트워크 바이트 순서
1) Big-Endian 표현 방식 => 네트워크 바이트 순서
    0x12 0x34 0x56 0x78
2) Little-Endian 표현 방식
    0x78 0x56 0x34 0x12

* 시스템이 Little-Endian 방식을 사용할 경우, 네트워크를 통해 데이터를 전송하기 전에  Big-Endian 방식으로 데이터를 변경해서 보내야 하고, 받을 때도 전송되어 오는 데이터를 역순으로 조합해야 한다.


바이트 순서 변환

' h ' : host byte order                        ' n ' : network byte order
' s ' : short( 16bit )  => port 정보         ' l ' :  long( 32bit ) => IP 주소

ex)
- unsigned short htons(unsigned short)
  16 비트 데이터를 Host Byte 순서에서 Network Byte 순서로 바꾸어 준다는 의미이다.

인터넷 주소 변환
ex) 203.212.114.26 을 unsigned long 값으로 변환해야 한다.

1) inet_addr(const chat *string)
  인자값으로 Dotted-Decimal Notation( ex) 203.212.114.26 ) 문자열의 포인터를 넘겨주게 되면, 해당하는  unsigned long 타입의 데이터값을 리턴
2) inet_aton() : inet_addr 함수보다 개선된 데이터 변환 함수
 개선된 방향
만약 inet_addr 함수를 사용할 경우 변환된 데이터가 unsigned long 타입의 값으로 리턴되기 때문에, 리턴한 값을 주소 정보 구조체 sockaddr_in 안에 선언되어 있는 in_addr 구조체 안에 대입하는 과정을 거쳐야 한다. 
BUT,!!  inet_aton 함수를 사용할 경우 대입하는 과정을 따로 거치지 않아도 된다.

+3) inet_ntoa()
 네트워크 바이트 순서의 32비트 값을 Dotted-Decimal Notation으로 변환하는 함수.

인터넷 주소 초기화

struct sockaddr_in addr;
// 인터넷 주소 정보를 나타내는 구조게 변수 생성
char *serv_port = " 9190 ";
memset(&addr, 0, sizeof(addr_len));
// 인자로 전달된 구조체 변수를 0으로 초기화해줌
addr.sin_family = AF_INET;
// ' 프로토콜 체계 ' 설정
addr.sin_addr.s_addr = htonl(INADDR_ANY);
addr.sin_port = htons(atoi(serv_port));

주소 정보 할당하기(소켓에 주소를 할당)

int bind(int sockfd, struct sockaddr *myaddr, int addrlen);
인자
- sockfd: 주소를 할당하고자 하는 소켓의 파일 디스크립터
- myaddr: 할당하고자 하는 주소 정보를 지니고 있는 sockaddr_in 구조체 변수의 포인터
- addrlen : 인자로 전달된 주소 정보 구조체의 길이



2015년 5월 9일 토요일

소스 분석

까먹어서 내가 친 소스를 내가 분석하다니... 노답
-서버
ChatServer 클래스
생성자: Vector(5,5)
giveAndTake 메소드 //서버를 실행시키는 메소드
서버 소켓 요청을 받아들이고 ServerSocketThread를 돌림
addClient 메소드
벡터에 클라이언트 추가
removeClient 메소드
벡터에서 클라이언트 제거
broadCasting 메소드
ServerSocketThread을 클라이언트에 캐스팅하고 Thread를 통해 메시지를 보낸다.

ServerSocketThread 클래스
생성자: (ChatServer, Socket)
sendMessage 메소드
문자열을 받아 출력
run
BufferedReader, PrintWriter를 만들어서 첫번째는 닉네임 두번째부터는 메시지를 주고 받을 수 있게 ChatServer의 broadCasting 메소드를 받아온다.

-클라이언트
StartingJFrame 클래스 // 클라이언트를 만들때 import하는 클래스
                             // JFrame를 상속 , GUI 메인 클래스에서 상속해감
intFrame 메소드
너비와 높이가 모니터보다 크면 최대화, 초기 위치 설정
processWindow 메소드
창 닫을 때의 이벤트 설정
setMainJPanel 메소드
Component를 추가

ClientGui 클래스 // JPanel을 상속받음
생성자:(JFrame, String ip, int port)
         ip 와 port 를 받아 Socket 객체를 새로 만든다.
actionPerformed 메소드
// requestFocus: 입력을 받게 되면 맨 앞의 창으로 띄어지게 된다.
giveAndTake 메소드
PrintWriter, BufferedReader 객체 생성 -> Thread 생성 -> 시작
run
textArea에 친 문자열을 받아들인다.


2015년 5월 6일 수요일

네트워크 (1)

$$ C언어를 기반으로 씀

네트워크 프로그래밍: 호스트들이 데이터를 주고 받을 수 있도록 프로그램을 구현하는 것

소켓의 이해
1) 전화기를 산다. == 소켓을 생성(Socket 함수)
2) 전화번호를 부여받는다. ==  소켓의 IP 주소 할당(Bind 함수)
3) 전화기를 케이블에 연결한다. == 소켓을 연결요청이 가능한 상태로 만듬(listen 함수)
4) 전화벨이 울리면 수화기를 든다. == 연결 요청이 들어오면 요청을 수락함(accept 함수)

클라이언트 소켓의 이해
1) 전화를 건다 == 서버에게 연결을 요청함(connect 함수)
??? 클라이언트에 전화번호를 할당하는 코드가 없다 ????

파일 조작하기
1) Low-Level == "시스템이 직접 제공해 주는"
2) 파일 디스크립터
  시스템이 만들어 놓은 것을 가리키기 좋게 하기 위해 시스템이 우리들에게 건네주는 숫자
   0 = 표준 입력
   1 = 표준 출력
   2 = 표준 에러 출력
   3
   4 ... 이런 식으로 차례로 넘버링이 됨
3) File 열기(open 함수)
   (파일의 경로를 포함하는 이름, 열고자 하는 파일의 모드)
                         O_CREAT, O_TRUNC, O_APPEND, O_RDONLY, O_WRONLY, O_RDWR
4) File 닫기(close 함수)
   (파일 디스크립터)
5) 데이터 쓰기(write 함수)
   (파일 디스크립터, 전송할 데이터를 가지는 버퍼의 포인터, 전송할 데이터의 바이트 수)
6) 데이터  읽기(read 함수)
   (파일 디스크립터, 수신할 데이터를 가지는 버퍼의 포인터, 수신할 데이터의 바이트 수)

윈속 기반의 데이터 입출력 함수
데이터를 입출력할 수 있는 기본적인 함수
send 함수 == 데이터를 전달할 때  옵션을 줄 수 있는 write 함수
(데이터를 전송할 호스트에 연결된 소켓의 핸들,
 전송할 데이터를 저장하고 있는 버퍼를 가리키는 포인터,
 전송할 바이트 수,
 함수 호출 시, 여러가지 옵션)

recv 함수
(데이터를 수신할 호스트에 연결된 소켓의 핸들,
 수신할 데이터를 저장하고 있는 버퍼를 가리키는 포인터,
 수신할 바이트 수,
 함수 호출 시, 여러가지 옵션)