Computer Science/Network

[Network] Socket Programming

HJChung 2022. 1. 29. 11:36
한양대학교 - 컴퓨터네트워크 수업을 듣고 정리한 내용입니다. 

 

1. Socket이란

client process와 server process간의 통신이 application program이라면 개발자나 사용자 입장에선 OS내부를 건들이는건 아니다. 그냥 OS에서 제공하는 API를 사용할 뿐!

이처럼 process와 process간 통신에서 사용하는 API를 socket이라고 한다. 그리고 이 API는 OS에서 제공해주는 것이기 때문에 제공해주는 것들 '만' 사용할 수 있다. 

 

2. Socket Type (protocol)

다른 관점에서 생각해보면, 

OS에는 application layer 아래의 layer들, 즉 transport layer부터 쭉 구현되어 있다. application layer 에서는 이 transport layer에서 제공해주는 걸 사용할 수밖에!

transport layer에 구현되어 있는 protocol은 TCP와 UDP이다. 그렇기 때문에 application layer에선 socket을 쓸 때 TCP socket(aka. SOCK_STREAM)이나 UDP socket(aka. SOCK_DGRAM)을 쓰게 된다. 

Socket is an interface between application and network.
- The application creates a socket.
- The socket type dictates the style of communication (protocol). 
    - reliable vs best effort
    - connection-oriented vs connectionless

 

 

3. Associated functions: Sockets API

 TCP case work flow는 크게 4단계로 이루어진다. 

  1. Creation and Setup
  2. Establishing a Connection (TCP)
  3. Sending and Receiving Data
  4. Tearing Down a Connnection (TCP)

이를 function단위로 조금 더 자세히 살펴보면,

1. 먼저 웹 서버가 TCP socket을 생성함

2. 그리고 생성한 이 socket을 특정 port에 bind시킴

3. 이 port로 listen

4. 나는 client로부터 요청받을 준비가 되어있으니 드루와! 

5. 그러면 서버는 client로부터 connection이 들어올 때까지 가만히 있는다. (block)

 

6. client는 요청을 위한 TCP socket을 생성함

7. 그리고 내가 원하는 server 프로세스로 connection을 함

8. three-way handshaking까지 잘 되면 client-server 사이 연결 완료!

9. 그 뒤로는 둘 사이에 요청 응답 주고받고가 가능해짐. 

10. data를 read , write 하고 난 뒤

11. 다 끝났다면 close로 socket을 해제시켜준다.

 

 

 

여기서 나온 function들을 정리해보자. 

1) socket

/// <summary>Create a socket.</summary>
/// <param name="domain">protocol family.
///		PF_INET for IPv4, PF_INET6 for IPv6, 
///		PF_UNIX or PF_LOCAL for Unix socket, PF_ROUTE for routing</param>
/// <param name="type">style of communication.
///		SOCK_STREAM for TCP(with PF_INET), SOCK_DGRAM for UDP(with PF_INET)</param>
/// <param name="protocol">protocol within family.
///		typically set to 0.
////	getprotobyname() to get list of protocols in /etc/protocols. </param>
/// <returns>ID of created socket.(file descriptor) or -1.</returns>

int socket (int domain, int type, int protocol);

2) bind

/// <summary>Bind a socket to a local IP address and port number.</summary>
/// <param name="sockfd">socket file descriptor returned from socket().</param>
/// <param name="myaddr">includes IP address and port number.
///		IP address sets by kernel if value passed is INADDR_ANY. else sets by caller.
///		port number sets by kernel if value passed is 0. else set by caller addrlen:length of address structure.
/// </param>
/// <param name="addrlen">length of address structure </param>

int bind (int sockfd, struct sockaddr* myaddr, int addrlen);

3) listen

/// <summary>Put socket into passive state wait for connections rather than initiate a connection.</summary>
/// <param name="sockfd"> socket file descriptor returned from socket()</param>
/// <param name="backlog"> bound on length of unaccepted connnection. queue connection backlog.
///		kernel will cap, thus better to set high</param>
/// <returns>Returns 0 on success, -1 and sets errno on failure.
///		listen() is non-blocking. so it returns immediately. </returns>

int listen (int sockfd, int backlog);

4) accept

/// <summary>Accept new connection.</summary>
/// <param name="sockfd">socket file descriptior returned from socket().</param>
/// <param name="cliaddr">IP address and port number of client returned from call().
///		그래서 server도 이를 통해 client의 IP번호를 알게된다. </param>
/// <param name="addrlen">length of address structure = pointer to int set ot sizeof.(struct sockaddr_in)</param>
/// <returns>Returns file descriptor or -1 and sets errno on failure.
///		accept() is blocking so waits for connection before returninig.</returns>

int accept (int sockfd, struct sockaddr* cliaddr, int* addrlen);

5) connect

/// <summary>Connect to another socket.</summary>
/// <param name="sockfd">socket file descriptor returned from socket().</param>
/// <param name="servaddr">IP address and port number of server.</param>
/// <param name="addrlen">length of address structure.</param>
/// <returns>Returns file descriptor or -1 and sets errno on failure.
///		connect() is blocking.</returns>

int connect (int sockfd, struct sockaddr* servaddr, int addrlen);

6) write

/// <summary>Write data to a stream(TCP).</summary>
/// <param name="sockfd">socket file descriptor returned from socket().</param>
/// <param name="buf">data buffer.</param>
/// <param name="nbytes">number of bytes to try to write</param>
/// <returns>Returns number of bytes written or -1 and sets errno on failure.
///		write() is blocking so returns only after data is sent.</returns>

int write (int sockfd, char* buf, size_t nbytes);

7) read

/// <summary>Read data from a stream.</summary>
/// <param name="sockfd">socket file descriptor returned from socket().</param>
/// <param name="buf">data buffer.</param>
/// <param name="nbytes">nubmer of bytes to try to read</param>
/// <returns>Returns number of bytes read or -1 and sets errno on failure.
///		read() is blocking so returns only after data is received.</returns>

int read (int sockfd, char* buf, size_t nbytes);

8) close

/// <summary>When finished using a socket, the socket should be closed.</summary>
/// <param name="sockfd">socket file descriptor returned from socket().</param>
/// <returns>Returns o if successful, -1 if error</returns>

int close (int sockfd);

 

 

4. sample code

1) TCP server

#include <stdio.h>
///.... 여러 header (생략)

#define PORT 3490 //3490 port 번호 사용
#define BACKLOG 10 //how many pending connectons queue will hold 값으로 10

main() {
    int sockfd, new_fd; /*listen on sock_fd, new connection on new_fd */
    /**
    /* struct sockaddr_in {
    /* 		short sin_family;
    /*		u_short sin_port;
    /* 		struct in_addr sin_addr;
    /* 	}
    **/
    struct sockaddr_in my_addr; /* my address */
    struct sockaddr_in their_addr; /* connector addr */
    int sin_size;
    
    /* 1. socket() 사용해서 웹 서버가 TCP socket을 생성함. SOCK_STREAM인걸 보니 TCP socket */
    if((sockfd = socket(PF_INET, SOCK_STREAM, 0)) == -1){
    	perror("socket");
        exit(1);
    }
    
    my_addr.sin_family = AF_INET;
    my_addr.sin_port = htons(MYPORT);
    my_addr.sin_addr.s_addr = htonl(INADDR_ANY); /* INADDR_ANY allows client to connect to any one of the host's IP address */
    
    /* 2. bind() 사용해서 생성한 이 socket을 특정 port에 bind시킴 */
    if (bind(sockfd, (struct sockaddr *)&my_addr, sizeof(struct sockeaddr)) == -1) { 
    	perror("bind");
        exit(1);
    }
    
    /* 3. 이 port로 listen */
    if (listen(sockfd, BACKLOG) == -1) {
    	perror("listen");
        exit(1);
    }
    
    /* 4. 나는 client로부터 요청받을 준비가 되어있으니 드루와! */
    while(1) { /* main accept() loof */
    	sin_size = sizeof(struct sockaddr_in);
        if ((now_fd = accept(sockfd, (struct sockaddr*)&their_Addr, &sin_size)) == -1) {
        	peeror("accept);
            continue;
        }
        printf("server: got connection from %s\n", inet_ntoa(their_addr.sn_addr));
    }

2) TCP Client

/* 6. client는 요청을 위한 TCP socket을 생성함 */
if ((sockfd = socket (PF_INET, SOCK_STREAM, 0)) == 01) {
	perror("socket");
    exit(1);
}

their_addr.sin_family = AF_INET;
their_addr.sin_port = htons(Server_Portnumber);
their_addr.sin_addr = htonl(Server_IP_address);

/* 7. 그리고 내가 원하는 server 프로세스로 connection을 함 */
if (connect (sockfd, (struct sockaddr*)&their_addr, sizeof(struct sockaddr)) == -1) {
	perror("connect");
    exit(1);
}