컴퓨터 공부/네트워크 프로그래밍

[TCP/IP 소켓 프로그래밍] 1. 네트워크 프로그래밍과 소켓의 이해

려리군 2009. 7. 2. 18:31

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함수로 소켓 입출력을 수행한다.