- 계산기 Server - Client 만들기
Server
- 1_server
: 계산기 서버 초기 구조
: log 메세지를 확인할 때, \n 빼먹으면 문제가 생길 가능성이 크다.
: 프로토콜 설계
: 데이터 파싱
#include <stdio.h> #include <unistd.h> #include <sys/types.h> #include <sys/socket.h> #include <arpa/inet.h> #include <netinet/in.h> int calculate(char op, int value1, int value2) { switch (op) { case '+': return value1 + value2; case '-': return value1 - value2; case '*': return value1 * value2; default: return -1; } } int main() { int sock = socket(PF_INET, SOCK_STREAM, 0); struct sockaddr_in saddr = {0, }; saddr.sin_family = AF_INET; saddr.sin_port = htons(4000); saddr.sin_addr.s_addr = INADDR_ANY; // 0.0.0.0 int option = true; socklen_t optlen = sizeof(option); setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &option, optlen); bind(sock, (struct sockaddr*)&saddr, sizeof saddr); listen(sock, 5); while (1) { struct sockaddr_in caddr = {0, }; socklen_t clen = sizeof(caddr); int csock = accept(sock, (struct sockaddr*)&caddr, &clen); char* cip = inet_ntoa(caddr.sin_addr); printf("Connected from %s\n", cip); // TCP 는 데이터의 경계가 존재하지 않는다. //------------- char buf[1024]; int ret = read(csock, buf, 1024); printf("ret : %d\n", ret); if (ret == 0) { close(csock); break; } else if (ret == -1) { perror("read()"); close(csock); break; } char* p = buf; char op = *p; ++p; int value1 = *(int*)p; p += sizeof(int); int value2 = *(int*)p; p += sizeof(int); printf("%2d%2c%2d\n", value1, op, value2); int result = calculate(op, value1, value2); write(csock, &result, sizeof result); //----------------- close(csock); } close(sock); }
- 2_server
: 수식 여러번 받아서 계산을 할 수 있는 구조
: tcp는 write와 read가 병합되는 형태의 스트림 단위의 처리방식, 데이터의 경계가 존재하지 않는다.(반면 udp는 패킷 덩어리로 보낸만큼 간다.)
: 100번 보낸다고해서 100번을 받는것이 아니다. -> 패킷을 분리해야한다. -> 무조건 패킷의 길이가 들어가야한다.
: 길이는 약 3배정도 여유있게 잡는 것이 좋다.
: 서버는 온전하게 동작하게 만든 뒤, 고성능 서버로 바꾸는 것!#include <stdio.h> #include <unistd.h> #include <sys/types.h> #include <sys/socket.h> #include <arpa/inet.h> #include <netinet/in.h> int calculate(char op, int value1, int value2) { switch (op) { case '+': return value1 + value2; case '-': return value1 - value2; case '*': return value1 * value2; default: return -1; } } int main() { int sock = socket(PF_INET, SOCK_STREAM, 0); struct sockaddr_in saddr = {0, }; saddr.sin_family = AF_INET; saddr.sin_port = htons(4000); saddr.sin_addr.s_addr = INADDR_ANY; // 0.0.0.0 int option = true; socklen_t optlen = sizeof(option); setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &option, optlen); bind(sock, (struct sockaddr*)&saddr, sizeof saddr); listen(sock, 5); while (1) { struct sockaddr_in caddr = {0, }; socklen_t clen = sizeof(caddr); int csock = accept(sock, (struct sockaddr*)&caddr, &clen); char* cip = inet_ntoa(caddr.sin_addr); printf("Connected from %s\n", cip); // TCP 는 데이터의 경계가 존재하지 않는다. //------------- // int len; // int ret = read(csock, &len, sizeof len); char buf[4096]; int ret = read(csock, buf, sizeof buf); printf("ret : %d\n", ret); if (ret == 0) { close(csock); break; } else if (ret == -1) { perror("read()"); close(csock); break; } while (ret > 0) { char* p = buf; int len = *(int*)p; p += sizeof(int); char op = *p; ++p; int value1 = *(int*)p; p += sizeof(int); int value2 = *(int*)p; p += sizeof(int); ret -= len; printf("%2d%2c%2d\n", value1, op, value2); int result = calculate(op, value1, value2); write(csock, &result, sizeof result); } //----------------- close(csock); } close(sock); }
- 3_server9
'10'
'20'
'-'
: 2_server은 위의 패킷(스트림)이 올 경우 9에서 끊어질 수가 있다. -> readn 구현이 필요 -> block이 필요
: TCP의 경계가 모호하거나, 데이터가 도달하지 않는 문제를 해결하기 위해서는 readn을 도입하면 된다.: ★★readn, tcp 패킷자체에 대한 프로토콜이 잘못될 가능성은 거의 없어진다. 즉, 길이 base로 만들어야한다.
#include <stdio.h> #include <unistd.h> #include <sys/types.h> #include <sys/socket.h> #include <arpa/inet.h> #include <netinet/in.h> int calculate(char op, int value1, int value2) { switch (op) { case '+': return value1 + value2; case '-': return value1 - value2; case '*': return value1 * value2; default: return -1; } } // TCP의 경계가 모호하거나, 데이터가 도달하지 않는 문제를 // 해결하기 위해서는 readn을 도입하면 된다. int readn(int sd, char* buf, int len) { int n; int ret; n = len; while (n > 0) { ret = read(sd, buf, len); if (ret < 0) return -1; if (ret == 0) return len - n; buf += ret; n -= ret; } return len; } int main() { int sock = socket(PF_INET, SOCK_STREAM, 0); struct sockaddr_in saddr = {0, }; saddr.sin_family = AF_INET; saddr.sin_port = htons(4000); saddr.sin_addr.s_addr = INADDR_ANY; // 0.0.0.0 int option = true; socklen_t optlen = sizeof(option); setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &option, optlen); bind(sock, (struct sockaddr*)&saddr, sizeof saddr); listen(sock, 5); while (1) { struct sockaddr_in caddr = {0, }; socklen_t clen = sizeof(caddr); int csock = accept(sock, (struct sockaddr*)&caddr, &clen); char* cip = inet_ntoa(caddr.sin_addr); printf("Connected from %s\n", cip); while (1) { int len; int ret = readn(csock, (char*)&len, sizeof(len)); if (ret == 0) { close(csock); break; } else if (ret == -1) { perror("readn()"); close(csock); break; } char buf[4096]; ret = readn(csock, buf, len); if (ret == 0) { break; } else if (ret == -1) { perror("readn()"); break; } // 이제 데이터를 파싱하면 됩니다. char* p = buf; char op = *p; ++p; int value1 = *(int*)p; p += sizeof(int); int value2 = *(int*)p; p += sizeof(int); printf("%2d%2c%-2d\n", value1, op, value2); int result = calculate(op, value1, value2); write(csock, &result, sizeof result); } //----------------- close(csock); } close(sock); }
- 4_server
: 패킷 객체화#include <stdio.h> #include <unistd.h> #include <sys/types.h> #include <sys/socket.h> #include <arpa/inet.h> #include <netinet/in.h> #include "Packet.h" int calculate(char op, int value1, int value2) { switch (op) { case '+': return value1 + value2; case '-': return value1 - value2; case '*': return value1 * value2; default: return -1; } } // TCP의 경계가 모호하거나, 데이터가 도달하지 않는 문제를 // 해결하기 위해서는 readn을 도입하면 된다. int readn(int sd, char* buf, int len) { int n; int ret; n = len; while (n > 0) { ret = read(sd, buf, len); if (ret < 0) return -1; if (ret == 0) return len - n; buf += ret; n -= ret; } return len; } int main() { int sock = socket(PF_INET, SOCK_STREAM, 0); struct sockaddr_in saddr = {0, }; saddr.sin_family = AF_INET; saddr.sin_port = htons(4000); saddr.sin_addr.s_addr = INADDR_ANY; // 0.0.0.0 int option = true; socklen_t optlen = sizeof(option); setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &option, optlen); bind(sock, (struct sockaddr*)&saddr, sizeof saddr); listen(sock, 5); while (1) { struct sockaddr_in caddr = {0, }; socklen_t clen = sizeof(caddr); int csock = accept(sock, (struct sockaddr*)&caddr, &clen); char* cip = inet_ntoa(caddr.sin_addr); printf("Connected from %s\n", cip); while (1) { int len; int ret = readn(csock, (char*)&len, sizeof(len)); printf("len : %d\n", len); if (ret != sizeof len) { close(csock); break; } else if (ret == -1) { perror("readn()"); close(csock); break; } char buf[4096]; ret = readn(csock, buf, len); if (ret != len) { break; } else if (ret == -1) { perror("readn()"); break; } // 이제 데이터를 파싱하면 됩니다. Packet packet(buf); char op = packet.getByte(); int value1 = packet.getInt32(); int value2 = packet.getInt32(); printf("%2d%2c%-2d\n", value1, op, value2); int result = calculate(op, value1, value2); write(csock, &result, sizeof result); } //----------------- close(csock); } close(sock); }
■ 패킷 클래스
∴ 게임에서는?
: 패킷을 만드는 클래스를 하나 둔다.
: Builder pattern? 객체가 만들어지는 공정은 동일하지만, 내부적으로 표현이 달라지는 형태의 객체를 만들 때 사용(ex. StringBuilder), 코드 반복을 줄일 수 있다.#ifndef PACKET_H #define PACKET_H class Packet { public: inline Packet(char* buf) : start(buf), pos(buf) {} inline int getInt32() const { int ret = *(int*)pos; pos += sizeof(int); return ret; } inline char getByte() const { char ret = *(char*)pos; ++pos; return ret; } inline Packet& putByte(char value) { *pos = value; ++pos; return *this; } inline Packet& putInt32(int value) { *(int*)pos = value; pos += sizeof(int); return *this; } inline int build() { int len = pos - start; return len; } private: char* start; mutable char* pos; }; #endif
Client
- 1_client
: 계산기 클라이언트 초기 구조
: 패킷 만들기
#include <unistd.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <errno.h> #include <stdio.h> #include <string.h> #include <iostream> using namespace std; // 계산기 클라이언트를 만들어보자. // 1. 프로토콜을 설계해야 한다. // [op:1][lvalue:4][rvalue:4] int main() { int sd = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP); struct sockaddr_in addr = {0, }; addr.sin_family = AF_INET; addr.sin_port = htons(4000); addr.sin_addr.s_addr = inet_addr("127.0.0.1"); int ret = connect(sd, (struct sockaddr*)&addr, sizeof(addr)); if (ret == -1) { perror("connect()"); return -1; } printf("Connection succeed!!\n"); //------------- char op = '+'; int value1 = 10, value2 = 20; // cout << "value 1: "; cin >> value1; // cout << "value 2: "; cin >> value2; // cout << "op : "; cin >> op; // 패킷을 만들어야 한다. char buf[1024]; char* p = buf; *p = op; ++p; *(int*)p = value1; p += sizeof(int); *(int*)p = value2; p += sizeof(int); int size = p - buf; for (int i = 0 ; i < 100; i++) { write(sd, buf, size); } for (int i = 0 ; i < 100; i++) { int result; int n = read(sd, &result, sizeof result); cout << i+1 << "- result : " << result << endl; } close(sd); }
- 2_client ~ 3_client
: 수식을 여러번 보내서, 여러 결과값을 받을 수 있는 구조
: 패킷의 길이까지 붙여서 패킷 만들기#include <unistd.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <errno.h> #include <stdio.h> #include <string.h> #include <iostream> using namespace std; // 계산기 클라이언트를 만들어보자. // 1. 프로토콜을 설계해야 한다. // [op:1][lvalue:4][rvalue:4] int main() { int sd = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP); struct sockaddr_in addr = {0, }; addr.sin_family = AF_INET; addr.sin_port = htons(4000); addr.sin_addr.s_addr = inet_addr("127.0.0.1"); int ret = connect(sd, (struct sockaddr*)&addr, sizeof(addr)); if (ret == -1) { perror("connect()"); return -1; } printf("Connection succeed!!\n"); //------------- char op = '+'; int value1 = 10, value2 = 20; // cout << "value 1: "; cin >> value1; // cout << "value 2: "; cin >> value2; // cout << "op : "; cin >> op; // 패킷을 만들어야 한다. char buf[1024]; char* p = buf; *p = op; ++p; *(int*)p = value1; p += sizeof(int); *(int*)p = value2; p += sizeof(int); int size = p - buf; for (int i = 0 ; i < 1000; i++) { write(sd, &size, sizeof size); write(sd, buf, size); } for (int i = 0 ; i < 1000; i++) { int result; int n = read(sd, &result, sizeof result); cout << i+1 << "- result : " << result << endl; } close(sd); }
- 4_client
: 패킷 클래스로 만들기
: 체이닝 방식 권장
#include <unistd.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <errno.h> #include <stdio.h> #include <string.h> #include <iostream> using namespace std; #include "Packet.h" int main() { int sd = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP); struct sockaddr_in addr = {0, }; addr.sin_family = AF_INET; addr.sin_port = htons(4000); addr.sin_addr.s_addr = inet_addr("127.0.0.1"); int ret = connect(sd, (struct sockaddr*)&addr, sizeof(addr)); if (ret == -1) { perror("connect()"); return -1; } printf("Connection succeed!!\n"); //------------- char op = '+'; int value1 = 10, value2 = 20; // 패킷을 만들어야 한다. char buf[1024]; // 체이닝 방식 - 완전한 객체로 Packet packet = Packet(buf).putByte(op) .putInt32(value1) .putInt32(value2); // vs // 불완전한 객체로 존재 // Packet packet(buf); // packet.putByte(op); // packet.putInt32(value1); // packet.putInt32(value2); int size = packet.build(); /* char* p = buf; *p = op; ++p; *(int*)p = value1; p += sizeof(int); *(int*)p = value2; p += sizeof(int); int size = p - buf; */ for (int i = 0 ; i < 10000; i++) { write(sd, &size, sizeof size); write(sd, buf, size); } for (int i = 0 ; i < 10000; i++) { int result; int n = read(sd, &result, sizeof result); cout << i+1 << "- result : " << result << endl; } close(sd); }
'Programing > Server Model' 카테고리의 다른 글
서버 모델 - I/O 모델 개요 (0) | 2016.02.22 |
---|---|
서버 모델 - 리눅스 소켓 프로그래밍(3) (0) | 2016.02.22 |
서버 모델 - 리눅스 소켓 프로그래밍(1) (0) | 2016.02.18 |
서버 모델 - 소켓 프로그래밍 (0) | 2016.02.18 |
서버 모델 - 기초 (0) | 2016.02.16 |