- 계산기 Server - Client 만들기
Server
- 1_server
: 계산기 서버 초기 구조
: log 메세지를 확인할 때, \n 빼먹으면 문제가 생길 가능성이 크다.
: 프로토콜 설계
: 데이터 파싱
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182#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배정도 여유있게 잡는 것이 좋다.
: 서버는 온전하게 동작하게 만든 뒤, 고성능 서버로 바꾸는 것!1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495#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로 만들어야한다.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115#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
: 패킷 객체화123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114#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), 코드 반복을 줄일 수 있다.123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354#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
: 계산기 클라이언트 초기 구조
: 패킷 만들기
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667#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
: 수식을 여러번 보내서, 여러 결과값을 받을 수 있는 구조
: 패킷의 길이까지 붙여서 패킷 만들기12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667#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
: 패킷 클래스로 만들기
: 체이닝 방식 권장
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879#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 |