[C] Socket Programming

박세윤·2022년 6월 4일
0
post-thumbnail

이 글은 학교 네트워크 프로그래밍 과목의 기말 시험을 공부하기 위한 글



📌 Socket Programming

✍ User Datagram Protocol (= UDP)

  • Client와 Server는 스트리밍 소켓을 기반으로 하며, TCP를 사용했다.

  • TCP를 쓰는 대안으로 UDP도 있다. UDP는 datagram socket으로 알려져있다.

  • UDP는 패킷 전송을 보장하지 않는다.

  • UDP datagrams는 손실되어 순서가 틀릴 수도 있다.

  • UDP datagrams는 여러번 복사할 수 있으며, 수신 노드가 처리하는 속도보다 더 빠르게 전송할 수 있다.
    : 받는 쪽에서 처리를 하든 못하든 그냥 막 전송한다. 전송해놓고 안본다. 받든말든

  • UDP 활용 예시 : DNS(Domain Name Service), NTP(Network Time Protocol), TFTP(Trivial File Transfer Protocol)


  • 예를 들어, 간단한 DNS 조회 요청은 기본적으로 요청의 이름 또는 번호와 응답의 DNS 서버로부터의 해당 답변(오류일 수도 있음)의 두 가지 정보만으로 구성됩니다.

  • 이렇게 간단한 통신을 위해 스트리밍 소켓을 열고 연결을 유지하는 데 드는 비용을 부담할 필요가 없습니다.

  • NTP 요청도 유사합니다. 질문("몇 시입니까?")과 서버의 답변으로 구성됩니다.

  • 지속적인 커뮤니케이션이 필요하지 않습니다.



✍ Exercise - port check

// bongbong@ssl:~$ nc -z -v -u kr.poolntp.org 123
// Connection to kr.pool.ntp.org 123 port [udp/ntp] succeeded!
// bongbong@ssl:~$ nc -z -v 223.194.7.95 21
// Connection to 223.194.7.95 21 port [tcp/ftp] succeeded!
// bongbong@ssl:~$ nc -z -v ssl.kw.ac.kr 80
// Connection to ssl.kw.ac.kr 80 port [tcp/http] succeeded!
// bongbong@ssl:~$ nc -z -v -u kns.kornet.net 53
// Connection to kns.kornet.net 53 port [udp/domain] succeeded!



✍ Exercise - port scan

// bongbong@ssl:~$ nc -z -v 223.194.7.95 1-255

// nc: connect to 223.194.7.95 port 1 (tcp) failed: Connection refused
// nc: connect to 223.194.7.95 port 2 (tcp) failed: Connection refused
.
.
.
// nc: connect to 223.194.7.95 7 port [tcp/echo] succeeded!
// nc: connect to 223.194.7.95 port 8 (tcp) failed: Connection refused
.
.
.



✍ Exercise - daytime port 13

// bongbong@ssl:~$ nc -v -u 223.194.7.95 13
// Connection to 223.194.7.95 13 port [udp/daytime] succeeded!
// 17 MAY 2022 14:11:32 KST
// 17 MAY 2022 14:11:33 KST
// 17 MAY 2022 14:11:34 KST
// 17 MAY 2022 14:11:35 KST

// bongbong@ssl:~$ nc -v 223.194.7.95 13
// Connection to 223.194.7.95 13 port [tcp/daytime] succeeded!
// 17 MAY 2022 14:12:27 KST



✍ Exercise - echo port 7

// bongbong@ssl:~/Lecture/netprog$ cat | nc -v 223.194.7.95 7
// Connection to 223.194.7.95 7 port [tcp/echo] succeeded!
// Hi
// Hi

// bongbong@ssl:~/Lecture/netprog@ cat | nc -v -u 223.194.7.95 7
// Connection to 223.194.7.95 7 port [udp/echo] succeeded!
// XXXX1234
// 1234
// 5678
// 5678



✍ UDP Server

  • 서버 없이는 클라이언트 코드를 못만든다. 그래서 간단한 UDP 서버 코드부터 만들어보자

  • 아래 헤더 파일들이 필요하다.
    -- #include <sys/types.h>
    -- #include <sys/socket.h>
    -- #include <netdb.h>
    -- #include <string.h>
    -- #include <stdio.h>

  • 그 다음, 우리는 1KB 사이즈의 상수를 정의할 것이다. 우리가 사용할 메세지 버퍼의 사이즈를 정의하기 위해서 상수를 사용한다.
    -- #define MAXBUF 1024


  • 우리는 MAXBUF 사이즈의 character 타입 buffer을 선언해야 한다.
    -- 그 버퍼로 소켓으로부터 받거나 소켓에게 전달하기 위해서다.
int main(int argc, char* argv[]) {
	int updSocket;
    int returnStatus = 0;
    int addrlen = 0;
    struct sockaddr_in udpServer, udpClient;
    char buf[MAXBUF];
}

  • 이 프로세스는 SOCK_STREAM 대신 SOCK_DGRAM을 사용한다.

  • UDP 에서는 SOCK_DGRAM, TCP에서는 SOCK_STREAM 사용

// Create a Socket
udpSocket = socket(AF_INET, SOCK_DGRAM, 0);

if(udpSocket == -1) {
	fprintf(stderr, "Could not create a socket!\n");
    exit(1);
}
else 
	printf("Socket created.\n");

  • INADDR_ANY를 사용한다. - 구조체 세팅에 사용된다.
    -- 소켓은 구성된 로컬 주소에 바인딩되며 인수로 전달된 포트 번호를 사용합니다.

  • port를 다룰때 htons()를 호출하는것을 꼭 명심하자!
    : 포트번호 세팅

// setup the server address and port
// use INADDR_ANY to bind to all local addresses
udpServer.sin_family = AF_INET;
udpServer.sin_addr.s_addr = htonl(INADDR_ANY); 
// 중요 ip주소 다룰때는 꼭 htonl

// use the port pased as argument
udpServer.sin_port = htons(atoi(argv[1])); 
// argv는 프로그램에서 파라미터로 받는 것인데, 문자열이므로 atoi를 통해서 integer 타입으로 변환한 다음 htons를 적용하여 포트번호를 세팅한다.

  • bind()함수 사용
// bind to the socket
returnStatus = bind(udpSocket, (struct sockaddr*)&udpServer, sizeof(udpServer));

if(returnStatus == 0)
	fprintf(stderr, "Bind Completed!\n");
else {
	fprintf(stderr, "Could not bind to address!\n");
    close(udpSocket);
    exit(1);
}

  • listen(), accept(), read() 함수들 대신에, recvfrom() 함수를 사용하는 것을 명심하자!
    -- udp는 recvfrom, tcp는 listen, accept, read

  • recvfrom()은 accept()와 마찬가지로 blocking function이다.

  • 이 함수는 통신이 수신될 때 까지 소켓에서 기다린다.

while(1) {
	addrlen = sizeof(udpClient);
    returnStatus = recvfrom(udpSocket, buf, MAXBUF, 0, (struct sockaddr*)&udpClient, &addrlen);
    
    if(returnStatus == -1) 
    	fprintf(stderr, "Could not receive message!\n");
    else {
    	printf("Received: %s\n", buf);
        
        // a message was received so send a confirmation
        strcpy(buf, "OK");
        returnStatus = sendto(udpsocket, buf, strlen(buf)+1, 0, (struct sockaddr*)&udpClient, sizeof(udpClient));
        if(returnStatus == -1)
        	fprintf(stderr, "Could not send confirmation!\n");
        else
        	printf("Confirmation sent.\n");
    }
}



✍ UDP Client

  • datagram socket을 생성하고, Client의 sockaddr_in 구조체에 우리가 가진 정보를 세팅한다.

  • 우리는 Client를 위해 특정 포트 번호를 선언할 필요가 없다.

  • OS가 랜덤으로 하나를 할당할 것이다.

  • 서버가 수신하는 UDP 패킷은 원본 IP 주소와 함께 헤더 필드를 포함하므로, 서버는 응답을 보낼 때 사용할 많은 정보를 가집니다.

  • 일단 정보가 준비되면, socket에 bind 하고, 전송을 준비한다.

// Create a socket
updSocket = socket(AF_INET, SOCK_DGRAM, 0);
if(udpSocket == -1) {
	fprintf(stderr, "Could not create a socket!\n");
}
else
	printf("Socket Created!\n");
    
// Client address
// use INADDR_ANY to use all local addresses
udpClient.sin_family = AF_INET;
udpClient.sin_addr.s_addr = INADDR_ANY;
udpClient.sin_port = 0;

returnStatus = bind(udpSocket, (struct sockaddr*)&udpClient, sizeof(udpClient));
if(returnStatus == 0)
	fprintf(stderr, "Bind Completed!\n");
else {
	fprintf(stderr, "Could not bind to address!\n");
    close(udpSocket);
    exit(1);
}

  • 전송하기 전에 메세지를 세팅해야 한다.

  • 또한 연결할 서버를 나타내는 데 사용할 sockaddr_in 구조체를 입력해야 합니다.

// set up the message to be sent to the server
strcpy(buf, "For Professionals, By Professionals\n");

// server address
// use the command-line arguments
udpServer.sin_family = AF_INET;
udpServer.sin_addr.s_addr = inet_addr(argv[1]);
udpServer.sin_port = htons(atoi(argv[2]));

  • 모든 세팅이 완료되면, sendto() 함수를 활용하여 서버에 요청을 전송한다.
returnStatus = sendto(udpSocket, buf, strlen(buf)+1, 0, (struct sockaddr*)&udpServer, sizeof(udpServer));
if(returnStatus == -1)
	fprintf(stderr, "Could not send message!\n");
else
	printf("Message sent.\n");

  • "send to()"에 대한 호출에서 반환된 값이 요청이 전송되었음을 나타내는 경우, 클라이언트는 "recvfrom()" 함수를 사용하여 서버의 응답을 수신할 준비를 합니다.
// message sent: look for confirmation

addrlen = sizeof(udpServer);
returnStatus = recvfrom(udpSocket, buf, MAXBUF, 0, (struct sockaddr*)&udpServer, &addrlen);
if(returnStatus == -1)
	fprintf("stderr, "Did not receive confirmation!\n");
else {
	buf[returnStatus] = 0;
    printf("Received: %s\n", buf);
}



✍ UDP Server 코드 전체

// UDP Server

#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
#include <string.h>
#include <stdio.h>

#define MAXBUF 1024

int main(int argc, char* argv[]) {
	int udpSocket;
    int returnStatus = 0;
    int addrlen = 0;
    struct sockaddr_in udpServer, udpClient;
    char buf[MAXBUF];
    
    // check for the right number of arguments
    if(argc < 2) {
    	fprintf(stderr, "Usage: %s <port>\n", argv[0]);
        exit(1);
    }
    
    // create a socket
    udpSocket = socket(AF_INET, SOCK_DGRAM, 0);
    
    if(udpSocket == -1) {
    	fprintf(stderr, "Could not create a socket!\n");
        exit(1);
    }
    else
    	printf("Socket created!\n");
        
    // setup the server address and port
    // use INADDR_ANY to bind to all local addresses
    udpServer.sin_family = AF_INET;
    udpServer.sin_addr.s_addr = htonl(INADDR_ANY);
    
    // use the port pased as argument
    udpServer.sin_port = htons(atoi(argv[1]));
    
    // bind to the socket
    returnStatus = bind(udpSocket, (struct sockaddr *)&udpServer, sizeof(udpServer));
    
    if(returnStatus == 0)
    	fprintf(stderr, "Bind completed!\n");
    else {
    	fprintf(stderr, "Could not bind to address!\n");
        close(udpSocket);
        exit(1);
    }
    
    while(1) {
    	addrlen = sizeof(udpClient);
        
        returnStatus = recvfrom(udpSocket, buf, MAXBUF, 0, (struct sockaddr*)&udpClient, &addrlen);
        
        if(returnStatus == -1)
        	fprintf(stderr, "Could not receive message!\n");
        else {
        	printf("Received: %s\n", buf);
            
            // a message was received so send a confirmation
            strcpy(buf, "OK");
            
            returnStatus = sendto(udpSocket, buf, strlen(buf)+1, 0, (struct sockaddr*)&udpClient, sizeof(udpClient));
            
            if(returnStatus == -1)
            	fprintf(stderr, "Could not send confirmation!\n");
            else
            	printf("Confirmation sent.\n");
        }
    }

// cleanup
close(udpSocket);
return 0;

}



✍ UDP Client 코드 전체

// UDP Client

#include <sys.types.h>
#include <sys.socket.h>
#include <netdb.h>
#include <string.h>
#include <stdio.h>

#define MAXBUF 1024

int main(int argc, char* argv[]) {
	int udpSocket;
    int returnStatus;
    int addrlen;
    struct sockaddr_in udpClient, udpServer;
    char buf[MAXBUF];
    
    if(argc < 3) {
    	fprintf(stderr, "Usage: %s <ip address> <port>\n", argv[0]);
        exit(1);
    }
    
    // create a socket
    udpSocket = socket(AF_INET, SOCK_DGRAM, 0);
    
    if(udpSocket == -1) {
    	fprintf(stderr, "Could not create a socket!\n");
        exit(1);
    }
    else 
    	printf("Socket created!\n");
        
    // client address
    // use INADDR_ANY to use all local addresses
    udpClient.sin_family = AF_INET;
    udpClient.sin_addr.s_addr = INADDR_ANY;
    udpclient.sin_port = 0;
    
    returnStatus = bind(udpSocket, (struct sockaddr*)&udpClient, sizeof(udpClient));
    
    if(returnStatus == 0) 
    	fprintf(stderr, "Bind Completed!\n");
    else {
    	fprintf(stderr, "Could not bind to address!\n");
     	close(udpSocket);
        exit(1);
    }
    
    // setup the message to be sent to the server
    strcpy(buf, "For Professionals, By Professionals.\n");
    
    // server address
    // use the command line arguments
    udpServer.sin_family = AF_INET;
    udpServer.sin_addr.s_addr = inet_addr(argv[1]);
    udpServer.sin_port = htons(atoi(argv[2]));
    
    returnStatus = sendto(udpSocket, buf, strlen(buf)+1, 0, (struct sockaddr*)&udpServer, sizeof(udpServer));
    
    if(returnStatus == -1)
    	fprintf(stderr, "Could not send message!\n");
    else {
    	printf("Message sent.\n");
        
        // message sent: look for confirmaiton
        addrlen = sizeof(udpServer);
        
        returnStatus = recvfrom(udpSocket, buf, MAXBUF, 0, (struct sockaddr*)&udpServer, &addrlen);
        
        if(returnStatus == -1)
        	fprintf(stderr, "Did not receive confirmation\n");
        else {
        	buf[returnStatus] = 0;
        	printf("Received: %s\n", buf);
        }
    }
    
    // cleanup
    close(udpSocket);
    return 0;
}



✍ File Transfer

  • 파일 전송하는데는, 우리는 TCP를 소켓을 사용할 것이다.

  • server는 특정 포트를 bind 할 것이고, 연결을 위해 listen 할 것이다.

  • 연결이 되면, client는 server에 파일 이름을 전송할 것이고, 그러면 server는 파일 이름을 읽을 것이고, 디스크로부터 파일을 검색할 것이고, client에게 파일을 다시 되돌려 줄 것이다.


  • 한가지 주요한 차이점은 소켓을 두 가지 사용한다는 점이다.

  • 소켓을 두 가지 사용하면, server는 다른 client로부터 더 많은 연결을 accept 하면서 client로부터의 요청을 다룰 수 있다.

  • 연결은 선착순으로 queue에 배치될 것이다.

  • 소켓 한 가지는 read/write 담당, 나머지 하나는 request 담당이다.



✍ File Transfer Server 전체 코드

// File transfer server xferServer.c

#include <fcntl.h>
#include <sys.types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <netdb.h>

#define SERVERPORT 8888
#define MAXBUF 1024

int main() {
	int socket1, socket2;
    int addrlen;
    struct sockaddr_in xferServer, xferClient;
    int returnStatus;
    
    // Create a socket
    socket1 = socket(AF_INET, SOCK_STREAM, 0);
    
    if(socket1 == -1) {
    	fprintf(stderr, "Could not create socket!7n");
        exit(1);
    }
    
    // bind to a socket, use INADDR_ANY for all local addresses
    xferServer.sin_family = AF_INET;
    xferServer.sin_addr.s_addr = INADDR_ANY
    xferServer.sin_port = htons(SERVERPORT);
    
    returnStatus = bind(socket1, (struct sockaddr*)&xferServer, sizeof(xferServer));
    
    if(returnStatus == -1) {
    	fprintf(stderr, "Could not listen on socket!\n");
        exit(1);
    }
    
    for(;;) {
    	int fd;
        int i, readCounter, writeCounter;
        char* bufptr;
        char buf[MAXBUF];
        char filename[MAXBUF];
        
        // wait for an incoming connection
        addrlen = sizeof(xferClient);
        
        // use accept() to handle incoming connection requests
        // and free up the original s ocket for other requests
        socket2 = accept(socket1, (struct sockaddr*)&xferClient, &addrlen);
        
        if(socket2 == -1) {
        	fprintf(stderr, "Could not accept connection!\n");
            exit(1);
        }
        
        // get the filename from the client over the socket
        i=0;
        if((readCounter = read(socket2, filename+1, MAXBUF) > 0)) 
    		i += readCounter;
            
        if(readCounter == -1) {
        	fprintf(stderr, "Could not read filename from socket!\n");
            close(socket2);
            continue;
        }
        
        filename[i+1] = '\0';
        
        printf("Reading file %s\n", filename);
        
        // open the file for reading
        fd = open(filename, O_RDONLY);
        
        if(fd == -1) {
        	fprintf(stderr, "Could not open file for reading!\n");
            close(socket2);
            continue;
        }
        
        // reset the read counter
        readCounter = 0;
        
        // read the file, and send it to the client in chunks of size MAXBUF
        while((readCounter = read(fd, buf, MAXBUF)) > 0) {
        	writeCounter = 0;
            bufptr = buf;
            
            while(writeCounter < readCounter) {
            	readCounter -= writeCounter;
                bufptr += write(socket2, bufptr, readCounter);
                
                if(writeCounter == -1) {
                	fprintf(stderr, "Could not write file to client!\n");
                    continue;
                }	
            }
        }	
    	
        close(socket2);
        close(fd);
    }
    
    close(socket1);
    return 0;
}



✍ File Transfer Client 코드 전체

// File transfer client xferClient.c

#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>
#include <stdio.h>
#include <sys/stat.h>
#include <fcntl.h>

#define SERVERPORT 8888
#define MAXBUF 1024

int main(int argc, char* argv[]) {
	int sockd;
    int counter;
    int fd;
    struct sockaddr_in xferServer;
    char buf[MAXBUF];
    int returnStatus;
    
    if (argc < 3) {
    	fprintf(stderr, "Usage: %s <ip address> <filename> [dest filename]\n", argv[0]);
        exit(1);
    }
    
    // create a socket
    sockd = socket(AF_INET, SOCK_STREAM, 0);
    
    if(sockd == -1) {
    	fprintf(stderr, "Could not create socket!\n");
        exit(1);
    }
    
    // set up the server information
    xferServer.sin_family = AF_INET;
    xferServer.sin_addr.s_addr = inet_addr.(argv[1]);
    xferServer.sin_port = htons(SERVERPORT);
    
    // connect to the server
    returnStatus = connect(sockd, (struct sockaddr*)&xferServer, sizeof(xferServer));
    
    if(returnStatus == -1) {
    	fprintf(stderr, "Could not connect to server!\n");
        exut(1);
    }
    
    // send the name of the file we want to the server
    returnStatus = write(sockd, argv[2], strlen(argv[2])+1);
    
    if(returnStatus == -1) {
    	fprintf(stderr, "Could not send filename to server!\n");
        exit(1);
    }
    
    // call the shutdown to set our socket to read only
    shutdown(sockd, SHUT_WR);
    
    // open up a handle to our destination file to receive the contents
    // from the server
    fd = open(argv[3], O_WRONLY | O_CREATE | O_APPEND);
    
    if(fd == -1) {
    	fprintf(stderr, "Could not open destination file, using stdout.\n");
        fd = 1;
    }
    
    // read the file from the socket as long as there is data
    while((counter =read(sockd, buf, MAXBUF)) > 0) {
    	// send the contents to stdout
        write(fd, buf, counter);
    }
        
    if(counter == -1) {
      	fprintf(stderr, "Could not read file from socket!\n");
       	exit(1);
 	}
    
    close(sockd);
    return 0;
}



✍ Example File Transfer Session

  • server start
// [user@host projects]$ cc -o xferServer xferServer.c
// [user@host projects]$ ./xferServer
  • compile and launch the client
// [user@host projects]@ cc -o xferClient xferClient.c
// [user@host projects]@ ./xferClient 127.0.0.1 filename new-filename



✍ Error Handling

  • 일반적으로 리턴값이 음수이면 오류이다.

  • 그러나 가끔 안그런 경우도 있긴하다

  • 그런 에러 코드들은 errno.h 파일에 정의되어있다.

  • /usr/include/asm/errno.h

profile
개발 공부!

0개의 댓글