프로세스 관점에서, 네트워크를 이용하기 위해서는 연결하는 일종의 창구가, 즉 인터페이스가 있어야 한다. 그리고 그 창구를 통해서 데이터를 read, write 해서 통신한다.
그러한 창구를 socket이라고 부른다.
여러 프로세스들이 통신하려고 하는 상황이 매우 일반적이기 때문에, 창구, 즉 소켓을 구분할 필요가 있다.
process는 PID, file은 inode # 처럼, socket 또한 구분해주는 ID가 있다.
socket을 구분하는 ID는 address(주소)를 통해 정의된다. 즉, address는 데이터를 전달하기 위해 필요한 정보라고 할 수 있다. 그리고 그 주소는 용도에 따라 여러 종류로 나뉠 수 있다.
IP 주소는 한 네트워크 상에서 하나의 디바이스(PC, 휴대폰, 서버 등)을 구별하기 위한 주소이다. (a.k.a. network address)
ex) PC A, B, C가 처음 학교 네트워크에 연결할 때, 각각의 NIC는 IP주소를 할당 받는다. 여기서 할당받는 단위는 PC가 아니라, NIC이기 때문에, 하나의 PC에 여러 개의 IP가 존재할 수 있다.
* 리눅스 체제에서는 ifconfig로 본인 IP주소를 알 수 있다.
IPv4 = 32bit, IPv6 = 128bit
IP주소는 Globally Unique할 수도 있고, Locally Unique(같은 네트워크 안에서 unique = private address)할 수 있다.
IP주소가 하나의 디바이스를 구별하기 위해서 사용된다면, 포트주소는 하나의 디바이스 안에서 다양한 프로세스가 요청하는 연결 하나하나를 구별하기 위한 주소이다.
ex) 내 PC에서 유튜브를 보면서, 네이버로 검색을 하고, 카카오톡을 동시에 사용하고 있는 상황. 우선 IP주소를 통해 해당 패킷이 내 것인지를 확인한 후, 맞다면 포트주소를 통해 각각의 알맞은 프로세스에 전달한다.
포트주소는 16bit
하나의 포트주소(프로세스)에도 여러 connection이 있을 수 있음. ex) 같은 포트주소이지만, 사용되는 protocol이 다른 경우
Socket's ID
소켓을 구분하는 ID는 다음과 같이 구성된다.
source(local) IP & port 주소 + destination IP & port 주소 + protocol(L4 protocol)
프로세스에서 소켓 생성. 만들어진 소켓은 프로세스에 file descriptor로 보여진다. 따라서, 소켓을 파일처럼 read, write할 수 있다.(read, write는 fd값을 파라미터로 받아 처리)
ex) server - client 간의 TCP/IP 연결
server : 소켓을 열어놓고 커넥션 요청을 기다링
client : 커넥션 요청
<syscall 다이어그램>

//1. 소켓 생성
server_fd = socket(AF_INET, SOCK_STREAM, 0); // TCP/IP 사용
//2. 주소 변환 및 할당
/*
inet_pton = internet_ Presentation TO Network -> 사람이 읽는 주소인 presentation format을 바이너리 형태인 network format로 변환
AF = Address Family
sin = Socket address INternet(IPv4)
htons = Host TO Network Short
*/
//주소 변환
address.sin_family = AF_INET;
inet_pton(AF_INET, "1.2.3.4", &address.sin_addr);
address.sin_port = htons(PORT);
//할당
bind(server_fd, (struct sockaddr*)&address, sizeof(address));
//3. 연결 요청 대기 **non-block, request are queued by kernel**
listen(server_fd, 5); //단순 상태 설정, 최대 5개 대기 가능
//4. 요청 수락 (accept는 block syscall. 들어올 때까지 기다림)
new_socket = accept(server_fd, (struct sockaddr*)&address, (socklen_t*)&addr_len);
//5. 데이터 송수신
read(new_socket, buffer, sizeof(buffer)); //buffer에 new_socket을 저장
printf("Client sent : %s\n", buffer);
write(new_socket, "쓰기 작업", strlen("쓰기 작업"));
//6. 닫기
close(new_socket);
close(server_fd);
//1. 소켓 생성
sock = socket(AF_INET, SOCK_STREAM, 0);
serv_addr.sin_family = AF_INET;
serv_addr.sin_port = htons(PORT_NUM);
//2. 주소 변환
inet_pton(AF_INET, SERVER_IP, &serv_addr.sin_addr);
//3. 연결
connect(sock, (struct sockaddr*)&serv_addr, sizeof(serv_addr); //serv_addr = 서버 IP, port 주소. bind는 일반적으로 하지 않음.
//4. 데이터 송수신
read();
write();
//5.닫기
close(sock);
accept() syscall--> 해결방법 : process 또는 thread fork()로 병렬처리 (멀티프로세싱, 멀티스레딩)
ex)
while (1) {
new_socket = accept(server_fd, (struct sockaddr*)&address, (socklen_t*)&addr_len);
//create new process
pid_t pid = fork();
if (pid == 0) { //child process
close(server_fd); //child process는 서버 소켓 안씀 **server_fd는 accept용
read(); or write();
close(new_socket); //연결 종료
exit(0);
}
else if (pid > 0) { //parent process
close(new_socket); //child에서 처리하므로 close -> 계속 accept 가능
}
}
2. read(), write() syscall
의도한 대로 read(), write() 가 이루어지지 않을 수 있음. E.g. 데이터 손실, 정해진 데이터 읽기 쓰기 순서가 안지켜진 경우
해결방법 1 : non-blocking IO
* IO operations in Linux
read(), write() syscall. 이게 디폴트값.O_NONBLOCK 활성화한 read(), write(). 데이터가 준비될 때까지 polling(계속 체크). 준비 안됐을 시 ERROR를 return, 준비되면 data return.cf) int aio_read(struct aiocb *aiocbp)
aiocb(aio control block) = fd, buffer location, transfer length 등등이 저장 돼있는 구조체.
aio operation이 수행되는 동안에는 다른 operation가능. aio가 성공적으로 read, write를 마치면 그때 signal로 알려줌.
해결방법 2 : IO multiplexing
실행과정:
epoll_create(int size) : 프로세스가 epoll 구조를 만든다. 하나만 만들면 됨. fd값 반환(epfd).epoll_ctl(int epfd, int op, int fd, struct spoll_event *event) : 관리할 소켓을 소켓의 fd값을 이용해 등록한다. epfd: 관리할 주체인 epoll. fd: epoll이 모니터링할 소켓. op: 이벤트 발생 시 epoll이 어떤 동작을 할 지 결정. event: 관리할 소켓의 이벤트.epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout) : blocking function. 이벤트 들어오거나, timeout으로 설정한 시간까지 block됨. events: 모니터링이 일어나는 소켓의 이벤트. maxevents:관찰할 이벤트 수. -> 위와 같은 방식으로 하면, 여러 소켓을 한번에 관리할 수 있음.