'리눅스 서버에 대해서'에 해당되는 글 90건

아래 소스는 인터넷에 떠돌고 있는 리눅스용 select 서버를 가져와서 윈도우 용으로 수정하였습니다.

출처는 : http://vessel.tistory.com/95 입니다.

 

리눅스에선 서버만 만들어 놓고 클라이언트는 telnet으로 접속이 가능합니다만.

저는 이걸 윈도우 용으로 전환하면서 클라이언트 프로그램도 그냥 만들었습니다… orz

윈도우도 telnet 이 있긴하지만.. 이건 좀

 

하여간 소스 들어갑니다.

 

Server.cpp

#include <winsock2.h>

#include <stdlib.h>

#include <stdio.h>

#include <string.h>

 

#define MAXLINE 512

#define MAX_SOCK 64

char *escapechar = "exit";

int maxfdp1;

int num_chat = 0;                                                                               // 채팅참가자수

int client_s[MAX_SOCK];

 

// 채팅탈퇴처리

void removeClient(int)                              // i번째참가자탈퇴

{

        closesocket(client_s[i]);                   // 해당소켓닫기

        if(i !=num_chat -1)

               client_s[i] = client_s[num_chat -1]; 

        num_chat --;                                // 채팅참가자수1명줄이기

        printf("채팅참가자1명탈퇴. 현재참가자수= %d\n",num_chat);

}

 

// client_s[]내의최대소켓번호얻기(k는초기치)

int getmax(int k)

{

        int max = k;

        int r;

        for(r = 0; r < num_chat ; r++)            // 채팅참가자수만큼소켓배열탐색

               if(client_s[r] > max)

                       max = client_s[r];

        return max;

}

 

int main()

{

        char rline[MAXLINE], my_msg[MAXLINE];               // buffer 생성

        char *start = "Connetctd to chat_server\n";         // 최초연결시보내는메세지

        int i,j,n;

        SOCKET s, client_fd, clilen;

        fd_set read_fds;

        struct sockaddr_in client_addr, server_addr;        // 소켓주소구조체선언

        WSADATA wsa;                                        // 윈소켓

 

        // 윈속초기화

        if(WSAStartup(MAKEWORD(2,2), &wsa) != 0)

               exit(1);

 

        // 초기소켓생성

        if((s = socket(AF_INET, SOCK_STREAM, 0)) < 0)

        {

               printf("Server: Can't open stream socket.");

               exit(0);

        }

        // server_addr 구조체의내용세팅

        ZeroMemory(&server_addr, sizeof(server_addr));       //초기화(0으로채운다)

        server_addr.sin_family = AF_INET;

        server_addr.sin_addr.s_addr = htonl(INADDR_ANY);

        server_addr.sin_port = htons(9000);

         
        // bind()
       
if(bind(s,(struct sockaddr *)&server_addr,sizeof(server_addr)) < 0 )     
        {

               printf("Server: Can't bind local address.\n");

               exit(0);

        }

 

        //클라이언트로부터연결요청을기다림

        listen(s,SOMAXCONN);                    // listen 접속대기(큐크기5)

        maxfdp1 = s + 1;                        // 최대소켓번호+1

        while(1)

        {

               FD_ZERO(&read_fds);              // fd_set  초기화(0으로세팅) 

               FD_SET(s,&read_fds);       // 소켓에해당하는file discripter1로세팅

               for(i=0; i<num_chat; i++)        // ""

                       FD_SET(client_s[i], &read_fds);

               maxfdp1 = getmax(s) +1;         // maxfdp1 재계산

 

               if(select(maxfdp1,&read_fds, (fd_set  *)0, (fd_set *) 0, (struct timeval *) 0) < 0)     // select setting

               {

                       printf("select error =< 0 \n");

                       exit(0);

               }

               if(FD_ISSET(s, &read_fds))  // read_fds s에해당하는비트가세팅되있다면

               {              

                       int addrlen;        // 의미없음ㅡㅡ.. 단순주소길이저장변수

                       addrlen = sizeof(client_addr);

                      

                       // == 연결요청이있다면

                       clilen = sizeof(client_addr);

                       client_fd = accept(s, (struct sockaddr *)&client_addr, &addrlen);                 
                       
//  accept()

                       if(client_fd == -1)

                       {

                              printf("accept error \n");

                              exit(0);

                       }

 

                       // 채팅클라이언트목록에추가

                       client_s[num_chat] = client_fd;

                       num_chat++;                       // 채팅참가자수1 증가

                   
    // "Connetctd to chat_server" 를접속한클라이언트에보냄                    
                       
send(client_fd,start,strlen(start),0);     
                   
   printf("%d번째사용자추가,\n",num_chat);

               }

 

               //클라이언트가보낸메시지를모든클라이언트에게방송

               for(i = 0; i<num_chat; i++)              // 모든클라이언트검색

               {

                       memset(rline,'\0',MAXLINE);      // buffer 초기화

                       if(FD_ISSET(client_s[i],&read_fds)) 
                     
// read
해당소켓에서read 할것이있는지확인

                       {

                              if((n = recv(client_s[i],rline,MAXLINE, 0 )) <= 0)

                              {

                                      removeClient(i);

                                      continue;

                              }

                              // 종료문자처리

                              if(strstr(rline,escapechar) != NULL)
                              //"exit"
가입력되면종료시키기

                              {

                                      removeClient(i);

                                      continue;

                              }

                              // 모든채팅참가자에게메시지방송

                              rline[n] = '\0';

                              for(j = 0; j < num_chat; j++)

                                      send(client_s[j],rline,n,0);

                              printf("%s\n",rline);

                       }

               }

        }

        closesocket(s);                                                                                                                                                        // closesocket()

        WSACleanup();                                                                                                                                                  // 윈속종료

        return 0;

}

 

 

아래는 클라이언트 소스입니다.

Client.cpp

/***********************************************

        스레드를사용한채팅클라이언트

   원래루트는recv 전용이고

   스레드를생성해서사용자입력대기하다가,

   입력받게되면send 해주는클라이언트임.

***********************************************/

 

#include <winsock2.h>

#include <stdlib.h>

#include <stdio.h>

#include <string.h>

 

#define MAXLINE     512

 

DWORD WINAPI ProcessInputSend(LPVOID arg);

 

char *escapechar = "exit";

char name[10]; // 채팅에서사용할이름

char line[MAXLINE], message[MAXLINE+1];

struct    sockaddr_in   server_addr;

SOCKET s;      // 서버와연결된소켓번호

 

int main()

{

        WSADATA wsa;                          // 윈속

 

        // 채팅참가자이름구조체초기화

        printf("채팅ID 입력: ");

        scanf("%s",name);

       

        // 윈속초기화

        if(WSAStartup(MAKEWORD(2,2), &wsa) != 0)

               exit(1);

 

        // 소켓생성

        if ((s = socket(AF_INET, SOCK_STREAM, 0)) < 0)

        {

               printf("Client : Can't open stream socket.\n");

               exit(0);

        }

 

        // 채팅서버의소켓주소구조체server_addr 초기화

        ZeroMemory(&server_addr, sizeof(server_addr));

        server_addr.sin_family = AF_INET;

        server_addr.sin_port = htons(9000);

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

       

        // 연결요청

        if(connect(s, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0)

        {

               printf("Client : Can't connect to server.\n");

               exit(0);

        }

        else

        {

               printf("서버에접속되었습니다. \n");

        }

 

        // 스레드생성

        HANDLE hThread;

        DWORD ThreadId;

        hThread = CreateThread(NULL, 0, ProcessInputSend, 0, 0, &ThreadId);

        if(hThread == NULL)

               printf("[오류] 스레드생성실패!\n");

        else

               CloseHandle(hThread);

 

        while(1)

        {

               ZeroMemory(message, sizeof(message));

               int size;

               if((size = recv(s, message, MAXLINE, 0)) == SOCKET_ERROR)

               {

                       printf("recv()");

                       exit(0);

               }

               else

               {

                       message[size] = '\0';

                       printf("%s \n", message);

               }

        }

        closesocket(s);                       // closesocket()

        WSACleanup();                 // 윈속종료

}

 

 

// 사용자입력부분

DWORD WINAPI ProcessInputSend(LPVOID arg)

{

        while(true)

        {

               if(fgets(message, MAXLINE, stdin))

               {

                       sprintf(line, "[%s] %s", name, message);

                       if (send(s, line, strlen(line), 0) < 0)

                              printf("Error : Write error on socket.\n");

                       if (strstr(message, escapechar) != NULL )

                       {

                              printf("Good bye.\n");

                              closesocket(s);

                              exit(0);

                       }

               }

        }

        return 0;

}

 

아래의 클래스를 사용해서 뻘짓을 하는 쓰레드 서버보단 이쪽이 훨씬 직관적이지 않습니까?

>_<..

여튼 이것도 좀 여러군데 손을 봐야 하긴 하지만..

이걸로 간단한 채팅이 되는 소스입니다.

 

클라이언트에서 쓰레드를 나눈이유는
recv처리와 send 처리를 동시에 하기 위해서 랍니다. 쓰레드를 나눔으로서
윈도우 프로그램으로 보면, 언제던지 server로부터 메시지를 받을수 있는 recv 프로세서와
언제던지 사용자가 키보드 입력을 받을수 있는 send 프로세서가 동시에 실행이 되는셈이죠

자세한 설명은.. 네트워크 프로그래밍 책의 대부분 뒷부분에 있는 select 설명을 보시는게 낳으실껍니다 >_<;;

블로그 이미지

프로그래머 지향자 RosaGigantea

바쁜 일상 생활중의 기억 장소

댓글을 달아 주세요


이 에코서버는 "윈도우 네트워크 프로그래밍 [한빛미디어, 김선우]"의 책에 있는 내용을 기초로 작성된 소스 입니다.

소스 설명에 앞서… 우선 이 에코 서버의 소스는 이미 약간의 네트워크 프로그래밍 지식이 있다는 전제 하에 설명합니다.

여기서 약간은 서버와 클라이언트의 1:1 TCP 에코 서버를 기준으로 이해를 하신 뒤에 그 이후 과연 1대의 서버로 여러 클라이언트의 접속을 어떻게 할까에 대한의문점을 풀어 들이는데 약간이나마 도움이 될 꺼 같아 작성합니다.

음.. 아래 소스는 class 형식으로 작성되었습니다.
허나! 서버 작성에 class 는 uml 차트를 그리신후 설계등 이런전문적인게 아니면 그냥 C로 작성하는걸 권유하는 편입니다.

이유로는

  1. 절차적 흐름으로 가는 네트워크 프로그래밍에 굳이 객체 지향을 할 필요가 없다.
  2. Class로 설계 하다보면 send, recv 쪽의 소스가 꼬일 가능성이 높다
  3. 유지보수 할 때 햇갈리기 쉽상이다

로 느껴집니다..

자세히는 이 위의 select server와 비교를 하시면, 왜 class로 하면 안되는지 이해가 가실꺼라 생각합니다.

일단 소스를 들어갑니다.
ws2_32.lib 의 링크를 설정 방법까진 설명하진 않습니다.


NetworkUtil.h // 여기는 윈도우 네트워크 프로그래밍 할 때 반복으로 쓰인 util함수 모음 헤더

#include <winsock2.h>
#include <stdlib.h>
#include <stdio.h>

// 소켓함수오류출력후종료
void err_quit(char *msg)
{
    LPVOID lpMsgBuf;
    FormatMessage(
        FORMAT_MESSAGE_ALLOCATE_BUFFER|
        FORMAT_MESSAGE_FROM_SYSTEM,
        NULL, WSAGetLastError(),
        MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
        (LPTSTR)&lpMsgBuf, 0, NULL);
    MessageBox(NULL, (LPCTSTR)lpMsgBuf, msg, MB_ICONERROR);
    LocalFree(lpMsgBuf);
    exit(-1);
}

// 소켓함수오류출력
void err_display(char *msg)
{
    LPVOID lpMsgBuf;
    FormatMessage(
        FORMAT_MESSAGE_ALLOCATE_BUFFER|
        FORMAT_MESSAGE_FROM_SYSTEM,
        NULL, WSAGetLastError(),
        MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
        (LPTSTR)&lpMsgBuf, 0, NULL);
    printf("[%s] %s", msg, (LPCTSTR)lpMsgBuf);
    LocalFree(lpMsgBuf);
}

// 사용자정의데이터수신함수
int recvn(SOCKET s, char *buf, int len, int flags)
{
    int received;
    char *ptr = buf;
    int left = len;
    while(left > 0){
        received = recv(s, ptr, left, flags);
        if(received == SOCKET_ERROR)
            return SOCKET_ERROR;
        else if(received == 0)
            break;
        left -= received;
        ptr += received;
    }
    return (len - left);
}


Server.h

#include <iostream>
#include "NetworkUtil.h"
using namespace std;
#define BUFSIZE 4096

DWORD WINAPI ProcessClient(LPVOID arg);     // 쓰레드
class Cilent_Connect
{
private:
    int retval;
    SOCKET client_sock;          // 접속하는 클라이언트의 소켓
    SOCKADDR_IN clientaddr;      // 접속하는 클라이언트의 구조체
    char buf[BUFSIZE];           // 버퍼
    void Client_Disconnect();    // 클라이언트를 종료하는 함수
    bool Send_Proc();            // 클라이언트에서 받은 Text 보내기
    bool Recv_Proc();            // 클라이언트로부터 Text 받기

public:
    Cilent_Connect(SOCKET client);
    ~Cilent_Connect();
    bool Server_Proc();
};

class
Server
{
private:
    int retval;                 // 공용변수ㅡㅡ.. 대략 어느함수의 리턴값에 활용
    WSADATA wsa;                // 윈소켓
    SOCKADDR_IN serveraddr;     // 서버소켓의 구조체
    SOCKET listen_sock;         // 대기중인 서버 소켓
    SOCKET client_sock;         // 접속하는 클라이언트의 소켓
    SOCKADDR_IN clientaddr;     // 접속하는 클라이언트의 구조체
    bool Client_Accept();       // 클라이언트 접근시 처리 함수

public:
    Server(char *ip, int port);    // socket 선언에서listen 단계까지 일괄처리단계
    ~Server();                     // 프로그램 종료시 마무리작업
    void Server_Loop();            // 서버루프
};



Server.cpp

#include "Server.h"

// 파일을 모두 받았으면 해당 클라이언트를 종료하는 함수
void Cilent_Connect::Client_Disconnect()
{
    // closesocket()
    closesocket(client_sock);
    printf("클라이언트: IP 주소=%s, 포트번호=%d의접속이끊김\n",
        inet_ntoa(clientaddr.sin_addr), ntohs(clientaddr.sin_port));
}

// 개별 클라이언트 에게 워드보내기
bool Cilent_Connect::Send_Proc()
{
    retval = send(client_sock, (char *)&buf, BUFSIZE, 0);
    if(retval == SOCKET_ERROR)
    {
        err_display("send()");
        closesocket(client_sock);
        return false;
    }        
    return true;
}

// 개별 클라이언트의 워드 받기
bool Cilent_Connect::Recv_Proc()
{
    // 대화 워드 받기
    ZeroMemory(buf, BUFSIZE);
    retval = recvn(client_sock, buf, BUFSIZE, 0);
    if(retval == SOCKET_ERROR)
    {
        err_display("recv()");
        closesocket(client_sock);
        return false;
    }
    printf("[Client IP = %s, port = %d] : %s\n",inet_ntoa(clientaddr.sin_addr),
                                               ntohs(clientaddr.sin_port), buf);
    return true;
}

// 초기화 쓰레드로부터 값 가져오기
Cilent_Connect::Cilent_Connect(SOCKET client)
{
    client_sock = client;
    int addrlen = sizeof(clientaddr);
    getpeername(client_sock, (SOCKADDR*)&clientaddr, &addrlen);
}

// 쓰레드종료시..
Cilent_Connect::~Cilent_Connect()
{
    Client_Disconnect();        
}

// 쓰레드 루트
bool Cilent_Connect::Server_Proc()
{
    while(1)
    {
        if(!Recv_Proc())            
            break;
        if(!Send_Proc())    
            break;
    }
    cout << "접속이종료됨." << endl;
    return true;
}

// 5. 개별 클라이언트 클래스호출... 정의는 해더파일 참고
DWORD WINAPI ProcessClient(LPVOID arg)
{
    Cilent_Connect Idle((SOCKET)arg);
    Idle.Server_Proc();
    return 0;
}

// 4.
bool Server::Client_Accept()            
{
    int addrlen;        // 의미없음ㅡㅡ.. 단순주소길이저장변수
    addrlen = sizeof(clientaddr);
    client_sock = accept(listen_sock, (SOCKADDR *)&clientaddr, &addrlen);
    if(client_sock == INVALID_SOCKET)
    {
        err_display("accept()");
        return false;
    }
    HANDLE hThread;
    DWORD ThreadId;
    printf("\n클라이언트접속: IP 주소=%s, 포트번호=%d\n",
        inet_ntoa(clientaddr.sin_addr), ntohs(clientaddr.sin_port));
    // 스레드생성
    hThread = CreateThread(NULL, 0, ProcessClient, (LPVOID)client_sock, 0,
                          ThreadId);
    if(hThread == NULL)
        printf("[오류] 스레드생성실패!\n");
    else
        CloseHandle(hThread);
    return true;
}

// 2. socket 선언에서listen 단계까지일괄처리단계
Server::Server(char *ip, int port)        // IP주소와port번호는따로입력받는다.
{
    cout << "******** 서버프로그램시작********\n";
    cout << "멀티스레드를 적용해서 여러 클라이언트의 말을 되돌려 주는 서버\n";
     
    // 윈속초기화
    if(WSAStartup(MAKEWORD(2,2), &wsa) != 0)
        exit(1);
   
    // 소켓정의, TCP사용임
    listen_sock = socket(AF_INET, SOCK_STREAM, 0);
    if(listen_sock == INVALID_SOCKET) err_quit("socket()");    
   
    // bind()        
    ZeroMemory(&serveraddr, sizeof(serveraddr));
    serveraddr.sin_family = AF_INET;
    serveraddr.sin_port = htons(port);
    serveraddr.sin_addr.s_addr = inet_addr(ip);
    retval = bind(listen_sock, (SOCKADDR *)&serveraddr, sizeof(serveraddr));
    if(retval == SOCKET_ERROR) err_quit("bind()");
 
    // listen()
    retval = listen(listen_sock, SOMAXCONN);
    if(retval == SOCKET_ERROR) err_quit("listen()");
}

// 프로그램 종료시 마무리작업
Server::~Server()
{
    closesocket(listen_sock);        // closesocket()
    WSACleanup();                // 윈속종료
}

// 3. 프로그램루프
void Server::Server_Loop()
{
    while(1)
    {
        if(!Client_Accept())        // accept 처리
            continue;
    }
}

// 1. 서버의 IP와 들어올 Port 넣어줌.
int main()
{
    Server process("127.0.0.1",9000);
    process.Server_Loop();
    return 0;
}


음.. 그러면 이 다중 클라이언트의 개별적인 소통 하는건 언제 쓰일까요?

간단히 생각하면 로그인 서버를 예로 들 수 있겠네요.

각각 클라이언트로부터 IP/Password 를 입력 받아 그것을 DB로부터 검색 및 그 결과를 클라이언트에 돌려주는 작업이죠.


그보다 위의 소스는 이해가 되시나요?

Main 함수부터 따라가시면…

  1. Server Class 를 생성 하고, 생성자에 초기값으로 IP, Port를 넘겨 서버의 listen 단계까지 처리합니다.
  2. 그리고 어느 클라이언트로부터 accept 이 들어오면 그 부분에서 쓰레드를 나눠서 만든 쓰레드로 클라이언트와 네트워크 처리 (send, recv)를 하고 원래 루트는 계속 listen 단계를 넣어 다른 클라이언트의 접속을 기다립니다.

클래스로 하다보니 아마 보기 여려울꺼라 생각됩니다.

이 프로그램에 대한 클라이언트 소스는 아래와 같습니다.

 

Client.h

#include <iostream>

#include "NetworkUtil.h"

 

#define BUFSIZE 4096

 

using namespace std;
 

class Client

{

private:

        int retval;                           // 여러통신간리턴값을저장하는변수

        WSADATA wsa;                          // 윈속

        SOCKET sock;                          // 클라이언트소켓

        SOCKADDR_IN serveraddr;               // 소켓구조체

 

        char buf[BUFSIZE];                    // 보낼 Text

 

        void Send_Proc();                     // send 처리

        void Recv_Proc();                     // recv 처리

 

public:

        Client();                             // 파일클라이언트connect 작업까지처리

        ~Client();                            // 기타소켓종료등. 마무리

        void ClientProc();            // 구동부분

};

 

 

 

Client.cpp

#include "Client.h"

 

void Client::Send_Proc()

{

        ZeroMemory(buf, BUFSIZE);

        cout << "보낼내용";

        cin >> buf;

              

        retval = send(sock, buf, BUFSIZE, 0);

        if(retval == SOCKET_ERROR) err_quit("send()");

}

 

void Client::Recv_Proc()

{

        ZeroMemory(buf, BUFSIZE);

       

        retval = recvn(sock, (char *)&buf, BUFSIZE, 0);

        if(retval == SOCKET_ERROR) err_quit("recv()");

 

        cout << "서버로부터받은메시지: " << buf << endl;

}

 

// 2.

// connet 까지일괄처리

// 아래define을주석처리하면ip, port를입력할수있는소스로바뀝니다

#define test

Client::Client()

{

        // 윈속초기화

        if(WSAStartup(MAKEWORD(2,2), &wsa) != 0)

               exit(1);

 

        // socket()

        sock = socket(AF_INET, SOCK_STREAM, 0);

        if(sock == INVALID_SOCKET) err_quit("socket()");    

 

#ifdef test

        char ip[20] = "127.0.0.1";

        int port = 9000;

#else

        char ip[20];

        int port;

        printf("접속하려는서버의IP주소를대시오: ");

        scanf("%s",ip);

        printf("접속하려는서버의Port를대시오: ");

        scanf("%d",&port);

#endif

 

        // connect()  

        ZeroMemory(&serveraddr, sizeof(serveraddr));

        serveraddr.sin_family = AF_INET;

        serveraddr.sin_port = htons(port);

        serveraddr.sin_addr.s_addr = inet_addr(ip);

        retval = connect(sock, (SOCKADDR *)&serveraddr, sizeof(serveraddr));

        if(retval == SOCKET_ERROR) err_quit("connect()");

}

 

Client::~Client()

{

        closesocket(sock);            // closesocket()

        WSACleanup();                 // 윈속종료

}

 

// 3. 기본루트

void Client::ClientProc()

{

        while(1)

        {

               Send_Proc();

               Recv_Proc();

        }

}

 

// 1.

int main()

{

        Client Client;

        Client.ClientProc();

        return 0;

}

 

 

'리눅스 서버에 대해서' 카테고리의 다른 글

Epoll 채팅 서버 소스  (0) 2008.05.25
Select 채팅 서버  (0) 2008.05.25
쓰레드 에코서버  (0) 2008.05.25
밑의 epoll 예제가 좀 복잡해서 좀 간단하게 수정함  (0) 2008.03.29
epoll 서버 프로그래밍  (0) 2008.03.29
겜서버 숙제  (0) 2008.03.24
블로그 이미지

프로그래머 지향자 RosaGigantea

바쁜 일상 생활중의 기억 장소

댓글을 달아 주세요

// 하앍 include 가 넘 많다...
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/epoll.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <errno.h>

#define EPOLL_SIZE 60
#define EPOLL_EVENT_SIZE 100
#define OPENPORT 5000

// main
int
main (int argc, char **argv)
{
 struct epoll_event *events;
 struct epoll_event ev;
 struct sockaddr_in addr, clientaddr;
 int clilen;
 int sfd, efd, cfd;
 int i;
 int max_got_events;
 int result;
 int readn;
 int sendflags = 0;
 char buf_in[256] = { '\0' };

 if ((efd = epoll_create (EPOLL_EVENT_SIZE)) < 0)
 {
  perror ("epoll_create (1) error");
  return -1;
 }
 
 // init pool
 events = (struct epoll_event *) malloc (sizeof (*events) * EPOLL_SIZE);
 if (NULL == events)
 {
  perror ("epoll_create (0) error");
  return -1;
 }

 clilen = sizeof (clientaddr);
 sfd = socket (AF_INET, SOCK_STREAM, 0);
 if (sfd == -1)
 {
  perror ("socket error :");
  close (sfd);
  return -1;
 }

 addr.sin_family = AF_INET;
 addr.sin_port = htons (OPENPORT);
 addr.sin_addr.s_addr = htonl (INADDR_ANY);
 if (bind (sfd, (struct sockaddr *) &addr, sizeof (addr)) == -1)
 {
  close (sfd);
  return -2;
 }
 listen (sfd, 5);
 
 if (sfd < 0)
 {
  perror ("init_acceptsock error");
  return 1;
 }

 printf("Running Server port %d\n",port);
 ev.events = EPOLLIN;
 ev.data.fd = sfd;
 result = epoll_ctl (efd, EPOLL_CTL_ADD, sfd, &ev);
 if (result < 0)
 {
  perror ("epoll_ctl error");
  return 1;
 }

 while (1)
 {
  max_got_events = epoll_wait (efd, events, EPOLL_SIZE, -1);
  for (i = 0; i < max_got_events; i++)
  {
   if (events[i].data.fd == sfd)
   {
    printf("Accepted\n");
    cfd = accept (sfd, (struct sockaddr *) &clientaddr, &clilen);
    if (cfd < 0)
    {
     perror ("Accept error");
     return -1;
    }

    ev.events = EPOLLIN;
    ev.data.fd = cfd;
    epoll_ctl (efd, EPOLL_CTL_ADD, cfd, &ev);
   }
   else
   {
    cfd = events[i].data.fd;

    memset (buf_in, 0x00, 256);
    readn = read (cfd, buf_in, 255);
    // if it occured ploblems with reading, delete from epoll event pool and close socket
    if (readn <= 0)
    {
     epoll_ctl (efd, EPOLL_CTL_DEL, cfd, &ev);
     close (cfd);
     printf ("Close fd ", cfd);
    }
    else
    {
     printf ("%s", buf_in);
     send (cfd, buf_in, strlen (buf_in), sendflags);
    }

   }
  }
 }
 return 1;
}

'리눅스 서버에 대해서' 카테고리의 다른 글

Epoll 채팅 서버 소스  (0) 2008.05.25
Select 채팅 서버  (0) 2008.05.25
쓰레드 에코서버  (0) 2008.05.25
밑의 epoll 예제가 좀 복잡해서 좀 간단하게 수정함  (0) 2008.03.29
epoll 서버 프로그래밍  (0) 2008.03.29
겜서버 숙제  (0) 2008.03.24
블로그 이미지

프로그래머 지향자 RosaGigantea

바쁜 일상 생활중의 기억 장소

댓글을 달아 주세요

epoll 리눅스에서 다루는 이벤트 통지형 서버 구조임.
Win32로 따지면 WSAEventSelect 모델 정도로 볼수 있을것이다.
Win32에선 WSAEventSelect를 더욱 개선한 Overlapped 모델을 지원하지만 실제 Overlapped 모델은 윈도우 컨디션에 따라 성능을 제대로 보장하지 못한다고 한다.
하여튼..
http://twopairs.tistory.com/101 사이트에 의하면

c epoll vs java epoll vs java poll

korean

test machine : linux, mem 2G, 2cpu
server's business logic is echo, client send 1 request per 5 second and
 data volumn is less then 1k.

result :

c epoll server
when 30000 socket is running
CPU 6-12%
there aren't special phenomenon.

java epoll server
In 30000 socket is running
CPU 10-17%
there aren't special phenomenon.

java poll server
In 3000 socket is running
cpu reach near 100%
In 10000 socket is running
listen socket accept connection but cpu reach 100%...

conclusion :
epoll is fantastic

음.. 하여간 epoll 이면서 c언어로 해야한다.

서버는 c++을 사용하지 않는데 그 이유로는.. class를 생성하면서 메모리 관리가 복잡해지기 때문이다.
그리고. 직관적으로 짜지 않으면 워낙 많은 변수로 안전성이 떨어진다. ㅡ_ㅡ..

잡스 형님의 말씀 처럼 Simple is best 인 코드는 서버 코드가 아닐까 생각된다.

이제 본론에 들어가서 epoll 서버 샘플 프로그래밍이다. 출처는 아래와 같다.

출처 :
http://www.xevious7.com/archive/200704

epoll 서버 샘플 프로그래밍

프로그래밍/서버프로그래밍 2007/04/18 10:19

epoll 서버 샘플 프로그래밍  by xevious7   http://www.xevious7.com/52
(문서의 작성일. 2006.5월9일)
(문서의 수정및 추가 2007년 4월)

거의 1년여전에 이 문서를 작성해놓고 실수로 공개를 안하고 있었습니다.
계속 비공개문서로 있었던 것이죠. 아마도 무슨 바쁜일 때문에 미처 공개하지
못하고 다른 문서들로 인하여 계속 비공개로 남아있었던 것 같습니다.
최근에 epoll에 대한 것을 테스트하다가 뒤늦게 문서가 비공개로 되어있는
것을 알았습니다. 그래서  내용도 조금 수정하고 테스트서버도 작성해보았습니다.
소스도 공개합니다.

epoll을 이용하여 작성된 완성된 훌륭한 서버소스가 많이 공개되어있지만 ,
일반적으로 공부하려는 프로그래머가 그 큰 소스의 내부를 분석하여
이해하기는 좀 무리가 있다고 생각이 듭니다.

아무래도 잘 짜여지고 다듬어지다보니
보다 추상화가 많이 되어있고 보다 감싸져있기 때문에 쓰기는 편해도 이해하기는
힘들다고 할까요
.

직관적으로 이해를 돕기 위해서 , 여기 공개하는 epoll응용
테스트서버는 C언어로 작성하였습니다.  단순한 에코형서버입니다.
접속한 모든 클라이언트에게 내용을 전달합니다. 두 클라이언트가 접속하면
서로 채팅하는것이 될것이고 여러클라이언트가 붙으면 대화방처럼 작동합니다.

여기에 간단한 프로토콜을 넣으면 (채널관리 ,룸관리) 간단한 채팅서버가
될것입니다.

다시 말하면 이해를 위한 뼈대구조의 하나의 샘플입니다.


소스코드가 매끄럽지 못합니다. 다만 직관적 이해를 위해 코드를 압축하거나
추상화하는 것은 자제했습니다.

관련링크

http://www.joinc.co.kr/modules/moniwiki/wiki.php/epoll
윗글은 epoll의 제작자가 만든 원래의 URL에(man페이지) 대한 번역과
이 링크에도 간단한 에코서버소스가 공개되어있습니다.

epoll 제작자의 글은 다음 URL에 있습니다.
http://www.xmailserver.org/linux-patches/epoll.txt


대용량 서버에 대한 방법론에 대한것은 다음문서
http://www.kegel.com/c10k.html 에서 자세히 볼 수 있습니다.

epoll을 사용하기위한 조건

epoll은 2001년 7월 11일에 다비드 라이프니치(David Libenzi)씨에 의해서
리얼타임 시그날 (RealTime Sinal)의 대안으로 제안된 이후로 2.5.x 커널에 추가되기
시작하였습니다.

그리고 2.6.x 커널대에서는 기본으로 탑재되어 있습니다.

(2.6.x 커널은 2003년 12월에 릴리즈 되었습니다. 현재까지(2007년3월) 계속되고 있습니다.)
9.x 대 리눅스라면 epoll을 사용하는데 전혀 무리가 없다는 이야기입니다.
만약  2.6.x 커널 및의 리눅스라면 위의 joinc 문서에서 사용여부 가능방법과 라이브러리
등의 설치등등의 확인 방법이 설명되어있으니 참조하기 바랍니다.

자신의 리눅스 시스템에서 epoll이 사용가능한지 체크하는 C 코드

      #include <stdio.h>
      #include <stdlib.h>
      #include <sys/epoll.h>
     
      int main(int argc, char** argv)
      {
               int epoll_fd;
                 
               if ( (epoll_fd = epoll_create(500)) == -1 )
               {
                       printf("epoll 파일디스크립터 생성 오류 \n");
               }
               else
               {
                       printf("epoll 파일디스크립터 생성성공 \n");
                      close(epoll_fd);
                      /* 생성된 fd는 다른 fd와 마찬가지로 프로그램 종료전에 반드시 닫아주어야한다. */

               }
        }
        /* EOF */

컴파일이 안되거나 , 실행시 오류가 난다면 무엇인가 설정이 잘못되어있는 것입니다. 

epoll 을 사용하기 위한 API

epoll을 사용하기 위해서 알아야 될 API는 다음 세가지입니다.
int epoll_create(int size);
int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout)
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event)

함수의 각각의 설명은 man페이지나 제작자의 다음 링크에서 볼 수 있습니다.
http://www.xmailserver.org/linux-patches/epoll_create.txt
http://www.xmailserver.org/linux-patches/epoll_ctl.txt
http://www.xmailserver.org/linux-patches/epoll_wait.txt

한글번역문서는 jacking님의 일본man 페이지 번역이 다음링크에 있습니다.
http://jacking75.cafe24.com/Network/epoll.htm

위의 각각의 3개의 API의 이해가 소스를 이해하는데 필수적입니다. 당연한
이야기이지만요.

소스는 epoll LT모드를 사용하고있습니다. ( ET를 선언하지 않은경우
자동으로 LT입니다. 소스에 LT를 따로 선언하지 않았습니다.)
ET모드는 socket이 반드시 non blocking 소켓이어야 하지만 LT는
상관없습니다. 그래서 따로 non blocking 부분을 설정하는 부분이 없고
소켓 그대로 사용하였습니다. 또한 닫혀진 소켓에 대한 epoll set에서
삭제하는 부분도 없습니다.  이부분은 epoll 제작자의 FAQ에서 보면
닫힌 소켓에대해서 epoll set에서 자동으로 없어지는가 하는 질문에
닫힌 소켓은 자동으로 epoll set에서 없어진다라고 되어있기 때문에
따로 처리하지 않았습니다. 소켓이 닫히는순간 epoll set에서 사라집니다.

주석은 영어로 되어있습니다.  ^^;;

테스트서버 소스
/*-----------------------------------------------------------------

  epoll server test program by EuiBeom Hwang. : 2005-2007.
  Platform : Linux 2.6.x (kernel)
  compiler Gcc: 3.4.3.
  License: GNU General Public License  
  descption : simple test server. multi client accept.
              sample skelecton epoll structure(conceptional)
              this code is just sample implemention code.
------------------------------------------------------------------*/

/* header files */
#include <stdio.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <sys/epoll.h>
#include <arpa/inet.h>
#include <signal.h>
#include <string.h>
#include <unistd.h>

/* definition */
#define MAX_CLIENT   10000
#define DEFAULT_PORT 9006
#define MAX_EVENTS   10000


/* global definition */
int g_svr_sockfd;              /* global server socket fd */
int g_svr_port;                /* global server port number */

struct {
         int  cli_sockfd;  /* client socket fds */
         char cli_ip[20];              /* client connection ip */
} g_client[MAX_CLIENT];

int g_epoll_fd;                /* epoll fd */

struct epoll_event g_events[MAX_EVENTS];

/* function prototype */
void init_data0(void);            /* initialize data. */
void init_server0(int svr_port);  /* server socket bind/listen */
void epoll_init(void);            /* epoll fd create */
void epoll_cli_add(int cli_fd);   /* client fd add to epoll set */

void userpool_add(int cli_fd,char *cli_ip);

/*--------------------------------------------------------------*/
/* FUNCTION PART
---------------------------------------------------------------*/

/*---------------------------------------------------------------
  function : init_data0
  io: none
  desc: initialize global client structure values
----------------------------------------------------------------*/
void init_data0(void)
{
  register int i;

  for(i = 0 ; i < MAX_CLIENT ; i++)
  {
     g_client[i].cli_sockfd = -1;
  }
}

/*-------------------------------------------------------------
  function: init_server0
  io: input : integer - server port (must be positive)
  output: none
  desc : tcp/ip listening socket setting with input variable
----------------------------------------------------------------*/

void init_server0(int svr_port)
{
  struct sockaddr_in serv_addr;
 
  /* Open TCP Socket */
  if( (g_svr_sockfd = socket(AF_INET,SOCK_STREAM,0)) < 0 )
  {
      printf("[ETEST] Server Start Fails. : Can't open stream socket \n");
      exit(0);
  }

  /* Address Setting */
  memset( &serv_addr , 0 , sizeof(serv_addr)) ;

  serv_addr.sin_family = AF_INET;
  serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
  serv_addr.sin_port = htons(svr_port);

  /* Set Socket Option  */
  int nSocketOpt = 1;
  if( setsockopt(g_svr_sockfd, SOL_SOCKET, SO_REUSEADDR, &nSocketOpt, sizeof(nSocketOpt)) < 0 )
  {
      printf("[ETEST] Server Start Fails. : Can't set reuse address\n");
      close(g_svr_sockfd);
      exit(0);
  }
 
  /* Bind Socket */
  if(bind(g_svr_sockfd,(struct sockaddr *)&serv_addr, sizeof(serv_addr)) < 0)
  {
     printf("[ETEST] Server Start Fails. : Can't bind local address\n");
     close(g_svr_sockfd);
     exit(0);
  }

  /* Listening */
  listen(g_svr_sockfd,15); /* connection queue is 15. */

  printf("[ETEST][START] Now Server listening on port %d\n",svr_port);
}
/*------------------------------- end of function init_server0 */

void epoll_init(void)
{
  struct epoll_event events;

  g_epoll_fd = epoll_create(MAX_EVENTS);
  if(g_epoll_fd < 0)
  {
     printf("[ETEST] Epoll create Fails.\n");
     close(g_svr_sockfd);
     exit(0);
  }
  printf("[ETEST][START] epoll creation success\n");

  /* event control set */
  events.events = EPOLLIN;
  events.data.fd = g_svr_sockfd;

  /* server events set(read for accept) */
  if( epoll_ctl(g_epoll_fd, EPOLL_CTL_ADD, g_svr_sockfd, &events) < 0 )
  {
     printf("[ETEST] Epoll control fails.\n");
     close(g_svr_sockfd);
     close(g_epoll_fd);
     exit(0);
  }

  printf("[ETEST][START] epoll events set success for server\n");
}
/*------------------------------- end of function epoll_init */

void epoll_cli_add(int cli_fd)
{
 
  struct epoll_event events;

  /* event control set for read event */
  events.events = EPOLLIN;
  events.data.fd = cli_fd;

  if( epoll_ctl(g_epoll_fd, EPOLL_CTL_ADD, cli_fd, &events) < 0 )
  {
     printf("[ETEST] Epoll control fails.in epoll_cli_add\n");
  }

}

void userpool_add(int cli_fd,char *cli_ip)
{
  /* get empty element */
  register int i;

  for( i = 0 ; i < MAX_CLIENT ; i++ )
  {
     if(g_client[i].cli_sockfd == -1) break;
  }
  if( i >= MAX_CLIENT ) close(cli_fd);

  g_client[i].cli_sockfd = cli_fd;
  memset(&g_client[i].cli_ip[0],0,20);
  strcpy(&g_client[i].cli_ip[0],cli_ip);

}

void userpool_delete(int cli_fd)
{
  register int i;

  for( i = 0 ; i < MAX_CLIENT ; i++)
  {
       if(g_client[i].cli_sockfd == cli_fd)
       {
          g_client[i].cli_sockfd = -1;
          break;
       }
  }
}

void userpool_send(char *buffer)
{
  register int i;
  int len;

  len = strlen(buffer);

  for( i = 0 ; i < MAX_CLIENT ; i ++)
  {
      if(g_client[i].cli_sockfd != -1 )
      {
          len = send(g_client[i].cli_sockfd, buffer, len,0);
          /* more precise code needed here */
      }
  }
 
}


void client_recv(int event_fd)
{
  char r_buffer[1024]; /* for test.  packet size limit 1K */
  int len;
  /* there need to be more precise code here */
  /* for example , packet check(protocol needed) , real recv size check , etc. */

  /* read from socket */
  len = recv(event_fd,r_buffer,1024,0);
  if( len < 0 || len == 0 )
  {
      userpool_delete(event_fd);
      close(event_fd); /* epoll set fd also deleted automatically by this call as a spec */
      return;
  }

  userpool_send(r_buffer);

}

void server_process(void)
{
  struct sockaddr_in cli_addr;
  int i,nfds;
  int cli_sockfd;
  int cli_len = sizeof(cli_addr);

  nfds = epoll_wait(g_epoll_fd,g_events,MAX_EVENTS,100); /* timeout 100ms */

  if(nfds == 0) return; /* no event , no work */
  if(nfds < 0)
  {
      printf("[ETEST] epoll wait error\n");
      return; /* return but this is epoll wait error */
  }

  for( i = 0 ; i < nfds ; i++ )
  {
      if(g_events[i].data.fd == g_svr_sockfd)
      {
          cli_sockfd = accept(g_svr_sockfd, (struct sockaddr *)&cli_addr,(socklen_t *)&cli_len);
          if(cli_sockfd < 0) /* accept error */
          {
          }
          else
          {
             printf("[ETEST][Accpet] New client connected. fd:%d,ip:%s\n",cli_sockfd,inet_ntoa(cli_addr.sin_addr));
             userpool_add(cli_sockfd,inet_ntoa(cli_addr.sin_addr));
             epoll_cli_add(cli_sockfd);
          }
          continue; /* next fd */
      }
          /* if not server socket , this socket is for client socket, so we read it */
         client_recv(g_events[i].data.fd);   

  } /* end of for 0-nfds */
}
/*------------------------------- end of function server_process */

void end_server(int sig)
{
  close(g_svr_sockfd); /* close server socket */
  printf("[ETEST][SHUTDOWN] Server closed by signal %d\n",sig);
  exit(0);
}

int main( int argc , char *argv[])
{
 
  printf("[ETEST][START] epoll test server v1.0 (simple epoll test server)\n");
  /* entry , argument check and process */
  if(argc < 3) g_svr_port = DEFAULT_PORT;
  else
  {
     if(strcmp("-port",argv[1]) ==  0 )
     {
        g_svr_port = atoi(argv[2]);
        if(g_svr_port < 1024)
        {
           printf("[ETEST][STOP] port number invalid : %d\n",g_svr_port);
           exit(0);
        }
     }
  }


  init_data0();  

  /* init server */
  init_server0(g_svr_port);
  epoll_init();    /* epoll initialize  */

  /* main loop */
  while(1)
  {
     server_process();  /* accept process. */
  } /* infinite loop while end. */

}

출처 : Tong - e돌람바님의 UNIX/LINUX C/C++통

'리눅스 서버에 대해서' 카테고리의 다른 글

Epoll 채팅 서버 소스  (0) 2008.05.25
Select 채팅 서버  (0) 2008.05.25
쓰레드 에코서버  (0) 2008.05.25
밑의 epoll 예제가 좀 복잡해서 좀 간단하게 수정함  (0) 2008.03.29
epoll 서버 프로그래밍  (0) 2008.03.29
겜서버 숙제  (0) 2008.03.24
블로그 이미지

프로그래머 지향자 RosaGigantea

바쁜 일상 생활중의 기억 장소

댓글을 달아 주세요

이거 할때마다 느끼지만 조낸 빡세다... orz

'리눅스 서버에 대해서' 카테고리의 다른 글

Epoll 채팅 서버 소스  (0) 2008.05.25
Select 채팅 서버  (0) 2008.05.25
쓰레드 에코서버  (0) 2008.05.25
밑의 epoll 예제가 좀 복잡해서 좀 간단하게 수정함  (0) 2008.03.29
epoll 서버 프로그래밍  (0) 2008.03.29
겜서버 숙제  (0) 2008.03.24
블로그 이미지

프로그래머 지향자 RosaGigantea

바쁜 일상 생활중의 기억 장소

댓글을 달아 주세요