• 계산기 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_server

    9

    '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);
    }
    


+ Recent posts