Tcp server / client

Preview:

Citation preview

TCP Server / client

NHN NEXT 장문익

TCP server / client 동작방식

서버는먼저실행하여클라이언트가접속하기를기다린다.(listen)

클라이언트가서버에접속(connect)하여 데이터를보낸다.(send)

서버는클라이언트접속을수용하고(accept), 클라이언트가보낸데이터를받아서(recv)처리한다.

서버는처리한데이터를클라이언트에보낸다.(send)

클라이언트는서버가보낸데이터를받아서(recv) 자신의목적에맞게사용한다.

TCP server / client 동작원리

서버는소켓을생성한후클라이언트가접속하기를기다린다. 이때서버가사용하는소켓은특정포트번호와 bind되어있어서이포트번호로접속하는클라이언트만수용할수있다.

클라이언트가접속한다. TCP 프로토콜수준에서연결설정을위한패킷교환이이루어진다.

프로토콜수준의연결절차가끝나면, 서버는접속한클라이언트와통신할수있는새로운소켓을생성한다. 기존소켓은새로운클라이언트접속을수용하는용도로계속사용한다.

서버함수

socket() 함수를이용하여소켓을생성한다.

bind() 함수를이용하여지역IP 주소와지역포트번호를결정한다.

listen() 함수를이용하여 TCP 상태를 LISTENING으로변경한다.

accept() 함수를이용하여자신에접속한클라이언트와통신할수있는새로운소켓을생성한다.

이때, 원격IP 주소와원격포트번호가결정된다.

send(), recv() 등의데이터전송함수를이용하여클라이언트와통신을수행한다.

closesocket() 함수를이용하여소켓을닫는다.

bind() 함수

int bind(

SOCKET s,

const struct sockaddr* name,

int namelen

);

s: 클라이언트접속을수용할목적으로만든소켓

name: 소켓주소구조체변수를지역 IP 주소와지역포트번호로초기화한후, 이변수의주소값을여기에대입한다.

namelen: 소켓주소구조체변수의길이를대입한다.

bind() 함수예시

SOCKADDR_INserveraddr; // 소켓주소구조체변수선언

ZeroMemory(&serveraddr, sizeof(serveraddr)); // ZeroMemory로초기화

serveraddr.sin_family = AF_INET; // 주소체계는 AF_INET

serveraddr.sin_port = htons(9000); // 서버의지역포트번호는 9000

serveraddr.sin_addr.s_addr = htonl(INADDR_ANY); // 서버의경우특정 IP 주소보다는

// INADDR_ANY 값을사용하는것이바람직하다.

// 서버가두개이상의 IP 주소를가진경우,

// INADDR_ANY 값을지역주소로설정하면

// 클라이언트가어느주소로접속하든처리가능

retval = bind(listen.sock, (SOCKADDR*)&serveraddr, sizeof(serveraddr)); // bind() 함수

// 두번째인자는항상 (SOCKADDR *)로 cast

if(retval == SOCKET_ERROR) err_quit("bind()");

listen() 함수

int listen(

SOCKET s,

int backlog

);

s: 클라이언트접속을수용할목적으로만든소켓, bind() 함수에의해지역IP 주소와지역포트번호가설정된상태

backlog: 서버가당장처리하지않더라도접속가능한클라이언트개수. 클라이언트의접속정보는 connection queue에저장되며,

backlog는 connection queue의길이를나타낸다. 지원가능한최대값을사용하려면SOMAXCONN을대입한다.

listen() 함수예시

retval = listen( listen_sock, SOMAXCONN );

if ( retval == SOCKET_ERROR ) err_quit( "listen()" );

listen_socket에클라이언트의접속을받는다.

backlog는최대값인 SOMAXCONN로설정

accept() 함수

SOCKET accept(

SOCKET s,

struct sockaddr* addr,

int * addrlen

);

s: 클라이언트접속을수용할소켓

addr: 소켓주소구제체변수를정의한후,

accept() 함수는 addr이가리키는메모리영역을클라이언트의 IP 주소와포트번호로채워넣는다.

addrlen: addr이가리키는메모리영역의크기로초기화한후, 이변수의주소값을addrlen에저장한다.

accept() 함수예시

// 데이터통신에사용할변수

SOCKET client_sock;

SOCKADDR_IN clientaddr;

int addrlen;

while ( 1 ) { // 서버는계속클라이언트요청을 accept() 해야하므로무한루프

// accept()

addrlen = sizeof( clientaddr );

client_sock = accept( listen_sock, ( SOCKADDR * )&clientaddr, &addrlen);

if ( INVALID_SOCKET == client_sock ) {

err_display( "accept()" );

continue;

}

클라이언트함수

socket() 함수를이용하여소켓을생성한다.

connect() 함수를이용하여서버에접속한다.

send(), recv() 등의데이터전송함수를이용하여서버와통신을수행한후, closesocket() 함수를이용하여소켓을닫는다.

socket(), closesocket(), recv()는 서버와공통

connet()는 유일한클라이언트함수이다.

connect() 함수

int connect(

SOCKET s,

const struct sockaddr* name,

int namelen

);

s: 서버와통신을하기위해만든소켓

name: 소켓주소구조체변수를서버주소로초기화한후, 이변수의주소값을여기에대입

namelen: 소켓주소구조체변수의길이를대입한다.

서버와달리 bind() 함수를호출하지않아도connect() 함수를호출하면운영체제는자동으로지역 IP 주소와지역포트번호를설정

connect() 함수예시

SOCKADDR_IN serveraddr;

serveraddr.sin_family = AF_INET;

serveraddr.sin_port = htons( 9000 );

serveraddr.sin_addr.s_addr = inet_addr( "127.0.0.1" );

retval = connect( sock, (SOCKADDR *)&serveraddr, sizeof( serveraddr ) );

if ( SOCKET_ERROR == retval ) error_quit ( "connect()" );

데이터전송함수

send()

recv()

WSA*() 형태의확장함수

소켓버퍼(socket buffer)

송신버퍼(send buffer): 데이터를전송하기전에임시로저장해두는영역

수신버퍼(receive buffer): 받은데이터를애플리케이션이처리하기전까지임시로저장해두는영역

send() 함수

애플리케이션데이터를송신버퍼에복사함으로써궁극적으로하부프로토콜(TCP/IP)에의해데이터가전송되도록한다.

데이터복사가성공적으로이루어지면곧바로리턴하므로 send() 함수가성공했다고실제데이터전송이완료된것은아니다.

send() 함수

int send(

SOCKET s,

const char* buf,

int len,

int flags

);

s: 통신할대상과 연결된소켓

buf: 보낼데이터를담고있는애플리케이션버퍼주소

len: 보낼데이터의크기

flags: send() 함수의동작을바꾸는옵션

send() 함수의리턴

blocking socket

송신버퍼의여유공간이 send() 함수의세번째인자인 len보다작을경우해당프로세스튼wait state가된다.

송신버퍼에충분한공간이생기면프로세스는깨어나고, len 크기만큼데이터복사가이루어진후 send() 함수가리턴한다.

이경우 send() 함수의리턴값은 len

Nonblocking socket

송선버퍼의여유공간만큼데이터를복사한후실제복하한데이터바이트수를리턴한다.

이경우 send() 함수의리턴값은최소 1, 최대len이된다.

recv() 함수

수신버퍼에도착한데이터를애플리케이션버퍼로복사하는역할을한다.

recv() 함수

int recv(

SOCKET s,

char* buf,

int len,

int flags

);

s: 통신할대상과연결된소켓

buf: 받은데이터를저장할애플리케이션버퍼의주소

len: 수신버퍼로부터복사할최대데이터크기.

이값은 buf가가리키는버퍼의크기보다작아야한다.

flags: recv() 함수의동작을바꾸는옵션

recv() 함수의리턴

수신버퍼에데이터가도달한경우

recv() 함수의세번째인자인 len보다크지않은범위내에서가능한많은데이터를애플리케이션버퍼로복사한다.

이경우복사한바이트수가리턴되며, 가능한최대리턴값은 len

접속이정상종료된경우

상대애플리케이션이 closesocket() 함수를사용하여접속을종료하면, TCP 프로토콜수준에서접속종료를위한패킷교환절차가이루어진다.

이경우 recv()함수는 0을리턴

recv()함수의 리턴값이 0인경우 normal

close

recv() 함수사용시주의할점

세번째인자인 len으로지정한크기보다작은데이터가애플리케이션버퍼로복사될수있다.

이는 TCP가메시지경계를구분하지않는다는특성에기인한다.

따라서자신이받을데이터의크기를미리알고있다면, 이크기만큼받을때까지 recv() 함수를여러번호출해야한다.

recv() 함수를이용한사용자정의함수

int recvn( SOCKET s, char* buf, int len, int flags )

{

int received;

char* ptr = buf; // 애플리케이션버퍼의시작주소, 데이터를읽을때마다 prt 변수는증가

int left = len; // 아직읽지않은데이터크기

while ( left > 0 ) {

received = recv( s, ptr, left, flags );

if ( received == SOCKET_ERROR )

return SOCKET_ERROR;

else if ( received == 0 ) // normal close

break;

left -= received;

ptr += received;

}

return ( len - left ); // normal close 경우를제외하고 left는 0, 리턴값을 len이된다.

}

Recommended