내장형 하드웨어/TCP/IP

TCP 프로토콜을 이용산 소켓 프로그래밍, udpecho_server/client

동화다아아 2011. 7. 5. 18:01

- TCP 프로토콜을 이용산 소켓 프로그래밍

server

clinet

서버에 소켓 생성(client와 연결을 위해 필요한 소켓) - socket()

서버에 주소 할당 - bind()

연결 요청 대기모드로 설정 - listen()

클라이언트 연결 요청시 새로운 소켓 생성 - accept()

송(write(), send()) / 수신(read(), recv())

클라이언트 소켓 닫기 - close()

소켓 생성 - socket()

 

 

 

연결 요청(접속할 서버의 IP, PORT) - connect()

송(write(), send()) / 수신(read(), recv())

소켓 닫기  - close()

        

⇒ TCP → 연결형 프로토콜(전화와 비슷)

        → 두 시스템 간 연결이 성공한 후에 데이터 전송 시작함                 

                                                                             (3-way handshaking)

        → 연결 요청이 왔을 때 위와 같이 3번 이동하며 연결한다.

        

1) 송신 호스트가 SYN Flag값을 1로 설정한 TCP packet과 임의의 Sequence Number를 수신 호스트로 보낸다.

        Ex)  송신  -------------------------->  수신

                        SYN=1, SEQ=J

2) 수신 호스트가 Session성립을 원하면 SYN Flag를 1로 설정하고 ACK를 송신 호스트가 보낸 SEQ번호의 다음 번호로 정하고 수신 호스트에 따로 설정한 SEQ번호를 보낸다.

        Ex)  송신  <--------------------------  수신

                        SYN=1, ACK=J+1, SEQ=K

3) 송신 호스트는 ACK 값을 수신지 호스트가 보낸 SEQ번호의 다음 번호로 정하여 수신 호스트에 보낸다.

        Ex)  송신  -------------------------->  수신

                        ACK=K+1

        → 신뢰성 보장(순차적 데이터 전달, 데이터 재전송)

        → 데이터 경계를 구분하지 않는다.


⇒ UDP → 비연결형 프로토콜(편지와 비슷)

        → 연결 없이 통신 가능

        → 비신뢰적인 데이터 전송(전송순서에 상관없이 가장 빠른 전송 지향, 전송도중 데이터가 손실되어도 재전송 되지 않음.)

        → 데이터의 경계를 구분함(Datagram 서비스)



⇒ 연결형 서버 → TCP 프로토콜을 이용, TCP 소켓을 사용한다.

   비연결형 서버 → UDP 소켓 사용. (연결 요청을 하지 않는다.)

⇒ iterative server → 순서대로 client와 송/수신

                     → client와 서비스 시간이 짧은 경우에 사용한다.

   concurrent server → server와 client 서비스 시간이 불균형 적일 때 사용한다.

⇒ client 연결 요청 간 새로운 소켓 생성(accept())부터 client 소켓 닫기(close()) 까지를 반복시키면 iterative server가 된다.



// helloworld_iterative_server.c에서 연결 요청 수락 부분에 반복문 사용을 통한 iterative server 만들기
     

        // 소켓에 주소 할당

        if( bind(serv_sock, (struct sockaddr*)& serv_addr, sizeof(serv_addr)) == -1)

        {

                error_handling("bind() error");

        }

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

        if(listen(serv_sock, 5== -1)

        {

                error_handling("listen() error");

        }       

        // 연결 요청 수락

        clnt_addr_size = sizeof(clnt_addr);       

        for(;;) // iterative 서버를 만들기 위해 반복한다.

        {

        clnt_sock = accept(serv_sock, (struct sockaddr*)&clnt_addr, &clnt_addr_size);

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

        serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);

        // IP주소, INADDR_ANY - 사용중인 ip의 주소를 자동으로 찾아준다.

        // htonl() - little endian 방식을 big endian 방식으로 바꾸어 준다.
     printf("%s\n", inet_ntoa(clnt_addr.sin_addr)); // IP 주소 출력 

        printf("%d\n", ntohs(clnt_addr.sin_port)); // PORT 번호 출력 
     
if(clnt_sock == -1)

        {

                error_handling("accept() error");

        }

        // 데이터 전송

        write(clnt_sock, message, sizeof(message));

        sleep(5);

        // 연결 종료

        close(clnt_sock);
     }

        return 0;
}
void error_handling(char *message)
{
        fputs(message, stderr);
        fputc(
'\n', stderr);
        exit(
1);
}  

⇒ iterative server 만들기에서 client 소켓 닫기를 하지 않으면 생성된 소켓이 계속 남아있게 된다.

⇒ write() / read() 는 send() / recv()로 바꾸어 사용할 수 있다. 단, linux에서만 가능.

⇒ send()와 recv()에 대해 살펴보면(man 2 send 명령으로도 찾을 수 있다.)

 → send() 호출의 선언은 아래와 같다.

    int send(int sockfd, const void *msg, int len, int flags);

 → sockfd는 socket()를 통해서 얻었거나 accept()를 통해서 새로 구한, 데이터를 보낼 소켓의 기술자, msg는 보낼 데이터를 가리키는 포인터, len은 보낼 데이터의 바이트 수,  flags는 대부분의 경우 0으로 해야 한다.

 → recv() 호출의 선언은 아래와 같다.

    int recv(int sockfd, void *buf, int len, unsigned int flags);

 → sockfd는 읽어올 소켓의 기술자이며 buf는 정보를 담을 버퍼. len은 버퍼의 최대 크기, flags는 대부분의 경우 0으로 세팅해야 한다.

⇒ write(clnt_sock, message, sizeof(message));

 → send(cnt_sock, message, sizeof(message), 0); 으로 바꾸어 쓸 수 있다.



- udpecho_server/ udpecho_client 테스트

⇒ udpecho_client         

 

⇒ udpecho_server