TCP Server - Client
- 웹 클라이언트(브라우저)는 사용자가 입력한 주소를 접속 대기 중인 웹 서버에 접속 후 HTTP를 이용해 요청
- 웹 서버는 웹 클라이언트에게 요청받은 메시지를 분석 후 HTTP를 이용해 응답 메시지를 만든다
- 데이터 교환 과정
- 두 프로그램 간의 연결을 위해 연결 설정을 해야 한다
- 서버 프로그램 : 연결을 위해 포트를 열어놓고 연결 요청이 오기를 기다린다
- 클라이언트 프로그램 : 서버 포트로 연결 요청을 보낸다
- 서버는 먼저 실행되어 클라이언트가 접속하기를 기다린다 --> listen()
- 클라이언트는 서버에 접속--> connect(), 데이터를 보낸다 --> send()
- 서버는 클라이언트 접속을 응답 --> accept(), 클라이언트 보낸 데이터를 받아서 처리 --> recv()
- 서버는 처리한 데이터를 클라이언트에게 보낸다 --> send()
- 클라이언트는 서버가 보낸 데이터를 받아서 처리 --> recv()
- 접속을 마치면 접속을 끊는다 --> closesocket()
※Socket
SOCKET socket(
int af, // 주소 체계
int type, // 소켓 타입
int protocol // 프로토콜
)
// 성공 : 새로운 소켓
// 실패 : INVALD_SOCKET
bind() 함수
- 소켓의 지역 IP주소와 지역 포트를 결정하는 함수
/* ========== Windows ========== */
#include <winsock2.h>
int bind(
SOCKET sock,
const struct sockaddr * addr,
int addrlen
);
// 성공 : 0
// 실패 : SOCKET_ERROR
/* ========== Linux ========== */
#include <sys/types.h>
#include <sys/socket.h>
int bind(
int sock,
const struct sockaddr * addr,
socklen_t addrlen
);
// 성공 : 0
// 실패 : -1
- sock : 클라이언트 접속을 수용할 목적으로 만든 소켓, 지역 IP주소와 지역 포트번호가 아직 결정되지 않은 상태
- addr : IP주소와 PORT번호를 지정한 sockaddr 구조체
- addrlen : 주소 정보를 담은 변수의 길이
listen() 함수
- 소켓의 TCP 포트 상태를 LISTENING으로 변경
/* ========== Windows ========== */
#include <winsock2.h>
int listen(
SOCKET sock,
int backlog
)
// 성공 : 0
// 실패 : SOCKET_ERROR
/* ========== Windows ========== */
#include <sys/types.h>
#include <sys/socket.h>
int listen(
int sock,
int backlog
);
// 성공 : 0
// 실패 : -1
- sock : 클라이언트 접소을 수용할 목적으로 만든 소켓, bind() 함수로 지역 IP와 포트번호가 설정된 상태
- backlog : 서버가 당장 처리하지 않더라도 접속 가능한 클라이언트 개수
- 클라이언트의 접었고 정보는 연결 큐에 저장
- Backlog는 연결 큐의 길이를 나타냄 -> SOMAXCONN(최댓값)으로 대입
accept() 함수
- 접속한 클라이언트와 통신할 수 있도록 새로운 소켓을 생성해서 리턴
- 접속한 클라이언트의 IP주소와 포트 번호를 알려줌
/* ========== Windows ========== */
#include <winsock2.h>
SOCKET accept(
SOCKET sock,
struct sockaddr * addr,
int *addrlen
);
// 성공 : 새로운 소켓
// 실패 : INVALID_SOCKET
/* ========== Linux ========== */
#include <sys/types.h>
#include <sys/socket.h>
int accept(
int sock,
struct sockaddr *addr,
socklen_t *addrlen
);
// 성공 : 새롱누 소켓
// 실패 : -1
- addr : 소켓 주소 구조체를 전달하면 접속한 클라이언트의 주소 정보(IP주소와 포트번호)로 채워진다
- adrlen : 정수형 변수를 addr이 가리키는 소켓 주소
- 구조체 크기로 초기화한 후 전달
- accept() 함수가 리턴하면 *addrlen 변수는 accept() 함수가 채워 넣은 주소 정보의 크기(바이트 단위)
connect() 함수
- TCP 프로토콜 수준에서 서버와 논리적 연결을 설정
/* ========== Windows ========== */
#include <winsock2.h>
int connect(
SOCKET sock,
const struct sockaddr * addr,
int addrlen
);
// 성공 : 0
// 실패 : SOCKET_ERROR
/* ========== Linux ========== */
#include <sys/types.h>
#include <sys/socket.h>
int connect(
int sock,
const struct sockaddr *addr;
socklen_t addrlen
);
// 성공 : 0
// 실패 : -1
- sock : 서버와 통신할 목적으로 만든 소켓
- addr : 소켓 주소 구조체를 서버 주소로 최기화 하여 전달(원격 IP 주소, 원격 포트번호)
- addrlen : 소켓 주소 구조체의 길이(바이트 단위)
데이터 전송 함수
- 소켓 버퍼 : 송신 버퍼와 수신 버퍼가 있다
- 송신 버퍼 : 데이터를 전송하기 전에 임시 저장
- 수신 버퍼 : 데이터를 수신하기 전에 임시 저장
- 입(수신) 출력(송신) 버퍼 특성
- 송·수신버퍼는 TCP 소켓 각각에 대해 별도로 존재한다
- 송·수신버퍼는 소켓 생성 시 자동으로 생성된다
- 소켓을 닫아도 출력 버퍼에 남아있는 데이터는 계속 전송이 이루어진다
- 소켓을 닫으면 입력 버퍼에 있는 데이터는 소멸이 되어버린다
send() 함수
- 응용 프로그램 데이터를 운영체제의 송신 버퍼에 복사함으로써 데이터를 전송
- 데이터 복사가 성공하면 그 크기만큼 반환
/* ========== Windows ========== */
#include<winsock2.h>
int send(
SOCKET sock,
const char *buf,
int len,
int flags
)
// 성공 : 보낸 바이트 크기
// 실패 : SOCKET_ERROR
/* ========== Linux ========== */
#include<sys/types.h>
#include<sys/socket.h>
ssize_t send(
int sock,
const void * buf,
size_t len,
int flags
)
// 성공 : 보낸 바이트 크기
// 실패 : -1
- sock : 통신할 대상과 연결된 소켓
- buf : 보낼 데이터를 담고 있는 응용 프로그램 버퍼 주소
- len : 보낼 데이터의 크기
- flags : send() 함수의 동작을 바꿈 -> 대부분 0을 사용
Send() 함수에서 소켓 특성에 따른 리턴 종류
- 블로킹 소켓
- 블로킹 소켓을 대상으로 send() 함수를 호출하면 송신 버퍼의 여유공간이 send() 함수의 len 보다 작을 경우 해당 프로세스는 대기 상태
- 송신 버퍼에 충분한 공간이 생기면 프로세스를 깨어나고 len 크기만큼 데이터 복사가 일어난 후 send 함수가 리턴 -> len
- 넌 블로킹 소켓
- 소켓 함수 호출 시 조건이 만족하지 않더라도 함수가 리턴되므로 스레드 중단 없이 다음 코드를 실행
- send() 함수 호출 하면 송신 버퍼의 여유공간만큼 데이터를 복사한 후 실제 복사한 바이트 수를 리턴 -> 1, len
recv() 함수
- 운영체제의 수신 버퍼에 도착한 데이터를 응용 프로그램 버퍼에 복사
- 소켓 내부의 수신 버퍼를 성공하면 그 크기를 반환함
/* ========== Windows ========== */
int recv(
SOCKET sock,
char *buf,
int len,
int flags
)
// 성공 : 받은 바이트 크기 또는 0
// 실패 : SOCKET_ERROR
/* ========== Linux ========== */
#include<sys/types.h>
#include<sys/socket.h>
ssize_t recv(
int sock,
void *buf,
size_t len,
int flags
)
// 성공 : 받은 바이트 크기 또는 0
// 실패 : -1
- 성공적인 리턴
- 수신 버퍼에 데이터가 도달할 경우 : 버퍼에 복산한 후 실제 복사한 바이트 크기를 리턴
- 접속이 정상 종료한 경우 : 리턴 값이 0이면 정상 종료
- MSG_PEEK : 수신 버퍼의 데이터를 응용프로그램 버퍼에 복사한 후 해당 데이터를 수신 버퍼에서 삭제
- MSG_WAITIALL : 수신 버퍼에 도착한 데이터를 최대 len 크기만큼 응용 프로그램 버퍼에 복사
- 수신 버퍼에 도착한 데이터 크기가 len보다 작더라도 도착할 때까지 기다리지 않고 현재 있는 만큼만 복사해서 리턴
TCP Server & Client 구현
#include "Common.h"
#define SERVERPORT 9000
#define BUFSIZE 512
int main() {
WSADATA wsa;
int retval;
// ===== 윈속 초기화 =====
if (WSAStartup(MAKEWORD(2, 2), &wsa) != 0) return 1;
// ===== 소켓 생성 =====
SOCKET listenSocket = socket(AF_INET, SOCK_STREAM, 0); // IPv4, TCP 통신 소켓
if (listenSocket == INVALID_SOCKET) printf("Socket ERROR!!!\n");
// ===== bind() =====
struct sockaddr_in serveraddr; //IPv4 소켓 주소 구조체
memset(&serveraddr, 0, sizeof(serveraddr)); // 주소 초기화
serveraddr.sin_family = AF_INET; // IPv4
serveraddr.sin_addr.s_addr = htonl(INADDR_ANY); // INADRR_ANY : 모든 IP에서 접속 가능!!
serveraddr.sin_port = htons(SERVERPORT); // h -> n Port Number 설정
retval = bind(listenSocket, (struct sockaddr*)&serveraddr, sizeof(serveraddr));
if (retval == SOCKET_ERROR) printf("BIND() ERROR!!\n");
// -- LISTEN() -- 대기 상태 변경, 클라이언트 갯수
retval = listen(listenSocket, SOMAXCONN);
if (retval == SOCKET_ERROR) printf("LISTEN() ERROR!!!\n");
printf("Server Listen...\n");
// 통신에 사용할 변수 선언
SOCKET clientSocket; // 클라이언트 소켓
struct sockaddr_in clientaddr; // 클라이언트 소켓 주소(원격 IP / 원격 PORT)
int addrlen; // 주소 길이
char buf[BUFSIZE + 1]; // 받은 데이터를 저장할 응용프로그램 버퍼
while (1) {
// ===== accept() =====
addrlen = sizeof(clientaddr);
clientSocket = accept(listenSocket, (struct sockaddr*)&clientaddr, &addrlen);
if (clientSocket == INVALID_SOCKET) {
printf("ACCEPT() ERROR!!\n");
break;
}
char addr[INET_ADDRSTRLEN];
inet_ntop(AF_INET, &clientaddr.sin_addr, addr, sizeof(addr)); // inet_ntop : 네트워크 바이트 정렬
printf("TCP Connect : IP = %s, PORT = %d\n", addr, ntohs(clientaddr.sin_port));
// inet_ntop 네트워크 바이트 정렬(숫자) -< 문자열로
// inet_btop(주소체계, IP주소, 저장공간, 크기)
while (1) {
// ===== recv() =====
retval = recv(clientSocket, buf, BUFSIZE, 0); // recv(원격소켓, 버퍼, 버퍼크기, 0);
// 수신 버퍼에 있는 값을 읽어 와서 응용프로그램 버퍼에 복사
if (retval == SOCKET_ERROR) { // 오류가 있을 경우
printf("RECV() ERROR!!!\n");
break;
}
else if (retval == 0) break; // 정상 종료
buf[retval] = '\0'; // 받은 마지막 데이터 표시
printf("[TCP : %s : %d] %s\n", addr, ntohs(clientaddr.sin_port), buf);
// ===== send() =====
retval = send(clientSocket, buf, retval, 0);
if (retval == SOCKET_ERROR) {
printf("SEND() ERROR!!!\n");
break;
}
}
// 클라이언트 소켓 닫기
closesocket(clientSocket);
printf("Client 종료 IP = %s, Port Number = %d\n", addr, ntohs(clientaddr.sin_port));
}
// Listen Socket 닫기
closesocket(listenSocket);
// 윈속 종료
WSACleanup();
return 0;
}
TCPServer.cpp
#include "Common.h"
#define SERVERPORT 9000
#define BUFSIZE 512
char* SERVERIP = (char*)"127.0.0.1";
int main(int argc, char* argv[]) {
int retval;
// 윈속 초기화
WSADATA wsa;
if (WSAStartup(MAKEWORD(2, 2), &wsa) != 0) return 1;
// 소켓 생성
SOCKET sock = socket(AF_INET, SOCK_STREAM, 0);
if (sock == INVALID_SOCKET) printf("SOCKET ERORR!!!\n");
struct sockaddr_in serveraddr;
memset(&serveraddr, 0, sizeof(serveraddr)); // 소켓 초기화
serveraddr.sin_family = AF_INET;
inet_pton(AF_INET, SERVERIP, &serveraddr.sin_addr);
serveraddr.sin_port = ntohs(SERVERPORT);
// ===== connect() =====
retval = connect(sock, (struct sockaddr*)&serveraddr, sizeof(serveraddr));
if (retval == SOCKET_ERROR) printf("CONNECT() ERORR!!!\n");
// 데이터 통신에 사용할 변수
char buf[BUFSIZE + 1];
int len;
while (1) {
// 데이터 입력
printf("\n[보낼 데이터] ");
if (fgets(buf, BUFSIZE + 1, stdin) == NULL) // fgets() 함수로 사용자로 부터 문자열을 입력 받고 검사한다
break;
len = (int)strlen(buf);
if (buf[len - 1] == '\n') buf[len - 1] = '\0'; // 입력을 했을 경우 \n을 지우고 \0으로 끝났다는 것을 표시
if (strlen(buf) == 0) break; // 사용자가 입력 하지 않을 경우
// ===== send() =====
retval = send(sock, buf, (int)strlen(buf), 0);
if (retval == SOCKET_ERROR) {
printf("SEND() ERROR!!!\n");
break;
}
printf("[TCP Client] %dByte를 보냈습니다\n", retval);
// ===== recv() =====
retval = recv(sock, buf, retval, MSG_WAITALL);
if (retval == SOCKET_ERROR) {
printf("RECV() ERROR!!!\n");
break;
}
else if (retval == 0) break;
buf[retval] = '\0';
printf("[TCP Client] %dByte를 받았습니다\n", retval);
printf("[recv Data] %s\n", buf);
}
closesocket(sock);
WSACleanup();
return 0;
}
TCPClient.cpp
- 공통
- TCP / IPv4 통신(SOCK_STREMA, AF_INET)
- 윈속 초기화, 소켓 생성
- 통신 후 소켓 닫기, 윈속 종료
- Server
- 서버 포트 : 9000
- bind(), listen(), accept() 함수 구현
- recv() 함수 구현
- Client
- 서버 IP 주소 : 127.0.0.1 (localhost)
- 서버 포트 : 9000
- send() 구현
[아이콘 저작권]
디지털 아이콘 제작자: Muhammad_Usman - Flaticon
Ui 아이콘 제작자: Ilham Fitrotul Hayat - Flaticon
지원 아이콘 제작자: Freepik - Flaticon
'개인 공부 > 네트워크' 카테고리의 다른 글
네트워크P-007 (데이터 전송 실습) (1) | 2023.04.15 |
---|---|
네트워크P-006 (데이터 전송 방식) (1) | 2023.04.13 |
네트워크P-004(소켓 구조체 & 바이트 정렬) (0) | 2023.04.10 |
네트워크P-003(윈속 & 소켓) (0) | 2023.04.09 |
네트워크P-002(네트워크 모델 & Socket) (0) | 2023.04.09 |