네트워크 프로그램 모델
1. 클라이언트-서버 모델
- 하나의 서비스는 서버와 클라이언트로 구성된다.
- 서버 프로그램이 먼저 실행되고, 클라이언트 프로그램은 필요할 때 실행되어 서버에 접속한다.
- 프로그램 작성이 쉽다.
- ex) www
2. Peer-to-Peer(P2P) 모델
- 서버와 클라이언트의 구분이 없다.
- 프로그램 작성이 어렵다.
- Torrent
UDP 클라이언트-서버
UDP 소켓
- 데이터를 항상 전달해 주지는 않는다.
- 간단하고 전송속도가 비교적 균일하다.
UDP 멀티플렉싱
: 클라이언트가 많아져도, 서버 소켓이 많아지지 않는다.
UDP 클라이언트-서버의 동작
UDP 소켓 함수
- 생성과 소멸: socket(), closesocket()
- 이름 붙이기: bind()
데이터 송신: 보낸 바이트수를 의미함. 음수는 에러코드
int sendto(
SOCKET s,
const char *buf, //소켓 디스크립터
int len, //보낼 데이터의 크기
int flag, //0으로 설정
const SOCKADDR *to, //상대편 소켓의 이름
int tolen //to의 길이
)
- 전송할 데이터를 OS 내부의 소켓 버퍼로 복사한다.
-> 복사하는 동안 아주 잠깐 블로킹 상태이다. 하지만 소켓 버퍼에 빈 공간이 부족하면 자리가 나기를 기다리며 계속 블로킹 상태가 된다.
-> 리턴되었다고 해서 데이터가 상대편으로 전송된 것은 아니다.
-> Sendto()가 호출될 때 소켓에 이름이 없으면, OS가 임의로 이름을 붙인다.
데이터 수신: 받은 바이트 수를 의미함. 음수는 에러코드
int recvfrom(
SOCKET s ,//소켓 디스크립터
char *buf, //받을 데이터가 담길 버퍼 주소
int len, //버퍼의 크기
int flag, //0으로 설정
const SOCKADDR *from, //상대편 소켓의 이름(OS가 채워 준다.)
int *fromlen //from의 길이(프로그래머/OS가 채워 준다.)
)
- OS 내부의 소켓 버퍼에서 buf로 데이터 복사
-> 복사하는 동안은 아주 잠깐 블로킹 상태이다.
-> 소켓 버퍼에 데이터가 없으면, 데이터를 기다리면서 계속 블로킹 상태이다.
-> UDP 패킷이 버퍼 크기 len보다 크면, 읽지 못한 부분은 버리고 에러코드를 리턴한다.
-> 데이터 바이트가 없는 UDP 패킷이 수신되면 리턴값은 0이다.
서버: 첫 번째 클라이언트로부터 데이터를 받은 후 종료된다.
//...
SOCKET s=socket(AF_INET, SOCK_DGRAM,0);
SOCKADDR_IN myAddress;
ZeroMemory(&myAddress, sizeof(myAddress));
myAddress.sin_family=AF_INET;
myAddress.sin_port=htons(50000);
myAddress.sin_addr.s_addr=INADDR_ANY;
bind(s,(SOCKADDR*)&myAddress,sizeof(myAddress));
SOCKADDR_IN clientAddress;
int iAddressLength=sizeof(clientAddress);
char chRxBuf[MAX_BUF]={0};
int iLength=recvfrom(s,chRxBuf,MAX_BUF,0,(SOCKADDR*)&clientAddress,&iAddressLength);
closesocket(s);
//...
클라이언트: 서버에게 데이터를 보낸 후 종료
//...
SOCKET s=socket(AF_INET,SOCK_DGRAM,0);
SOCKADDR_IN serverAddress;
ZeroMemory(&serverAddress, sizeof(serverAddress));
serverAddress.sin_family=AF_INET;
serverAddress.sin_port=htons(50000);
inet_pton(AF_INET,"127.0.0.1", &(serverAddress.sin_addr.s_addr));
const char chTxBuf[MAX_BUF]="Hello, UDP Server! I am your Client!";
int iLength=sendto(s,chTxBuf,strlen(chTxBuf),0,(SOCKADDR*)&serverAddress,sizeof(serverAddress));
closesocket(s);
//...
NULL로 끝나는 문자열이면 직접 세지 않고 strlen()
C 언어는 배열의 초기값으로 문자열을 저장할 때, 자동으로 NULL을 끝에 붙여준다.
UDP 소켓 프로그램 생성
클라이언트
#pragma comment(lib, "ws2_32.lib");
#include "Winsock2.h"
#include <stdio.h>
#include <WS2tcpip.h>
#include <time.h>
#define MAX_BUF 5000
//21812167
//1. UDP 소켓 하나 생성
//2. 서버에게 메시지 송신
//3. 서버로부터 현재 시간 수신한 후 종료
//4. 화면에 모든 송수신 문자열을 출력할 것
int main(void) {
WSADATA wsa;
if (WSAStartup(MAKEWORD(2, 2), &wsa) != 0) {
//소켓 라이브러리가 로딩이 됐는지 안 됐는지 확인
//0아닌 값이 넘어오면 로딩이 안 된 것
printf("Error in starting up Winsock\n");
return -1;
}
SOCKET s = socket(AF_INET, SOCK_DGRAM, 0);
if (s == INVALID_SOCKET) {
printf("Error in socket(), Error code: %d\n", WSAGetLastError());
WSACleanup();
return -1;
}
SOCKADDR_IN serverAddress;
ZeroMemory(&serverAddress, sizeof(serverAddress));
serverAddress.sin_family = AF_INET;
serverAddress.sin_port = htons(50000);
inet_pton(AF_INET, "192.168.219.103", &(serverAddress.sin_addr.s_addr));
const char chTxBuf[MAX_BUF] = "Hello, UDP Time Server!";
char chRxBuf[MAX_BUF] = { 0 };
int iLength = sendto(s, chTxBuf, strlen(chTxBuf), 0, (SOCKADDR*)&serverAddress, sizeof(serverAddress));
if (iLength == SOCKET_ERROR) {
printf("Error in sendto(), Error code: %d\n", WSAGetLastError());
closesocket(s);
WSACleanup();
return -1;
}//sendto 함수에서 에러가 난 경우
printf("Message send to the server:%s\n", chTxBuf);
int rlen=sizeof(serverAddress);
int rLength = recvfrom(s, chRxBuf, MAX_BUF, 0, (SOCKADDR*)&serverAddress,&rlen);
if (rLength == SOCKET_ERROR) {
printf("Error in recvfrom(), Error code: %d\n", WSAGetLastError());
closesocket(s);
WSACleanup();
return -1;
}//recvfrom() 함수에서 에러가 난 경우
printf("Message receive from the server: %s \n", chRxBuf);
closesocket(s);
WSACleanup();
return 0;
}
서버
#pragma comment(lib, "ws2_32.lib");
#include "Winsock2.h"
#include <stdio.h>
#include <WS2tcpip.h>
#include <time.h>
#define MAX_BUF 5000
//21812167
//1. UDP소켓을 하나 생성
//2. 클라이언트의 접속을 기다림
//3. 클라이언트가 접속하여 메시지를 보내면
//4. 현재 시각으로 응답
//5. 화면에 모든 송수신 문자열을 출력하고 프로그램이 영원히 종료되지 않고 다음 클라이언트를 기다려야 함
int main() {
WSADATA wsa;
time_t t = time(NULL);
struct tm tm;
localtime_s(&tm, &t);
if (WSAStartup(MAKEWORD(2, 2), &wsa) != 0) {
//소켓라이브러리가 로딩이 됐는지 안 됐는지 확인한다.
//0아닌 값이 넘어오면 로딩이 안 된 것이다.
printf("Error in starting up Winsock\n");
return -1;
}
SOCKET s = socket(AF_INET, SOCK_DGRAM, 0);
if (s == INVALID_SOCKET) {
printf("Error in socket(), Error code: %d\n", WSAGetLastError());
WSACleanup();
return -1;
}
SOCKADDR_IN myAddress;
ZeroMemory(&myAddress, sizeof(myAddress));
myAddress.sin_family = AF_INET;
myAddress.sin_port = htons(50000);
myAddress.sin_addr.s_addr = INADDR_ANY;
if (bind(s, (SOCKADDR*)&myAddress, sizeof(myAddress)) == SOCKET_ERROR) {
printf("Error in bind(), Error code: %d\n", WSAGetLastError());
closesocket(s);
WSACleanup();
return -1;
} //bind()에서 오류가 난 경우
SOCKADDR_IN clientAddress;
int iAddressLength = sizeof(clientAddress);
char chRxBuf[MAX_BUF] = { 0 };
char chTxBuf[MAX_BUF] = "Hi, Client. Current time is ...";
//int chTxBuf1 = tm.tm_hour;
//int chTxBuf2 = tm.tm_min;
while (1) {
printf("Waiting...\n");
int iLength = recvfrom(s, chRxBuf, MAX_BUF, 0, (SOCKADDR*)&clientAddress, &iAddressLength);
if (iLength == SOCKET_ERROR) {
printf("Error in recvfrom(), Error code: %d\n", WSAGetLastError());
closesocket(s);
WSACleanup();
return -1;
}
printf("Message receive from the Client: %s\n", chRxBuf);
int rLength = sendto(s, chTxBuf, strlen(chTxBuf), 0, (SOCKADDR*)&clientAddress, sizeof(clientAddress));
if (rLength == SOCKET_ERROR) {
printf("Error in sendto(), Error code: %d\n", WSAGetLastError());
closesocket(s);
WSACleanup();
return -1;
}
printf("Message send to the Client: %s\n", chTxBuf);
}
closesocket(s);
WSACleanup();
return 0;
}
결과
댓글