일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | |||||
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |
- C++
- Four Squares
- Window-Via-c/c++
- 스프링 입문
- 윤성우 저자
- BOJ
- FIFO paging
- Spring
- 에러핸들링
- 토마토
- Operating System.
- HTTP
- n타일링2
- 10026번
- Operating System
- 이펙티브코틀린
- 제프리리처
- 2475번
- C#
- 김영한
- redis
- 운영체제
- inflearn
- TCP/IP
- 열혈 tcp/ip 프로그래밍
- 스프링 핵심 원리
- OS
- 열혈 TCP/IP 소켓 프로그래밍
- 우아한 테크 세미나
- 우아한레디스
- Today
- Total
나의 브을로오그으
#1. 네트워크 프로그래밍과 소켓의 이해 본문
#1. 간단한 소켓 생성
[리눅스]
서버 소켓의 생성 과정
1단계. 소켓 생성 : socket 함수 호출
2단계. IP주소와 PORT 번호 할당 : bind 함수 호출
3단계. 연결요청 가능상태로 변경 : listen 함수 호출
4단계. 연결요청에 대한 수락 : accept 함수 호출
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
void error_handling(char* message);
int main(int argc, char* argv[])
{
int serv_sock;
int clnt_sock;
struct sockaddr_in serv_addr;
struct sockaddr_in clnt_addr;
socklen_t clnt_addr_size;
char message[] = "Hello World!";
if (argc != 2)
{
printf("Usage : %s <port>\n", argv[0]);
exit(1);
}
serv_sock = socket(PF_INET, SOCK_STREAM, 0);
if (serv_sock == -1)
error_handling("socket() error");
memset(&serv_addr, 0, sizeof(serv_addr));
serv_addr.sin_family=AF_INET;
serv_addr.sin_addr.s_addr=htonl(INADDR_ANY);
serv_addr.sin_port=htons(atoi(argv[1]));
if (bind(serv_sock, (struct sockaddr*) &serv_addr, sizeof(serv_addr)) == -1)
error_handling("bind() error");
if (listen(serv_sock, 5) == -1)
error_handling("listen() error");
clnt_addr_size = sizeof(clnt_addr);
clnt_sock=accept(serv_sock, (struct sockaddr*)&clnt_addr, &clnt_addr_size);
if (clnt_sock == -1)
error_handling("accept() error");
write(clnt_sock, message, sizeof(message));
close(clnt_sock);
close(serv_sock);
return 0;
}
void error_handling(char* message)
{
fputs(message, stderr);
fputc('\n', stderr);
exit(1);
}
클라이언트 소켓의 생성 과정
1단계. 소켓 생성 : socket 함수 호출
2단계. 소켓 연결 요청 : connect함수 호출
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
void error_handling(char* message);
int main(int argc, char* argv[])
{
int sock;
struct sockaddr_in serv_addr;
char message[30];
int str_len;
if (argc != 3)
{
printf("Usage : %s <IP> <port>\n", argv[0]);
exit(1);
}
sock=socket(PF_INET, SOCK_STREAM, 0);
if (sock == -1)
error_handling("socket() error");
memset(&serv_addr, 0, sizeof(serv_addr));
serv_addr.sin_family=AF_INET;
serv_addr.sin_addr.s_addr=inet_addr(argv[1]);
serv_addr.sin_port=htons(atoi(argv[2]));
if (connect(sock, (struct sockaddr*)&serv_addr, sizeof(serv_addr)) == -1)
error_handling("connect() error!");
str_len=read(sock, message, sizeof(message)-1);
if(str_len==-1)
error_handling("read() error!");
printf("Message from server : %s \n", message);
close(sock);
reutrn 0;
}
void error_handling(char* message)
{
fputs(message, stderr);
fputc('\n', stderr);
exit(1);
}
#2. 리눅스 기반 파일 조작
- 리눅스는 소켓을 파일의 일종으로 구분한다. 즉, 파일입출력을 소켓 입출력(네트워크 상의 데이터 통신)으로 사용 가능하다. 반면, 윈도우는 소켓과 파일을 구분하고 있다. (사실 소켓은 네트워크 통신을 위한 소프트웨어 장치를 추상화한 파일을 의미하며 특별하게 소켓이라고 부른다. 결국 파일임. )
- 저수준 파일 입출력(Low-level File Access)과 파일 디스크립터(File Descriptor)
"저수준" 이라는 의미는 "표준에 상관없이 운영체제에 독립적으로 제공하는~" 이라고 받아들이면 된다.
즉, 리눅스에서 제공하는 함수일 뿐, ANSI 표준 함수가 아니다. 리눅스에서만 사용 가능한 함수라는 의미~
여기서 말하는 파일 디스크립터는 시스템으로부터 할당 받은 파일 또는 소켓에 부여된 정수를 의미한다.
각각 0, 1, 2 파일 디스크립터(정수)는 표준 입력(Standard Input), 표준 출력(Standard Output), 표준 에러(Standard Error)으로 여긴다. (참고로 표준 입/출력, 에러는 프로그램 실행 시 자동 할당되는 파일 디스크립터들이다.)
리눅스는 소켓을 파일과 동일하게 다룬다고 하였다. 즉, 파일이나 소켓을 생성하면 시스템으로부터 파일 디스크립터가 할당되고, 이것을 소켓 통신에 활용한다.
파일 디스크립터와 관련된 사이드 이야기!(27page 참고)
전화 한 통만 하면 필요한 논문을 복사해 주는 곳이 있다. 그리고 그곳에는 단골손님 영수가 있다. 그런데 이 녀석은 매번 똑같은 논문의 일부분을 복사해 달라고 한다.
"아저씨~ '고도읜 정보화 사회가 되어 가면서, 인간의 .... 원리에 대한 연구'라는 논문 26쪽부터 30쪽까지 복사해 주세요."
이 녀석을 보통 이런 식으로 하루에도 몇 번씩 부탁을 한다. 설상가상으로 말하는 속도도 매우 느린 편이다. 그래서 아저씨께서 말씀하셨다.
"그 논문은 이제부터 너의 18번이다.! 그냥 저의 18번 논문 26쪽부터 30쪽까지 복사해 주세요. 라고 해라!"
영수는 그 후로도 최소 50자가 넘는 제목의 논문만 복사 주문을 했다. 그리고 그때마다 아저씨는 논문에 중복되지 않는 새로운 번호(숫자)를 할당해 주셨다. 그래야 영수와의 대화 속에서 스트레스를 받지 않고, 업무에도 영향을 주지 않기 때문이다.
여기서 아저씨는 "운영체제"이고, 영수는 "우리"이다. 숫자는 "파일 디스크립터"이고, 논문은 "파일" 또는 "소켓"이다. 우리가 파일 또는 소켓을 생성할 때마다 운영체제는 파일 또는 소켓에 부여된 숫자 하나를 건네 줄 것이다. 그것이 운영체제와 우리가 편한게 대화(통신)하는 방법이 될테니까 말이다.!
파일 디스크립터는 결국 운영체제가 만든 파일 또는 소켓의 지칭을 편히 하기 위해서 부여된 숫자에 불과하다. ("파일 디스크립터"를 "파일 핸들"이라고도 부른다. 그러나 핸들이라는 표현은 윈도우에서 사용되는 용어이기에 이 책에서는 윈도우 기반을 설명 할 때는 핸들로, 리눅스 기반을 설명 할 때는 디스크립터라는 표현을 사용했다.)
- 파일 열기
파일을 열 때 사용하는 함수가 있는데 첫 번째 인자로는 대상이 되는 파일의 이름, 두 번째 인자로는 파일의 오픈 모드 정보(파일의 특성 정보)를 전달한다.
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int open(const char* path, int flag);
-> 성공 시 파일 디스크립터, 실패 시 -1 반환
path : 파일 이름을 나타내는 문자열의 주소 값 전달
flag : 파일의 오픈 모드 정보 전달
오픈 모드 | 의 미 |
O_CREATE | 필요하면 파일을 생성 |
O_TRUNC | 기존 데이터 전부 삭제 |
O_APPEND | 기존 데이터 보존하고, 뒤에 이어서 저장 |
O_RDONLY | 읽기 전용으로 파일 오픈 |
O_WRONLY | 쓰기 전용으로 파일 오픈 |
O_RDWR | 읽기, 쓰기 겸용으로 파일 오픈 |
- 파일 닫기
#include <unistd.h>
int close(int fd);
-> 성공 시 0, 실패 시 -1 반환
fd : 닫고자 하는 파일 또는 소켓의 파일 디스크립터 전달
해당 함수는 파일 뿐만아니라 소켓을 닫을 때에도 호출하는 함수이다. (즉, 리눅스에서는 파일과 소켓을 구분하지 않는다.)
- 파일에 데이터 쓰기
소켓과 파일을 구분하지 않기에 소켓을 통해서 다른 컴퓨터에 데이터를 전송할 때에도 이 함수를 사용할 수 있다.
#include <unistd.h>
ssize_t write(int fd, const void* buf, size_t nbytes);
-> 성공 시 전달한 바이트 수, 실패 시 -1 반환
fd : 데이터 전송대상을 나타내는 파일 디스크립터 전달.
buf : 전송할 데이터가 저장된 버퍼의 주소 값 전달.
nbytes : 전송할 데이터의 바이트 수 전달.
(참고로 ssize_t는 signed size_t이다. = signed int)
[low_open.c]
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
void error_handling(char* message);
int main(void)
{
int fd;
char buf[] = "Let's go!\n";
fd = open("data.txt", O_CREATE|O_WRONLY|O_TRUNC);
if (fd == -1)
error_handling("open() error!\n");
printf("file descriptor: %d \n", fd);
if (write(fd, buf, sizeof(buf)) == -1)
error_handling("write() error!\n");
close(fd);
return 0;
}
void error_handling(char* message)
{
fputs(message, stderr);
fputc('\n', stderr);
return;
}
파일 모드가 O_CREATE|O_WRONLY|O_TRUNC이므로 파일이 없으면 생성하고, 있으면 기존 데이터를 전부 제운다. 또한 파일을 쓰기전용으로 연다.
- 파일에 저장된 데이터 읽기
#include <unistd.h>
ssize_t read(int fd, void *buf, size_t nbytes);
-> 성공 시 수신한 바이트 수(단 파일의 끝을 만나면 0), 실패시 -1 반환
fd : 데이터 수신대상을 나타내는 다스크립터 전달.
buf : 수신한 데이터를 저장할 버퍼의 주소 값전달
nbytes : 수신 할 최대 바이트 수 전달
[low_read.c]
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#define BUF_SIZE 100
void error_handling(char* message);
int main(void)
{
int fd;
char buf[BUF_SIZE];
fd = open("data.txt", O_RDONLY);
if (fd == -1)
error_handling("open() error!\n");
printf("file descriptor: %d \n", fd);
if (read(fd, buf, BUF_SIZE) == -1)
error_handling("read() error!\n");
printf("file data: %s \n", buf);
close(fd);
return 0;
}
void error_handling(char* message)
{
fputs(message, stderr);
fputc('\n', stderr);
return;
}
- 파일 디스크립터와 소켓
소켓 생성과 파일 디스크립터 값 비교 코드
[fd_seri.c]
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/socket.h>
int main(void)
{
int fd1, fd2, fd3;
fd1 = socket(PF_INET, SOCK_STREAM, 0);
fd2 = open("test.dat", O_CREAT|O_WRONLY|O_TRUNC);
fd3 = socket(PF_INET, SOCK_DGRAM, 0);
printf("file descrpitor 1: %d\n", fd1);
printf("file descrpitor 2: %d\n", fd2);
printf("file descrpitor 3: %d\n", fd3);
close(fd1); close (fd2); close(fd3);
return 0;
}
[출력 결과]
file descripter 1: 3
file descripter 2: 4
file descripter 3: 5
(3부터 시작하는 이유는 0, 1, 2는 시스템에서 이미 설정된 디스크립터 값이다. 표준 입력, 출력, 에러에 할당되어 있음,
일련의 순서대로 넘버링 됨.)
#3. 윈도우 기반으로 구현하기
'윈도우 소켓'은 상당부분 BSD 계열 유닉스 소켓을 참고하여 설계되었다. 때문에 많은 부분 리눅스 소켓과 유사하다. 따라서 리눅스 기반으로 구현된 네크워크 프로그램의 일부만 변경하면 윈도우에서의 실행이 가능하다.
- 리눅스와 윈도우 기반으로 동시에 공부해야 하는 이유
보통 서버쪽을 윈도우나 리눅스 계열의 운영체제 기반으로 개발하는데, 클라이언트의 경우 윈도우가 거의 절대적이다. 또한 윈도우 서버를 리눅스 기반으로, 리눅스 서버를 윈도우 기반으로 변경하는 경우도 종종 발생하므로 둘 다 해둘 필요가 있다. (둘다 매우 유사함)
- 윈도우 소켓을 위한 헤더와 라이브러리 설정
- 헤더파일 winsock2.h를 포함
- ws2_32.lib 라이브러리를 링크
#include <WinSock2.h>
#pragma comment(lib, "ws2_32.lib")
(책에 기술된 것처럼 프로젝트 우측 클릭 -> 속성 -> Link -> 입력 -> 추가 종속성 에서 ws2_32.lib를 추가하는 방법도 있지만, 코드로 추가하는게 편해서 이렇게 진행)
[WindowSocket 시작]
#include <WinSock2.h>
int WSAStartup(WORD wVersionRequested, LPWSADATA lpWSAData);
-> 성공 시 0, 실패시 0이 아닌 에러코드 값 반환
wVersionRequested : 프로그래머가 사용한 윈도우 소켓의 버전정보 전달.
ex) 2.1버전 = 0x0201 = 매크로 함수 제공 : MAKEWORD(2, 1))
lpWSAData : WSADATA라는 구조체 변수의 주소 값 전달.
[Window Socket 초기화 코드]
int main(int argc, char* argv[])
{
WSADATA wsaData;
...
if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) {
ErrorHandling("WSAStartup() error!\n");
}
...
return 0;
}
(파일을 열때의 코드와 마찬가지로 다음과 같은 방법으로 거의 공식처럼 쓰임)
[WindowSocket 해제]
#include <winsock2.h>
int WSACleanup(void);
-> 성공 시 0, 실패 시 SOCKET_ERROR 반환
(해당 함수 호출 시 window OS에 반환하고, 윈도우 소켓 관련 함수 호출이 불가능해진다. 따라서 더이상 Window Socket을 쓰지 않을 경우(프로그램 종료)에만 호출하자.)
#4. 윈도우 기반의 소켓관련 함수와 예제
윈도우 소켓생성도 리눅스 소켓 생성과 거의 유사하다.
#include <WinSock2.h>
// 1
SOCKET socket(int af, int type, int protocol);
-> 성공 시 소켓 핸들, 실패 시 INVALID_SOCKET 반환
// 2
int bind(SOCKET s, const struct sockaddr* name, int namelen);
-> 성공 시 0, 실패 시 SOCKET_ERROR 반환
// 3
int listen(SOCKET s, int backlog);
-> 성공 시 0, 실패 시 SOCKET_ERROR 반환
// 4
SOCKET accept(SOCKET s, struct sockaddr* addr, int* addrlen);
-> 성공 시 소켓 핸들, 실패 시 INVALID_SOCKET 반환
// 5
int connect(SOCKET s, const struct sockaddr* name, int namelen);
-> 성공 시 0, 실패 시 SOCKET_ERROR 반환
// 6
int closesocket(SOCKET s);
-> 성공 시 0, 실패 시 SOCKET_ERROR 반환
1. socket() : 소켓 생성 후 반환
2. bind() : IP, PORT번호 할당
3. listen() : 소켓이 클라이언트와 연결 요청을 받을 수 있는 상태로 변경
4. accept() : 클라이언트 프로그램에서의 연결 요청 수락 시 호출
5. connect() : 클라이언트 프로그램에서 소켓 기반 연결 요청 시 호출
6. closesocket() : 소켓을 닫을 때 호출하는 함수
- 윈도우에서의 파일 핸들과 소켓 핸들
리눅스에서 소켓도 일반 파일과 동일하게 취급하기 때문에, 파일을 생성하건 소켓을 생성하건 파일 디스크립터가 반환된다. 윈도우에서는? 윈도우에서도 시스템 콜을 통해서 파일을 생성 할 때 Handle(핸들)을 반환한다. 핸들을 반환한다는 관점에서 소켓을 생성하건, 일반 파일을 생성하건 동일하지만 리눅스처럼 완벽히 동일하지 않다.
윈도우 소켓이 리눅스 계열의 소켓인 BSD 소켓을 모델로 삼았다면, 리눅스와 동일해야 할텐데 왜 소켓과 일반 파일을 구분해서 처리할까? 여러가지 말이 많은데 두 운영체제 커널의 구조가 다르기에 그렇다고 한다.
- 윈도우 기반 서버, 클라이언트 예제 작성
[hello_server_win.c]
#include <stdio.h>
#include <stdlib.h>
#include <WinSock2.h>
#pragma comment(lib, "ws2_32.lib")
void ErrorHandling(char* message);
int main(int argc, char* argv[]) {
WSADATA wsaData;
SOCKET hServSock, hClntSock;
SOCKADDR_IN servAddr, clntAddr;
int szClntAddr;
char message[] = "Hello World!";
if (argc != 2)
{
printf("Usage : %s <port>\n", argv[0]);
exit(1);
}
if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)
{
ErrorHandling("WSAStartup() error!");
}
hServSock = socket(PF_INET, SOCK_STREAM, 0);
if (hServSock == INVALID_SOCKET)
{
ErrorHandling("socket() error!");
}
memset(&servAddr, 0, sizeof(servAddr));
servAddr.sin_family = AF_INET;
servAddr.sin_addr.S_un.S_addr = htonl(INADDR_ANY);
servAddr.sin_port = htons(atoi(argv[1]));
if (bind(hServSock, &servAddr, sizeof(servAddr)) == SOCKET_ERROR)
{
ErrorHandling("bind() error!");
}
if (listen(hServSock, 5) == SOCKET_ERROR)
{
ErrorHandling("listen() error!");
}
szClntAddr = sizeof(clntAddr);
hClntSock = accept(hServSock, (SOCKADDR*)&clntAddr, &szClntAddr);
if (hClntSock == INVALID_SOCKET)
{
ErrorHandling("accept() error!");
}
send(hClntSock, message, sizeof(message), 0);
closesocket(hServSock);
closesocket(hClntSock);
WSACleanup();
return 0;
}
void ErrorHandling(char* message)
{
fputs(message, stderr);
fputc('\n', stderr);
exit(1);
}
[hello_client_win.c]
#include <stdio.h>
#include <stdlib.h>
#include <WinSock2.h>
#pragma comment(lib, "ws2_32.lib")
void ErrorHandling(char* message);
int main(int argc, char* argv[])
{
WSADATA wsaData;
SOCKET hSocket;
SOCKADDR_IN servAddr;
char message[30] = { 0 };
int strLen;
if (argc != 3)
{
printf("Usage : %s <IP> <port>\n", argv[0]);
exit(1);
}
if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)
{
ErrorHandling("WSAStartup() error!");
}
hSocket = socket(PF_INET, SOCK_STREAM, 0);
if (hSocket == INVALID_SOCKET)
{
ErrorHandling("socket() error!");
}
memset(&servAddr, 0, sizeof(servAddr));
servAddr.sin_family = AF_INET;
servAddr.sin_addr.S_un.S_addr = inet_addr(argv[1]);
servAddr.sin_port = htons(atoi(argv[2]));
if (connect(hSocket, (SOCKADDR*)&servAddr, sizeof(servAddr)) == SOCKET_ERROR)
{
ErrorHandling("connect() error!");
}
strLen = recv(hSocket, message, sizeof(message) - 1, 0);
if (strLen == -1)
{
ErrorHandling("recv() error!");
}
printf("Message from server: %s \n", message);
closesocket(hSocket);
WSACleanup();
return 0;
}
void ErrorHandling(char* message)
{
fputs(message, stderr);
fputc('\n', stderr);
exit(1);
}
* Visual Studio - Tools - Command Line - Development Command Prompt에서 실행
[실행결과 : hello_server_win.c]
// 뷜드
C:\Users\Desktop>cl hello_server_win.c
// 실행
C:\Users\Desktop> hello_server_win 9190
[실행결과 : hello_client_win.c]
// 뷜드
C:\Users\Desktop>cl hello_client_win.c
// 실행
C:\Users\Desktop> hello_client_win 127.0.0.1 9190
윈도우 기반 입출력 함수
리눅스는 소켓통신 시 일반 파일과 마찬가지로 read, write를 이용해서 데이터 송수신이 가능하다.
윈도우의 경우 소켓 입출력 함수가 엄연히 구분되어 있다.
(리눅스에도 동일하게 send 함수가 있음. 다만 리눅스에서 소켓 통신 시 일반 파일 read/write와 똑같음을 강조하기 위해 read/write를 사용)
#include <WinSock2.h>
int send(SOCKET s, const char* buf, int len, int flags);
-> 성공 시 전송된 바이트 수, 실패 시 SOCKET_ERROR 반환
s : 데이터 전송 대상과의 연결을 의미하는 소켓의 핸들 값 전달.
buf : 전송할 데이터를 저장하고 있는 버퍼의 주소 값 전달.
len : 전송할 바이트 수 전달.
flags : 데이터 전송 시 적용할 다양한 옵션 정보 전달.
int recv(SOCKET s, const char* buf, int len, int flags);
-> 성공 시 수신한 바이트 수(단 EOF 전송 시 0), 실패 시 SOCKET_ERROR 반환
s : 데이터 수신 대상과의 연결을 의미하는 소켓을 핸들 값 전달.
buf : 수신된 데이터를 저장할 버퍼의 주소 값 전달.
len : 수신할 수 있는 최대 바이트 수 전달.
flags : 데이터 수신 시 적용할 다양한 옵션 정보 전달.
[내용 확인문제]
01. 네트워크 프로그래밍에서 소켓이 담당하는 역할은 무엇인가? 그리고 소켓이라는 이름이 붙은 이유는 어디에 있는가?
답)
물리적으로 연결된 네트워크상에서의 데이터 송수신에 사용할 수 있는 소프트웨어적인 장치로써 기기간 통신을 할 수 있도록 하는 역할을 한다. '소켓'은 연결이라는 의미가 담겨있다. 네트워크 망에 연결되어 있는 컴퓨터간의 연결을 의미하므로 '소켓'이라는 이름이 붙었다.
02. 서버 프로그램에서는 소켓생성 이후에 listen 함수와 accept 함수를 차례대로 호출한다. 그렇다면 이들의 역할은 각각 무엇인지 비교해서 설명해보자.
답)
listen은 서버 소켓 생성이 완료되었으며, 연결 요청을 받아들일 수 있는 상태가 된다. accept()함수를 호출하면 서버 소켓에서 연결 요청 수락을 의미하며, 만약 연결요청이 없는 상태에서는 연결 요청이 올때까지 기다리고 있는다. (함수가 반환하지 않음)
03. 리눅스의 경우 파일 입출력 함수를 소켓 기반의 데이터 입출력에 사용할 수 있다. 반면 윈도우에서는 이것이 불가능하다. 그렇다면 리눅스에서는 가능하고, 윈도우에서는 불가능한 이유는 무엇인가?
답)
리눅스는 각 파일들을 파일디스크립터로(양의 정수)구분하며 소켓과 일반 파일을 동일하게 처리한다. 따라서 파일 입출력함수를 데이터 송수신에 사용 할 수 있다. 반면, 윈도우에서는 파일을 핸들(양의 정수)을 통해 구분한다.
그런데 소켓의 경우 일반 파일과 분리해서 처리하기 때문에 send()와 recv()함수를 사용하여 데이터송수신을 해야 한다.
04. 소켓을 생성한 다음에는 주소할당의 과정을 거친다. 그렇다면 주소할당이 필요한 이유는 무엇이며, 이를 목적으로 호출하는 함수는 또 무엇인가?
답)
주소 할당이 필요한 이유는 주소(IP)는 네트워크로 연결된 기기의 식별자(구분 번호)이다. 따라서 할당해야 하며(추가로 PORT번호도 할당) 이는 bind() 함수를 호출하여 할당한다.
05. 리눅스의 파일 디스크립터와 윈도우의 핸들이 의미하는 바는 사실상 같다. 그렇다면 이들이 의미하는 바가 무엇인지 소켓을 대상으로 설명해보자.
답)
파일디스크립터와 핸들은 파일을 구분하는 유일한 값이다.(unique) 소켓 역시 파일이기에 리눅스에서 파일디스크립터값을 갖고, 윈도우에서는 핸들값을 갖는다. 다만 윈도우에서는 일반 파일처리(읽기/쓰기)와 구분하여 처리(송/수신)한다.
06. 저 수준 파일 입출력 함수와 ANSI 표준에서 정의하는 파일 입출력 함수는 어떠한 차이가 있는가?
답)
일반적으로 ANSI는 미국 표준 협회로 ANSI 표준 정의는 표준 협회에서 정의한 표준 라이브러리다. 저 수준은 "표준에 상관없이 운영체제가 독립적으로 제공하는~" 이라는 의미이다. 따라서 저수준 파일입출력 함수라면, 표준과 상관없이 운영체제에서 제공하는 함수이므로, 특정 운영체제가 동작하는 환경이라면 어디서든 사용 가능하다.
07. 본문에서 보인 예제 low_open.c와 low_read.c를 참조하여 파일 복사 프로그램을 작성하되, 저 수준 파일 입출력 함수를 기반으로, 그리고 ANSI 표준 입출력 함수를 기반으로 각각 한번씩 작성해보자. 그리고 복사 프로그램의 사용방법은 여러분이 임의로 결정하기 바란다.
답)
[low_open.c]
#include <stdio.h>
#include <io.h>
#include <fcntl.h>
#include <sys/stat.h>
void error_handling(const char* message);
int main(void) {
int hFile = 0;
char buf[] = "Let's go!\n";
if (_sopen_s(&hFile, "data.txt", _O_RDWR | _O_CREAT, _SH_DENYNO, _S_IREAD | _S_IWRITE) != 0) {
error_handling("open() error! \n");
}
printf("file handle : %d \n", hFile);
int i = 0, len = 0;
while (buf[i] != '\0') { ++i; ++len; }
if (_write(hFile, buf, sizeof(buf)) == -1) {
error_handling("write() error! \n");
}
_close(hFile);
return 0;
}
void error_handling(const char* message)
{
fputs(message, stderr);
fputc('\n', stderr);
exit(1);
}
[low_read.c]
#include <stdio.h>
#include <io.h>
#include <fcntl.h>
#include <sys/stat.h>
#define BUF_SIZE 100
void error_handling(const char* message);
int main(void) {
int hFile = 0;
char buf[BUF_SIZE] = { 0 };
if (_sopen_s(&hFile, "data.txt", _O_RDONLY, _SH_DENYWR, _S_IREAD) != 0) {
error_handling("open() error\n");
}
printf("file handle : %d\n", hFile);
if (_read(hFile, buf, BUF_SIZE) == -1)
{
error_handling("read() error\n");
}
printf("file data : %s \n", buf);
_close(hFile);
return 0;
}
void error_handling(const char* message) {
fputs(message, stderr);
fputc('\n', stderr);
exit(1);
}
'네트워크 > 열혈 TCP_IP 소켓프로그래밍' 카테고리의 다른 글
#6. UDP 기반 서버/클라이언트 (0) | 2022.08.02 |
---|---|
#5. TCP 기반 서버/클라이언트 2 (0) | 2022.07.30 |
#4. TCP기반 서버/클라이언트 1 (0) | 2022.07.24 |
#3. 주소체계와 데이터 정렬 (0) | 2022.07.20 |
#2. 소켓의 타입과 프로토콜의 설정 (0) | 2022.07.13 |