목차
1. 기능 목록
1. 소켓 방식
#include <sys/types.h> /* See NOTES */ #include <sys/socket.h> int socket(int domain, int type, int protocol);
기능
- 통신용 소켓을 만들고 해당 소켓을 가리키는 파일 설명자를 반환합니다.
매개변수
- 도메인: 소켓의 프로토콜 제품군을 지정합니다. 일반적인 값은 AF_INET(IPv4) 및 AF_INET6(IPv6)입니다.
- 유형: 소켓 유형을 지정합니다. 일반적인 값은 SOCK_STREAM(연결 지향 신뢰할 수 있는 바이트 스트림) 및 SOCK_DGRAM(연결 없는 데이터그램)입니다.
- 프로토콜: 프로토콜을 지정합니다. 일반적으로 0이 사용되며 이는 기본 선택을 의미합니다.
반환 값
- 성공하면 새 소켓에 대한 파일 설명자를 반환합니다. 오류가 발생하면 -1이 반환되고 errno가 설정됩니다.
2. 바인드 방법
#include <sys/types.h> /* See NOTES */ #include <sys/socket.h> int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
기능
- 소켓을 특정 IP 주소 및 포트에 바인딩합니다.
매개변수
- sockfd: socket이 반환한 소켓 설명자.
- addr: 바인드할 로컬 주소를 가리키는 구조(보통 sockaddr_in 또는 sockaddr_in6 구조).
- addrlen: 로컬 주소의 길이(일반적으로 sizeof(struct sockaddr_in) 또는 sizeof(struct sockaddr_in6)).
반환 값
- 성공하면 0을 반환합니다. 오류가 발생하면 -1이 반환되고 errno가 설정됩니다.
3. 듣기 방법
#include <sys/types.h> /* See NOTES */ #include <sys/socket.h> int listen(int sockfd, int backlog);
기능
- 지정된 소켓에서 연결 요청 수신 대기를 시작합니다.
매개변수
- sockfd: socket이 반환한 소켓 설명자.
- 백로그: 연결 대기 큐의 최대 길이입니다. 연결 요청이 도착했을 때 대기열이 가득 찬 경우 클라이언트는 ECONNREFUSED로 표시된 오류를 수신할 수 있으며 기본 프로토콜이 재전송을 지원하는 경우 연결이 성공하면 나중에 연결을 다시 시도할 수 있도록 요청을 무시할 수 있습니다.
반환 값
- 성공하면 0을 반환합니다. 오류가 발생하면 -1이 반환되고 errno가 설정됩니다.
4. 수락 방식(블로킹 기능)
#include <sys/types.h> /* See NOTES */ #include <sys/socket.h> int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
기능
- 연결 요청을 수락하고 클라이언트와 통신하기 위해 새 소켓 설명자를 반환합니다.
매개변수
- sockfd: socket이 반환한 소켓 설명자.
- addr: 클라이언트 주소를 저장하는 데 사용되는 구조에 대한 포인터. 일반적으로 struct sockaddr_in 구조로 지정됩니다.
- addrlen: addr 구조의 길이를 전달하는 데 사용됩니다.
반환 값
- 성공하면 이러한 시스템 호출은 수락된 소켓의 파일 설명자(음이 아닌 정수)를 반환합니다. 오류가 발생하면 -1을 반환하고 errno를 적절하게 설정하고 addrlen을 변경하지 않고 유지합니다.
5. recv 방식(블로킹 기능)
#include <sys/types.h> #include <sys/socket.h> ssize_t recv(int sockfd, void *buf, size_t len, int flags);
기능
- 연결된 소켓에서 데이터를 받습니다.
매개변수
- sockfd: 수락에 의해 반환된 소켓 설명자.
- buf: 데이터를 받기 위한 버퍼.
- len: 버퍼의 길이.
- flags: 수신 작업을 위한 플래그로 일반적으로 0으로 설정됩니다.
반환 값
- 받은 바이트 수를 반환하거나 오류가 발생한 경우 -1을 반환합니다. 오류가 발생하면 오류를 나타내도록 errno가 설정됩니다. 클라이언트 연결이 닫히면 반환 값은 0이 됩니다.
6. 보내기 방법
#include <sys/types.h> #include <sys/socket.h> ssize_t send(int sockfd, const void *buf, size_t len, int flags);
기능
- 연결된 소켓에 데이터를 보냅니다.
매개변수
- sockfd: 수락에 의해 반환된 소켓 설명자.
- buf: 보낼 데이터를 포함하는 버퍼.
- len: 보낼 데이터의 길이.
- flags: 보내는 작업의 플래그이며 일반적으로 0으로 설정됩니다.
반환 값
- 성공하면 이러한 호출은 전송된 바이트 수를 반환합니다. 오류가 발생하면 -1이 반환되고 errno가 설정됩니다.
7. 닫기 방법
#include <unistd.h> int close(int fd);
기능
- 파일 설명자를 닫고 관련 리소스를 해제합니다.
매개변수
- fd: 닫을 파일 설명자.
반환 값
- 성공 시 0을 반환합니다. 오류가 발생하면 -1이 반환되고 errno가 설정됩니다.
8. htonl 방법
#include <arpa/inet.h> uint32_t htonl(uint32_t hostlong);
기능
- 호스트 바이트 순서의 32비트(4바이트) 부호 없는 정수를 네트워크 바이트 순서의 정수로 변환합니다.
9. htons 방법
#include <arpa/inet.h> uint16_t htons(uint16_t hostshort);
기능
- 호스트 바이트 순서의 16비트(2바이트) 부호 있는 짧은 정수를 네트워크 바이트 순서의 정수로 변환합니다.
10. fcntl 방법
#include <unistd.h> #include <fcntl.h> int fcntl(int fd, int cmd, ... /* arg */ );
기능
- 작동 파일 디스크립터의 동작 및 속성은 비차단 IO로 설정할 수 있습니다.
매개변수
- fd: 설정할 파일 설명자.
- cmd: fd에서 작업을 수행하는 명령으로, 일반적인 명령은 다음과 같으며 일반적으로 F_GETFL, F_SETFL을 사용합니다.
F_DUPFD
: 파일 설명자를 복사합니다.F_GETFD
: 파일 설명자 플래그를 가져옵니다.F_SETFD
: 파일 설명자 플래그를 설정합니다.F_GETFL
: 파일 상태 플래그를 가져옵니다.F_SETFL
: 파일 상태 플래그를 설정합니다.F_GETLK
: 파일 잠금을 획득합니다.F_SETLK
: 파일 잠금을 설정합니다.F_SETLKW
: 파일 잠금을 설정하고 잠금을 사용할 수 없는 경우 대기합니다.반환 값
- 성공적인 호출을 위해서는 동작 명령에 따라 반환 값이 달라지며 에러가 발생하면 -1이 반환되고 errno가 적절하게 설정됩니다.
둘째, 코드 구현
1. 차단 서버
TCP 서버 프로그램의 일반적인 흐름
소켓 만들기:
socket
시스템 호출을 사용하여 TCP 소켓을 만듭니다. 소켓은 네트워크 통신을 위한 끝점입니다.주소 및 포트 바인딩(Bind): 서버의 IP 주소와 포트 번호를 소켓에 바인딩하고
bind
시스템 호출을 사용하여 바인딩 작업을 완료합니다.청취 연결 요청(Listen): 소켓을 청취 상태로 두고 클라이언트의 연결 요청을 기다립니다. 시스템 호출을 사용하여
listen
소켓의 청취 대기열 길이를 설정하십시오.연결 요청 수락(Accept): 클라이언트가 연결을 요청하면
accept
시스템 호출을 사용하여 연결 요청을 수락합니다. 이렇게 하면 클라이언트와 통신하기 위한 새 소켓이 생성되고 원래 수신 소켓은 계속해서 새 연결 요청을 수신합니다.통신: 데이터 통신을 위해 받은 소켓을 사용합니다.
read/recv
시스템write/send
호출 또는 기타 고급 I/O 기능을 사용하여 데이터를 송수신할 수 있습니다 .소켓 닫기(Close): 통신이 끝나면
close
시스템 콜을 이용해 소켓을 닫고 관련 자원을 해제한다.샘플 코드
#include <stdio.h> #include <unistd.h> #include <sys/socket.h> #include <netinet/in.h> int main() { // 创建套接字 int serverSocket = socket(AF_INET, SOCK_STREAM, 0); // 绑定地址和端口 struct sockaddr_in serverAddress; serverAddress.sin_family = AF_INET; //ipv4 serverAddress.sin_addr.s_addr = htonl(INADDR_ANY); serverAddress.sin_port = htons(8888); bind(serverSocket, (struct sockaddr *)&serverAddress, sizeof(serverAddress)); // 监听连接请求 listen(serverSocket, 5); // 接受连接请求 int clientSocket = accept(serverSocket, NULL, NULL); // 进行通信 char buffer[1024]; read(clientSocket, buffer, sizeof(buffer)); printf("Received message: %s\n", buffer); // 关闭套接字 close(clientSocket); close(serverSocket); return 0; }
TCP 클라이언트 프로그램의 일반적인 흐름
소켓 만들기:
socket
시스템 호출을 사용하여 TCP 소켓을 만듭니다.서버 주소 및 포트 번호 설정:
struct sockaddr_in
구조를 사용하여 서버의 주소 및 포트 번호를 나타냅니다. 이 구조를 서버의 IP 주소와 포트 번호로 채웁니다.서버에 연결(Connect):
connect
시스템 호출을 사용하여 소켓을 서버에 연결합니다. 서버의 주소와 포트 번호를 매개변수로connect
함수에 전달합니다.데이터 통신(통신): 연결된 소켓을 사용하여 데이터를 읽고 씁니다. 데이터는
read
및 시스템 호출을 사용하여 읽고 보낼 수 있습니다 .write
소켓 닫기(Close): 통신이 완료되면
close
시스템 콜을 이용해 소켓을 닫고 관련 자원을 해제한다.샘플 코드
#include <stdio.h> #include <unistd.h> #include <sys/socket.h> #include <netinet/in.h> int main() { // 创建套接字 int clientSocket = socket(AF_INET, SOCK_STREAM, 0); // 设置服务器地址和端口号 struct sockaddr_in serverAddress; serverAddress.sin_family = AF_INET; serverAddress.sin_port = htons(8888); serverAddress.sin_addr.s_addr = inet_addr("服务器IP地址"); // 连接服务器 connect(clientSocket, (struct sockaddr *)&serverAddress, sizeof(serverAddress)); // 进行数据通信 char *message = "Hello, server!"; send(clientSocket, message, strlen(message),0); // 关闭套接字 close(clientSocket); return 0; }
전체 코드
- accept와 recv는 모두 차단 기능으로, accept에서는 클라이언트의 연결이 차단되고 recv에서는 연결된 클라이언트의 데이터를 읽을 수 없도록 차단됩니다.
- 클라이언트와의 지속적인 통신을 위해서는 recv를 마스터 루프에 배치하여 항상 클라이언트가 보낸 데이터를 읽어야 합니다.
#include <stdio.h> #include <sys/types.h> #include <sys/socket.h> #include <arpa/inet.h> #include <errno.h> #include <string.h> #include <stdlib.h> #include <unistd.h> #define BUFFER_LENGTH 1024 //初始化服务端,返回其文件描述符 int init_server(int port){ //返回服务端fd,通常为3,前面0,1,2用于表示标准输入,输出,错误值 int sfd = socket(AF_INET,SOCK_STREAM,0); if(-1 == sfd){ printf("socket error code: %d codeInfo: %s\n",errno,strerror(errno)); return -1; } struct sockaddr_in servAddr; memset(&servAddr,0,sizeof(struct sockaddr_in)); servAddr.sin_family = AF_INET; //ipv4 servAddr.sin_addr.s_addr = htonl(INADDR_ANY); //0.0.0.0 servAddr.sin_port = htons(port); //端口号 //绑定IP和端口号 if(-1 == bind(sfd,(struct sockaddr*)&servAddr,sizeof(struct sockaddr_in))) { printf("bind error code: %d codeInfo: %s\n",errno,strerror(errno)); return -1; } //监听该套接字上的连接 if(-1 == listen(sfd,SOMAXCONN)) { printf("listen error code: %d codeInfo: %s\n",errno,strerror(errno)); return -1; } return sfd; } int main(int argc,char *argv[]){ if(argc < 2)return -1; int port = atoi(argv[1]); //atoi:将字符串转换为整数 int sfd = init_server(port); printf("server fd: %d\n",sfd); struct sockaddr_in clientAddr; socklen_t len = sizeof(struct sockaddr_in); int cfd = accept(sfd,(struct sockaddr*)&clientAddr,&len); //阻塞函数 printf("client fd: %d\n",cfd); while (1) { char data[BUFFER_LENGTH]={0}; int recvLen = recv(cfd,data,BUFFER_LENGTH,0); //阻塞函数 if(recvLen < 0){ printf("recv client fd %d errno: %d\n",cfd,errno); }else if(recvLen == 0){ printf("client fd %d close\n",cfd); close(cfd); //关闭客户端文件描述符,释放资源 break; }else{ printf("recv client fd %d data: %s\n",cfd,data); send(cfd,data,recvLen,0); } } close(sfd); //关闭服务端文件描述符,释放资源 printf("server fd %d close\n",sfd); return 0; }
실행 결과
- 테스트 도구: NetAssist는 클라이언트 도구를 시뮬레이트하여 서버 코드를 테스트합니다.
2. 논블로킹 서버
Non-blocking TCP 서버의 일반적인 흐름
소켓 만들기:
socket
시스템 호출을 사용하여 TCP 소켓을 만듭니다.소켓을 비차단 모드로 설정:
fcntl
함수를 사용F_SETFL
하여 소켓의 파일 상태 플래그를 명령을 통해 비차단 모드로 설정합니다. 즉,O_NONBLOCK
플래그를 사용합니다.주소 및 포트 바인딩(Bind): 서버의 IP 주소와 포트 번호를 소켓에 바인딩하고
bind
시스템 호출을 사용하여 바인딩 작업을 완료합니다.청취 연결 요청(Listen): 소켓을 청취 상태에 놓고 클라이언트의 연결 요청을 기다린 후 시스템
listen
호출을 사용하여 소켓의 청취 대기열 길이를 설정합니다.연결 요청 수락(Accept):
accept
시스템 호출을 사용하여 연결 요청을 수락합니다. 이렇게 하면 클라이언트와 통신하기 위한 새 소켓이 생성되고 원래 수신 소켓은 계속해서 새 연결 요청을 수신합니다.새 소켓을 비차단 모드로 설정: 마찬가지로
fcntl
함수를 사용하여 새 소켓을 비차단 모드로 설정합니다.데이터 통신(통신): 비차단 소켓을 사용하여 데이터를 읽고 씁니다.
read/recv
데이터는 시스템 호출 또는 기타 비차단 I/O 기능 과write/send
교환될 수 있습니다 .소켓 닫기(Close): 통신이 끝나면
close
시스템 콜을 이용해 소켓을 닫고 관련 자원을 해제한다.샘플 코드
#include <stdio.h> #include <unistd.h> #include <sys/socket.h> #include <netinet/in.h> #include <fcntl.h> int main() { // 创建套接字 int serverSocket = socket(AF_INET, SOCK_STREAM, 0); // 设置套接字为非阻塞模式 int flags = fcntl(serverSocket, F_GETFL, 0); fcntl(serverSocket, F_SETFL, flags | O_NONBLOCK); // 绑定地址和端口 struct sockaddr_in serverAddress; serverAddress.sin_family = AF_INET; serverAddress.sin_addr.s_addr = htonl(INADDR_ANY); serverAddress.sin_port = htons(8888); bind(serverSocket, (struct sockaddr *)&serverAddress, sizeof(serverAddress)); // 监听连接请求 listen(serverSocket, 5); while (1) { // 接受连接请求 struct sockaddr_in clientAddress; socklen_t clientAddressLength = sizeof(clientAddress); int clientSocket = accept(serverSocket, (struct sockaddr *)&clientAddress, &clientAddressLength); if (clientSocket > 0) { // 设置新的套接字为非阻塞模式 flags = fcntl(clientSocket, F_GETFL, 0); fcntl(clientSocket, F_SETFL, flags | O_NONBLOCK); // 进行数据通信 char buffer[1024]; ssize_t bytesRead = read(clientSocket, buffer, sizeof(buffer)); if (bytesRead > 0) { // 读取到数据 printf("Received message from client: %s\n", buffer); } // 关闭客户端套接字 close(clientSocket); } } // 关闭服务端套接字 close(serverSocket); return 0; }
전체 코드
- 소켓을 비차단 모드로 설정합니다.
- 다음은 위의 코드 조각이 서버를 비차단으로 설정하는지 여부를 테스트하는 데 사용됩니다.
#include <stdio.h> #include <sys/types.h> #include <sys/socket.h> #include <arpa/inet.h> #include <errno.h> #include <string.h> #include <stdlib.h> #include <unistd.h> #include <fcntl.h> #define BUFFER_LENGTH 1024 int init_server(int port){ //获取服务端fd,通常为3,前面0,1,2用于指定输入,输出,错误值 int sfd = socket(AF_INET,SOCK_STREAM,0); if(-1 == sfd){ printf("socket error code: %d codeInfo: %s\n",errno,strerror(errno)); return -1; } //设置服务端套接字为非阻塞模式 int flags = fcntl(sfd,F_GETFL,0); fcntl(sfd,F_SETFL,flags | O_NONBLOCK); struct sockaddr_in servAddr; memset(&servAddr,0,sizeof(struct sockaddr_in)); servAddr.sin_family = AF_INET; //ipv4 servAddr.sin_addr.s_addr = htonl(INADDR_ANY); //0.0.0.0 servAddr.sin_port = htons(port); //服务端绑定ip地址和端口号 if(-1 == bind(sfd,(struct sockaddr*)&servAddr,sizeof(struct sockaddr_in))) { printf("bind error code: %d codeInfo: %s\n",errno,strerror(errno)); return -1; } //监听该套接字上的连接请求 if(-1 == listen(sfd,SOMAXCONN)) { printf("listen error code: %d codeInfo: %s\n",errno,strerror(errno)); return -1; } return sfd; } int main(int argc,char *argv[]){ if(argc < 2)return -1; int port = atoi(argv[1]); int sfd = init_server(port); printf("server fd: %d\n",sfd); //接受连接请求 struct sockaddr_in clientAddr; socklen_t len = sizeof(struct sockaddr_in); int cfd = accept(sfd,(struct sockaddr*)&clientAddr,&len); if(cfd == -1){ printf("accept error code: %d codeInfo: %s\n",errno,strerror(errno)); return -1; } printf("client fd: %d\n",cfd); //设置新的套接字为非阻塞模式 int flags = fcntl(cfd,F_GETFL,0); fcntl(cfd,F_SETFL,flags | O_NONBLOCK); while (1) { char data[BUFFER_LENGTH]={0}; int recvLen = recv(cfd,data,BUFFER_LENGTH,0); if(recvLen < 0){ printf("recv client fd %d errno: %d\n",cfd,errno); }else if(recvLen == 0){ //客户端断开连接 printf("client fd %d close\n",cfd); close(cfd); //关闭客户端文件描述符,释放资源 break; }else{ printf("recv client fd %d data: %s\n",cfd,data); send(cfd,data,recvLen,0); } } close(sfd); //关闭服务端文件描述符,释放资源 printf("server fd %d close\n",sfd); return 0; }
실행 효과
- 성공적으로 컴파일 및 실행한 후 다음 오류가 보고되어 서버가 정상적으로 실행되지 않습니다.
문제 분석
- Linux에서 "리소스를 일시적으로 사용할 수 없음"(리소스를 일시적으로 사용할 수 없음)의 해당 오류 코드는 EAGAIN(오류 값 11) 또는 EWOULDBLOCK입니다. 이 오류 코드는 일반적으로 비차단 I/O 작업 중에 발생하며 현재 사용 가능한 리소스가 없거나 작업이 진행 중임을 나타냅니다.
- 네트워크 프로그래밍에서 읽기 또는 쓰기 작업을 위해 비차단 모드의 소켓을 사용할 때 사용 가능한 데이터가 없거나 쓰기 작업을 즉시 완료할 수 없는 경우 이 오류 코드가 반환될 수 있습니다. 이는 논블로킹 모드의 I/O 작업이 논블로킹이기 때문입니다. 즉, 즉시 완료되거나 기다리지 않고 오류 코드를 반환합니다.
해결책
비동기 I/O(Asynchronous I/O): 비동기 I/O 작업을 사용하면 현재 스레드를 차단하지 않고 모든 I/O 작업 후에 반환할 수 있습니다. Linux는 aio_read 및 aio_write와 같은 비동기 I/O 기능을 제공합니다. 비동기 I/O 작업을 사용하면 작업이 완료될 때 알림을 받을 콜백 함수를 등록할 수 있습니다.
다루기 위한 후속 연구. . .