반응형


- 본 내용은 Linux (Ubuntu 14.04 lts)를 기반으로 제작되었습니다. -


소캣 프로그래밍은 UDP 기반으로 하였습니다.





서버와 클라이언트에 다음과 같은 조건을 두고 UDP로 제작해본다.


서버 프로그램 조건


1. 서버에게 문자열을 전송한 클라이언트의 주소와 포트를 최대 4개까지 저장시킨다.


2. 한 클라이언트가 문자열을 전송하면 나머지 클라이언트에게 문자열을 전송한다.


3. 문자열 전송 클라이언트가 종료되면 새로 들어오는 클라이언트가 문자열 전송을 담당.


4. 닉네임을 부여한다.




클라이언트 프로그램 조건


1. 사용자로부터 문자열을 입력받으면 서버에게 문자열을 전송한다.


2. 서버로부터 문자열을 받으면 화면에 출력한다.


**

이 조건을 보면 스레드를 이용하거나 다양한 방법들을 이용해야된다 생각이 들 수 있지만, 단순히 while문과 클라이언트의 port번호 혹은 주소를 통해서도 충분히 해결 할 수 있는 방법을 소개하고자 한다.

**


전반적인 핵심 알고리즘


클라이언트와 서버의 통신을 특정 바이트를 주고 받게 함으로써 통신의 수단으로 이용해보았다.

예를 들어 100바이트를 강제로 보내면 그것이 서버에서는 어떻게 받아들이는지, 클라이언트에서는 어떻게 받아들이는지 서로 다르게 구성되었다. (ex : 100바이트를 서버->클라이언트 전송 시 채팅이 가능한 클라이언트로 만들어준다.) 

이렇게 구성하게 되면 string을 실제 100바이트 보내게 될 때 문제가 되는데, 이것은 최대 길이를 90바이트로 예외 처리를 하면 된다.(91~100은 통신키로 이용)




server.c에서 이용되는 알고리즘


서버에서 receivedBytes가 100이면 닉네임을 수신하게 되는 신호이고, 이때 chatter가 0이면 채팅이 가능하도록 클라이언트를 설정해준다. 


이때 다른 클라이언트가 미리 접속하였다면 chatter은 이미 1이 되어있고, 듣기 전용 클라이언트로 설정해준다. 


그리고 만약 portArr에 모든 방이 꽉 찼다면 채팅방 접속 거부를 내린다.


98바이트를 받게 되면 문자열을 보내던 클라이언트가 종료했음을 의미하고(exit로 종료한다.) 해당하는 모든 값을 초기화해준다. 


그리고 다른 클라이언트에게도 종료를 알린다. 그리고 그 외에는 일반적 통신처럼 메시지를 받고 보내도록 한다. 


이때 메시지 보내는 클라이언트 외에 보내는 법은 현재 로컬 주소(127.0.0.1)로 모든 클라이언트가 접속하므로, 포트 번호를 저장한 후, 포트 번호만 바꾸어주며 sendto함수를 이용한다.





client.c에서 이용되는 알고리즘


클라이언트에서는 처음 닉네임을 설정하고 100바이트로 송신을 하여 만약 100바이트가 돌아오면 


문자열을 보내는 주인공이 되고, 99바이트가 오면 듣는 입장이 된다.


그리고 98바이트가 오면 방이 꽉 찼음을 의미하기에 종료한다.(입장 불가능)


그리고 변수 canChat가 1 즉, 문자열 보내는 클라이언트면 sendto를 통해 서버에 문자를 보낸다. 


그리고 만약 exit를 입력하면 Chatter은 종료되고 그것을 98바이트로 서버에 보내며 종료를 알린다.


canChat이 0이면 recvfrom을 통해 서버로부터 듣기만 한다.





< header.h >


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//header.h
 
# include <stdio.h>
# include <string.h>
# include <stdlib.h>
# include <netinet/in.h>
# include <sys/socket.h>
# include <arpa/inet.h>
# include <unistd.h>
 
# define PORT 20162
# define BUFFER_SIZE 4096 // 서버에 이용
# define BUFF_SIZE 100 // 클라이언트에 이용
 
//                                                       This source code Copyright belongs to Crocus
//                                                        If you want to see more? click here >>
Crocus








< server.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
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
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
//server.cpp
 
# include "header.h"
 
int main(int argc, char *argv[])
{
    char readBuff[BUFFER_SIZE];
    char sendBuff[BUFFER_SIZE];
    struct sockaddr_in serverAddress, clientAddress;
    int server_fd, client_fd;
    int client_addr_size;
    ssize_t receivedBytes;
    ssize_t sentBytes;
 
    /*
    if (argc != 2)
    {
        printf("사용법 : ./filename 포트번호 \n");
        exit(0);
    }
    */
 
    socklen_t clientAddressLength =0;
 
    memset(&serverAddress, 0sizeof(serverAddress));
    memset(&clientAddress, 0sizeof(clientAddress));
 
    serverAddress.sin_family = AF_INET;
    serverAddress.sin_addr.s_addr = htonl(INADDR_ANY);
    serverAddress.sin_port = htons(20162);
 
 
    // 서버 소켓 생성 및 서버 주소와 bind
    
    // 서버 소켓 생성(UDP니 SOCK_DGRAM이용)
    if((server_fd = socket(AF_INET, SOCK_DGRAM, 0)) == -1// SOCK_DGRAM : UDP
    {
        printf("Sever : can not Open Socket\n");
        exit(0);
    }
 
    // bind 과정
    if(bind(server_fd, (struct sockaddr *)&serverAddress, sizeof(serverAddress)) < 0)
    {
        printf("Server : can not bind local address");
        exit(0);
    }
    
 
    printf("Server: waiting connection request.\n");
 
    char nickname[5][10];
    int chatter = 0;
    int portArr[5= {-1,-1,-1,-1,-1};
    int pNum = 0;
    int i = 0;
 
    while(1)
    {
        
    
    //채팅 프로그램 제작
    client_addr_size = sizeof(clientAddress);
 
    receivedBytes = recvfrom(server_fd, readBuff, BUFF_SIZE, 0, (struct sockaddr*)&clientAddress, &client_addr_size);
 
    // 닉네임을 닉네임 배열에 저장해준다.
    readBuff[receivedBytes] = '\0';
    sprintf(nickname[pNum],"%s",readBuff);
    fflush(stdout);
 
        // 닉네임 수신 할 때 100바이트를 수신한다.
        if(receivedBytes == 100)
        {
            // 채팅 유저가 없으면 100바이트라는 임시 암호를 보낸다.
            if(chatter == 0)
            {
                sentBytes = sendto(server_fd, "You can 'send Message'\n"1000, (struct sockaddr*)&clientAddress, sizeof(clientAddress));
                chatter = 1;                
 
                int pt = clientAddress.sin_port;
                portArr[0= pt;
                pNum = 0;
                printf("Login Succeed ! \n Client : %s port[%d]\n\n",nickname[pNum], pt);
                pNum++;
    
            }
    
            // 채팅 유저가 있으면 99바이트라는 임시 암호를 보낸다.
            else if(chatter == 1)
            {
                
                
                int pt = clientAddress.sin_port;
 
                for(i = 1;; i++)
                {
                    if(i <= && portArr[i] == -1)
                    {
                        portArr[i] = pt;
                        printf("Login Succeed ! \n Client :: %s port :: %d\n",nickname[i], portArr[i]);
                        pNum++// 닉네임 넘버를 위해 넣어둔다.
                        break;
                    }
                    // 채팅방이 꽉 찼으면 거절한다.
                    else if(i > 4)
                    {
                        sentBytes = sendto(server_fd, "This chatting room is full\n"980, (struct sockaddr*)&clientAddress, sizeof(clientAddress));
                        break;
                    }
                }
 
                sentBytes = sendto(server_fd, "You can't send Message 'Only hear'\n"990, (struct sockaddr*)&clientAddress, sizeof(clientAddress));
            }
        }
 
        // Chatter이 접속을 종료하면 배열 및 변수를 초기화해준다.
        else if(receivedBytes == 98)
        {
            chatter = 0;
            portArr[0= -1;
            nickname[0][0= '\0';
 
            readBuff[receivedBytes] = '\0';
            fputs(readBuff, stdout);
            fflush(stdout);
 
            sprintf(sendBuff,"%s",readBuff);
            
            // 다른 클라이언트에게도 종료를 알려준다.
            for(i = 1; portArr[i] != -1; i++)
            {
                clientAddress.sin_port = portArr[i];
                sentBytes = sendto(server_fd, sendBuff, strlen(sendBuff), 0, (struct sockaddr*)&clientAddress, sizeof(clientAddress));
            }
        }
        
        // 그 외에는 문자를 수신받는다.
        else
        {
            printf("%lu bytes read\n", receivedBytes);
            readBuff[receivedBytes] = '\0';
            fputs(readBuff, stdout);
            fflush(stdout);
    
            sprintf(sendBuff,"%s",readBuff);
 
            // 현재 로컬 주소인 127.0.0.1에서 행동중이니 고유 포트 번호를 변경해주며 이용한다.
            for(i = 1; portArr[i] != -1; i++)
            {
                clientAddress.sin_port = portArr[i];
                sentBytes = sendto(server_fd, sendBuff, strlen(sendBuff), 0, (struct sockaddr*)&clientAddress, sizeof(clientAddress));
            }
            
        }
    }
 
    // 서버 소켓 close 
    close(server_fd);
 
    return 0;
}
 
//                                                       This source code Copyright belongs to Crocus
//                                                        If you want to see more? click here >>
Crocus





< 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
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
//client.cpp
 
# include "header.h"
 
int main(int argc, char* argv[])
{
    /*
    if(argc != 2)
    {    // argv[0]에 ./client가 들어간다.
        printf("사용법 : %s IPv4-address\n", argv[0]);
        return -1;
    }
    */
 
    int client_socket;
    struct sockaddr_in serverAddress;
    int server_addr_size;
    char sendBuff[BUFF_SIZE];
    char readBuff[BUFF_SIZE];
 
 
    ssize_t receivedBytes = 0;
    ssize_t sentBytes;
 
 
    memset(&serverAddress, 0sizeof(serverAddress));
 
    serverAddress.sin_family = AF_INET;
    inet_aton("127.0.0.1", (struct in_addr*&serverAddress.sin_addr.s_addr);
    serverAddress.sin_port = htons(20162);
 
    // 소켓을 생성
    if((client_socket = socket(PF_INET, SOCK_DGRAM, 0)) == -1)
    {
        printf("socket 생성 실패\n");
        exit(0);
    }
    
 
    // 닉네임 설정 시 닉네임과 암호키 100을 전송한다.
    printf("닉네임을 설정하세요 : ");
    char nickname[10= {0};
    fgets(nickname,10,stdin);
    sendto(client_socket, nickname, 1000, (struct sockaddr*)&serverAddress, sizeof(serverAddress));
 
    // 채팅유저인지 아닌지 설정한다.
    int canChat = 0;
    
    // 채팅가능 판별을 위해 기다린다.
    while((receivedBytes = recvfrom(client_socket, readBuff, BUFF_SIZE, 0, (struct sockaddr*)&serverAddress, &server_addr_size)) == 0){}
 
    // 채팅 가능하면 메시지 출력
    if(receivedBytes == 100)
    {
        readBuff[receivedBytes] = '\0';
        fputs(readBuff, stdout);
        fflush(stdout);
        canChat = 1;
    }
 
    // 채팅 불가능하면 메시지 출력
    else if(receivedBytes == 99)
    {
        readBuff[receivedBytes] = '\0';
        fputs(readBuff, stdout);
        fflush(stdout);
        canChat = 0;
    }
 
    // 방이 꽉찬경우
    else if(receivedBytes == 98)
    {
        readBuff[receivedBytes] = '\0';
        fputs(readBuff, stdout);
        fflush(stdout);
        return 0;
    }
 
    while(canChat == 1)
    {
        // 채팅 프로그램 제작
        server_addr_size = sizeof(serverAddress);
        
        //클라이언트에서 메세지 전송
        printf("클라이언트에서 보낼 말을 입력하세요 :: ");
    
        char msg[BUFF_SIZE];
        fgets(msg,BUFF_SIZE,stdin);
 
        // exit를 입력하면 98바이트 암호키를 통해 종료한다.
        if(strcmp(msg,"exit\n"== 0)
        {
            sentBytes = sendto(client_socket, "Chatter is Logout\n"980, (struct sockaddr*)&serverAddress, sizeof(serverAddress));
 
            return 0;
        }
 
        sprintf(sendBuff,"%s",msg);
 
        sentBytes = sendto(client_socket, sendBuff, strlen(sendBuff), 0, (struct sockaddr*)&serverAddress, sizeof(serverAddress));
 
        fflush(stdout);
    }
    while(canChat == 0)
    {
        receivedBytes = recvfrom(client_socket, readBuff, BUFF_SIZE, 0, (struct sockaddr*)&serverAddress, &server_addr_size);
        readBuff[receivedBytes] = '\0';
        fputs(readBuff, stdout);
        fflush(stdout);
        canChat = 0;
        
    }
    
    // 소켓을 close 
    close(client_socket);
    return 0;
}
 
//                                                       This source code Copyright belongs to Crocus
//                                                        If you want to see more? click here >>
Crocus




< 첫화면 >




< 서버 접속 >





< 첫번째 클라이언트 접속 시 server화면에 닉네임과 클라이언트 포트 번호가 나타난다. >




< 나머지 클라이언트 접속 시 server에 나타나는 출력 >





< 통신과정 1 >




< 통신과정 2 >






< 통신도중 가장 우측하단 guest-4의 접근이 있었지만 이미 채팅방 인원이 가득차서 접근이 제한된다. >









< exit를 입력하여 채팅방을 나가는 장면 >






< 가장 우측 하단의 터미널에서 접근을 성공하였다. >





< 통신과정 >





< 상단 중앙의 chul-su 클라이언트가 다시 접근하려 하였으나 인원 제한에 걸려 접근이 불가능하다. >





















반응형