TCP 네트워크 프로그래밍 - 1

2019. 7. 22. 00:34드론

 

TCP는 연결 기반의 신뢰성 있는 통신 방식으로 FTP,  메일, 웹 서비스 등 인터넷을 이용한 다양한 서비스에서 사용되고 있다. 신뢰성을 추구하다보니 UDP보다는 복잡한 절차와 방식으로 서버와 클라이언트가 구현된다

 

bind() 하는 과정은 UDP 서버와 동일하다. UDP와는 다르게, TCP는 클라이언트가 3 Way Handshaking을 통해서 서버에 접속하기 때문에, 클라이언트의 대기를 처리하는 큐가 필요하다. 이를 위해 listen 함수를 오해 대기 큐를 설정하고, accept를 통해서 클라이언트의 접속을 기다릴 수 있다. 참고로 3 Way Handshaking은 신뢰성 있는 연결을 위해 3번의 패킷 교환(SYN, SYN/ACK, ACK)으로 연결하는 동작을 의미한다

 

TCP 클라이언트는 connect 함수를 이용해서 서버에 접속하게 되고, 이후에 send와 recv 함수를 사용하거나 read와 write를 통해서 통신할 수 있다. UDP와는 달리, 이미 서버에 접속해 있으므로 데이터를 보낼 때 서버의 정보를 사용할 필요가 없다

 

클라이언트 서비스의 준비가 완료되었다면, 서버에서 클라이언트의 접속을 처리해야 할 메모리 공간이 필요하게 된다. 이 때 listen 함수를 통해 대기 큐를 설정할 수 있다. 함수 원형은 다음과 같은데, 

int listen(int sockfd, int backlog)

두 번째 인자는 다음과 같이 설명되어 있다

The backlog argument defines the maximum length to which the queue of pending connections for sockfd may grow.

즉 연결 요청을 할 수 있는 클라이언트 소켓 수를 의미하고, 이를 넘어간다면 연결 시도가 거부된다

 

그리고 마지막으로 accept에서는 접속한 클라이언트 정보를 얻기 위해 주소 구조체를 사용한다. 인자를 받아오고 나서는, 구조체 필드 중 IP와 포트는 빅 엔디안으로 사용되기 때문에 인코딩 변환이 필요하다. 그리고 accept는 성공했을 시 접근한 클라이언트의 fd를 반환한다

 

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/socket.h>
#define TCP_PORT 5100
#define BUF_SIZE 256
int main(int argc, char **argv)
{
int ssock;
int clen;
struct sockaddr_in servaddr;
char buf[BUF_SIZE];
if(argc < 2) {
printf("Usage :%s IP_ADDRESS\n", argv[0]);
return -1;
}
if((ssock = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
perror("socket");
return -1;
}
memset(&servaddr, 0, sizeof(servaddr));
servaddr.sin_family = AF_INET;
// 문자열을 IP 주소 포맷으로 바꿔주는 과정
servaddr.sin_addr.s_addr = inet_addr(argv[1]);
// htons의 매개변수는 uint32_t 혹은 uint16_t로, 만일 argv로 받을 때 atoi 처리를 해야만 한다
servaddr.sin_port = htons(TCP_PORT);
clen = sizeof(servaddr);
if(connect(ssock, (struct sockaddr *)&servaddr, clen) < 0) {
perror("connect()");
return -1;
}
fgets(buf, BUF_SIZE, stdin);
if(write(ssock, buf, BUF_SIZE) <= 0) {
perror("write");
return -1;
}
memset(buf, 0, BUF_SIZE);
if(read(ssock, buf, BUF_SIZE) <= 0) {
perror("read");
return -1;
}
printf("Received data : %s", buf);
close(ssock);
return 0;
}
view raw tcp_client.c hosted with ❤ by GitHub
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/socket.h>
#define TCP_PORT 5100
#define BUF_SIZE 256
int main(int argc, char **argv)
{
int ssock;
socklen_t clen;
int n;
struct sockaddr_in servaddr, cliaddr;
char buf[BUF_SIZE] = "Hello World";
if((ssock = socket(AF_INET, SOCK_STREAM, 0)) < 0 ) {
perror("socket()");
return -1;
}
memset(&servaddr, 0, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
servaddr.sin_port = htons(TCP_PORT);
if(bind(ssock, (struct sockaddr *)&servaddr, sizeof(servaddr)) < 0) {
perror("bind");
return -1;
}
/* 동시에 접속하는 클라이언트의 처리를 위한 대기 큐를 설정 */
if(listen(ssock, 8) < 0) {
perror("listen");
return -1;
}
clen = sizeof(cliaddr);
while(1) {
/* blocking 상태로 소켓이 연결될 때까지 대기한 후 생성 */
int csock = accept(ssock, (struct sockaddr *)&cliaddr, &clen);
printf("Client is connected : %s\n", inet_ntoa(cliaddr.sin_addr));
if((n = read(csock, buf, BUF_SIZE)) <= 0) {
perror("read");
}
printf("Received data : %s\n", buf);
// 서버에서 write/read를 할 때의 fd는 클라이언트 fd
if(write(csock, buf, n) <= 0) {
perror("write");
}
close(csock);
}
close(ssock);
return 0;
}
view raw tcp_server.c hosted with ❤ by GitHub

 

클라이언트는 호스트와의 연결을 종료하기 위해 close 함수를 사용해 소켓을 닫았다. 출력 스트림을 종료하면 소켓을 통해 EOF 메세지가 전달되는데, EOF 전송으로 데이터 전송의 끝을 알릴 수 있다. EOF 전송 시 상대 호스트의 수신 함수는 0을 반환하게 된다

 

[출처] 사물인터넷을 위한 리눅스 프로그래밍