0.시작하면서...
BSD 소켓 프로그래밍은 MFC 소켓에 비하면 쓰기가 까다롭고 알아야할 부분이 많다. 특히나 윈도우 프로그래밍을 하면 MFC 소켓에 익숙해지기때문에 까먹기가 십상이다.
이번에 NDS 소켓 프로그래밍을 하면서 우연히 다시 볼 기회가 생겨 정리한다.
1.참고 함수들
1.1 select 함수
Single Thread로 Multi-Socket을 컨트롤 하는 방법은 여러가지가 있겠지만, 가장 대표적인 것이 select이다. select는 아래와 같은 원형을 가지고 있다.
- int select(
int nfds,
fd_set FAR* readfds,
fd_set FAR* writefds,
fd_set FAR* exceptfds,
const struct timeval FAR* timeout
);
select 함수는 nfds에 설정된 소켓의 수만큼 소켓을 체크하므로 반드시 가장 큰 소켓 번호 + 1의 크기만큼을 nfds로 넘겨줘야 함을 잊지 말자( ex: fd + 1 )
return 값은 아래와 같은 의미를 가진다.
양수 : readfds or writefds or exceptfds 중에 양수 개의 fd가 이벤트가 발생했다.
- fds에 이벤트가 발생한 fd만 플래그가 설정되므로 FD_ISSET 매크로를 이용해서 해당 socket을 찾을 수 있다.
- timeout에 남은 시간이 저장되므로 이를 활용하면 추가적인 처리가 가능하다
- fds에 이벤트가 발생한 fd만 플래그가 설정되므로 FD_ISSET 매크로를 이용해서 해당 socket을 찾을 수 있다.
- 0 : timeout이 되었다. timeout 값의 경우 0으로 설정되면 무한대로 대기한다.
- 음수 : readfds에 닫힌 소켓이 있거나 기타 에러가 발생했다.
fd_set 및 timeval은 구조체로 아래와 같은 형태를 가진다.
- typedef struct fd_set {
u_int fd_count;
SOCKET fd_array[FD_SETSIZE];
} fd_set;
- struct timeval {
long tv_sec; // second 단위
long tv_usec; // millisecond 단위
};
timeval은 위에서 보는 것 그대로 second/millisecond 단위로 설정해서 사용하면 된다.
하지만 fd_set과 같은 경우 어떻게 사용해야할지 좀 막막하다. 다행이 이를 처리해주는 매크로가 있으니 아래와 같다.
- FD_ZERO( fd_set* fdset ) : fdset을 초기화. 처음에는 반드시 한번 호출해야 함
- FD_SET( int fd, fd_set* fdset ) : fdset에 fd 소켓을 등록한다.
- FD_CLR( int fd, fd_set* fdset ) : fdset에 fd 소켓을 삭제한다.
- FD_ISSET( int fd, fd_set* fdset ) : fdset에 fd 소켓이 있는가 확인한다.
자주 사용하는 매크로이니 한번쯤은 읽어두자. 자세한 사용 방법은 아래의 Linux 예제를 보면 된다.
2.윈도우(Window) 환경
2.1 서버(Server)
- #include <winsock2.h>
#include <stdio.h>
#include <string.h>
- #define DEFAULT_PORT 2924
#define DEFAULT_BUFFER_SIZE 4096
- int main()
{
char Buffer[DEFAULT_BUFFER_SIZE + 1];
WSAData wsd;
- if(WSAStartup(MAKEWORD(2, 2), &wsd) != 0) {
printf("Winsock 초기화 에러!\n");
return -1;
}
- SOCKET ls = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
- if (ls == INVALID_SOCKET) {
printf("소켓 생성 실패!\n");
return -1;
}
- sockaddr_in service;
memset(&service, 0, sizeof(service));
service.sin_family = AF_INET;
service.sin_addr.s_addr = INADDR_ANY;
service.sin_port = htons(DEFAULT_PORT);
if (bind(ls, (SOCKADDR*) &service, sizeof(service)) == SOCKET_ERROR) {
printf("bind 실패!\n");
return -1;
}
if (listen(ls, 1) == SOCKET_ERROR) {
printf("listen() 실패!\n");
return -1;
}
- SOCKET as;
- printf("클라이언트 연결 대기.\n");
- while (1) {
as = accept(ls, NULL, NULL);
if (as == SOCKET_ERROR) continue;
printf("클라이언트 연결됨.\n");
- int nbyte = recv(as, Buffer, DEFAULT_BUFFER_SIZE, 0);
- if (nbyte <= 0) {
printf("recv 에러!\n");
break;
}
Buffer[nbyte] = '\0';
printf("에코 : %s\n", Buffer);
- send(as, Buffer, nbyte, 0);
- if (strncmp(Buffer, "quit", 4) == 0) {
printf("클라이언트의 요청에 의해 서버 종료\n");
break;
}
closesocket(as);
printf("클라이언트 연결 해제.\n새로운 클라이언트 연결 대기.\n");
}
- closesocket(ls);
WSACleanup();
return 0;
}
2.2 클라이언트(Client)
- #include <winsock2.h>
#include <stdio.h>
#include <string.h>
- #define DEFAULT_PORT 2924
#define DEFAULT_BUFFER_SIZE 4096
- int main(int argc, char** argv)
{
char Buffer[DEFAULT_BUFFER_SIZE + 1];
WSAData wsd;
- if (argc != 2) {
printf ("사용법 : %s [IP 주소]\n", argv[0]);
return -1;
}
if(WSAStartup(MAKEWORD(2, 2), &wsd) != 0) {
printf("Winsock 초기화 에러!\n");
return -1;
}
SOCKET cs = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (cs == INVALID_SOCKET) {
printf("소켓 생성 실패!\n");
return -1;
}
sockaddr_in client;
memset(&client, 0, sizeof(client));
client.sin_family = AF_INET;
client.sin_addr.s_addr = inet_addr(argv[1]);
client.sin_port = htons(DEFAULT_PORT);
if (connect(cs, (SOCKADDR *)&client, sizeof(client)) == SOCKET_ERROR) {
printf("connect 에러!\n");
return -1;
}
- printf("입력 : ");
gets(Buffer);
- send(cs, Buffer, strlen(Buffer), 0);
int nbyte = recv(cs, Buffer, DEFAULT_BUFFER_SIZE, 0);
- Buffer[nbyte] = '\0';
printf("에코 : %s", Buffer);
- closesocket(cs);
WSACleanup();
return 0;
}
3.Linux or Unix or NDS
3.1 서버(Server)
윈도우쪽 소스를 조금 수정했다.
- #include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
- int main()
{
char Buffer[256 + 1];
- int ls = socket(AF_INET, SOCK_STREAM, 0);
if (ls == INVALID_SOCKET) {
printf("소켓 생성 실패!\n");
return -1;
}
- sockaddr_in service;
memset(&service, 0, sizeof(service));
service.sin_family = AF_INET;
service.sin_addr.s_addr = INADDR_ANY;
service.sin_port = htons(DEFAULT_PORT);
if (bind(ls, (SOCKADDR*) &service, sizeof(service)) == SOCKET_ERROR) {
printf("bind 실패!\n");
return -1;
}
if (listen(ls, 1) == SOCKET_ERROR) {
printf("listen() 실패!\n");
return -1;
}
int as;
printf("클라이언트 연결 대기.\n");
while (1) {
as = accept(ls, NULL, NULL);
if (as == SOCKET_ERROR) continue;
printf("클라이언트 연결됨.\n");
int nbyte = recv(as, Buffer, DEFAULT_BUFFER_SIZE, 0);
if (nbyte <= 0) {
printf("recv 에러!\n");
break;
}
Buffer[nbyte] = '\0';
printf("에코 : %s\n", Buffer);
send(as, Buffer, nbyte, 0);
if (strncmp(Buffer, "quit", 4) == 0) {
printf("클라이언트의 요청에 의해 서버 종료\n");
break;
}
close(as);
printf("클라이언트 연결 해제.\n새로운 클라이언트 연결 대기.\n");
}
close(ls);
return 0;
}
3.2 클라이언트(Client)
NDS에서 사용하는 예제를 조금 수정했다.
- #include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
- int main(void)
{
- //////////////////////////////////////////////////////////////////////////
// Let's send a simple HTTP request to a server and print the results!
- // store the HTTP request for later
const char * request_text =
"GET / HTTP/1.1\r\n\r\n";
// "Host: www.akkit.org\r\n"
// "User-Agent: Nintendo DS\r\n\r\n";
- // Find the IP address of the server, with gethostbyname
// DNS를 이용해서 Name으로 IP를 얻는다.
// 2007/10/24 현재, 아직 잘 안되는 것 같다.
iprintf( "DNS Resolve Start\n" );
//struct hostent * myhost = gethostbyname( "www.google.org" );
//iprintf("Found IP Address![www.google.org] [%08X]\n",
// myhost->h_addr_list[0] );
// Tell the socket to connect to the IP address we found, on port 80 (HTTP)
struct sockaddr_in sain;
sain.sin_family = AF_INET;
sain.sin_port = htons(80);
// Host Resolve가 끝났으면 아래와 같이 사용한다.
//sain.sin_addr.s_addr= *( (unsigned long *)(myhost->h_addr_list[0]) );
// 아래는 google의 IP 주소이다.
sain.sin_addr.s_addr = inet_addr( "72.14.235.99" );
// Create a TCP socket
int my_socket;
fd_set readfd;
fd_set tempfd;
struct timeval stTime;
struct timeval stTempTime;
int iRet;
stTime.tv_sec = 5;
stTime.tv_usec = 0;
Retry:
my_socket = socket( AF_INET, SOCK_STREAM, 0 );
iprintf("Created Socket!\n");
iprintf( "Try To Connect\n" );
connect( my_socket,(struct sockaddr *)&sain, sizeof(sain) );
iprintf("Connected to server!\n");
- // send our request
send( my_socket, request_text, strlen(request_text), 0 );
iprintf("Sent our request!\n");
- // Print incoming data
iprintf("Printing incoming data:\n");
- int recvd_len;
char incoming_buffer[256];
- iprintf("Recv Start\n");
FD_ZERO( &readfd );
FD_SET( my_socket, &readfd );
while( 1 )
{
tempfd = readfd;
stTempTime = stTime;
iRet = select( my_socket + 1, &tempfd, NULL, NULL, &stTempTime );
if( iRet > 0 )
{
recvd_len = recv( my_socket, incoming_buffer, 255, 0 );
iprintf("Recv End Size[%d]\n", recvd_len );
if( recvd_len > 0 )
{ // data was received!
incoming_buffer[ recvd_len ] = 0; // null-terminate
iprintf( incoming_buffer );
}
}
// Time Expired
else if( iRet == 0 )
{
iprintf( "Time Expired If You Press B, Exit Receiving Process\n" );
if( ~REG_KEYINPUT & KEY_B )
{
break;
}
}
else
{
iprintf( "Error~~!!\n" );
break;
}
}
iprintf("Other side closed connection!\n");
- shutdown(my_socket,0); // good practice to shutdown the socket.
- close(my_socket); // remove the socket.
iprintf( "Press Any A Key To Retry\n" );
while( REG_KEYINPUT & KEY_A ) ;
goto Retry;
- while(1);
- return 0;
}
4.마치면서...
간단하게나마 코드 조각을 정리해 보았다. 이제 구글링할 필요없이 바로 붙여넣고 쓰면된다. 다시한번 네트워크의 세계로 빠져보자.
'프로그래밍 > VC++ 개발 코딩' 카테고리의 다른 글
오픈 라이센스 Dia Program 소개 (0) | 2014.01.23 |
---|---|
CppCheck (정적분석도그) 프로그램 VS 6.0 연동 방법 (0) | 2014.01.23 |
Raw Sockets (0) | 2014.01.17 |
fopen 사용방법 (0) | 2014.01.17 |
VS6.0 STL 사용 주의사항 (0) | 2014.01.17 |