데이터 전송과정의 컨트롤의 공부
1.1 TCP/IP 프로토콜 스택
↗↙ TCP계층 ↘↖ Application 계층 IP계층 <-> LINK 계층 ↘↖ UDP 계층 ↗↙
여기서 TCP 계층만 거쳐가면 TCP 프로토콜
UDP 계층만 거쳐가면 UDP 프로토콜이된다
1.2 TCP/IP 프로토콜의 탄생 배경
인터넷을 이용한 효율적인 데이터의 송수신을 해결하기 위해 고안된 개념
이를 위해 프로토콜과 계층구조가 만들어지게 되었다.
1.3 LINK 계층(OSI 7 Layer)
1.4 IP 계층
1.5 TCP/IP 계층(Transport Layer, 전송계층)
1.6 APPLICATION 계층
socket() 소켓 생성
↓
bind() 소켓 주소 할당
↓
listen() 연결 요청 대기상태
↓
accept() 연결 허용
↓
read()/write() 데이터 송수신
↓
close() 연결 종료
2.2 연결 요청 대기상태로의 진입
1. listen() 함수
#include<sys/socket.h>
int listen(int sock, int backlog);
-파라미터
int sock : 연결요청 대기상태에 두고자 하는 소켓의 파일 디스크립터 전달,
이 함수의 인자로 전달된 디스크립터의 소켓이 서버 소켓(리스팅 소켓)이 된다
int backlog : 연결 요청 대기 큐(Queue)의 크기정보 전달,
5가 전달되면 큐의 크기가 5가 되어 클라이언트의 연결 요청을 5개까지 대기시킬 수 있다.
bind()함수를 통해 소켓에 주소까지 할당했다면
listen() 함수를 통해서 연결 대기상태로 넘긴다.
2.3 클라이언트의 연결요청 수락
listen함수 호출 이후 연결 요청이 들어오면, 들어온 순서대로 연결 요청을 수락해야 한다.
1. accept() 함수
#include<sys/socket.h>
int accept(int sock, struct sockaddr* addr, socklen_t* addrlen);
int sock : 서버 소켓의 파일 디스크립터 전달
struct sockaddr* addr : 연결 요청 한 클라이언트의 주소 정보를 담을 변수의 주소값 전달.
함수 호출이 완료되면 인자로 전달된 주소의 변수에는 클라이언트의 주소 정보가 채워진다.
socklen_t* addrlen: 두번째 매개변수 addr에 전달된 주소의 변수 크기를 바이트 단위로 전달.
단 크기 정보를 변수에 저장한 다음에 변수의 주소 값을 전달한다.
그리고 함수호출이 완료되면 크기정보로 채워져 있던 변수에는 클라이언트의 주소정보 길이가
바이트 단위로 계산되어 채워진다
2.4 TCP 클라이언트의 기본적인 함수 호출 순서
#include<sys/socket.h>
int connect(int sock, struct sockaddr* servaddr, socklen_t addrlen);
int sock : 클라이언트 소켓의 파일 디스크립터 전달
struct sockaddr* servaddr: 연결요청 할 서버의 주소정보를 담은 변수의 주소 값 전달
socklen_t addrlen : 두번째 매개변수 servaddr에 전달된 주소의 변수 크기를 바이트 단위로 전달
socket() 소켓 생성
↓
connect() 연결요청
↓
read()/write() 데이터 송수신
↓
close() 연결 종료
서버와 다르단 점에서 주의!
소켓의 할당 과정
connect 함수가 호출될 때
운영체제(커널)에서
IP는 컴퓨터에 할당된 IP로 PORT는 임의 선택
3.1 iterative 서버의 구현
socket()
↓
bind()
↓
listen() ←
↓
accept() ↑
↓
read()/write() ↑
↓
close(client) ↑
↓
close(client) ↑
↓ →
close(server)
3.2 iterative 에코서버, 에코 클라이언트
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#define BUF_SIZE 1024
void error_handling(char *message);
int main(int argc, char *argv[])
{
int serv_sock, clnt_sock;
char message[BUF_SIZE];
int str_len, i;
struct sockaddr_in serv_adr, clnt_adr;
socklen_t clnt_adr_sz;
if (argc != 2)
{
printf("Usage: %s <port>\n", argv[0]);
exit(1);
}
//소켓 초기화 복습
// PF_INET: Protocol Family INET,
// SOCK_STREAM: 스트림소켓 사용, TCP 프로토콜을 사용하겠다는 의미
serv_sock = socket(PF_INET, SOCK_STREAM, 0);
if (serv_sock == 1)
{
error_handling("socket() error");
}
memset(&serv_adr, 0, sizeof(serv_adr));
serv_adr.sin_family = AF_INET;
serv_adr.sin_addr.s_addr == htonl(INADDR_ANY);
serv_adr.sin_port = htons(atoi(argv[1]));
// bind 실시, 여기서 소켓에 포트를 지정한다. 실패시 에러메시지를 반환 후 종료.
if (bind(serv_sock, (struct sockaddr *)&serv_adr, sizeof(serv_adr)) == -1)
error_handling("bind() error");
// listen 실시, 클라이언트의 접속을 기다리며, 실패서 에러메시지를 반환 후 종료
if (listen(serv_sock, 5) == -1)
error_handling("listne() error");
clnt_adr_sz = sizeof(clnt_adr);
for (i = 0; i < 5; i++)
{
clnt_sock = accept(serv_sock, (struct sockaddr *)&clnt_adr, &clnt_adr_sz);
if (clnt_sock == -1)
error_handling("accept() error");
else
printf("Connected client %d \n", i + 1);
while ((str_len = read(clnt_sock, message, BUF_SIZE)) != 0)
write(clnt_sock, message, str_len);
}
close(clnt_sock);
return 0;
}
void error_handling(char *message)
{
fputs(message, stderr);
fputc('\n', stderr);
exit(1);
}
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#define BUF_SIZE 1024
void error_handling(char *message);
int main(int argc, char *argv[])
{
int sock;
char message[BUF_SIZE];
int str_len;
struct sockaddr_in serv_adr;
if (argc != 3)
{
printf("Usage : %s <IP> <port>\n", argv[0]);
exit(1);
}
//소켓 초기화, TCP 소켓으로 선언한다.
sock = socket(PF_INET, SOCK_STREAM, 0);
if (sock == -1)
error_handling("socket() error");
memset(&serv_adr, 0, sizeof(serv_adr));
serv_adr.sin_family = AF_INET;
serv_adr.sin_addr.s_addr = inet_addr(argv[1]);
serv_adr.sin_port = htons(atoi(argv[2]));
//여기서, 바인딩된 포트와 연결한다. TCP 세션에서 클라이언트의 함수 connect
if (connect(&serv_adr, 0, sizeof(serv_adr)) == -1)
error_handling("connect() error");
else
puts("Connected.....");
while (1)
{
fputs("Input message(Q to quit): ", stdout);
fgets(message, BUF_SIZE, stdin);
if (!strcmp(message, "q\n") || !strcmp(message, "Q\n"))
break;
write(sock, message, strlne(message));
str_len = read(sock, message, BUF_SIZE - 1);
message[str_len] = 0;
printf("Message from server: %s", message);
}
close(sock);
return 0;
}
void error_handling(char *message)
{
fputs(message, stderr);
fputc('\n', stderr);
exit(1);
}
4.1 윈도우 기반 에코 서버
1. 리눅스 기반 예제를 윈도우 기반으로 변경하는 핵심내용
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#define BUF_SIZE 1024
void error_handling(char *message);
int main(int argc, char *argv[])
{
int sock;
char message[BUF_SIZE];
int str_len;
struct sockaddr_in serv_adr;
if (argc != 3)
{
printf("Usage : %s <IP> <port>\n", argv[0]);
exit(1);
}
//소켓 초기화, TCP 소켓으로 선언한다.
sock = socket(PF_INET, SOCK_STREAM, 0);
if (sock == -1)
error_handling("socket() error");
memset(&serv_adr, 0, sizeof(serv_adr));
serv_adr.sin_family = AF_INET;
serv_adr.sin_addr.s_addr = inet_addr(argv[1]);
serv_adr.sin_port = htons(atoi(argv[2]));
//여기서, 바인딩된 포트와 연결한다. TCP 세션에서 클라이언트의 함수 connect
if (connect(&serv_adr, 0, sizeof(serv_adr)) == -1)
error_handling("connect() error");
else
puts("Connected.....");
while (1)
{
fputs("Input message(Q to quit): ", stdout);
fgets(message, BUF_SIZE, stdin);
if (!strcmp(message, "q\n") || !strcmp(message, "Q\n"))
break;
write(sock, message, strlne(message));
str_len = read(sock, message, BUF_SIZE - 1);
message[str_len] = 0;
printf("Message from server: %s", message);
}
close(sock);
return 0;
}
void error_handling(char *message)
{
fputs(message, stderr);
fputc('\n', stderr);
exit(1);
}
#pragma comment(lib, "ws2_32.lib")
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<winsock2.h>
#define BUF_SIZE 1024;
void ErrorHandling(char* message);
int main(int argc) {
//WSADATA 객체를 선언한다.
WSADATA wsaData;
//SOCKET 객체를 선언한다.
SOCKET hSocket;
char message[BUF_SIZE];
int strLen;
SOCKADDR_IN servAdr;
if (argc != 3) {
printf("Usage : %s <IP> <port> \n", argv[0]);
exit(1);
}
//WSAStartup()을 이용해
if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)
ErrorHandling("WSAStartup() error");
//socket을 선언한다. protocol family는 IP, 연결방식은 TCP
hSocket = socket(PF_INET, SOCK_STREAM, 0);
if (hSocket == INVALID_SOCKET)
ErrorHandling("socket() error");
memset(&servAdr, 0, sizeof(servAdr));
servAdr.sin_family = AF_INET;
servAdr.sin_addr.s_addr = inet_addr(argv[1]);
servAdr.sin_port = htons(atoi(argv[2]));
if (connect(hSocket, (SOCKADDR*)&servAdr, sizeof(servAdr)) == SOCKET_ERROR)
ErrorHandling("connect() error");
else
puts("Connect....");
while (1) {
fputs("Input message(Q to quit): ", stdout);
fgets(message, BUF_SIZE, stdin);
if (!strcmp(message, "q\n") || strcmp(message, "Q\n"))
break;
send(hSocket, message, strlen(message), 0);
strLen = recv(hSocket, message, BUF_SIZE - 1, 0);
message[strLen] = 0;
printf("Message from server : %s", message);
}
closesocket(hSocket);
WSACleanup();
return 0;
}
void ErrorHandling(char* message) {
fputs(message, stderr);
fputc('\n', stderr);
exit(1);
}