본 글은 윤성우의 TCP/IP 소켓 프로그래밍을 참고하였습니다
순서
I. 소켓 프로그래밍 절차
II. 클라이언트 함수 호출 정보
socket() - connect() - read()/write() - close()
III. 클라이언트 전체 구현 코드
I. 소켓 프로그래밍 절차
클라이언트는 매우 짧다.
사실상 데이터 송수신, 연결종료는 너무 간단하고,
socket()은 서버측과 동일하고
connect()는 서버측의 bind()와 90% 동일하다.
서버보다 훨~씬 쉽고 간결하다.
II. 클라이언트 호출 함수 정보
#include <sys/socket.h>
1. socket()함수 (서버측과 (아래 글도) 동일)
- socket 함수는 어떤 프로토콜을 가진 소켓으로 통신할 것인지 결정하여 생성하는 함수이다.
#include <sys/socket.h>
int socket( int domain, int type, int protocol );
--> 성공 시 파일디스크립터(이하 fd), 실패시 -1 반환.
ㄴ. domain : 소켓이 사용할 프로토콜 체계(Protocol Family) 정보 전달
ㄴ. type : 소켓의 데이터 전송방식에 대한 정보 전달
ㄴ. protocol : 두 컴퓨터간 통신에 사용되는 프로토콜 정보 전달
"IPv4 인터넷 프로토콜 체계에서 동작하는 연결지향형 데이터 전송 소켓"
int tcp_socket = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
"IPv4 인터넷 프로토콜 체계에서 동작하는 비 연결지향형 데이터 전송 소켓"
int udp_socket = socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP);
인자1. int domain
프로토콜 체계
우리는 주로 IPv4를 사용한다.
IPv6은 IP주소가 모두 고갈될 것을 염려하여 만들어진 16byte 주소 표준이다.
(+ 주소 표준이란?)
주로 사용하는 IPv4 (4byte)기준으로 ABCD 4개의 주소 클래스가 있는데,
차례대로 네트워크 ID의 Byte크기가 1,2,3,4이고
호스트 ID의 Byte크기가 3,2,1,0 이다.
이 주소 클래스에 따라 127.x.x.x 인지, 192.x.x.x인지 첫 번째 바이트 범위가 나누어진다.
A : 0~127
B: 128~191
C : 192~223
우리는 주로 IPv4를 사용한다.
IPv6은 IP주소가 모두 고갈될 것을 염려하여 만들어진 16byte 주소 표준이다.
(+ 주소 표준이란?)
주로 사용하는 IPv4 (4byte)기준으로 ABCD 4개의 주소 클래스가 있는데,
차례대로 네트워크 ID의 Byte크기가 1,2,3,4이고
호스트 ID의 Byte크기가 3,2,1,0 이다.
이 주소 클래스에 따라 127.x.x.x 인지, 192.x.x.x인지 첫 번째 바이트 범위가 나누어진다.
A : 0~127
B: 128~191
C : 192~223
인자2. int type
전송방식
위 II - 3에서 설명한 TCP/UDP 계층을 선택한다.
TCP : SOCK_STREAM
UDP : SOCK_DGRAM
인자3. int protocol
프로토콜 정보
하나의 프로토콜 체계 안에 데이터의 전송방식이 동일한 프로토콜이 둘 이상 존재할 때 필요한 인자이다.
말이 길고 어려운데, 우리가 원하는 TCP, UDP를 사용할 때는 0을 넣어도 무관하다. (만족하는 것이 딱 1개 이므로)
2. connect() 함수
listen중인 서버 소켓에 연결요청을 한다.
connect()가 성공적으로 끝나면 바로 read/write가능
#include <sys/socket.h>
#include <string.h>
int connect(int sock, (struct sockaddr) *servaddr, socklen_t addrlen);
--> 성공 시 0, 실패 시 -1 반환
ㄴ. sock : 클라이언트 소켓의 파일 디스크립터
ㄴ. servaddr : 연결요청 할 서버의 주소정보를 담은 "구조체 변수의 주소 값"
ㄴ. addrlen : 두번 째 인자인 servaddr 구조체 변수의 크기정보.
int clnt_sock;
struct sockaddr_in serv_addr; // 이 구조체 개념 중요. 글 하단 설명 참고
char *serv_ip = "127.0.0.1";
char *serv_port = "9190";
// sock()함수 사용해 서버의 파일 디스크립터 얻음.
clnt_sock = socket(PF_INET, SOCK_STREAM, 0)
// 구조체 주소정보 초기화 ** 이부분 개념 중요. 글 하단 설명 참고
memset(&serv_addr, 0, sizeof(serv_addr)); // 메모리 초기화
serv_addr.sin_family = AF_INET;
serv_addr.sin_addr.s_addr = inet_addr(serv_ip);
serv_addr.sin_port = htons ( atoi(serv_port) );
// connect() (struct sockaddr*)로 캐스팅*
connect(clnt_sock, (struct sockaddr*) &serv_addr, sizeof(serv_addr));
구조체에 대한 분석
struct sockaddr_in
{
sa_family_t sin_family; 주소체계(Address Family)
uint16_t sin_port; 16비트 TCP/UDP PORT번호
struct in_addr sin_addr; 32비트의 IP주소
char sin_zero[8]; 사용되지 않음 -> ??
}
struct in_addr{
in_addr_t s_addr; 32비트의 IPv4 인터넷 주소가 담긴다.
}
struct sockaddr
{
sa_family_t sin_family 주소체계(Address Family)
char sa_data[14]; 주소정보
}
인자1. int sock
소켓 파일 디스크립터
sock()함수를 통해 얻은 클라이언트 소켓의 파일 디스크립터이다.
인자2. (struct sockaddr) *serv_addr
*sockaddr 구조체로 캐스팅 된 내가 정의한 sockaddr_in 구조체 주소
캐스팅 된 이유와 개념은
앞의 서버구현 글에 있다.
클라이언트 -> 서버로
연결요청을 하기 때문에 주소정보가 서버측과 동일해야 한다.
따라서 두번 째 인자에서 서버측 구현과 동일하게 서버의 주소정보 구조체가 들어간다.
이 점을 유의하자.
인자3. socklen_t addrlen
인자2에 넣어준 구조체 변수의 길이정보 (sizeof)
socklen_t는 <sys/socket.h>에 저장된 길이정보 자료형이다.
그냥 sizeof(serv_addr) 하면 된다.
3.read()/write()
데이터를 수신/송신한다.
#include <sys/socket.h>
int str_len;
ssize_t read(int fd, void *buf, size_t nbytes);
-> 성공 시 수신한 byte 수(단 파일의 끝을 만나면 0), 실패 시 -1 반환
ㄴ. fd : 데이터를 받을 소켓의 파일 디스크립터(클라이언트)
ㄴ. buf : 수신한 데이터를 저장할 버퍼의 주소 값 전달
ㄴ. nbytes : 수신할 최대 바이트 수 전달.
ssize_t write(int fd, void *buf, size_t nbytes);
--> 성공 시 전달한 byte 수, 실패 시 -1 반환.
ㄴ. fd : 데이터를 보낼 소켓의 파일 디스크립터(클라이언트)
ㄴ. buf : 전송할 데이터가 저장된 버퍼의 주소 값 전달
ㄴ. nbytes : 전송할 데이터의 바이트 수 전달.
int clnt_sock; //파일 디스크립터
char read_message[BUF_SIZE];
char send_message[BUF_SIZE];
int str_len, send_str_len;
str_len = read(cnlt_sock, read_message, BUF_SIZE);
write(clnt_sock, send_message, send_str_len);
인자1. fd
read : 데이터를 받을 대상 소켓의 파일 디스크립터 (클라이언트)
write : 데이터를 보낼 대상 소켓의 파일 디스크립터 (클라이언트)
인자2. buf
read : 데이터를 받을 버퍼의 주소 값
write : 데이터를 보낼 버퍼의 주소 값
인자3. nbytes
read : 수신할 최대 바이트 수 전달
write : 송신할 데이터의 바이트 수 전달
4. close()
연결된 소켓을 종료한다.
#include <unistd.h>
int close(int fd);
--> 성공 시 0, 실패 시 -1반환
ㄴ. fd : 닫고자 하는 파일 또는 소켓의 파일 디스크립터
III. 클라이언트 전체 구현 코드
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h> // 파일 관리 함수 헤더
#include <arpa/inet.h>
#include <sys/socket.h> // 소켓프로그래밍 함수선언
void errhandle(char *errmsg){
fputs(errmsg, stderr);
fputc('\n', stderr);
exit(1);
}
int main(){
// 클라이언트 소켓 파일디스크립터 변수 선언
int clnt_sock;
// sockaddr_in 구조체 변수 선언
struct sockaddr_in st_serv_addr;
// 보내고 받을 버퍼 정의
char sendmsg[] = "Test_clnt";
char recvmsg[1024];
// ip, port 정의
char ip[] = "127.0.0.1" //
int port = 10000;
// 클라이언트 소켓 TCP/IP 프로토콜 생성
clnt_sock = socket(PF_INET, SOCK_STREAM, 0);
if(serv_sock == -1) errhandle("socket() ERR!")
// serv_sock에 bind로 주소 넣기 위한 밑작업
memset(&st_serv_addr,0,sizeof(st_serv_addr));
st_serv_addr.sin_family = AF_INET;
st_serv_addr.sin_addr.s_addr = inet_addr(ip);
st_serv_addr.sin_port = htons(port)
// connect()으로 서버소켓에 연결요청
int connret = connect(clnt_sock,
(struct sockaddr*) &st_serv_addr,
sizeof(st_serv_addr));
if(connret == -1) errhandle("connect() ERR!");
// sendmsg 보내고, recvmsg에 수신된 string데이터 담기
write(clnt_sock, sendmsg, sizeof(sendmsg) );
int readstrlen = read(clnt_sock, recvmsg, sizeof(recvmsg)-1);
//소켓은 파일이다! 닫아야 한다.
close(clnt_sock);
return 0;
}
'Programming > Network(C++)' 카테고리의 다른 글
[Network][TCP/IP]소켓 구현(4)_윈도우 클라이언트 구현 (0) | 2020.12.12 |
---|---|
[Network][TCP/IP] 소켓 구현(3)_윈도우 서버 구현 (2) | 2020.12.12 |
[Network][TCP/IP] 소켓 구현(1)_ 리눅스 서버 구현 (0) | 2020.12.11 |
[Network][TCP/IP] 소켓 프로그래밍_개념 및 절차 (2) | 2020.12.10 |