day4에서 실습한 select로 구현한 chat_server.c. 는 서버소켓과 클라이언트 소켓을 fd_set의 비트테이블에 등록해 비동기적으로 구현했다. 이번에는 여기에 서버 실행 화면에서 키보드 입력을 받도록 fd_set을 추가한다. 그리고 입력이 발생하면 "input: 입력내용" gudxofh cnffurgownsek.

-15장의 "소켓과 표준입출력"의 fileno(), fopen() 을 참고

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
/* chat_server.c
 * select 기반으로 구현한 채팅 서버
 * 교재 2장/6.동시처리 p72 부터 설명하는 서버
 * - stdin 입력을 select
 * - SO_REUSEADDR 옵션 사용
 */
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/time.h>
#include <sys/socket.h>
#include <netinet/in.h>
 
#define BUFSIZE 100
 
int main(int argc, char **argv)
{
    int stdfd;
    int serv_sock;
    struct sockaddr_in serv_addr;
    fd_set reads, temps;
    int fd_max;
    char message[BUFSIZE];
    int str_len;
    struct timeval timeout;
 
    if (argc != 2)
    {
        printf("Usage : %s <port>\n", argv[0]);
        return -1;
    }
    int so_reuseaddr = 1;
    int state = setsockopt(serv_sock, SOL_SOCKET, SO_REUSEADDR, (void *)&so_reuseaddr, sizeof(so_reuseaddr));
 
    serv_sock = socket(PF_INET, SOCK_STREAM, 0);
    serv_addr.sin_family = AF_INET;
    serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
    serv_addr.sin_port = htons(atoi(argv[1]));
 
    if (bind(serv_sock, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) == -1)
    {
        perror("bind() error");
        return -1;
    }
    if (listen(serv_sock, 5== -1)
    {
        perror("linsten() error");
        return -1;
    }
 
    stdfd = fileno(stdin); // stdin 파일 디스크립터
 
    // while (1)    {
    FD_ZERO(&reads);
    FD_SET(serv_sock, &reads); // 파일 디스크립터 설정(서버)
    FD_SET(stdfd, &reads);     // stdio
 
    fd_max = serv_sock + 1;
 
    while (1)
    { // 무한 루프
        int clnt_fd, fd, str_len;
        int clnt_sock, clnt_len;
        struct sockaddr_in clnt_addr;
        temps = reads; // select 함수 변화가 생긴 디스크립터를
        // 제외한 나머지 비트들을 0으로 초기화
        timeout.tv_sec = 5;  // 초 단위 설정
        timeout.tv_usec = 0// 마이크로 초 단위 설정
 
        if (select(fd_max + 1&temps, 00&timeout) == -1)
        {
            perror("select() error");
            return -1;
        }
 
        unsigned char rv;
 
        for (fd = 0; fd < fd_max + 1; fd++)
        {
            if (FD_ISSET(fd, &temps))
            {
                if (fd == serv_sock)
                {
                    clnt_len = sizeof(clnt_addr);
                    clnt_sock = accept(serv_sock,
                                       (struct sockaddr *)&clnt_addr, &clnt_len);
                    FD_SET(clnt_sock, &reads);
                    if (fd_max < clnt_sock)
                        fd_max = clnt_sock;
                    printf("클라이언트 연결 : FD(%d) \n", clnt_sock);
                }
                else if (fd == stdfd)
                {
                    if ((str_len = read(stdfd, message, BUFSIZE)) > 0)
                    {
                        printf("input %s\n", message);
                    }
                }
                else
                {
                    str_len = read(fd, message, BUFSIZE);
                    if (str_len == 0)
                    {
                        FD_CLR(fd, &reads);
                        close(fd);
 
                        printf("클라이언트 종료 : FD(%d)\n", fd);
                    }
                    else
                    {
                        for (clnt_fd = serv_sock + 1; clnt_fd < fd_max + 1; clnt_fd++)
                            write(clnt_fd, message, str_len);
                    }
                }
            }
        }
    }
    // }
    return 0;
}
http://colorscripter.com/info#e" target="_blank" style="color:#4f4f4f; text-decoration:none">Colored by Color Scripter
http://colorscripter.com/info#e" target="_blank" style="text-decoration:none; color:white">cs

 

 

 

fdopen 함수를 이용한 FILE 구조체 포인터로의 변환

#include <stdio.h>

FILE * fdopen(int fildes, const char * mode); //성공 시 변환된 FILE 구조체 포인터, 실패시 NULL

fildes : 변환할 파일 디스크립터를 인자로 전달.

mode : 생성할 FILE 구조체 포인터의 모드(mode)정보 전달.

 

desto.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include <stdio.h>
#include <fcntl.h>
 
int main(void)
{
    FILE *fp;
    int fd=open("data.dat", O_WRONLY|O_CREAT|O_TRUNC);
    if(fd==-1)
    {
        fputs("file open error", stdout);
        return -1;
    }
 
    fp=fdopen(fd, "w");
    fputs("Network C programming \n", fp);
    fclose(fp);
    return 0;
}
 
http://colorscripter.com/info#e" target="_blank" style="color:#4f4f4f; text-decoration:none">Colored by Color Scripter
http://colorscripter.com/info#e" target="_blank" style="text-decoration:none; color:white">cs

 

 

fileno 함수를 이용한 파일 디스크립터로의 변환

#include <stdio.h>

int fileno(FILE * stream); //성공 시 변환된 파일 디스크립터, 실패 시 -1 반환

todes.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include <stdio.h>
#include <fcntl.h>
 
int main(void)
{
    FILE *fp;
    int fd = open("data.dat", O_WRONLY | O_CREAT | O_TRUNC);
    if (fd == -1)
    {
        fputs("file open error", stdout);
        return -1;
    }
 
    printf("First file descriptor: %d\n", fd);
    fp = fdopen(fd, "w");
    fputs("TCP/IP SOCKET PROGRAMMING\n", fp);
    printf("Second file descriptor: %d\n", fileno(fp));
    fclose(fp);
    return 0;
}
http://colorscripter.com/info#e" target="_blank" style="color:#4f4f4f; text-decoration:none">Colored by Color Scripter
http://colorscripter.com/info#e" target="_blank" style="text-decoration:none; color:white">cs

역으로 fileno 함수호출을 통해

 

소켓 기반에서의 표준 C 입출력 함수의 호출 예

echo_stdserv.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
 
#define BUF_SIZE 1024
void error_handling(char *message);
 
int main(int argc, char *argv[])
{
    int serv_sock, clnt_sock;
    char message[BUF_SIZE];
    int str_len, i;
    
    struct sockaddr_in serv_adr;
    struct sockaddr_in clnt_adr;
    socklen_t clnt_adr_sz;
    FILE * readfp;
    FILE * writefp;
    
    if(argc!=2) {
        printf("Usage : %s <port>\n", argv[0]);
        exit(1);
    }
    
    serv_sock=socket(PF_INET, SOCK_STREAM, 0);   
    if(serv_sock==-1)
        error_handling("socket() error");
    
    memset(&serv_adr, 0sizeof(serv_adr));
    serv_adr.sin_family=AF_INET;
    serv_adr.sin_addr.s_addr=htonl(INADDR_ANY);
    serv_adr.sin_port=htons(atoi(argv[1]));
 
    if(bind(serv_sock, (struct sockaddr*)&serv_adr, sizeof(serv_adr))==-1)
        error_handling("bind() error");
    
    if(listen(serv_sock, 5)==-1)
        error_handling("listen() error");
    
    clnt_adr_sz=sizeof(clnt_adr);
 
    for(i=0; i<5; i++)
    {
        clnt_sock=accept(serv_sock, (struct sockaddr*)&clnt_adr, &clnt_adr_sz);
        if(clnt_sock==-1)
            error_handling("accept() error");
        else
            printf("Connected client %d \n", i+1);
    
        readfp=fdopen(clnt_sock, "r");
        writefp=fdopen(clnt_sock, "w");
    
        while(!feof(readfp))
        {
            fgets(message, BUF_SIZE, readfp);
            fputs(message, writefp);
            fflush(writefp);
        }
        fclose(readfp);
        fclose(writefp);
    }
    close(serv_sock);
    return 0;
}
 
void error_handling(char *message)
{
    fputs(message, stderr);
    fputc('\n', stderr);
    exit(1);
}
 
http://colorscripter.com/info#e" target="_blank" style="color:#4f4f4f; text-decoration:none">Colored by Color Scripter
http://colorscripter.com/info#e" target="_blank" style="text-decoration:none; color:white">cs

 

echo_client.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
 
#define BUF_SIZE 1024
void error_handling(char *message);
 
int main(int argc, char *argv[])
{
    int sock;
    char message[BUF_SIZE];
    int str_len;
    struct sockaddr_in serv_adr;
    FILE * readfp;
    FILE * writefp;
 
    if(argc!=3) {
        printf("Usage : %s <IP> <port>\n", argv[0]);
        exit(1);
    }
    
    sock=socket(PF_INET, SOCK_STREAM, 0);   
    if(sock==-1)
        error_handling("socket() error");
    
    memset(&serv_adr, 0sizeof(serv_adr));
    serv_adr.sin_family=AF_INET;
    serv_adr.sin_addr.s_addr=inet_addr(argv[1]);
    serv_adr.sin_port=htons(atoi(argv[2]));
    
    if(connect(sock, (struct sockaddr*)&serv_adr, sizeof(serv_adr))==-1)
        error_handling("connect() error!");
    else
        puts("Connected...........");
 
    readfp=fdopen(sock, "r");
    writefp=fdopen(sock, "w");    
 
    while(1
    {
        fputs("Input message(Q to quit): ", stdout);
        fgets(message, BUF_SIZE, stdin);
        if(!strcmp(message,"q\n"|| !strcmp(message,"Q\n"))
            break;
 
        fputs(message, writefp);
        fflush(writefp);
         fgets(message, BUF_SIZE, readfp);
        printf("Message from server: %s", message);
    }    
    fclose(writefp);
    fclose(readfp);
    return 0;
}
 
void error_handling(char *message)
{
    fputs(message, stderr);
    fputc('\n', stderr);
    exit(1);
}
 
http://colorscripter.com/info#e" target="_blank" style="color:#4f4f4f; text-decoration:none">Colored by Color Scripter
http://colorscripter.com/info#e" target="_blank" style="text-decoration:none; color:white">cs

 

입력용, 출력용 FILE 구조체 포인터를 각각 생성해야 한다.

표준 C 입출력 함수를 사용할 경우 소켓의 버퍼 이외에 버퍼링이 되기 때문에 필요하다면, fflush 함수를 직접 호출해야 한다.

 

일반적인 순서

  1. 파일 디스크립터를 FILE 구조체 포인터로 변환
  2. 표준 입출력 함수의 호출
  3. 함수 호출 후 fflush 함수호출을 통해서 버퍼 비움

 

 

 

입력 스트림과 출력 스트림의 분리

 

스트림 분리의 이점

 

스트림 분리의 목적

  • 입력루틴(코드)과 출력루틴의 독립을 통한 구현의 편의성 증대
  • 입력에 상관없이 출력이 가능하게 함으로 인해서 속도의 향상 기대

스트림 분리의 이점

  • FILE 포인터는 읽기모드와 쓰기모드를 구분해야 하므로,
  • 읽기모드와 쓰기모드의 구분을 통한 구현의 편의성 증대
  • 입력버퍼와 출력버퍼를 구분함으로 인한 버퍼링 기능의 향상

 

스트림 분리 이후의 EOF에 대한 문제점

  • half-close shutdown(sock,SHUT_WR)
  • 출력 스트림에 대해서 half-close 진행 시 EOF 전달

sep_serv.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#define BUF_SIZE 1024
 
int main(int argc, char *argv[])
{
    int serv_sock, clnt_sock;
    FILE * readfp;
    FILE * writefp;
    
    struct sockaddr_in serv_adr, clnt_adr;
    socklen_t clnt_adr_sz;
    char buf[BUF_SIZE]={0,};
 
    serv_sock=socket(PF_INET, SOCK_STREAM, 0);
    memset(&serv_adr, 0sizeof(serv_adr));
    serv_adr.sin_family=AF_INET;
    serv_adr.sin_addr.s_addr=htonl(INADDR_ANY);
    serv_adr.sin_port=htons(atoi(argv[1]));
    
    bind(serv_sock, (struct sockaddr*&serv_adr, sizeof(serv_adr));
    listen(serv_sock, 5);
    clnt_adr_sz=sizeof(clnt_adr); 
    clnt_sock=accept(serv_sock, (struct sockaddr*)&clnt_adr,&clnt_adr_sz);
    
    readfp=fdopen(clnt_sock, "r");
    writefp=fdopen(clnt_sock, "w");
    
    fputs("FROM SERVER: Hi~ client? \n", writefp);
    fputs("I love all of the world \n", writefp);
    fputs("You are awesome! \n", writefp);
    fflush(writefp);
    
    fclose(writefp);    
    fgets(buf, sizeof(buf), readfp); fputs(buf, stdout); 
    fclose(readfp);
    return 0;
}
 
http://colorscripter.com/info#e" target="_blank" style="color:#4f4f4f; text-decoration:none">Colored by Color Scripter
http://colorscripter.com/info#e" target="_blank" style="text-decoration:none; color:white">cs

 

sep_clnt.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#define BUF_SIZE 1024
 
int main(int argc, char *argv[])
{
    int sock;
    char buf[BUF_SIZE];
    struct sockaddr_in serv_addr;
 
    FILE * readfp;
    FILE * writefp;
    
    sock=socket(PF_INET, SOCK_STREAM, 0);
    memset(&serv_addr, 0sizeof(serv_addr));
    serv_addr.sin_family=AF_INET;
    serv_addr.sin_addr.s_addr=inet_addr(argv[1]);
    serv_addr.sin_port=htons(atoi(argv[2]));
  
    connect(sock, (struct sockaddr*)&serv_addr, sizeof(serv_addr));
    readfp=fdopen(sock, "r");
    writefp=fdopen(sock, "w");
  
    while(1)
    {
        if(fgets(buf, sizeof(buf), readfp)==NULL
            break;
        fputs(buf, stdout);
        fflush(stdout);
     }  
 
    fputs("FROM CLIENT: Thank you! \n", writefp);
    fflush(writefp);
    fclose(writefp); fclose(readfp);
    return 0;
}
 
http://colorscripter.com/info#e" target="_blank" style="color:#4f4f4f; text-decoration:none">Colored by Color Scripter
http://colorscripter.com/info#e" target="_blank" style="text-decoration:none; color:white">cs

 

 

스트림 종료 시 half-close가 진행되지 않는 이유

하나의 파일 디스크립터를 대상으로 FILE 포인터가 생성되었으니, FILE 포인터가 종료되면, 연결된 FILE 디스크립터도 종료된다.

 

파일 디스크립터를 복사한 다음에 각각의 파일 디스크립터를 대상으로 FILE 포인터를 만들면, FILE 포인터 소멸 시 해당 파일 포인터에 연결 된 파일 디스크립터만 소멸된다.

 

하지만 위의 경우에도 half-close는 진행되지 않는다. 여전히 하나의 FILE 디스크립터가 남아있고, 이를 이용해서 입출력이 가능해야 하기 때문에

 

 

 

파일 디스크립터의 복사

#include <unistd.h>

int dup(int fildes);

int dup2(int fildes, int fildes2); // 성공 시 복사된 파일 디스크립터, 실패 시 -1 반환

fildes 복사할 파일 디스크립터 전달.

fildes2 명시적으로 지정할 파일 디스크립터의 정수 값 전달.

 

dup.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include <stdio.h>
#include <unistd.h>
 
int main(int argc,char *argv[])
{
    int cfd1, cfd2;
    char str1[]="Hi~ \n";
    char str2[]="It's nice day~ \n";
 
    cfd1=dup(1);
    cfd2=dup2(cfd1,7);
 
    printf("fd1=%d, fd2=%d \n",cfd1,cfd2);
    write(cfd1,str1,sizeof(str1));
    write(cfd2,str2,sizeof(str2));
 
    close(cfd1);
    close(cfd2);
    write(1,str1,sizeof(str1));
    close(1);
    write(1,str2,sizeof(str2));
    return 0;
}
http://colorscripter.com/info#e" target="_blank" style="color:#4f4f4f; text-decoration:none">Colored by Color Scripter
http://colorscripter.com/info#e" target="_blank" style="text-decoration:none; color:white">cs

총 두 개의 파일 디스크립터를 복사한다. 그리고 복사된 파일의 디스크립터까지 모두 종료를 한다. 때문에 마지막 행에 존재하는 write 함수의 호출은 성공하지 못한다.

 

 

파일 디스크립터의 복사 후 스트림의 분리

sep_serv2.c

 

dup 함수호출을 통해서 복사된 파일 디스크립터를 대상으로 FILE 구조체 포인터를 생성하고 있다.

 

파일 디스크립터로 변환해서 shutdown 함수를 호출한다. 따라서 이제 half-close가 진행되고, 이로 인해서 상대방 영역으로 EOF가 전달된다.

EOF 전달을 목적으로 하는 half-close는 파일 디스크립터를 이용해서 진행해야 한다.

+ Recent posts