1. TCP 서버 - 클라이언트 개념
▷핵심 동작 과정
- 서버는 먼저 실행하여 클라이언트가 접속하기를 기다린다(listen).
- 클라이언트는 서버에 접속(connect)하여 데이터를 보낸다(send).
- 서버는 클라이언트 접속을 수용하고(accept), 클라이언트가 보낸 데이터를 받아서 처리(recv)한다.
- 서버는 처리한 데이터를 클라이언트에 보낸다(send).
- 클라이언트는 서버가 보낸 데이터를 받아서 처리한다(recv).
- 데이터를 주고받는 과정을 모두 마치면 접속을 끊는다(close).
- 윈도우에서는 closesocket, 리눅스에서는 close를 사용
TCP 서버 클라이언트의 대표적인 예는, 웹 서버와 웹 브라우저의 동작 과정이다.
▷TCP 서버 - 클라이언트 동작 원리
- 서버는 소켓을 생성한 후 클라이언트 접속을 기다린다.
- 서버가 사용하는 소켓은 특정 포트 번호와 결합되어 있어 이 포트 번호로 접속하는 클라이언트만 수용 가능
- ex) 9000
- 클라이언트가 서버에 접속
- TCP 프로토콜 수준에서 연결 설정을 위한 패킷 교환이 일어남
- SYN, SYN/ACK, ACK를 주고 받는다.
- 서버는 접속한 클라이언트와 통신할 수 있는 새로운 소켓을 생성
- 이 소켓을 이용하여 서버와 클라이언트는 데이터를 주고 받음
- 기존에 만들었던 소켓은 새로운 클라이언트의 접속을 수용하는데 사용
- 새 클라이언트가 서버에 접속
- 서버에는 총 3개의 소켓이 존재하며, 두 개의 소켓은 각각의 클라이언트와 통신하는 용도로 사용
- 서버 측 소켓과 클라이언트 측 소겟이 일대일로 대응
- 하나의 클라이언트가 둘 이상의 소켓을 사용하여 서버에 접속 가능
▷TCP 서버 - 클라이언트 구현
◆IPv4 기반 TCP 서버 - 클라이언트
- 서버
- 클라이언트가 보낸 데이터를 받아 이를 문자열로 간주하여 화면에 출력
- 그런 다음 데이터 변경 없이 다시 클라이언트에 전송
- 받은 데이터를 그대로 다시 보낸다는 뜻으로 에코 서버(Echo Server)라고 부름
- 클라이언트
- 사용자가 키보드로 입력한 문자열을 서버에 전송
- 서버가 회신하면 클라이언트는 이를 화면에 출력
- 에코 서버와 통신한다는 의미로 에코 클라이언트(Echo Client)라고 부름
서버 Code)
#include "../Common.h"
#define SERVERPORT 9000
#define BUFSIZE 512
int main(int argc, char *argv[])
{
int retval;
// 소켓 생성
SOCKET listen_sock = socket(AF_INET, SOCK_STREAM, 0);
if (listen_sock == INVALID_SOCKET) err_quit("socket()");
// bind()
struct sockaddr_in serveraddr;
memset(&serveraddr, 0, sizeof(serveraddr));
serveraddr.sin_family = AF_INET;
serveraddr.sin_addr.s_addr = htonl(INADDR_ANY);
serveraddr.sin_port = htons(SERVERPORT);
retval = bind(listen_sock, (struct sockaddr *)&serveraddr, sizeof(serveraddr));
if (retval == SOCKET_ERROR) err_quit("bind()");
// listen()
retval = listen(listen_sock, SOMAXCONN);
if (retval == SOCKET_ERROR) err_quit("listen()");
// 데이터 통신에 사용할 변수
SOCKET client_sock;
struct sockaddr_in clientaddr;
socklen_t addrlen;
char buf[BUFSIZE + 1];
while (1) {
// accept()
addrlen = sizeof(clientaddr);
client_sock = accept(listen_sock, (struct sockaddr *)&clientaddr, &addrlen);
if (client_sock == INVALID_SOCKET) {
err_display("accept()");
break;
}
// 접속한 클라이언트 정보 출력
char addr[INET_ADDRSTRLEN];
inet_ntop(AF_INET, &clientaddr.sin_addr, addr, sizeof(addr));
printf("\n[TCP 서버] 클라이언트 접속: IP 주소=%s, 포트 번호=%d\n",
addr, ntohs(clientaddr.sin_port));
// 클라이언트와 데이터 통신
while (1) {
// 데이터 받기
retval = recv(client_sock, buf, BUFSIZE, 0);
if (retval == SOCKET_ERROR) {
err_display("recv()");
break;
}
else if (retval == 0)
break;
// 받은 데이터 출력
buf[retval] = '\0';
printf("[TCP/%s:%d] %s\n", addr, ntohs(clientaddr.sin_port), buf);
// 데이터 보내기
retval = send(client_sock, buf, retval, 0);
if (retval == SOCKET_ERROR) {
err_display("send()");
break;
}
}
// 소켓 닫기
close(client_sock);
printf("[TCP 서버] 클라이언트 종료: IP 주소=%s, 포트 번호=%d\n",
addr, ntohs(clientaddr.sin_port));
}
// 소켓 닫기
close(listen_sock);
return 0;
}
클라이언트 Code)
#include "../Common.h"
char *SERVERIP = (char *)"127.0.0.1";
#define SERVERPORT 9000
#define BUFSIZE 512
int main(int argc, char *argv[])
{
int retval;
// 명령행 인수가 있으면 IP 주소로 사용
if (argc > 1) SERVERIP = argv[1];
// 소켓 생성
SOCKET sock = socket(AF_INET, SOCK_STREAM, 0);
if (sock == INVALID_SOCKET) err_quit("socket()");
// connect()
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 = htons(SERVERPORT);
retval = connect(sock, (struct sockaddr *)&serveraddr, sizeof(serveraddr));
if (retval == SOCKET_ERROR) err_quit("connect()");
// 데이터 통신에 사용할 변수
char buf[BUFSIZE + 1];
int len;
// 서버와 데이터 통신
while (1) {
// 데이터 입력
printf("\n[보낼 데이터] ");
if (fgets(buf, BUFSIZE + 1, stdin) == NULL)
break;
// '\n' 문자 제거
len = (int)strlen(buf);
if (buf[len - 1] == '\n')
buf[len - 1] = '\0';
if (strlen(buf) == 0)
break;
// 데이터 보내기
retval = send(sock, buf, (int)strlen(buf), 0);
if (retval == SOCKET_ERROR) {
err_display("send()");
break;
}
printf("[TCP 클라이언트] %d바이트를 보냈습니다.\n", retval);
// 데이터 받기
retval = recv(sock, buf, retval, MSG_WAITALL);
if (retval == SOCKET_ERROR) {
err_display("recv()");
break;
}
else if (retval == 0)
break;
// 받은 데이터 출력
buf[retval] = '\0';
printf("[TCP 클라이언트] %d바이트를 받았습니다.\n", retval);
printf("[받은 데이터] %s\n", buf);
}
// 소켓 닫기
close(sock);
return 0;
Result)
2. TCP 서버 - 클라이언트 분석
응용 프로그램이 통신하려면 다음과 같은 요소가 필요
- 프로토콜(protocol)
- 통신 규약으로, 소켓을 생성할 때 결정
- 지역(local) IP 주소와 지역 포트 번호
- 서버 또는 클라이언트 자신의 주소
- 원격(remote) IP 주소와 원격 포트 번호
- 서버 또는 클라이언트가 통신하는 상대의 주소
1) TCP 서버 함수
- socket() 함수로 소켓을 생성함으로써 사용할 프로토콜 결정
- bind() 함수로 소켓의 지역 IP 주소와 지역 포트 번호를 결정
- listen() 함수로 소켓의 TCP 상태를 LISTENING으로 변경
- accept() 함수로 클라이언트 접속을 수용하고, 접속한 클라이언트와 통신할 수 있는 새로운 소켓 생성
이때 원격 IP 주소와 원격 포트번호가 결정됨 - send() 함수와 recv() 함수 등의 데이터 전송 함수로 클라이언트와 통신을 수행한 후 close() 함수로 소켓을 닫음
2) TCP 클라이언트 함수
- socket() 함수로 소켓을 생성함으로써 사용할 프로토콜 결정
- connect() 함수로 서버에 접속. 모든 IP 주소와 포트 번호가 결정됨
- send() 함수와 recv() 함수 등의 데이터 전송 함수로 클라이언트와 통신을 수행한 후 close() 함수로 소켓을 닫음
함수의 자세한 구조는 추후 설명 예정
3. TCP 서버 - 클라이언트(IPv6)
- IPv4와 달라지는 것
- 소켓 생성 시 AF_INET 대신 AF_INET6를 사용
- 소켓 주소 구조체로 sockaddr_in 대신 sockaddr_in6를 사용
- 이렇게 변경하고 나면 IPv6로 동작하는 서버와 클라이언트만 통신 가능하다.
서버 Code)
include "../Common.h"
#define SERVERPORT 9000
#define BUFSIZE 512
int main(int argc, char *argv[])
{
int retval;
// 소켓 생성
SOCKET listen_sock = socket(AF_INET6, SOCK_STREAM, 0);
if (listen_sock == INVALID_SOCKET) err_quit("socket()");
// bind()
struct sockaddr_in6 serveraddr;
memset(&serveraddr, 0, sizeof(serveraddr));
serveraddr.sin6_family = AF_INET6;
serveraddr.sin6_addr = in6addr_any;
serveraddr.sin6_port = htons(SERVERPORT);
retval = bind(listen_sock, (struct sockaddr *)&serveraddr, sizeof(serveraddr));
if (retval == SOCKET_ERROR) err_quit("bind()");
// listen()
retval = listen(listen_sock, SOMAXCONN);
if (retval == SOCKET_ERROR) err_quit("listen()");
// 데이터 통신에 사용할 변수
SOCKET client_sock;
struct sockaddr_in6 clientaddr;
socklen_t addrlen;
char buf[BUFSIZE + 1];
while (1) {
// accept()
addrlen = sizeof(clientaddr);
client_sock = accept(listen_sock, (struct sockaddr *)&clientaddr, &addrlen);
if (client_sock == INVALID_SOCKET) {
err_display("accept()");
break;
}
// 접속한 클라이언트 정보 출력
char addr[INET6_ADDRSTRLEN];
inet_ntop(AF_INET6, &clientaddr.sin6_addr, addr, sizeof(addr));
printf("\n[TCP 서버] 클라이언트 접속: IP 주소=%s, 포트 번호=%d\n",
addr, ntohs(clientaddr.sin6_port));
// 클라이언트와 데이터 통신
while (1) {
// 데이터 받기
retval = recv(client_sock, buf, BUFSIZE, 0);
if (retval == SOCKET_ERROR) {
err_display("recv()");
break;
}
else if (retval == 0)
break;
// 받은 데이터 출력
buf[retval] = '\0';
printf("[TCP/%s:%d] %s\n", addr, ntohs(clientaddr.sin6_port), buf);
// 데이터 보내기
retval = send(client_sock, buf, retval, 0);
if (retval == SOCKET_ERROR) {
err_display("send()");
break;
}
}
// 소켓 닫기
close(client_sock);
printf("[TCP 서버] 클라이언트 종료: IP 주소=%s, 포트 번호=%d\n",
addr, ntohs(clientaddr.sin6_port));
}
// 소켓 닫기
close(listen_sock);
return 0;
}
클라이언트 Code)
#include "../Common.h"
char *SERVERIP = (char *)"::1";
#define SERVERPORT 9000
#define BUFSIZE 512
int main(int argc, char *argv[])
{
int retval;
// 명령행 인수가 있으면 IP 주소로 사용
if (argc > 1) SERVERIP = argv[1];
// 소켓 생성
SOCKET sock = socket(AF_INET6, SOCK_STREAM, 0);
if (sock == INVALID_SOCKET) err_quit("socket()");
// connect()
struct sockaddr_in6 serveraddr;
memset(&serveraddr, 0, sizeof(serveraddr));
serveraddr.sin6_family = AF_INET6;
inet_pton(AF_INET6, SERVERIP, &serveraddr.sin6_addr);
serveraddr.sin6_port = htons(SERVERPORT);
retval = connect(sock, (struct sockaddr *)&serveraddr, sizeof(serveraddr));
if (retval == SOCKET_ERROR) err_quit("connect()");
// 데이터 통신에 사용할 변수
char buf[BUFSIZE + 1];
int len;
// 서버와 데이터 통신
while (1) {
// 데이터 입력
printf("\n[보낼 데이터] ");
if (fgets(buf, BUFSIZE + 1, stdin) == NULL)
break;
// '\n' 문자 제거
len = (int)strlen(buf);
if (buf[len - 1] == '\n')
buf[len - 1] = '\0';
if (strlen(buf) == 0)
break;
// 데이터 보내기
retval = send(sock, buf, (int)strlen(buf), 0);
if (retval == SOCKET_ERROR) {
err_display("send()");
break;
}
printf("[TCP 클라이언트] %d바이트를 보냈습니다.\n", retval);
// 데이터 받기
retval = recv(sock, buf, retval, MSG_WAITALL);
if (retval == SOCKET_ERROR) {
err_display("recv()");
break;
}
else if (retval == 0)
break;
// 받은 데이터 출력
buf[retval] = '\0';
printf("[TCP 클라이언트] %d바이트를 받았습니다.\n", retval);
printf("[받은 데이터] %s\n", buf);
}
// 소켓 닫기
close(sock);
return 0;
Result)
※서버와 클라이언트 연결정보 확인하기
- IPv4
- netstat -a -n --tcp | grep 9000
- IPv6
- netstat -a -n -p tcpv6 | grep 9000
'Network > 소켓통신(Linux)' 카테고리의 다른 글
TCP 동작 과정 및 함수 정리 (0) | 2023.11.19 |
---|---|
데이터 전송하기 (0) | 2023.09.17 |
23.03.09) TCP/IP 소켓 프로그래밍(Linux) 2 (0) | 2023.03.09 |
23.03.08) TCP/IP 소켓 프로그래밍(Linux) 1 (0) | 2023.03.09 |
23.01.16) 리눅스 기본 명령어 (0) | 2023.01.16 |