Part 01
Ch. 01. Socket Network Programming
Ch. 02. File Descriptor, open(), close(), write(), read()
Ch. 05. Network Byte Order, Big Endian, Little Endian
Part 2
Ch.10.
Ch.11.
Ch.12.
Ch.13.
Ch.14.
Ch.15.
Ch.16.
Ch.17.
Ch.18.
소켓 통신 프로그래밍
API / 클래스 | 설명 | 제공 기능 | OS/PL | |
Unix | Socket API | 네트워크 표준 API | TCP, UDP 등 다양한 네트워크에 사용 | Unix(Ubuntu, Redhat..) |
Win | WinSock2 API | Socket API 윈도우 버전 | Window(C/C++) | |
C# .NET |
Sockets | WinSock2 API C# 버전 | Window(C#) | |
TcpClient | Sockets 클래스 내재됨 | TCP, UDP 전용 | ||
TcpListner | ||||
UdpClient |
소켓(socket)은 버클리 유닉스 (BSD Unix)에서 처음 개발되었다.
단말(eg. 전화기)을 논리적으로 추상화한 소켓(Socket)을 통해, 통신 구조에서 소켓단의 구현만 고려하면 되는 프로그래밍 이점을 가진다.
클라이언트-서버 통신 모델
대부분 네트워크 프로그램은 클라이언트-서버(C/S) 모델로 구현되는데 서비스를 받는 클라이언트의 요구(request)에 대하여 서비스를 제공하는 서버가 응답(response)을 보내는 방식으로 동작이 이루어진다.
소켓 네트워크 실행 흐름
서버 (Server) 흐름 | 클라이언트(Client) 흐름 |
1. 서버 소켓(Server Socket) [1]생성(socket) 2. 서버가 사용할 IP 주소와 Port 번호를 [2]결합(bind) 3. 클라이언트로부터 [3] 연결 요청 대기, 저장(listen) 4. 요청 정보를[4]수락(accept) 후 연결 소켓을 생성 5. 연결 소켓을 통한 데이터를 [5]송수신(write/read, send/recv) 6. 통신이 완료되면, 소켓(Socket)을 [6]닫음(close) |
1. 클라 소켓(Socket)을 [1]생성(socket) 2. 서버로 [2]연결(connect) 요청 3. 서버 소켓에서 연결 수락 후, 데이터 [3]송수신(write/read, send/recv) 4. 통신이 완료되면, 소켓(Socket) [4]닫음(close) |
서버 소켓 프로그래밍 (Server)
1. 서버 소켓 생성
int socket(int domain, int type, int protocol)
// 성공시 파일 디스크립터 반환, 실패시 -1 반환
소켓은 서버와 클라이언트가 동일한 형태다.
2. 서버 소켓 결합
int bind(int sockfd, struct sockaddr *myaddr, socklen_t adrlen);
// 성공시 0, 실패시 -1
서버에서 생성된 소켓(Server Socket)에 서버가 사용할 IP주소, Port번호를 결합(bind)시킨다.
3. 서버의 클라이언트 연결 요청 대기
int listen(int sockfd, int backlog);
// 성공시 0, 실패시 -1
listen( )의 역할은, 바인딩된 서버 소켓(Server Socket)으로 수신될 외부 클라이언트의 connect( ) 요청을 대기하며, 연결 요청을 수신 받으면 해당 "클라이언트 정보"를 시스템 내부 큐(Queue)에 쌓는다. 수신 성공(0) 시점에서 서버와 클라이언트는 아직 연결되지 않은 상태다.
listen( )이 성공하더라도, 리턴 값에 클라이언트의 요청에 대한 정보는 들어 있지 않으며 접근도 불가능하다.
리턴 값으로 판단할 수 있는 것은 클라이언트 연결 요청을 잘 수신하여 내부에 저장하였는지(0), 그렇지 않고 에러가 발생했는지(-1) 뿐이다.
4. 서버와 클라이언트 연결 수립
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
// 성공시 파일 디스크립터 반환, 실패시 -1 반환
accept( ) 실행 이후에 실질적인 클라이언트-서버 연결(Connection)이 이루어진다.
주의할 점은 accept( )를 통해 생성된 소켓의 파일 디스크립터는, 앞서 bind( )와 listen( )에 사용된 서버 소켓(Server Socket)이 아니다.
accept( ) 내부에서 서버 소켓(Server Socket)과 클라이언트 소켓(Client Socket)을 연결한 새로운 소켓(connected socket)의 파일 디스트립터를 반환하고, 실질적인 데이터 송수신에 이를 이용한다.
서버 소켓(Server Socket)의 목적은 클라이언트와 연결된 새로운 소켓을 만드는 것이다.
연결된 새로운 소켓(connected socket)은 연결된 두 기기간의 데이터 송수신을 목적이다.
5. 데이터 송수신
ssize_t write(int fd , const void *buf , size_t nbytes);
// 성공시 전달한 바이트 수, 실패시 -1 반환
ssize_t read(int fd , void *buf , size_t nbytes);
// 성공시 전달한 바이트 수, 실패시 -1 반환
클라이언트와 동일하다.
6. 소켓 연결 종료
int close(int fd);
// 성공시 0, 실패시 -1
클라이언트와 동일하다.
클라이언트 소켓 프로그래밍 (Client )
1. 클라이언트 소켓 생성
int socket(int domain, int type, int protocol)
// 성공시 디스크립터 반환, 실패시 -1 반환
처음 소켓이 만들어지는 시점에는 "연결 대상(서버)"에 대한 어떠한 정보도 들어 있지 않다.
2. 연결 요청
int connect(int sockfd, struct sockaddr *serv_addr, socklen_t addrlen);
// 성공시 0, 실패시 -1
connect() 는 "IP주소"와 "Port 번호"로 식별되는 연결 대상(서버)으로 연결 요청을 보낸다.
3. 데이터 송수신
ssize_t write(int fd , const void *buf , size_t nbytes);
// 성공시 전달한 바이트 수, 실패시 -1 반환
ssize_t read(int fd , void *buf , size_t nbytes);
// 성공시 전달한 바이트 수, 실패시 -1 반환
데이터를 보내는 write(), send()의 경우, 주체가 자기 자신이기 때문에, 얼마만큼의 데이터를 보낼 것인지, 언제 보낼 것인지를 알 수 있다.
데이터를 전송 받는 경우, 대상이 언제, 어떤 데이터를 보낼 것인지를 특정할 수 없기 때문에 read(), recv() 가 한번 실행되면 언제 끝날지 모르는 상태다.
4. 소캣 닫기
int close(int fd);
// 성공시 0, 실패시 -1
코드
// Server
#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);
}
// Client
#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);
return 0;
}
void error_handling(char *message)
{
fputs(message, stderr);
fputc('\n', stderr);
exit(1);
}
네트워크 프로그래밍의 분류
1. 응용 계층 프로그래밍
▶ 컴퓨터 시스템이 지원하는 네트워크 유틸리티, 응용 프로그램을 활용한 프로그래밍 인터페이스를 제공
▶ 웹(web)의 HTML(Hyper Text Markup Language) 등
2. 트랜스포트, 인터넷 계층 프로그래밍
▶ 트랜스포트 계층을 이용해 호스트 종점간의 연결을 직접 관리하고 패킷 단위의 데이터 송수신을 제어
▶ 대표적으로 Socket API(Application Program Interface)를 이용
▶ 소켓 API는 운영체제에 따라 UNIX BSD(Berkeley Software Distribution) socket(1982년), 윈도우 소켓(Winsock, 1992년) 등이 있으며, 현재는 컴퓨터 기종 및 운영체제에 무관하게 대부분의 TCP/IP를 제공하는 컴퓨터에서는 기본적으로 지원
3. 디바이스 계층 프로그래밍
▶ OSI(Open System Interconnection)의 계층 2 이하의 인터페이스, 즉 링크 계층이나 하드웨어 디바이스를 구동하여 프레임 단위의 데이터 송수신을 직접 다루는 프로그래밍
▶ 프레임을 전송 또는 수신하는 단순한 기능만 제공하므로 흐름제어, 오류제어, IP 주소 관리와 같은 기능은 사용자가 별도로 구현 필요
출처 & 참고
1. recipes4dev.tistory.com/153