본문 바로가기

IT/Linux(Unix)

리눅스 프로그래밍 - 커널 소스 분석 ( 소켓, 서버 )

반응형



1. socket

int socket(int family, int type, int protocol);
family : 인터넷 프로토콜 체계 명명
type : soket type
protocol : TCP, UPD 이런 것 중 어느 것을 사용 할 지

socket 사용 코드

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <errno.h>
#include <string.h>
#include <sys/stat.h>
#include <fcntl.h>

void err_quit ( char *msg)
{

    printf ("socket error\n");
    printf ("errno=%d, %s\n",errno, strerror(errno) );
    perror (msg);
    exit (0);
}
int Socket(int domain, int type, int protocol)
{
    int sd;
    /* soket input
     * 1st : 인터넷 프로토콜 버전
     * 2st : 소켓 형태
     */
    sd = socket(AF_INET, SOCK_STREAM, 0);
    if ( sd < 0 )
    {
        err_quit ("socket()");
    }
}
int main()
{
    int fd;
    int sd;
    fd = open ("/etc/password", O_RDONLY, 0);
    sd = Socket(AF_INET, SOCK_STREAM, 0);
    if ( sd < 0 )
    {
        err_quit ("socket()");
    }
    printf ("fd=%d\n",fd);
    printf ("sd=%d\n",sd);
    return 0;

}


socket을 통해 구글 사이트 가져오기 ( client )

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <errno.h>
#include <string.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <arpa/inet.h>
#include <netinet/in.h>
 #include <unistd.h>
int main()
{
    int fd;
    int sd;
    int nret;
    char buf[1024];
    struct sockaddr_in addr;

    addr.sin_family = AF_INET;
    addr.sin_port = htons(80);
    addr.sin_addr.s_addr = inet_addr("142.250.76.142");

    /* network에서 사용 하는 초기화 함수 */
    bzero (addr.sin_zero, 8);

    sd = socket(AF_INET, SOCK_STREAM, 0);
    printf ("sd = %d\n",sd);

    /* 구글에 connect */
    nret = connect( sd,(struct sockaddr*)&addr, sizeof(addr) );
    printf ("connct nret = %d\n",nret);

    write ( sd, "GET /\n\n", 7);

    nret = read (sd, buf, sizeof(buf) );
    printf ("read nret = %d\n",nret);
    while ( nret = read ( sd, buf, sizeof(buf) ) )
    {
        write ( 1, buf , nret );

    }
    close(sd);
    return 0;

}


echo 서버 만들기 ( Server )

/* server */
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <errno.h>
#include <string.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <unistd.h>

int main()
{
    int fd;
    int sd;
    int new_sd;
    int nret;
    char buf[1024];
    struct sockaddr_in addr;

    addr.sin_family = AF_INET;
    addr.sin_port = htons(7777);
    addr.sin_addr.s_addr = inet_addr("001.001.01.001");

    /* network에서 사용 하는 초기화 함수 */
    bzero (addr.sin_zero, 8);

    sd = socket(AF_INET, SOCK_STREAM, 0);
    printf ("sd = %d\n",sd);

    /* 생성된 소켓에 Local Prtocol Address(Ip+Port)를 지정 */
    nret = bind(sd , ( struct sockaddr *) &addr, sizeof ( addr ));
    printf ("nret = %d\n",nret);

    /* 대기듕 */
    nret = listen ( sd, 5);
    printf ("nret = %d\n",nret);

    while ( ( new_sd = accept(sd , NULL, NULL ) ) >= 0 )
    {
        /* complete queue에 대기중인 연결요청에 대해 새로운 소켓을 만듬 */
        /* 대기중인 연결요청 항목이 없는 경우 block */
        printf ("new_sd = %d\n",new_sd);

        while ( nret = read(new_sd, buf, sizeof ( buf ) ) )
        {
            write ( new_sd , buf , nret );;
        }
        close (new_sd);
    }

    close (sd);


    return 0;

}

/* client */
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <errno.h>
#include <string.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <unistd.h>
int main()
{
    int fd;
    int sd;
    int nret;
    char buf[1024];
    struct sockaddr_in addr;

    addr.sin_family = AF_INET;
    addr.sin_port = htons(7777);
    addr.sin_addr.s_addr = inet_addr("001.001.01.001");

    /* network에서 사용 하는 초기화 함수 */
    bzero (addr.sin_zero, 8);

    sd = socket(AF_INET, SOCK_STREAM, 0);
    printf ("sd = %d\n",sd);

    nret = connect( sd,(struct sockaddr*)&addr, sizeof(addr) );
    printf ("connct nret = %d\n",nret);

    while (1)
    {
        nret = read (0, buf, sizeof(buf) ); /* 키보드입력을 받음 */
        buf[nret-1] = 0;
        if ( strcmp ( buf, "exit" ) == 0 )
            break;
        write ( sd, buf, nret );
        nret = read ( sd , buf, sizeof ( buf ) );
        write ( 1, buf, nret );
        printf ("read nret = %d\n",nret);
    }
    close(sd);
    return 0;

}


* bind

* listen

* accep
용도
- complete queue에 대기중인 연결요청에 대해 새로운 소켓을 만듬
- 대기중인 연결요청 항목이 없는 경우 block된다

왜 새로운 소켓을 만드는가?
- 연결용 소켓(listen socket)과 데이터 처리용 소켓이 따로 필요하기 때문.
- listen socket ( client의 정보가 지정되어 있지 않은 소켓 - daddr : 0, dport : 0)
- 새로운 client의 최초 socket의 경우 반드시 listen socket으로 처음 등장한다.
- 3 way handshaking을 하는 동안 daddr과 dport가 지정된 새로운 소켓이 생성 된다.



2. fork
fork - create a child process
단 부모와 동일한 프로그램 주소를 갖고 있다.
parent와 child 둘이 동시성을 갖을 수 있다.
return : child - 0, parent - 0보다 큰 값 ( = PID )
child process die, parent process live, child의 자원이 모두 해체되지 않음 -> zombie process

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>

void my_sig(int signo)
{
	int status;
	int pid;
    /* child가 10000개 이럴경우 너무 많은 child가 한번에
     * 죽게 되면서 parent들을 다 죽이지 못함 */
    /* 죽어야 하는 parent가 여럿일 경우를 위해 loop를 설정 */
    //while ( ( pid = wait(&status) ) > 0 ) // 자식중에 살아있는 프로세스가 있다면 동시성을 잃게 됨
	while( (pid = waitpid(-1, &status, WNOHANG)) > 0 ) //nonblocking 지원
		printf("my_sig(%d), pid=%d\n", signo, pid);
}

int main()
{
	int i,j;
	int pid;

	signal(SIGCHLD, my_sig);
	for(i=0; i<5; i++)
	{
		pid = fork();
		if (pid == 0 )
		{
			for(j=0; j<i+1; j++)
			{
				printf("\t\t\tchild\n");
				sleep(1);
			}
			exit(3);
		}
	}

	while(1)
	{
		printf("parent\n");
		sleep(1);
	}
	return 0;
}

exit - 프로세스를 죽이고 프로세스의 exit_code에 값을 남긴다.

wait - 프로세스의 상태를 확인, child가 죽을 때까지 대기. 자식 프로세스가 종료되었다면 함수는 즉시 리턴되며, 자식이 사용한 모든 시스템자원을 해제한다.


3. 동시 접속 가능 서버 만들기 ( nonblock )
blocking : 특정이벤트가 있을 때까지 자신의 동작을 멈춤.
server로 치면 accept가 끝나고 read로 넘어가면 accept가 blocking 상태로 변하게 된다.
ex : A가 채팅 하고, B가 채팅 하는 것은 가능. 그러나 A가 여러번 채팅 하는 것이 불가능 하다. A가 채팅을 1회 하고, A는 blocking 되었기 때문에 동작을 멈춘 상태.
신규 client가 accept 하는 것이 불가한 상태. -> nonblock이 되어야 함. fcntl()를 활용!

이렇게 nonblock을 사용하는 경우 무척, 퍼포먼스가 떨어진다. -> multi process로 해결

[fork server 만들기]
multi process
자식 프로세스에서 한줄 read하고, 자식 프로세스를 바로 죽임 -> 반복, 마치 nonblocking 처럼 작동

#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <signal.h>
#include <sys/wait.h>

void my_sig(int signo)
{
	int status;
	int pid;
	while( (pid=waitpid(-1, &status, WNOHANG )) > 0 )
		printf("my_sig(%d), pid=%d\n", signo, pid );
}

int main()
{
	int fd;
	int sd, new_sd;
	int ret;
	char buff[1024];
	struct sockaddr_in addr;
	struct sockaddr_in client_addr;
	int addrlen;
	int optval;

	signal(SIGCHLD, my_sig);
	addr.sin_family = AF_INET;
	addr.sin_port = htons(7777);
	addr.sin_addr.s_addr = inet_addr("001.001.01.001");
	bzero( addr.sin_zero, 8 );

 	sd = socket(AF_INET, SOCK_STREAM, 0);

 	/* 서버가 비정상 종료 되어 포트가 busy 하더라도-> server가 time wait인 경우
 	 * 서버 재기동시 무조건 기동되도록. */
	optval=1;
	setsockopt(sd, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(optval));
	ret = bind(sd, (struct sockaddr *)&addr, sizeof(addr));
	if( ret < 0 )
	{
		perror("bind()");
	    exit(0);
	}

	ret = listen( sd, 10 );

	addrlen = sizeof(client_addr);
	while(1)
	{
		/* 클라이언트 추적을 위해 주소를 담을 공간을 인자로 넘겨줌 */
		new_sd = accept( sd, (struct sockaddr *)&client_addr, &addrlen ); 
		printf("새로운 클라이언트 접속 : ip=%s, port=%u\n", 
			inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port));

		if(fork()==0)
		{
			/* 자식을 만들 후 그 안에서 소켓을 닫고
		 	 * 자식프로세스를 죽이는 것을 반복 */
			close(sd);
			while( ret = read(new_sd, buff, sizeof buff ) )
			{
				write( new_sd, buff, ret );
			}
			close(new_sd);
			exit(0);
		}
		close(new_sd);
	}

	close(sd);
	return 0;
}


[select version]

select ()
process가 kernel에게 명령하여 다수의 event 중 하나를 기다리다 지정된 시간이 지나가거나 사건이 발생하면 process를 깨우도록 함. fd_set이라는 구조체를 사용하며, FD_ISSET을 통해 발생한 이벤트를 판단한다. 원하는 이벤트가 발생하면 로직 진행.

#if 1
#include <unistd.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/time.h>
#include <sys/select.h>

int main()
{
	int fd;
	char buff[1024];
	int ret;
	fd_set readfds;

	fd = open("myfifo", O_RDWR);  // # mkfifo myfifo

	while(1)
	{
		FD_ZERO(&readfds);
        FD_SET(0, &readfds);
        FD_SET(fd, &readfds);
		select( fd+1, &readfds,  0, 0, 0); 
		if( FD_ISSET(0, &readfds) )
		{
			ret = read(0, buff, sizeof buff );
			buff[ret-1] = 0;
			printf("키보드 입력 : [%s]\n", buff );
		}

		if( FD_ISSET(fd, &readfds) )
		{
			ret = read(fd, buff, sizeof buff );
			buff[ret-1] = 0;
			printf("myfifo 입력 : [%s]\n", buff );
		}
	}
	close(fd);
	return 0;
}
#endif
#if 0
#include <unistd.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

int main()
{
	int fd;
	char buff[1024];
	int ret;


	fd = open("myfifo", O_RDWR);  // # mkfifo myfifo
	while(1)
	{
		ret = read(0, buff, sizeof buff );
		buff[ret-1] = 0;
		printf("키보드 입력 : [%s]\n", buff );

		ret = read(fd, buff, sizeof buff );
		buff[ret-1] = 0;
		printf("myfifo 입력 : [%s]\n", buff );
	}
	close(fd);
	return 0;
}
#endif


fd_set : 비트를 활용한 구조체


참고
$ man : 섹션 별로 자료를 갖고 있다.
$ man 1 open : 리눅스 명령어 open
$ man 2 open : 프로그램 open

pid : 프로세스 아이디, ppid : 프로세스의 parent 아이디
$kill -l : kill 의 값들을 볼 수 있다.
cat > myinfo : myinfo라는 곳에 키보드를 통해 실시간으로 데이터 입력이 가능.

반응형