• Server
    - 5_server
     : 클래스로 설계

     
    #include <stdio.h>
    #include <string.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;
    }
    
    
    
    // 클래스를 설계할 때 지켜야하는 원칙 : SRP
    
    // 객체 지향 5대 원칙 - 로버트 C 마틴(클린 코드)
    // SRP: 단일 책임 원칙
    // OCP: 개방 폐쇄 원칙
    // LSP: 리스코프 치환 원칙
    // ISP: 인터페이스 분리 원칙
    // DIP: 의존관계 역전 원칙
    
    
    class NetAddress
    {
    	sockaddr_in addr;
    public:
    	NetAddress(const char* ip, short port)
    	{
    		memset(&addr, 0, sizeof addr);
    
    		addr.sin_family = AF_INET;
    		addr.sin_port = htons(port);
    		addr.sin_addr.s_addr = inet_addr(ip);
    	}
    
    	sockaddr* getRawAddress()
    	{
    		return (sockaddr*)&addr;
    	}
    
    };
    
    class Socket
    {
    	int sock;
    public:
    	Socket() { sock = socket(PF_INET, SOCK_STREAM, 0); }
    
    	explicit Socket(int sockFd) : sock(sockFd) {}
    
    	void bind(NetAddress* address)
    	{
    		// 다시 C 함수롤 호출해야 한다.	
    		::bind(sock, address->getRawAddress(), sizeof(sockaddr_in));
    	}
    
    
    	void setReuseAddress(bool b)
    	{
    		int option = b;
    		socklen_t optlen = sizeof(option);
    		setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &option, optlen);
    	}
    
    	void listen(int backlog)
    	{
    		::listen(sock, backlog);
    	}
    
    	Socket accept()
    	{
    		struct sockaddr_in caddr = {0, };
    		socklen_t clen = sizeof(caddr);
    		int csock = ::accept(sock, (struct sockaddr*)&caddr, &clen);
    
    		return Socket(csock);
    	}
    
    	inline int read(char* buf, size_t size)
    	{ return ::read(sock, buf, size); }
    
    	inline int write(char* buf, size_t len)
    	{ return ::write(sock, buf, len); }
    
    	inline int readn(char* buf, int len)
    	{ return ::readn(sock, buf, len); }
    
    	inline int close()
    	{ return ::close(sock); }
    
    };
    
    
    int main()
    {
    	// int sock = socket(PF_INET, SOCK_STREAM, 0);
    	Socket sock;
    
    	NetAddress address("0.0.0.0", 4000); 
    	// 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
    
    	sock.setReuseAddress(true);
    	// int option = true;
    	// socklen_t optlen = sizeof(option);
    	// setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &option, optlen);
    
    	sock.bind(&address);
    	// bind(sock, (struct sockaddr*)&saddr, sizeof saddr);
    
    	sock.listen(5);
    	// listen(sock, 5);
    
    	while (1) 
    	{
    
    		Socket csock = sock.accept();
    		// 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 = csock.readn((char*)&len, sizeof(len));
    
    			printf("len : %d\n", len);
    			if (ret != sizeof len) {
    				break;
    			} else if (ret == -1) {
    				perror("readn()");
    				break;
    			}
    
    			char buf[4096];
    			ret = csock.readn(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);
    
    			csock.write((char*)&result, sizeof(result));
    		}
    
    		//-----------------
    		csock.close();
    	}
    
    	sock.close();
    }
    


    - 6_server
     : SRP - Facade Pattern 적용

     
    #include <stdio.h>
    #include <string.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;
    }
    
    
    
    // 클래스를 설계할 때 지켜야하는 원칙 : SRP
    
    // 객체 지향 5대 원칙 - 로버트 C 마틴(클린 코드)
    // SRP: 단일 책임 원칙
    // OCP: 개방 폐쇄 원칙
    // LSP: 리스코프 치환 원칙
    // ISP: 인터페이스 분리 원칙
    // DIP: 의존관계 역전 원칙
    
    
    class NetAddress
    {
    	sockaddr_in addr;
    public:
    	NetAddress(const char* ip, short port)
    	{
    		memset(&addr, 0, sizeof addr);
    
    		addr.sin_family = AF_INET;
    		addr.sin_port = htons(port);
    		addr.sin_addr.s_addr = inet_addr(ip);
    	}
    
    	sockaddr* getRawAddress()
    	{
    		return (sockaddr*)&addr;
    	}
    
    };
    
    class Socket
    {
    	int sock;
    public:
    	Socket() { sock = socket(PF_INET, SOCK_STREAM, 0); }
    
    	explicit Socket(int sockFd) : sock(sockFd) {} // explicit로 지정함으로써 암시적 형변환을 막는다.
    
    	void bind(NetAddress* address)
    	{
    		// 다시 C 함수롤 호출해야 한다.	
    		::bind(sock, address->getRawAddress(), sizeof(sockaddr_in));
    	}
    
    
    	void setReuseAddress(bool b)
    	{
    		int option = b;
    		socklen_t optlen = sizeof(option);
    		setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &option, optlen);
    	}
    
    	void listen(int backlog)
    	{
    		::listen(sock, backlog);
    	}
    
    	Socket accept()
    	{
    		struct sockaddr_in caddr = {0, };
    		socklen_t clen = sizeof(caddr);
    		int csock = ::accept(sock, (struct sockaddr*)&caddr, &clen);
    
    		return Socket(csock);
    	}
    
    	inline int read(char* buf, size_t size)
    	{ return ::read(sock, buf, size); }
    
    	inline int write(char* buf, size_t len)
    	{ return ::write(sock, buf, len); }
    
    	inline int readn(char* buf, int len)
    	{ return ::readn(sock, buf, len); }
    
    	inline int close()
    	{ return ::close(sock); }
    
    };
    
    // TCP 서버 프로그래밍을 하는데 있어서 필요한 클래스와 절차를
    // 단순화 시켜주는 상위 개념의 클래스를 제공하자.
    // -> Facade Pattern
    
    class TCPServer
    {
    	Socket sock;
    public:
    	void start(short port)
    	{
    		NetAddress address("0.0.0.0", port); 
    		sock.setReuseAddress(true);
    		sock.bind(&address);
    		sock.listen(5);
    
    		while (1) 
    		{
    
    			Socket csock = sock.accept();
    
    			while (1) 
    			{
    				int len;
    				int ret = csock.readn((char*)&len, sizeof(len));
    
    				printf("len : %d\n", len);
    				if (ret != sizeof len) {
    					break;
    				} else if (ret == -1) {
    					perror("readn()");
    					break;
    				}
    
    				char buf[4096];
    				ret = csock.readn(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);
    
    				csock.write((char*)&result, sizeof(result));
    			}
    
    			//-----------------
    			csock.close();
    		}
    
    		sock.close();
    
    
    	}
    
    };
    
    
    int main()
    {
    	TCPServer server;
    	server.start(4000);
    }
    


  • 계산기 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);
    }
    


  • Server
    - 1_server
     : 기본 서버 구조

     
    #include <stdio.h>
    
    #include <unistd.h>
    #include <sys/types.h>
    #include <sys/socket.h>
    
    #include <arpa/inet.h>
    #include <netinet/in.h>
    
    int main()
    {
    	// 1. socket 생성		
    	int sock = socket(PF_INET, SOCK_STREAM, 0);
    
    	// 2. bind() : 소켓 주소 지정
    	struct sockaddr_in saddr = {0, };
    	saddr.sin_family = AF_INET;
    	saddr.sin_port = htons(4000);
    
    	// 연결을 받을 주소
    	saddr.sin_addr.s_addr = inet_addr("127.0.0.1");
    	bind(sock, (struct sockaddr*)&saddr, sizeof saddr);
    
    	// 3. 연결 대기 상태로 만든다. : listen
    	listen(sock, 5);
    
    
    	// 4. 클라이언트 요청 수락 : accept
    	struct sockaddr_in caddr = {0, };
    	socklen_t clen = sizeof(caddr);
    	int csock = accept(sock, (struct sockaddr*)&caddr, &clen);
    
    	
    	printf("연결이 수립되었습니다.\n");
    
    	close(csock);
    	close(sock);
    }
    


    - 2_server
     : 여러 클라이언트 받아들이기

     
    #include <stdio.h>
    
    #include <unistd.h>
    #include <sys/types.h>
    #include <sys/socket.h>
    
    #include <arpa/inet.h>
    #include <netinet/in.h>
    
    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 = inet_addr("127.0.0.1");
    	// 문제점 : 127.0.0.1로 오는 연결만 받을 수 있다.
    
    	// 해결방법 : INADDR_ANY(0.0.0.0);
    	saddr.sin_addr.s_addr = INADDR_ANY; // 0.0.0.0
    
    	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);
    		close(csock);
    	}
    
    	close(sock);
    }
    

  • - 3_server
     : bind(): Address already in use 해결
     
    #include <stdio.h>
    
    #include <unistd.h>
    #include <sys/types.h>
    #include <sys/socket.h>
    
    #include <arpa/inet.h>
    #include <netinet/in.h>
    
    // 1. bind() : Adress already in use
    //  먼저 연결을 종료한 측의 소켓은 유지되어야 한다. - TIME_WAIT
    //  => TIME_WAIT 상태가 종료 되기 이전에 해당 포트로 서버를 바인드
    //     하는 것이 불가능하다.
    
    // 2. TIME_WAIT : 같은 포트의 연결을 손상 시킬 수 있다.
    // 3. 클라이언트 포트는 동일한 포트로 바인딩 되지 않는다.
    // 4. 최신 네트워크 커널의 구현은 SYN의 시퀀스 번호가 TIME-WAIT 상태의
    //    연결로부터 마지막 시퀀스 번호보다 클 경우에만 연결을 허용한다.
    // 5. TIME_WAIT은 먼저 연결을 종료한 쪽에서 발생한다.
    //    클라이언트는 포트가 변경되기 때문에 문제가 없다.
    
    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
    
    	//----------------
    	// 6. 해결방법
    	// 서버는 약속된 포트로부터 연결을 받아야 하므로
    	// 반드시 주소를 재할당 할 수 있도록 변경해주어야 한다.
    	int option = true;
    	socklen_t optlen = sizeof(option);
    	setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &option, optlen);
    	//-----------------
    
    	int ret = bind(sock, (struct sockaddr*)&saddr, sizeof saddr);
    	if (ret == -1) {
    		perror("bind()");
    		return -1;
    	}
    
    
    
    	listen(sock, 5);
    
    	struct sockaddr_in caddr = {0, };
    	socklen_t clen = sizeof(caddr);
    	int csock = accept(sock, (struct sockaddr*)&caddr, &clen);
    
    	close(csock);
    	close(sock);
    }
    

  • Client
    - 1_client ~ 2_client
     : 기본 클라이언트 구조

     
    #include <stdio.h>
    
    #include <unistd.h>
    #include <sys/types.h>
    #include <sys/socket.h>
    #include <netinet/in.h>
    #include <arpa/inet.h>
    
    #include <errno.h>
    #include <string.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");
    	close(sd);
    }
    


    - 3_client
     : TIME_WAIT 문제 확인

     
    #include <stdio.h>
    
    #include <unistd.h>
    #include <sys/types.h>
    #include <sys/socket.h>
    #include <netinet/in.h>
    #include <arpa/inet.h>
    
    #include <errno.h>
    #include <string.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");
    
    	sleep(5);
    	close(sd);
    }
    
    


+ Recent posts