반응형

※ 연결에 대한 내용이므로 TCP/IP 프로토콜에 대한 이야기.

※ 우아한 종료 = graceful close. (MSDN 참고)


7-1 소켓 연결 종료의 문제점

2개의 가상의 통로가 생성 (각 통로는 단방향)


스트림 : 연결된 상태 (데이터를 주고 받을 수 있도록 연결된 시스템의 내부적인 상황)

입력 스트림 : 데이터 수신을 위한 스트림

출력 스트림 : 데이터 전송을 위한 스트림


※ TCP 소켓은 데이터를 전송하면서 동시에 수신도 가능하다.


소켓 연결 종료의 문제점

1. close, closesocket 함수 호출 : 입력, 출력 스트림 완전 종료

 => 데이터를 송수신 할 수 없다. 데이터가 소멸된다.

2. 일방적인 방식의 완전 종료는 경우에 따라서 문제가 될 수 있다.



7-2 우아한 소켓의 연결 종료

half close : 입력 혹은 출력 스트림 중 하나의 스트림만 종료하는 행위

예시) A 호스트의 출력 스트림만 종료

#include <sys/socket.h>

int shutdown(int s, int how);

리턴 : 성공시 0, 실패시 -1

s : 종료하고자 하는 소켓의 파일 디스크립터

how : 종료 모드를 인자로 전달한다.

 상수값 모드 정의 
 0  SHUT_RD  입력 스트림 종료 
 1  SHUT_WR 출력 스트림 종료 
 2  SHUT_RDWR  입출력 스트림 종료


출력 스트림의 종료의 필요성

※ Thank you를 받기 위해 shutdown함수를 사용한다.


1. 출력 스트림을 종료하게 되면, 연결되어 있던 호스트로 EOF 메세지 전달.

※ 출력 스트림을 종료하는 방법 : close(closesocket), shutdown 함수.

2. EOF 전송으로 데이터 전송의 끝을 알려 줄 수 있다.

3. EOF 전송 시, 상대 호스트의 데이터 수신 함수(read, recv)는 0을 리턴.


※ EOF를 생략하고 보내면 서버도 read() 무한 대기, 클라이언트도 파일의 끝을 모르므로 read() 무한대기.


※ EOF의 값 : -1 (int type) -> 1바이트를 4바이트로 casting하면 -1이 아닐 수 있다.


7-3. 윈도우즈 기반으로 구현하기

※ 윈도우즈 기반에서는 파일 열기를 위해 표준 라이브러리를 사용하였다.


#include <winsock2.h>

int shutdown(SOCKET s, int how);

리턴 : 성공시 0, 실패시 SOCKET_ERROR

s : 종료하고자 하는 소켓의 파일 디스크립터

how : 종료 모드를 인자로 전달한다.

 상수값 모드 정의 
 0  SD_RECEIVE 입력 스트림 종료 
 1  SD_SEND 출력 스트림 종료 
 2  SD_BOTH 입출력 스트림 종료

반응형
반응형

※ TCP와 UDP를 특징을 비교하면서 공부하는 것이 좋다.

6-1. UDP의 이해

1. IP를 기반으로 데이터를 전송한다. (TCP와 공통점)

2. 흐름제어(flow control)을 하지 않기 때문에 데이터 전송을 보장 받지 못한다. (TCP와 차이점)

3. 연결설정 및 연결 종료 과정도 존재하지 않는다. (TCP와 차이점)

4. 연결 상태가 존재하지 않는다. (TCP와 차이점)


UDP의 역할

포트 정보에 의한 프로세스의 구분

UDP 패킷 = 데이터 그램(Datagram)


6-2. UDP 기반 서버/클라이언트의 구현

※ 소켓을 생성하고 bind까지만 하면 바로 입출력함수를 사용하면 된다. 

(socket~bind : 서버, socket : 클라이언트)

일반적으로 연결 설정 과정을 거치지 않는다.

데이터를 주고 받기 위한 소켓(우체통에 비유)은 하나만 생성해도 된다. (패킷은 우편)


※ UDP는 비연결 프로토콜이기 때문에 주소 정보를 알아야 한다.

데이터 전송함수

int sendto(int sock, const void* msg, int len, unsigned flags, const struct sockaddr *addr, int addrlen);

int sendto(SOCKET s, const char FAR *buf, int len, int flags, const struct sockaddr FAR *to, int tolen);

1~4번째 인자는 TCP send함수와 비슷.

addr : 목적지 주소 정보

addrlen : 목적지 주소 정보 구조체 크기


데이터 수신함수

int recvfrom(int sock, void *buf, int len, unsigned flags, struct sockaddr *addr, int *addrlen);

int recvfrom(SOCKET s, char FAR *buf, int len, int flags, struct sockaddr FAR *from, int FAR *fromlen);

1~4번째 인자는 TCP recv함수와 비슷.

addr : 목적지 주소 정보

addrlen : 목적지 주소 정보 구조체 크기


※ uecho_client_win.c : 윈도우 UDP 에코 클라이언트는 리눅스 용과 다르게 제작되어 있음.

※ UDP에서 클라이언트의 주소는 언제 할당될까? 6-4에 있음.


6-3. 데이터의 경계(boundary)가 존재하는 UDP 소켓

UDP 소켓은 데이터를 송수신하는데 필요한 함수 호출의 수를 정확히 일치 시켜야 한다.


6-4. connect 함수 호출을 통한 성능의 향상

1. TCP 소켓에서의 connect 함수의 의미

 - IP와 포트의 할당

 - Three-way handshaking

2. UDP 소켓에서의 connect 함수의 의미

 - IP와 포트의 할당

※ connect함수가 없으면 sendto 함수가 제일 처음 호출되는 시점에 IP와 Port가 할당된다. 

클라이언트에서 IP와 Port가 한 번 할당되면 close()함수 호출시까지 변하지 않는다.


TCP/UDP 소켓에서 공통적으로 가지는 connect의 의미


connect함수의 의미 

커널과 소켓이 논리적으로 연결하고 그것을 유지한다.


connect함수를 호출하지 않을 때 UDP 클라이언트의 데이터 송수신

1. sendto, recvfrom 함수를 호출하면 커널과 소켓이 연결되고 소켓과 호스트와 통신할 준비를 한다.

2. 호출이 끝나면 커널과 소켓의 연결이 해제된다.

※ 위의 1~2를 반복하는 데 많은 시간을 잡아먹고 커널과 소켓을 연결했다 해제했다 하기 때문에 성능 효율이 떨어진다.


connect 함수 호출이 주는 이점

1. 데이터를 주고 받는 속도가 빨라진다.

2. TCP 소켓 기반의 데이터 입출력 함수를 그대로 사용할 수 있다.


반응형
반응형

5-1. 에코 클라이언트! TCP 기반에서의 완벽 구현

TCP 기반의 데이터 전송 특징

한 번의 데이터 전송함수 호출이 늘 하나의 패킷을 형성하는 것은 아니다.

※ 버퍼 : 서버 프로그램 상의 문자 배열.

※ ABCD를 클라이언트에서 서버로 보냈지만 A,B,C,D와 같이 패킷이 나누어져서 서버가 클라이언트로 보낼 수 있다. 

※ echo server에서는 클라이언트에서 문제가 된다.

※ 구현 : 클라이언트는 여러번 read(recv)함수를 호출해야 한다. -> for문에서 읽어들인 바이트수가 보낸 바이스만큼 읽을 때까지 반복한다.


패킷이 나누어져 보내지는 이유? 뒤에 밝힘


5-2. 경계가 없는 TCP 기반의 데이터 전송

데이터 송수신 함수의 호출 회수는 큰 의미를 지니지 않는다.

예시) 위의 내용을 증명하기 위한 예제 프로그램

※ 서버에서는 한 번에 메세지를 읽기 위해서 sleep(5);를 한다.

※ 클라이언트에서는 4번에 나누어서 메세지 수신을 하기 위해 sleep(10);을 추가하였다.


이미 전송된 데이터는 어디에 존재하고 있었는가?

예시) 서버가 전송하면 클라이언트 (소켓)버퍼에 서버에서 받은 내용을 저장하고 있다.


서버에서 accept 했을 때 생기는 소켓에서 입력버퍼와 출력버퍼가 생성된다.

클라이언트는 소켓에 입출력버퍼가 있다.

※ read함수는 입력버퍼에 있는 내용을 읽는다. 바로 읽지 못할 수 있기 때문에 버퍼가 존재한다.


출력버퍼의 역할?

※ (서버에서) 송신할 때 (클라이언트의) 입력버퍼보다 큰 양을 보낼 수 없다.

=> 가능한 이유 : 클라이언트가 입력버퍼의 양만큼만 보내라는 신호를 보내기 때문에...

※ 이 이유때문에 출력(송신)버퍼가 필요하다.


※ 흐름제어를 위해서 입출력 버퍼가 존재한다.


TCP 기반의 전송제어

1. 버퍼가 수용할 수 있는 크기 이상의 데이터 전송은 이루어지지 않는다.

2. 따라서 TCP 기반의 데이터 전송 함수는 여러 개의 패킷을 생성하기도 한다.

3. 슬라이딩 윈도우 프로토콜 : 남은 버퍼양만큼만 데이터 전송을 하도록 돕는다.


5-3 TCP의 내부 구조

TCP의 데이터 전송 과정

(서버와 클라이언트는 서로 의견을 존중한다.)


1. 연결 설정 단계

클라이언트가 connect 함수 호출 시 진행

Three-way handshaking

① SYN(C) : 동기화. 싱크. 데이터를 주고 받을 수 있습니까?

② SYN + ACK(S) : 대화가 가능(OK)합니다. SYNchronize(동기화)에 대한 ACKnowgement(확인)의 기능.

③ ACK(C) : 잘 받았다라는 뜻으로 확인한다.

※ ③을 보내야 하는 이유 : 클라이언트가 ACK을 보내지 않으면 서버 입장에서 확인 메세지를 못받았다고 생각한다.


SEQ : 시퀀스 번호. 메세지를 잘 받았는 지 확인하기 위해서 붙이는 번호.

(메세지 번호) 내가 보낸 번호를 ACK으로 대답해 달라.

ACK : 확인 번호. 잘 받았다는 것을 응답하기 위해 붙이는 번호.

다음 SEQ 번호를 보내라는 뜻.


2. 데이터 송수신 단계

서버/클라이언트 간 데이터 송수신 함수 호출 과정에서 진행.

1. SEQ:1301,100바이트를 보낸다.

2. 100바이트를 잘 받았다는 것을 알리기 위해 SEQ번호+100을 해서 ACK을 보낸다.

※ SEQ번호는 응답받은 ACK번호와 같아야 한다.

3. SEQ:1401, 100바이트를 보냈는데 손실되었다. 보내는 동시에 타이머를 작동.

손실되었으므로 응답이 없다. 상대방이 못받았다고 생각하므로

4. SEQ:1401, 100바이트를 다시 보낸다.

5. ACK을 보낸다.


3. 연결 종료 단계

클라이언트 혹은 서버가 close(closesocket) 함수 호출 시 진행

four-way handshaking

1. FIN(A->B) : 종료 요청의 메세지를 담은 패킷을 보낸다. FINish

2. ACK(B->A) : 단순히 패킷을 잘 받았다는 신호만 보낸다. 아직 종료할 상황이라는 뜻은 아니다.

3. FIN(B->A) : 종료해도 좋다는 의미의 FINish를 보낸다.

4. ACK(A->B) : 최종적인 수신 응답 메세지 ACK을 전송한다.

반응형
반응형

4-1 TCP/UDP에 대한 이해

TCP/IP 프로토콜 스택

응용프로그램 계층      : 응용프로그램     응용프로그램

전송(Transport)  계층 :       TCP                UDP

네트워크 (Network)계층:                  IP

데이터링크 계층 :                          LINK

물리 계층 


데이터링크 계층

LAN, WAN, MAN과 같은 네트워크 표준과 관련된 프로토콜의 정의한다.


IP 계층

어떻게 길을 찾아 갈 것인가?

라우터끼리는 (도로사정)이 어떤지 서로 통신하여 패킷을 보낼 최적의 길을 찾는다.

신뢰할 수 없는 프로토콜(데이터가 손실 될 수 있고) 경로가 일정치 않으며 패킷을 보낸 순서도 보장하지 못한다. 비연결 지향 프로토콜이다.


TCP/UDP

(IP 계층을 기반으로 하여 ) 데이터를 어떻게 전송할 것인가?

TCP/UDP(2가지 프로토콜) 중에 원하는 프로토콜을 선택한다.


TCP와 IP의 관계

IP를 기반으로 길을 찾고 TCP를 통해 연결 지향 및 신뢰성 있는 통신을 제공한다.

호스트대 호스트가 어떻게 데이터를 주고 받을 것인지 약속.

IP(편지에 비유) -> 패킷(편지)을 잃어버림


TCP의 역할 

A 호스트가 B 호스트에게 패킷을 하나 전송한다.

1. B가 잘 받았을 경우 : 응답용 패킷을 A에게 전송한다.

2. B가 잘 못 받았을 경우 : A는 B가 데이터를 수신하지 못했다고 간주하고 임의의 시간 후 재전송.


4-2. TCP 기반 서버의 구현

socket - 소켓 생성

bind - 주소할당

listen - 연결 요청 대기 상태 (친구에게 전화가 올 수 있는 상태)

accept - 연결 허용(수화기를 든다.)

read & write - 데이터 송수신

close - 연결 종료


'연결 요청 대기 상태'로의 진입

1. listen 함수는 전달되는 인자의 소켓을 '서버 소켓'이 되게 한다.

2. listen 함수는 backlog의 수(대기실의 크기)만큼 '연결 요청 대기 큐'를 생성 한다.

3. 성공하면 '연결 요청 대기 상태'가 된다.

#include<sys/type.h>

int listen(int s, int backlog);

#include<winsock2.h>

int listen(SOCKET s, int backlog);

 - socket : 서버소켓으로 만들 핸들 혹은 파일 디스크립터.

 - backlog : 대기 큐(실)의 크기, 클라이언트 수.


서버의 역할과 연결요청 대기상태

서버 소켓은 일종의 '문지기'이다. (클라이언트의 연결 요청을 감지하고 받는다.)

클라이언트 - 서버로의 연결요청 - 서버 소켓 -> 대기실

※ 여러 사람의 요청을 받게 하기 위해 '큐(대기실)'를 만든다.


연결요청 수락하기

연결요청 대기 큐에 존재하는 클라이언트의 연결 요청 수락.


#include<sys/type.h>

#include<sys/socket.h>

int accept(int s, struct sockaddr *addr, int *addrlen);

#include<winsock2.h>

SOCKET accept(SOCKET s, struct sockaddr FAR *addr, int FAR *addrlen);

리턴값 : 클라이언트와 연결하기 위한 (밑의 그림의)New 소켓

addr : 클라이언트의 주소 정보가 채워진다.

addrlen : 정보구조체의 크기 정보가 채워진다.

 

listen ~ accept 과정

※ New 소켓은 자동적으로 만들어지며 이 New 소켓으로 클라이언트와 데이터 송수신이 가능하다.


4-3 TCP 기반 클라이언트의 구현


#include<sys/types.h>

#include<sys/socket.h>

int connect(int sockfd, struct sockaddr *serv_addr, int addrlen);

sockfd : 연결 요청을 할 소켓

serv_addr : 주소정보

addrlen : 구조체 크기


※ 소켓은 연결하기 전에 미리 운영체제(커널)에 의해 임의로 할당된다.


4-4 서버/클라이언트 함수 호출 관계




※ listen()에서 대기 큐를 만들고 연결 요청을 받을 수 있는 서버 소켓을 만든다.

※ connect 함수는 서버의 IP, 포트를 설정해 주면 자동으로 클라이언트 컴퓨터 내의 임의의 포트에 운영체제가 통신하기 위한 소켓을 생성한다.

※ accept() 함수는 연결 요청 대기 큐가 비어있을 때까지 혹은 클라이언트가 연결할 때까지 block되어 있다.

=> 리턴 되면서 연결된다.


4-5 Iterative 서버의 구현

Iterative 서버 : 반복해서 클라이언트의 요청을 처리한다.

여러 클라이언트가 서버로 연결 요청을 한다. -> 연결 요청 대기 큐로 들어간다. -> 서버가 accept를 호출할 때마다 대기 큐에서 하나의 소켓을 꺼낸다. -> 그 소켓과 통신을 하고 끝나면 반복해서(Iterative) accept를 호출한다. 


Iterative Server Template

※ 리턴 클라이언트 소켓은 하나다.


4-6 에코(echo) 서버/클라이언트의 구현

concurrent 서버(서버 하나와 클라이언트 여러 개 관계)

※ 버퍼 : 여기서는 프로그램 상의 버퍼. 문자열을 저장할 수 있는 배열.

※ 클라이언트에서 close(소켓핸들);을 호출하면 서버의 read함수의 리턴 값은 0이 된다.

반응형
반응형

3-1 Internet Address

IP(Internet Address) 

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

점이 찍힌 십진수 표현 방식(Dotted-decimal Notation) : 211.217.10.9


클래스

Class A : 0.0.0.0 ~ 127.255.255.255, 1바이트는 네트워크 ID, 3바이트는 호스트 ID

Class B : 128.0.0.0 ~ 191.255.255.255, 2바이트는 네트워크 ID, 2바이트는 호스트 ID

Class C : 192.0.0.0 ~ 223.255.255.255, 3바이트는 네트워크 ID, 1바이트는 호스트 ID

Class D : 멀티캐스트 주소. 224.0.0.0 ~ 239.255.255.255

Class E : 예약됨. 240.0.0.0 ~ 255.255.255.255


IP 주소 : 네트워크 주소(네트워크를 구분지음) + 호스트 주소(호스트를 구분지음)

subnet mask에 따라서 네트워크 주소(ID)와 호스트 주소(ID)로 나누어진다.


※ 라우터는 네트워크 ID(주소)만 참조한다.

※ 루프백 주소 : 127.x.x.x, 네트워크 상으로 패킷을 전송하지 않고 자기자신에게 돌려준다.


3-2 Port란 무엇인가?

Port

호스트 내에서 실행되고 있는 프로세스를 구분 짓기 위한 16비트의 논리적인 값. (소켓에 할당)

논리적인 값 : 소프트웨어 적으로 구현. 

Well-known ports : 0~1023


3-3 주소 정보의 표현

IPV4의 주소 체계를 나타내는 구조체

※ 하나의 프로토콜 내에 2개 이상 프로토콜이 있을 것을 대비해서 만든 구조체. ()는 윈도우즈용.

※ 모든 데이터는 네트워크 바이트 순서로 저장해야 한다.

struct sockaddr_in

{

sa_family_t(short) sin_family; // 주소 체계(address_family) - AF_INET

uint16_t(unsigned short) sin_port; // 16비트 TCP 혹은 UDP 포트

struct in_addr sin_addr; // 32비트 IPv4 주소

char  sin_zero[8]; // 사용되지 않음. padding 용도

};


struct in_addr

{

uint32_t(unsigned long) s_addr; // 32비트 IPv4 인터넷 주소

};



3-4 네트워크 바이트 순서

0x12345678을 Big-Endian으로는 메모리 순서대로 0번지 0x12, 1번지 0x34, 2번지 0x56, 3번지 0x78

0x12345678을 Little-Endian으로는 메모리 순서대로 0번지 0x78, 1번지 0x56, 2번지 0x34, 3번지 0x12

형태로 저장.


호스트 바이트 순서 

어떤 시스템(motorola, sun사 기계)은 Big-Endian, 어떤 시스템(intel사)은 Little-Endian을 사용하므로 일정하지 않다.

※ 데이터 표현 방식이 다르므로 다른 플랫폼끼리 문제가 발생한다.


네트워크 바이트 순서

Big-Endian 방식을 적용하기로 하였다.


바이트 순서 변환 함수

※ 어느 시스템에서 돌아갈 지 모르기 때문에 반드시 사용해야 한다.

unsigned short htons(unsigned short); // 호스트 -> 네트워크 short

unsigned short ntohs(unsigned short); // 네트워크 -> 호스트 short

unsigned long htonl(unsigned long); // 호스트 -> 네트워크 long

unsigned long ntohl(unsigned long); // 네트워크 -> 호스트 long


'h' : host byte order

'n' : network byte order

's' : short(16비트)

'l' : long(32비트)


3-5 인터넷 주소 조작하기

1. 점이 찍힌 십진수 방식(Dotted-Decimal Notation)을 Big-Endian 32비트 정수형 데이터로 바꿔주는 함수(네트워크)

#include<sys/socket.h>

#include<netinet/in.h>

#include<arpa/inet.h>

unsigned long inet_addr(const char *string);

리턴 : 성공시 32비트 big-endian값, 실패시 INADDR_NONE


#include<sys/socket.h>

#include<netinet/in.h>

#include<arpa/inet.h>

int inet_aton(const char* string, struct in_addr *addr);
리턴 : 성공시 true, 실패시 false
※ 바로 struct in_addr에 값을 제공할 수 있다.


2. Big-Endian 32비트 정수형 데이터를 점이 찍힌 십진수 방식로 바꿔주는 함수(네트워크)

#include<sys/socket.h>

#include<netinet/in.h>

#include<arpa/inet.h>

char* inet_ntoa(struct in_addr addr);

※ 따로 inet_ntoa 내부적으로 static 배열을 통해 주소에 대한 문자열이 존재한다.


3-6 인터넷 주소 초기화

struct sockaddr_in addr;

char *serv_ip = "...";

char *serv_port = "...";

memset(&addr, 0, sizeof(addr_len)); // 주소에 해당하는 구조체 값을 0으로 초기화. 좋은 습관.

addr.sin_family = AF_INET;

addr.sin_addr.s_addr = inet_addr(serv_ip);

addr.sin_port = htons(atoi(serv_port));


INADDR_ANY?

addr.sin_addr.s_addr = htons(INADDR_ANY);

// 서버의 IP가 뭐더라? 답은 INADDR_ANY : 내 시스템의 IP 주소를 찾아 알아서 할당.


3-7 주소 정보 할당하기

리눅스

#include<sys/types.h>

#include<sys/socket.h>

int bind(int sockfd, struct sockaddr *myaddr, int addrlen);

윈도우즈

#include<winsock2.h>

int bind(SOCKET s, const struct sockaddr FAR *name, int namelen);


sockfd/s : 주소를 할당하고자 하는 소켓의 파일 디스크립터 인자/핸들 인자

myaddr : 할당하기를 원하는 주소 정보를 지닌 sockaddr_in 구조체 변수의 포인터. 

※ sockaddr 형태로 형변환 해서 넘겨야 한다. 

※ 프로토콜에 독립적으로 사용하기 위해서. void 포인터 보다 먼저 이 함수가 개발되었다. 

※ FAR 포인터는 과거의 잔재. 현재는 무시한다.

addrlen/namelen : 주소 정보 구조체의 길이.


3-8 윈도우즈 기반으로 구현하기

※ SOCKADDR_IN = struct sockaddr_in

※ SOCKADDR = struct sockaddr

※ 서버 프로그래밍을 할 때는 htons(INADDR_ANY);를 사용한다.


3-9 WSAStringToAddress & WSAAddressToString

주소 정보를 나타내는 문자열을 가지고 주소 정보 구조체 변수를 채운다.

※ 윈도우즈 기반 주소 변환 함수.

#include<winsock2.h>

INT WSAStringToAddress(

LPTSTR AddressString, // 점이 찍힌 십진수 표현(포트정보 포함)과 같은 주소 정보 문자열 포인터.

INT AddressFamily,      // 주소 정보 문자열이 속한 주소 체계(AF_INET)

LPWSAPROTOCOL_INFO lpProtocolInfo,   // 프로토콜 제공자를 설정. 일반적으로 NULL.

LPSOCKADDR lpAddress,  // 주소 정보 구조체 변수 포인터

LPINT lpAddressLength);  // lpAddress 포인터가 가리키는 버퍼의 크기.

리턴 : 성공시 0, 실패시 SOCKET_ERROR


#include<winsock2.h>

INT WSAAddressToString(

LPSOCKADDR lpsaAddress, // 문자열로 변환할 주소 정보를 가진 구조체 포인터

dwAddressLength,              // lpsaAddress 포인터가 가리키는 변수의 크기

LPWSAPROTOCOL_INFO lpProtocolInfo,   // 프로토콜 제공자를 설정. 일반적으로 NULL.

LPTSTR lpszAddressString,  // 문자열로 변경된 결과를 저장할 버퍼 포인터.

LPDWORD lpdwAddressStringLength);     // lpszAddressString 버퍼의 크기.

리턴 : 성공시 0, 실패시 SOCKET_ERROR




반응형
반응형

1-1. 네트워크 프로그래밍의 이해

네트워크 : 호스트(End-system)들을 연결하는 시스템

호스트 : PC, workstation, PDA

인터넷 : 멀리 떨어진 둘 이상의 네트워크가 연결되 이뤄진 거대한 네트워크 -> 라우터 : 이기종 네트워크를 연결하는 장비


1.2 소켓 이해하기

클라이언트/서버 모델

기계 아님. 

서버 : 연결 요청을 기다린다.

 - Iterative(반복적인) Server : 한 순간에 하나의 클라이언트에게 응담

 - Concurrent Server : 동시에 여러 클라이언트에게 응답한다.

클라이언트 : 서버에 요청하고 응답을 기다리는 호스트.


네트워크 프로그래밍

네트워크로 연결된 두 호스트 간의 데이터 송수신

소켓 : 원격에 존재하는 두 호스트를 연결시켜 주는 매개체. 운영체제에서 제공한다.

(소켓을 꽂으면 전원을 받는다.)


서버 소켓(리눅스 함수)

소켓 생성(전화기 구입) - socket


#include<sys/types.h>

#include<sys/socket.h>

int socket(int domain, int type, int protocol);

리턴 : 성공시 파일 디스크립터 실패시 -1


IP 주소, 포트 할당(전화번호 할당) - bind

#include<sys/socket.h>

int bind(int sockfd, struct sockaddr *myaddr, int addrlen);

리턴 : 성공시 0, 실패시 -1


연결 요청 대기 상태(케이블에 연결) - listen

#include<sys/socket.h>

int listen(int sockfd, int backlog);

리턴 : 성공시 0, 실패시 -1


연결 수락(수화기를 든다) - accept

#include<sys/socket.h>

int accept(int sockfd, struct sockaddr *addr, int *addrlen);

리턴 : 성공시 파일 디스크립터 실패시 -1


클라이언트 소켓

소켓 생성(전화기 구입) - socket


연결 요청(전화 걸기) - connect

int connect(int sockfd, struct sockaddr *addr, int addrlen);

리턴 : 성공시 0, 실패시 -1


리눅스 프로그램 컴파일(링크) 하는 법 : gcc ?.c -o ?

실행하는 법 : ./?

※ 로컬 컴퓨터의 IP : 127.0.0.1

※ 당연히 서버부터 수행한다.


1.3 파일 조작하기

리눅스에서는 모든 것(소켓등)을 파일로 간주한다.

파일의 생성, 삭제, 데이터 입력 및 출력.

관리는 운영체제가 한다.


저수준 파일 입출력 

저수준은 시스템이 직접 제공해 준다는 뜻.

표준입력 : 기본은 키보드, fd 0번

표준출력 : 기본은 모니터, fd 1번

표준에러출력 : 기본은 모니터, 버퍼가 없음. fd 2번

※ 전송은 출력, 수신은 입력은 같은 의미.

※ 표준입출력함수 : ANSI 표준에서 제공해 주는 함수. printf, scanf등...


파일 디스크립터(file descriptor) 

시스템이 만든 것을 가리키기 좋게 하기(포인터) 위해 시스템이 사용자에게 건내주는 숫자값

윈도우의 핸들과 비슷.

모든 파일을 관리하기 위해 운영체제에서 파일 디스크립터를 할당한다.

※ 파일 디스크립터는 redirection가능.


파일 열기

#include <fcntl.h>

#include<sys/types.h>

#include<sys/stat.h>


int open(const char *path, int flag);

리턴 : 성공시 파일 디스크립터, 실패시 -1

path : 파일에 대한 경로

flag : 모드 설정. |(bit wise)를 통해 연산가능.

O_CREAT : 파일이 없을 때 파일 생성. 

O_TRUNC : 파일이 있다면 새로 생성.

O_RDONLY : 읽기 전용 모드


파일 닫기

#include<unistd.h>

int close(int filedes);

리턴 : 성공시 0, 실패시 -1

filedes : 닫아줄 파일의 파일 디스크립터


데이터 쓰기

#include<unistd.h>

ssize_t write(int filedes, const void * buf, size_t nbytes);

filedes : 데이터 전송 영역의 파일 디스크립터.

buf : 전송할 데이터를 가지고 있는 버퍼(데이터)의 포인터. 

nbytes : 전송할 데이터의 바이트수.

ssize_t = signed int, size_t = unsigned int

※ 타입이름을 새로 정의하는 이유는 다른 시스템에서 실행시키기 위해(코드 확장성)

=> 소스 코드를 바꾸지 않고 컴파일만 다시 하면 새로운 시스템에서 잘 돌아간다.


데이터 읽기

#include<unistd.h>

ssize_t read(int filedes, void *buf, size_t nbytes);

filedes : 데이터를 수신 받을 대상을 가리키는 파일 디스크립터.

buf : 수신한 데이터를 저장할 버퍼의 포인터

nbytes : 수신할 최대 바이트수.


1-4 윈도우즈 기반으로 구현하기

WinSock을 위한 헤더 및 라이브러리 설정.

1. #include<winsock2.h>

2. #pragma comment(lib,ws2_32.lib)

ws2_32.lib을 위와 같이 라이브러리를 링크

3. winsock 라이브러리 초기화(standby) 및 해제(리소스 반환)


Winsock 초기화하기

#include<winsock2.h>

int WSAStartup(WORD wVersionRequested, LPWSADATA lpWSAData);

wVersionRequested : 프로그램에서 요구하는 winsock 최상위 버젼을 알려준다.

WORD : 16비트 unsigned int

예) Version 3.4 = MAKEWORD(3 /*주버젼*/,4 /*부버젼*/) =  0x0403 

lpWSAData : WSADATA 타입의 변수 포인터.


Winsock 해제하기

#include<winsock2.h>

int WSACleanup();


기본적인 Template

int main(int argc, char **argv)

{

    WSADATA wsaData;

    if(WSAStartup(MAKEWORD(2,2), &wsaData) !=0)

         error_handling("WSAStartup() error!");

    ...

    WSACleanup();

    return 0;

}


소켓의 생성

#include<winsock2.h>

SOCKET socket(int af, int type, int protocol);

리턴 : (정수형 데이터 타입)성공시 소켓 핸들, 실패시 INVALID_SOCKET 리턴.


주소와 포트 할당

#include<winsock2.h>

int bind(SOCKET s, const struct sockaddr FAR *name, int namelen);

리턴 : 성공시 0, 실패시 SOCKET_ERROR


연결요청대기상태로의 진입

#include<winsock2.h>

int listen(SOCKET s, int backlog);

리턴 : 성공시 0, 실패시 SOCKET_ERROR


연결 수락

#include<winsock2.h>

SOCKET accept(SOCKET s, struct sockaddr FAR *addr, int FAR *addrlen);

리턴 : (정수형 데이터 타입)성공시 소켓 핸들, 실패시 INVALID_SOCKET 리턴.


연결 요청

#include<winsock2.h>

int connect(SOCKET s, const struct sockaddr FAR *name, int namelen);

리턴 : (정수형 데이터 타입)성공시 소켓 핸들, 실패시 INVALID_SOCKET 리턴.


데이터 출력

#include<winsock2.h>

int send(SOCKET s, const char FAR *buf, int len, int flags);

리턴 : 성공시 전송한 바이트 수, 실패시 SOCKET_ERROR

s : 전송할 호스트에 연결된 소켓의 핸들

buf : 전송할 데이터를 저장하고 있는 버퍼의 포인터

len : 전송할 바이트 수를 인자로 전달

flags : 여러가지 옵션을 설정.


데이터 입력

#include<winsock2.h>

int recv(SOCKET s, char FAR *buf, int len, int flags);

리턴 : 성공시 수신한 바이트 수, 실패시 SOCKET_ERROR

s : 수신할 영역을 나타내는 소켓의 핸들.

buf : 수신할 데이터를 저장한 버퍼의 포인터

len : 수신할 최대 바이트수

flags : 여러가지 옵션을 설정.


※ 리눅스에도 send, recv함수가 있지만 리눅스에서는 소켓도 파일로 처리한다는 것을 강조하기 위해 read, write함수를 사용하였고 윈도우즈에서는 read, write함수가 없기 때문에 send, recv함수로 소켓 입출력을 수행한다.

반응형

+ Recent posts