[제14장] 소켓

정보 | 2007/06/25 19:08

=================================================================
  * Subject : [제14장] 소켓
  * Writer: w0rm9 (research.hackerschool.org)
  * Date: 2004/02/20
=================================================================

/* 책에 있는 대로 안하고, 스터디했던 로그를 중심으로 몽이님 예제소스를 분석하고,
 * 함수들을 알아보는 형식으로 정리했습니다.
 */


0x01. 해커스쿨의 25번 포트로 접속한 후 sendmail의 배너를 받아오는 프로그램

[w0rm9@work SOCKET]$ cat get_banner.c
#include <stdio.h>
#include <unistd.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>

int main()
{
        int sockfd, ret, length;
        struct sockaddr_in target_addr;
        char data[100];

        sockfd = socket(AF_INET, SOCK_STREAM, 0);    // 1. 새로운 소켓 생성
          // 2. 접속할 목표 호스트의 정보를 지정
        target_addr.sin_family = AF_INET;
        target_addr.sin_port = htons(25);     // 3. htons() 리틀 엔디안 값을 빅 엔디안 방식의 값으로 바꿔주는 역할
        target_addr.sin_addr.s_addr = inet_addr("211.189.88.58");  
        memset(&target_addr.sin_zero, 0, sizeof(target_addr.sin_zero));

        ret = connect(sockfd, (struct sockaddr *)&target_addr, sizeof(target_addr)); // 4. 접속 요청
        if(ret < 0){
                fprintf(stderr, "making socket error\n");
                exit(-1);
        }

        length = recv(sockfd, data, 100, 0);     // 5. 데이터를 받아옴


        printf("I received : %s\n", data);     // 6. 출력

        close(sockfd);
}

[w0rm9@work SOCKET]$ gcc -o get_banner get_banner.c
[w0rm9@work SOCKET]$ ./get_banner
I received : 220 localhost.localdomain ESMTP Sendmail 8.12.8/8.11.6; Fri, 20 Feb 2004 11:04:53 +0900
[@pc@

■ 소켓 생성하기
사용법)
#include <sys/types.h>
#include <sys/socket.h>

int socket(int domain, int type, int protocol);
=> 소켓을 생성하고, 소켓을 액세스하기 위해 사용할 수 있는 기술자를 반환
   domain은 어드레스 계열을 지정, type는 소켓과 함께 사용할 통신 형태 지정, procotol은 사용할 프로토콜을 지정

□ 어드레스 계열
 AF_UNIX  유닉스 기본의 파일 체계 소켓
 AF_INET  ARPA 인터넷 프로토콜(유닉스 네크워트 소켓)
 AF_ISO  ISO 표준 프로토콜
 AF_NS  Xerox 네트워크 시스템 프로토콜
 AF_IPX  Novell IPX 프로토콜
 AF_APPLETALK Applettalk DDS

□ 통신 형태
 SOCK_STREAM 순차적이고 신뢰적이고 접속 기반의 양방향 바이트 스트림
 SOCK_DGRAM 작고 고정된 최대 크기의 메시지를 전달하기 위해 사용, 메시지가 전달되거나 네트워크에서 재요청되지 않을 것임을 보장할 수는 없다.

■ 소켓 어드레스
사용법)
□ AF_UNIX 소켓
struct sockaddr_un {
 sa_family_t  sun_family; // AF_UNIX
 char   sun_path[];
};

□ AF_INET 소켓
struct sockaddr_in {
 short int   sin_family; // AF_INET
 unsigned short int sin_port; // 포트 번호
 struct in_addr  sin_addr; // 인터넷 어드레스
};

struct in_addr {
 unsigned long int s_addr;
};

■ 접속 요청하기
사용법)
#include <sys/socket.h>

int connect(int socket, const struct sockaddr *address, size_t address_len);
=> 클라이언트 프로그램의 소켓과 서버의 소켓 사이에 접속을 설립하여 서버에 접속한다.


0x02. 250포트에 접속하면 sendmail의 배너를 출력하는 프로그램

[w0rm9@work SOCKET]$ cat server.c
#include <stdio.h>
#include <unistd.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>

int main()
{
        int my_sockfd, your_sockfd, length;
        struct sockaddr_in my_address, your_address;
        char data[] = "220 localhost.localdomain ESMTP Sendmail 8.12.8/8.11.6; Thu, 19 Feb 2004 23:15:06 +0900";

        my_sockfd = socket(AF_INET, SOCK_STREAM, 0);    // 서버 소켓 생성

        my_address.sin_family = AF_INET;
        my_address.sin_port = htons(2004);
        my_address.sin_addr.s_addr = INADDR_ANY;
        memset(&my_address.sin_zero, 0, sizeof(my_address.sin_zero));

        bind(my_sockfd, (struct sockaddr *)&my_address, sizeof(my_address)); // 소켓 명명

        listen(my_sockfd, 5);       // 소켓 큐 생성

        length = sizeof(your_address);

        your_sockfd = accept(my_sockfd, (struct sockaddr *)&your_address, &length);  // 접속을 받아들이기 위해 새로운 소켓 생성
        printf("Connected from : %s\n", inet_ntoa(your_address.sin_addr.s_addr));
        send(your_sockfd, data, sizeof(data), 0);     // 데이타 전달
        close(your_sockfd);
        close(my_sockfd);
}
[w0rm9@work SOCKET]$ gcc -o server server.c
[w0rm9@work SOCKET]$ ./server &
[1] 19516
[w0rm9@work SOCKET]$ telnet localhost 2004
Trying 127.0.0.1...
Connected from : 127.0.0.1
Connected to localhost.
Escape character is '^]'.
220 localhost.localdomain ESMTP Sendmail 8.12.8/8.11.6; Thu, 19 Feb 2004 23:15:06 +0900Connection closed by foreign host.
[1]+  Done                    ./server
[w0rm9@work SOCKET]$


■ 소켓 명명하기
사용법)
#include <sys/socket.h>

int bind(int socket, const struct sockaddr *address, size_t address_len);
=> 생성한 소켓을 다른 프로세스에서 사용할 수 있도록 만들기 위해 소켓에 이름을 줌

■ 소켓 큐 생성하기
사용법)
#include <sys/socket.h>

int listen(int socket, int backlog);
=> 소켓에서 전달된 접속을 받아들이기 위해 남아있는 요청을 저장하기 위한 큐를 생성

■ 접속 받아들이기
사용법)
#include <sys/socket.h>

int accept(int socket, struct sockaddr *address, size_t *address_len);
=> 소켓에 대해 접속을 수행할 수 있도록 기다림
   클라이언트와 통신하는 새로운 소켓을 생성하고, 이것을 기술자로 반환


0x03. 멀티 서버

[w0rm9@work SOCKET]$ cat multi_server.c
#include <stdio.h>
#include <unistd.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>

int main()
{
        int my_sockfd, your_sockfd, length;
        struct sockaddr_in my_address, your_address;
        char data[] = "220 localhost.localdomain ESMTP Sendmail 8.12.8/8.11.6; Thu, 19 Feb 2004 23:15:06 +0900";

        my_sockfd = socket(AF_INET, SOCK_STREAM, 0);

        my_address.sin_family = AF_INET;
        my_address.sin_port = htons(2004);
        my_address.sin_addr.s_addr = INADDR_ANY;
        memset(&my_address.sin_zero, 0, sizeof(my_address.sin_zero));

        bind(my_sockfd, (struct sockaddr *)&my_address, sizeof(my_address));

        listen(my_sockfd, 5);

        length = sizeof(your_address);

        while(1){          // 무한루프
                your_sockfd = accept(my_sockfd, (struct sockaddr *)&your_address, &length);  // 접속을 받아들이기 위한 소켓 생성
                printf("Connected from : %s\n", inet_ntoa(your_address.sin_addr.s_addr));  
                if(fork()==0){
                        send(your_sockfd, data, sizeof(data), 0);    // 자식프로세스가 데이타 전달
                        close(your_sockfd);
                        close(my_sockfd);
                        exit(0);
                }
                else
                        close(your_sockfd);
        }
}
[w0rm9@work SOCKET]$ gcc -o multi_server multi_server.c
[w0rm9@work SOCKET]$ ./multi_server &
[1] 19525
[w0rm9@work SOCKET]$ telnet localhost 2004
Trying 127.0.0.1...
Connected from : 127.0.0.1
Connected to localhost.
Escape character is '^]'.
220 localhost.localdomain ESMTP Sendmail 8.12.8/8.11.6; Thu, 19 Feb 2004 23:15:06 +0900Connection closed by foreign host.
[w0rm9@work SOCKET]$
[w0rm9@work SOCKET]$ telnet localhost 2004
Trying 127.0.0.1...
Connected from : 127.0.0.1
Connected to localhost.
Escape character is '^]'.
220 localhost.localdomain ESMTP Sendmail 8.12.8/8.11.6; Thu, 19 Feb 2004 23:15:06 +0900Connection closed by foreign host.


0x04. 채팅 프로그램

[w0rm9@work SOCKET]$ cat chat.c
#include <stdio.h>
#include <unistd.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <pthread.h>

void *func(void *arg){
        char data[80];
        char my_msg[100];
        int your_sockfd;

        your_sockfd = (int)arg;

        while(1){
        fgets(data, 80, stdin);
        sprintf(my_msg, "My message : %s", data);     // 내가 입력한 값
        printf("%s\n", my_msg);
        send(your_sockfd, my_msg, strlen(my_msg), 0);     // 입력값을 클라이언트에게 전달
        }
}

int main()
{
        fd_set fds, saved_fds;
        struct timeval time_value;

        int my_sockfd, your_sockfd, length;
        struct sockaddr_in my_address, your_address;
        char data[] = "220 localhost.localdomain ESMTP Sendmail 8.12.8/8.11.6; Thu, 19 Feb 2004 23:15:06 +0900";
        char my_msg[100], your_msg[100];
        pthread_t thread_id;

        my_sockfd = socket(AF_INET, SOCK_STREAM, 0);     // 소켓 생성

        my_address.sin_family = AF_INET;
        my_address.sin_port = htons(2004);
        my_address.sin_addr.s_addr = INADDR_ANY;
        memset(&my_address.sin_zero, 0, sizeof(my_address.sin_zero));

        if(bind(my_sockfd, (struct sockaddr *)&my_address, sizeof(my_address))<0){ // 소켓 명명
                fprintf(stderr, "I can't bind socket\n");
                exit(-1);
        }

        listen(my_sockfd, 5);        // 소켓 큐 생성

        length = sizeof(your_address);

        your_sockfd = accept(my_sockfd, (struct sockaddr *)&your_address, &length);  // 접속 받아들이기
        printf("Connected from : %s\n", inet_ntoa(your_address.sin_addr.s_addr));

        pthread_create(&thread_id, 0, func, (void *)your_sockfd);   // 스레드 생성

        FD_ZERO(&fds);
        FD_SET(your_sockfd, &fds);       // fds를 클라이언트의 입력으로 설정

        saved_fds = fds;

        time_value.tv_sec = 10;        // 10초로 지정함
        time_value.tv_usec = 0;

        while(1){

                fds = saved_fds;
                time_value.tv_sec = 10;

                if(select(your_sockfd+1, &fds, 0, 0, &time_value) == 0){  // 10초간 클라이언트의 입력이 없으면(읽기 상태)
                        printf("클라이언트가 잠수를 타다니!\n");   // 10에서 부터 1씩 감소함.
                        close(your_sockfd);
                        close(my_sockfd);
                        exit(0);
                }

                printf("passed : %d\n", time_value.tv_sec);    // 감소되고 남은 시간을 나타냄

                length = recv(your_sockfd, your_msg, 100, 0);    // 클라이언트의 입력값을 받음
                your_msg[length-1] = 0;
                sprintf(data, "Your message : %s", your_msg);
                printf("%s\n", data);
                send(your_sockfd, data, strlen(data), 0);    // 클라이언트의 입력값을 클라이언트에게 전달
}
        close(your_sockfd);
        close(my_sockfd);
}

■ select
사용법)
#include <sys/types.h>
#include <sys/time.h>

int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *errorfds, struct timeval *timeout);
=> 파일 기술자 모음의 어떤 것이 읽기나 쓰기 상태로 준비되어 있는지, 또는 준비될 때까지 에러 조건을 가지고 선택적으로 방지되는지 테스트하는데 사용
   nfds는ㄴ 테스트할 파일 기술자의 수, readfds에서는 읽기 상태로 준비되거나, writefds에서는 쓰기 상태로 준비되거나, errorfds에서는 에러 조건을 가지면 반환
   timeout는 지정된 시간후에 반환을 지정

□ 구조체를 다루기 위한 매크로
#include <sys/types.h>
#include <sys/time.h>

void FD_ZERO(fd_set *fdset);   // 초기화
void FD_CLR(int fd, fd_set *fdset);  // 기술자 모음의 요소를 삭제
void FD_SET(int fd, fd_set *fdset);  // 기술자 모음의 요소를 설정
void FD_ISSET(int fd, fd_set *fdset);  // fd가 가리키는 파일 기술자가 fdset이 가리키는 fd_set의 요소라면 0이 아닌값 반환

□ struct timeval 멤버
struct timeval {
 time_t tv_set;  // 초 단위
 long tv_usec; // 밀리 초 단위
}

/* 테스트는 직접 :)

_eof_

Trackback Address :: http://badnom.com/trackback/296 관련글 쓰기
Name
Password
Homepage
Secret
< PREV |  1  |  ...  823  |  824  |  825  |  826  |  827  |  828  |  829  |  830  |  831  |  ...  1058  |  NEXT >