비동기 소켓이란.
동 기 : 소켓이 서로 통신할 때 데이터를 언제 보낼 것인가와 언제 받을 것인가를
알고 있는 경우의 통신. 만약 데이터를 보냈는데 받지 못하는 상황이 발생
하거나 데이터를 받을 준비를 했는데 아무것도 오지 않는 상황이 발생한다면
이로 인해 프로그램이 멈추게 된다. 언제 데이터가 오고 가는지 알고 있어야만
통신이 가능해진다.
비동기 : 서로 통신할 시기를 알지 못하는 경우를 생각해보면 언제 올지 모르는 데이터를
계속 기다리는 것은 비효율적인 작동방식이 된다. 이를 효율적으로 작동시키기 위
해서는 언제 데이터가 오는지는 알 필요 없이 데이터가 왔는지 안 왔는지만 확인해
서 다른 작업을 하다가 데이터가 왔다면 그 때 도착한 데이터를 받아 들인 후 처리
하면 된다.
블러킹모드와 넌블러킹모드
블러킹모드 : 소켓은 기본적으로 socket()함수를 써서 소켓을 생성하게 되면 블러킹모드로
만들어지게 된다. 블러킹모드에서 send()나 recv()함수를 호출하게 되면 데이
터가 가거나 오지 않는 이상 이 함수는 리턴을 하지 않고 계속 머무르게 되어
있다.
넌블러킹모드: fcntl()함수를 이용하여 소켓을 넌블러킹모드로 만들 수 있다. 이렇게 해서 만
들 경우 소켓은 데이터를 보내거나 받을 시 데이터가 있으면 바로 보내거나
받게 되고 없다면 errno변수(errno.h 헤더에 있다)에 상태를 기록하게 된다.
이 상태변수를 보고 무엇을 할지 결정하면 된다.
함수의 블록상태와 넌블럭상태
함수를 호출했을 때 리턴이 되지 않고 어떠한 상태가 될 때까지 무한히 기다리게 되는 경우가 있게되면 이것은 블록킹함수이고 이러한 상태가 없이 어떤 결과든 리턴해주게되는 경우가 넌블럭킹함수이다.
비동기의 방식
블록과 넌블록함수는 프로그램의 상태를 말해주는 의미이고 동기와 비동기는 읽기와 쓰기의 상태에 때라 구별되는 개념이다. 동기란 입력과 출력의 상호간에 약속에 의해 동시간에 이루어지는 것이다. 즉 A라는 상대가 1시에 파일을 보내기로 했다면 이것을 받는 B라는 상대도 1시에 정확히 파일을 받는 것을 의미한다. 만약 B가 1시 이전부터 파일을 받으려고 한다면 1시까지는 기다려야 함을 의미하며 B가 1시에 기다리고 있는데 A가 보내지 않는 다면 B는 A가 보낼 때까지 기다려야 함을 의미한다. 비동기는 언제 보내고 언제 받을지를 결정하지 않고 상대가 보낸다는 신호를 받으면 그 때 받는 행동을 하는 것이다. 비동기의 예를 들면 전화나 편지가 있다. 상대가 언제 보낼지 알 수 없고 전화벨이 울리면 그 때 받으면 된다. 전화벨이 울리기 전까지는 다른 일을 해도 된다.
비동기의 방식에는 여러가지가 있는데 윈도우와 유닉스의 방식에는 같이 쓸 수 있는 방법이
select()함수를 이용한 방법이 있다. 그리고 윈도우에서 지원해주는 여러가지 함수를 이용해서 다양한 방법을 쓸 수 있다. 리눅스에서는 select()나 poll()함수를 이용하여 만들 수 있는데 이 방법은 엄밀히 말하면 비동기가 아니다. 단지 운영체제가 언제 recv나 send함수를 호출해야 하는 지를 가르쳐주기 때문에 통신은 동기로 이루어 지지만 방식은 비동기에 가깝다. 진정한 비동기 방식은 운영체제에서 프로그램에 인터럽트를 걸고 데이터가 왔다고 알린 후 콜백함수를 등록하여 이를 호출하여 처리되는 식으로 동작할 것이다. 하지만 이런 방식은 구현이 까다롭고 OS에 대해 잘 알고 있어야 하며 select나 poll 방식에 비해 효율적이라고 할 수도 없어서 대부분 다른 방식을 사용한다고 한다.
이런 방식으로 SIGNAL 방식이 있다. 이것은 운영체제에 어떠한 신호를 등록해두고 자신은 다른 일을 하다가 신호가 오면 운영체제가 신호를 받아서 미리 등록해둔 방식으로 동작하도록 하는 것이다. 이 방식은 쓰레드를 써야하며 멀티플렉싱이 이루어 져야 할 때는 모든 소켓을 뒤져야 하므로 poll이나 select 방식에 비해 장점이 없다. 이러한 단점을 극복한 방법으로 REALTIME SIGNAL이라는 방법이 있지만 구현이 까다롭다고 한다.
다중 비동기 소켓 클래스
poll방식을 사용하여 비동기방식으로 다중 소켓을 처리할 수 있는 소켓클래스이다.
http://www.joinc.co.kr/modules/moniwiki/wiki.php/%BA%F1%B5%BF%B1%E2%BC%D2%C4%CF%BC%AD%B9%F6%C5%AC%B7%A1%BD%BA
이곳에서 구현해놓은 클래스와 예제가 있어서 분석해보았다.
파일 리스트를 보면
sock.h // sock클래스 구현 socket의 기본 기능을 구현
sock.cpp
asyncSocket.h // 다중 비동기 통신을 위하여 AsyncSocket클래스 구현
asyncSocket.cpp
log.h // 에러 보고를 위한 함수 선언 헤더
log.cpp
이렇게 있다.
Sock 클래스
소켓의 기본 기능인 connect나 bind, send등의 함수를 구현해 놓은 클래스이다.
특이점은 처음 connect를 할 때 블러킹되는 것을 방지하기 위하여 fcntl함수를 이용하여
소켓을 넌블러킹모드로 만들었다가 다시 블러킹으로 바꿔준다는 것이다.
그외에는 소켓의 기본 기능이 모두 들어가 있다.
AsyncSocket 클래스
다중접속을 위해 만들어진 소켓클래스이다. 소켓을 얼마나 열지는 클래스 생성자의 인자를 이용해 줄 수 있다. 언제나 0번 소켓은 서버용으로 사용되고 입력을 받는데 사용된다.
그외의 소켓은 accept함수에서 지정해주는 소켓을 받아서 통신에 사용하게 된다.
AsyncSocket클래스는 Sock클래스에서 상속을 받지 않고 다중소켓을 만들기 위하여 Sock개체를 포함하여 다수의 Sock객체를 가지고 있다. poll()함수를 사용하는 함수이기 때문에
apoll() 메소드가 가장 중요한 부분이 된다.
중요한 속성변수로
struct polldf* pFd;
ConInfo* conSock;
Sock * sock;
이 있다. pFd 는 poll함수에서 사용하기 위한 변수로 자세한 설명은
http://www.joinc.co.kr/modules/moniwiki/wiki.php/Site/Network_Programing/Documents/Poll
이곳에서 참고하였다.
ConInfo* conSock; 이 변수는 socket의 상태를 저장하기 위한 변수이다. 이것을 보고 현재
소켓이 접속되었는지 아닌지를 판단하게 된다.
poll함수는 pollfd형 포인터로 여러 개의 pollfd구조체변수를 등록해서 이 중에 어떤 소켓에
입력이나 출력등의 신호가 왔는지를 판단해 주게 된다.
이 AsyncSocket클래스는 예제가 있어 사용하기에는 불편함이 없지만 TCP에 국한되어있고
사용하는데 불편한 점이 있어서 직접 클래스를 작성하기로 하였다.
select방식의 비동기 입출력
select 방식의 소켓의 방식을 알기 위해 www.sourceforge.net 에서 찾아보았다.
Asynchronous Socket Library로 검색하면 나오게 되는 예제소스가 있는데
Select의 동작방식은 알 수 있었지만 클래스단위로 구현되어 있지 않고 전역함수를 사용하는 등의 방식으로 이루어져 있어서 사용하기에 까다로워 보였다. 그래서 새로 하나 만들어보기로 했다.
우선 새로 만든 클래스는 소켓의 기본적인 역할을 하는 클래스와 이를 이용해서 비동기로 작동하게 하는 클래스를 만들었다.
기본 소켓 클래스
CSocket
이 클래스는 소켓이 할 수 있는 대부분의 동작을 메소드로 구현해 두었으며 이 클래스만 가지고 통신을 할 수 있게 만들었다.
생성자의 인자를 이용해 TCP나 UDP로 이용할 수 있게 만들어 두었다.
클래스 헤더는 다음과 같다.
#ifndef _CSOCKET_H_
#define _CSOCKET_H_
typedef int SOCKET;
class CSocket
{
private:
SOCKET m_sockfd;
short m_port;
uint m_ip;
int m_family ; //AF_INET
int m_type; //SOCK_STREAM or SOCK_DGRAM
int flag;
sockaddr_in m_addr;
static socklen_t m_addrlen;
sockaddr_in m_client_addr;
static socklen_t m_clientlen;
bool isNonBlock;
public:
CSocket();
CSocket(int family,int type,int protocol);
~CSocket();
int Send(char * buf,int length,int flag=0);
int Sendto(char * buf,int length,sockaddr* addr,socklen_t len);
int Recv(char * buf,int length,int flag=0);
int Recvfrom(char * buf,int length,sockaddr* addr,socklen_t* len);
int Bind(void);
int Connect(int retry);
int Listen(int backlog=5);
int Accept(int srvfd);
int Close();
int setToBroadcast();
int setSockOpt(int level, int optname, const void *optval, socklen_t optlen);
int setNonBlockSock();
int setaddr(const char* ip, int port);
int setsvraddr(const char* ip, int port);
int setSocket(int family, int type, int protocol);
int setSockfd(int fd);
int getfd();
};
#endif //_CSOCKET_H_
CAsyncSock
CSocket클래스 객체를 포함하는 클래스이다. 상속을 사용하지 않은 이유는 CAsyncSock클래스는 다중접속을 위한 클래스이고 CSocket클래스는 하나의 소켓만 고려한 클래스이기 때문에 상속을 받기에는 부적합하기 때문이다.
다음은 CAsyncSock의 클래스 선언부이다.
class CAsyncSock
{
private:
vector <int> fds;
vector<ConInfo> m_sockinfo;
int maxi;
int conNum;
fd_set read_fds, write_fds;
public:
CAsyncSock();
CAsyncSock(int maxNum
virtual ~CAsyncSock();
int Close(int i);
locker < vector <CSocket > > m_sockList; //socket array
void InitAsync();
int aListen(int domain, int type, int protocol, const char* ip, int port, int backlog);
int aConnect(int domain, int type, int protocol, const char* ip, int port, int retry, int t) ;
void AsyncNotificationLoop(bool noWrites=false);
unsigned int GetAvailBytes(int i);
int getconNum(void);
int getSockStatus(int i) ;
void increaseSock(int i, bool increase);
void Select(timeval *timeout, bool noWrites);
virtual int OnRead(int i);
virtual int OnWrite(int i);
virtual int OnClose(int i);
};
locker<> 클래스는 템플릿 인자를 상속받아서 만들어지는 클래스로 작업도중 뮤텍스락을 걸수 있도록 해서 못건드리도록 하는 작업을 하는 클래스이다.
Select함수를 이용했기 때문에 select메소드에서만 입력에 대해 확인하고 대응되는 동작은
OnRead나 OnClose함수를 재정의해서 사용하면 된다.