인터넷 소켓 통신 (TCP/IP)
소켓 통신은 소켓 프로그래밍을 사용하여 구현되며, 컴퓨터 네트워크를 통해 데이터를 주고받는 통신을 하기 위한 API(Application Programming Interface)를 제공합니다. 소켓은 IP주소와 포트번호로 식별되는 네트워크 통신에서 사용되는 엔드포인트로, 통신을 수행하는 두 개의 시스템 사이에서 데이터를 주고받을 수 있는 인터페이스를 제공합니다.
인터넷 소켓은 TCP/IP 프로토콜을 기반으로 하는 소켓으로, 연결지향 통신이라고도 합니다.
소켓 통신은 클라이언트와 서버 간의 통신을 하는 구조로, 클라이언트는 서버에 연결 요청을 보내고, 서버는 클라이언트의 요청을 차리하고 응답을 반환합니다.
소켓 통신 과정
[클라이언트 소켓]
1) socket() : 소켓을 생성하고 초기화(네트워크 통신을 위한 소켓의 타입, 프로토콜, 네트워크 도메인 정의)
각소켓은 고유한 파일디스크립터를 가진다. 이를 통해 해당 소켓을 관리하고 사용하며, 다수의 서버와 소켓을 열고 닫고 데이터를 읽고 쓰는 등의 통신 작업을 수행할 수 있다.
: int socket(int domain, int type, int protocol)
* clientFd = socket(AF_INET, SOCK_STREAM, DEFAULT_PROTOCOL)
-> IP4(AF_INET), TCP(SOCK_STREAM), 기본 프로토콜 DEFAULT_PROTOCOL
2) connect() : 서버 소켓에 연결 요청 (연결이 성공하거나 실패할 때까지 대기)
return이 0이면 연결 성공, return -1이면 연결 실패
: int connect (int sockfd, const struct sockaddr *addr, socklen_t addrlen)
(sockfd 연결을 시도할 소켓의 파일디스크립터(클라이언트 소켓), addr 연결하려는 소켓의 주소정보를 가지고 있는 구조체의 포인터, 소켓 정보구조체의 크기)
3) send/recv : 데이터의 송신 및 수신
- read/write (파일 시스템 입출력 작업에 사용되는 파일 디스크립터에서 사용 표준입력 0/표준출력 1/표준오류 2와 같은 값으로 시작)
: int write(fd, buffer, count) / read(fd,buffer,count)
- recv/send (네트워크 통신을 위한 파일 디스크립터로, 파일 시스템과는 무관하게 네트워크를 통해 데이터 송수신)
: int send(socket, buffer, length, flags), int recv(socket, buffer, length, flags)
4) close() : 소켓 파일 디스크립터 종료
: int close (int fd)
[서버 소켓]
1) socket() : 소켓 생성하여 서버 파일 디스크립터에 저장
2) bind() : 소켓 파일 디스크립터를 서버의 ip주소, 포트번호와 연결)
클라이언트에게 서버의 위치를 알려주고, 클라이언트가 서버에 연결할 수 있도록 한다.
: int bind(int sockfd, const struct sockaddr *addr, socklent_t addrlen);
* bind(listenfd, (const struct sockaddr*)&serveraddr, sizeof(serveraddr)
3) listen() : 클라이언트의 연결요청 대기
클라이언트의 연결 요청을 수락할 수 있는 상태로 전환
대기모드 전환에 성공하면 0 return 실패하면 -1 리턴
: int listen(int sockfd, int backlog)
* listen(listenfd, 5)
-> 백로그 : 연결대기모드로 전환할 수 있는 최대 가능 개수 (대기열의 크기)
4) accept() : 클라이언트의 연결요청 수락
클라이언트와의 연결 및 통신에 사용할 new file discriptor 반환 (대기모드가 될 수 없는 파일 디스크립터 새로 생성)
연결 요청에 수락하여 연결이 설정된 이후 연결된 클라이언트의 정보 출력
: int accept(listenfd, (struct sockaddr *)&addr, socklen_t *addrlen)
* accept( listenfd, (struct sockaddr*)&clientaddr, &clientlen)
5) recv/send() : 동일 호스트 내에서 파일 시스템을 입출력하는 경우 read/write를 사용하며, 소켓통신에서 데이터 송수신 시 일반적으로 병렬처리 및 프로세스 격리를 위해 자식 프로세스를 생성하여 독립적인 메모리 공간에서 수행한다
6) close() : 연결된 서버 소켓 파일 디스크립터 종료
예제 코드 1) 동일 호스트에서 시스템 간 로컬 파일시스템의 파일 데이터 송수신(인터넷 소켓)
[client]
#include <stdio.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#define DEFAULT_PROTOCOL 0
#define MAXLINE 100
//client socket 통신 연결 요청을 송신
int readLine(int fd, char* str)
{
int n;
do {
n = read(fd, str, 1); //읽어들인 바이트 수 return
//n = recv(fd, str, 1, 0)과 같아
} while(n > 0 && *str++ != 0x00);
return(n > 0);
}
//struct sockaddr_in {
// short sin_family; // 주소 체계(Address Family), AF_INET으로 설정
// unsigned short sin_port; // 16비트 포트 번호, 네트워크 바이트 순서
// struct in_addr sin_addr; // 32비트 IP 주소, 네트워크 바이트 순서
// char sin_zero[8]; // 사용하지 않음. sockaddr와 sockaddr_in의 크기를 맞추기 위해 존재
//};
//struct hostent {
// char* h_name; // 공식 도메인 이름 (Official Name)
// char** h_aliases; // 대체 도메인 이름 (Alternative Name)
// int h_addrtype; // 주소 타입 (Address Type)
// int h_length; // 주소 길이 (Address Length)
// char** h_addr_list; // IP 주소 목록 (List of IP Addresses)
// };
/* 파일 클라이언트 */
int main (int argc, char* argv[]) //ip, port 번호를 인자로 받아
{
int clientFd, port, result;
char *host;
char inmsg[MAXLINE], outmsg[MAXLINE];
//사용자의 입력을 저장하는 문자열 포인터
//client : inmsg 사용자의 입력을 배열에 저장하여 서버에 전송
//server : outmsg 서버로부터 읽은 파일의 내용을 저장하여 서버에 전송
struct sockaddr_in serverAddr; // 서버 구조체 변수 : 서버에 연결하기 위한 주소정보 저장, 연결 정보 설정, 서버주소 정보 저장 및 재사용
struct hostent *hp; //ip 주소 구조체
if (argc != 3) {
fprintf(stderr, "사용법 : %s <host> <port>\n", argv[0]);
exit(0);
}
/*소켓 생성*/
host = argv[1]; //IP4 IP 주소
port = atoi(argv[2]); //PORT 번호
//소켓 생성(client fd 반환) int socket(int domain, int type, int protocol)
// IP4 -> AF_INET, TCP -> SOCK_STREAM, 기본 DEFALUT_PROTOCOL
clientFd = socket(AF_INET, SOCK_STREAM, DEFAULT_PROTOCOL);
/* 접속지 서버 IP 주소와 포트 정보 할당 */
//struct hostent* gethostbyname(const char*name);
if ((hp = gethostbyname(host)) == NULL) { //gethostbyname 함수를 이용하여 IP주소를 해석
perror("gethostbyname error"); // 호스트 찾기 오류 에러출력
}
bzero((char *) &serverAddr, sizeof(serverAddr));
//bzero(void *s, size_t n) 데이터 초기화
serverAddr.sin_family = AF_INET;
bcopy((char *)hp->h_addr_list[0], (char *)&serverAddr.sin_addr.s_addr, hp->h_length);
//bcopy(const void*src, void *dest, size_t n) ip 주소목록을 서버 구조체에 저장
serverAddr.sin_port = htons(port);
//port 번호 저장
do { /* 연결 요청 */
result = connect(clientFd, (const struct sockaddr*)&serverAddr, sizeof(serverAddr));
//int connect(int sockfd, const struct sockaddr *addr, socklent_t addrlen)
//return 0이면 연결 성공, return -1이면 연결 실패
if (result == -1) {
sleep(1);
}
} while (result == -1); //연결될 때까지 대기
/*사용자의 입력(파일 이름)을 배열에 저장하여 서버에 전송*/
printf("파일 이름 입력:");
scanf("%s", inmsg);
write(clientFd,inmsg,strlen(inmsg)+1);
//send(clientFd, inmsg, strlen(inmsg)+1, 0)
//클라이언트가 서버로 보내고자하는 데이터를 inmsg에 저장, 파일 디스크립터를 이용하여 데이터를 주고 받아)
/* 소켓으로부터 파일 내용 읽어서 프린트 */
while (readLine(clientFd,outmsg)) {
printf("%s", outmsg);
}
close(clientFd); //소켓을 열고 닫고, 파일을 읽고 쓸때 파일 디스크립터 사용
exit(0);
}
[server]
#include <stdio.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#define DEFAULT_PROTOCOL 0
#define MAXLINE 100
int readLine(int fd, char* str)
{
int n;
do {
n = read(fd, str, 1);
//ssize_t read(int fd, void*buf, size_t count);
} while(n > 0 && *str++ != 0x00); //NULL문자가 나타날 때까지 한글자씩 읽어들여
return(n > 0);
}
/* 파일 서버 */
int main (int argc, char* argv[])
{
int listenfd, connfd, port;
unsigned int clientlen;
FILE *fp;
char inmsg[MAXLINE], outmsg[MAXLINE];
struct sockaddr_in serveraddr, clientaddr; //서버구조체와 클라이언트 구조체 변수 생성
char *ipaddr;
signal(SIGCHLD, SIG_IGN);
if (argc != 2) {
fprintf(stderr, "사용법: %s <port>\n", argv[0]);
exit(0);
}
port = atoi(argv[1]);
listenfd = socket(AF_INET, SOCK_STREAM, DEFAULT_PROTOCOL);
//서버 소켓 생성하여 파일 디스크립터에 저장
bzero((char *) &serveraddr, sizeof(serveraddr));
//서버 구조체 초기화
//memset(&serveraddr, 0x00, sizeof(serveraddr));
serveraddr.sin_family = AF_INET;
serveraddr.sin_addr.s_addr = htonl(INADDR_ANY); //32bit 다음으로 확장
serveraddr.sin_port = htons((unsigned short)port); //16bit 다음으로 확장
bind(listenfd, (const struct sockaddr*)&serveraddr, sizeof(serveraddr));
//int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
//빈 소켓에 address를 부여
listen(listenfd, 5);
//int listen(int sockfd, int backlog); backlog = 대기열의 크기
//연결 요청 수락 가능 여부 확인에 성공하면 0 return 실패하면 -1 return;
//listenfd accept로부터 클라이언트 연결 요청 수락을 기다리는 서버소켓
while (1) {
clientlen = sizeof(clientaddr);
connfd = accept(listenfd, (struct sockaddr *)&clientaddr, &clientlen);
//int accept(int sockfd, struct sockaddr* addr, socklen_t *addrlen);
//return new file discrpter : 클라이언트와의 연결 및 통신에 사용
//연결 요청에 수락한 후
/* 클라이언트 정보 가져오기 */ //클라이언트와 서버 간의 연결이 설정된 이후
//클라이언트의 ip주소와 port 번호를 확인하고 출력
gethostbyaddr((char *)&clientaddr.sin_addr.s_addr, sizeof(clientaddr.sin_addr.s_addr), AF_INET);
ipaddr = inet_ntoa(clientaddr.sin_addr);
printf("서버: (%s) %d에 연결됨\n", ipaddr, clientaddr.sin_port);
if (fork() == 0) { //자식 프로세스에서 파일 읽고 써
readLine(connfd, inmsg);
fp = fopen(inmsg, "r");
if (fp == NULL) {
write(connfd, "not found file\n", 15);
} else {
while(fgets(outmsg, MAXLINE, fp) != NULL) {
write(connfd, outmsg, strlen(outmsg)+1);
}
}
close(connfd);
exit (0);
}
else {
close(connfd);
}
} // while
} // main
예제 2) 네트워크 통한 데이터 송수신 예제
[client]
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <sys/socket.h>
#define PORT 8080
#define SERVER_IP "127.0.0.1"
int main() {
int clientSock;
struct sockaddr_in serverAddr;
// 클라이언트 소켓 생성
if ((clientSock = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
perror("Socket creation failed");
exit(EXIT_FAILURE);
}
serverAddr.sin_family = AF_INET;
serverAddr.sin_port = htons(PORT);
serverAddr.sin_addr.s_addr = inet_addr(SERVER_IP);
// 서버에 연결 시도
if (connect(clientSock, (struct sockaddr *)&serverAddr, sizeof(serverAddr)) == -1) {
perror("Connection failed");
exit(EXIT_FAILURE);
}
const char *message = "Hello, Server!";
send(clientSock, message, strlen(message), 0);
char buffer[1024];
int bytesRead = recv(clientSock, buffer, sizeof(buffer), 0);
buffer[bytesRead] = '\0';
printf("Received from server: %s\n", buffer);
close(clientSock);
return 0;
}
[server]
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <sys/socket.h>
#define PORT 8080
#define MAX_BUFFER_SIZE 1024
int main() {
int serverSock, clientSock;
struct sockaddr_in serverAddr, clientAddr;
socklen_t addrLen = sizeof(struct sockaddr_in);
// 서버 소켓 생성
if ((serverSock = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
perror("Socket creation failed");
exit(EXIT_FAILURE);
}
// 서버 주소 설정
serverAddr.sin_family = AF_INET;
serverAddr.sin_addr.s_addr = INADDR_ANY;
serverAddr.sin_port = htons(PORT);
// 소켓을 주소와 바인딩
if (bind(serverSock, (struct sockaddr *)&serverAddr, sizeof(serverAddr)) == -1) {
perror("Binding failed");
exit(EXIT_FAILURE);
}
// 연결 대기
if (listen(serverSock, 5) == -1) {
perror("Listen failed");
exit(EXIT_FAILURE);
}
printf("Server is listening on port %d\n", PORT);
// 클라이언트 연결 대기
if ((clientSock = accept(serverSock, (struct sockaddr *)&clientAddr, &addrLen)) == -1) {
perror("Accept failed");
exit(EXIT_FAILURE);
}
char buffer[MAX_BUFFER_SIZE];
int bytesRead;
// 클라이언트로부터 데이터 수신
if ((bytesRead = recv(clientSock, buffer, sizeof(buffer), 0)) == -1) {
perror("Receive failed");
exit(EXIT_FAILURE);
}
buffer[bytesRead] = '\0';
printf("Received from client: %s\n", buffer);
// 클라이언트에 응답 전송
const char *response = "Hello, Client!";
send(clientSock, response, strlen(response), 0);
close(clientSock);
close(serverSock);
return 0;
}
'리눅스' 카테고리의 다른 글
01. 리눅스_시그널(signal) 정의 및 함수 (0) | 2024.03.11 |
---|