Linux 환경에서의 애플리케이션 프로그래밍 (5) : 네트워크 프로그래밍

 

I. 소개

       Linux 시스템에서 제공하는 고전적인 프로세스 간 통신 메커니즘 (IPC) : 파이프, 공유 메모리, 메시지 대기열 및 세마포. 이러한 메커니즘을 사용하면 동일한 컴퓨터에서 실행되는 프로세스가 서로 통신 할 수 있으며 서로 다른 컴퓨터 (A를 통해 연결된 네트워크) 프로세스 간 통신은 새로운 메커니즘을 도입합니다. 네트워크 프로세스 간 통신, 프로세스는 소켓 네트워크 프로세스 간 통신 인터페이스를 통해 서로 통신 할 수 있으며, 소켓 인터페이스와 통신하는 데 다양한 네트워크 프로토콜을 사용할 수 있습니다.

1. 5 계층 네트워크 프로토콜 스택 :

애플리케이션 계층 프로토콜 : FTP, HTTP, SMTP

전송 계층 프로토콜 : TCP 프로토콜, UDP 프로토콜

네트워크 계층 프로토콜 : IP 프로토콜

이 장에서 설명하는 TCP 프로토콜과 UDP 프로토콜은 전송 계층에 속합니다. 5 계층 구조의 특정 기능은 https://blog.csdn.net/weixin_37719279/article/details/82846226을 참조하십시오.

2. TCP와 UDP 프로토콜의 차이점

TCP는 상위 계층에 연결 지향의 안정적인 서비스를 제공하고 UDP는 상위 계층에 비 연결 및 비 신뢰 서비스를 제공합니다. UDP는 TCP 전송만큼 정확하지는 않지만 실시간 요구 사항이 높은 시나리오에서 자주 사용됩니다.

3. TCP의 3 자 핸드 셰이크와 네 손을 흔들었다

(1) 세 악수

첫 번째 핸드 셰이크 : 연결을 설정할 때 클라이언트는 서버에 syn 패킷 (syn = j)을 전송하고 SYN_SENT 상태에 들어가 서버가 확인을 기다립니다 .SYN : 시퀀스 번호 동기화.

두 번째 핸드 셰이크 : 서버는 syn 패킷을 수신하고 클라이언트의 SYN (ack = j + 1)을 확인해야하며 동시에 SYN 패킷 (syn = k), 즉 SYN + ACK 패킷을 전송하고 서버가 SYN_RECV 상태가됩니다.

세 번째 핸드 셰이크 : 클라이언트는 서버로부터 SYN + ACK 패킷을 수신하고 확인 패킷 ACK (ack = k + 1)를 서버로 보냅니다. 패킷이 전송 된 후 클라이언트와 서버는 ESTABLISHED (TCP 연결 성공 ) 상태가 완료되면 악수를 세 번합니다.

(2) 네 번 흔들기

클라이언트 프로세스가 연결 해제 메시지를 보내고 데이터 전송을 중지합니다. 데이터 메시지의 헤더 FIN = 1을 해제하면 시퀀스 번호는 seq = u (이전에 전송 된 데이터의 마지막 바이트 시퀀스 번호에 1을 더한 값과 같음)가됩니다. 이때 클라이언트는 FIN- WAIT-1 (대기 종료 1) 상태. TCP는 FIN 세그먼트가 데이터를 전달하지 않더라도 시퀀스 번호를 사용한다고 규정합니다.
b. 서버는 연결 해제 메시지를 수신하고 확인 메시지 ACK = 1, ack = u + 1을 보내고 자체 일련 번호 seq = v를 가져옵니다.이 때 서버는 CLOSE-WAIT (닫기 대기) 상태로 들어갑니다. . TCP 서버는 상위 애플리케이션 프로세스에 클라이언트가 서버의 방향으로 해제되었음을 알려줍니다. 이때 클라이언트가 보낼 데이터가없는 상태 인 반 폐쇄 상태입니다. 데이터를 보내더라도 클라이언트는 여전히 데이터를 수락해야합니다. 이 상태는 잠시 동안, 즉 전체 CLOSE-WAIT 상태의 기간 동안 계속됩니다.
c. 클라이언트가 서버로부터 확인 요청을받은 후, 이때 클라이언트는 FIN-WAIT-2 (종료 대기 2) 상태로 들어가 서버가 연결 해제 메시지를 보낼 때까지 기다립니다 (그 전에는 마지막 데이터 수락).
d. 서버가 최종 데이터를 보낸 후 클라이언트에게 연결 해제 메시지를 보냅니다. FIN = 1, ack = u + 1, 이는 반 폐쇄 상태이기 때문에 서버는 더 많은 데이터를 보낼 수 있습니다. 이 시간을 가정하면 시퀀스 번호는 seq = w이고 이때 서버는 LAST-ACK (마지막 확인) 상태로 들어가 클라이언트의 확인을 기다립니다.
e. 클라이언트가 서버로부터 연결 해제 메시지를 수신 한 후에는 승인 (ACK = 1, ack = w + 1)을 보내야하며 일련 번호는 seq = u + 1이며 이때 클라이언트는 TIME- WAIT (시간 대기) 상태. 이때 TCP 연결은 해제되지 않았으며 2 *∗ MSL (메시지 세그먼트 수명이 가장 긴) 시간을 통과해야하며 클라이언트가 해당 TCB를 취소하면 CLOSED 상태가됩니다.
f. 서버가 클라이언트로부터 확인을받는 한 즉시 CLOSED 상태로 들어갑니다. 마찬가지로 TCB가 철회 된 후 TCP 연결이 종료됩니다. 보시다시피 서버는 클라이언트보다 먼저 TCP 연결을 종료합니다.

상세 참조 : https://www.cnblogs.com/bj-mr-li/p/11106390.html

2 : 네트워크 프로그래밍

1. 주소 지정

(1) 바이트 순서

응용 프로그램은 때때로 프로세서 바이트 순서와 네트워크 바이트 순서간에 전환해야합니다. 응용 프로그램의 경우 프로세서 바이트 순서와 네트워크 바이트 순서간에 변환하는 데 사용되는 4 개의 함수가 있습니다.

"H"는 "호스트"바이트 순서, "n"은 "네트워크"바이트 순서, "l"은 "긴"(예 : 4 바이트) 정수, "s"는 "짧은"(예 : 2 바이트) 정수를 의미합니다.

#include <arpa/inet.h>

uint32_t htonl(uint32_t hostint32); 返回值:以网络字节序表示的32位整数
uint16_t htons(uint16_t hostint16); 返回值:以网络字节序表示的16位整数
uint32_t ntohl(uint32_t netint32);  返回值:以主机字节序表示的32位整数
uint16_t ntohs(uint16_t netint16);  返回值:以主机字节序表示的16位整数

(2) 주소 형식

        주소는 특정 통신 도메인의 소켓 끝점을 식별합니다. 주소 형식은이 특정 통신 도메인과 관련이 있습니다. 다른 형식의 주소를 소켓 함수에 전달할 수 있도록 주소는 일반 주소 구조로 강제 적용됩니다.

struct sockaddr{
    unisgned short  sa_family; /*地址族*/
    char sa_data[14];          /*14字节的协议地址,包含该socket的IP地址和端口号。*/
};

시스템 호환성 문제로 인해 다음을 나타내는 데 새 주소 구조가 사용됩니다.

struct sockaddr_in{
    unsigned short          sin_family;     /*地址族*/
    unsigned short int      sin_port;       /*端口号*/
    struct in_addr          sin_addr;       /*IP 地址*/
    unsigned char           sin_zero[8];    /*填充0 以保持与struct sockaddr 同样大小*/
}

(3) 주소 형식 변환

일반적으로 사용자는 주소를 표현할 때 점으로 구분 된 십진수 표기법 (또는 콜론으로 구분 된 IPv6 주소)을 사용하지만 일반적으로 사용되는 소켓 프로그래밍에서는 이진 값이 사용되며이 두 값을 결합해야하는 값이 변환됩니다. 여기서 IPv4에서 사용되는 함수는 inet_aton (), inet_addr () 및 inet_ntoa ()이고 IPv4 및 IPv6 호환 함수는 inet_pton () 및 inet_ntop ()입니다.

#include <arpa/inet.h>

int inet_pton(int family, const char *strptr, void *addrptr)
int inet_ntop(int family, void *addrptr, char *strptr, size_t len)

family:{
    AF_INET:IPv4 协议
    AF_INET6:IPv6 协议
}
strptr:要转化的值
addrptr:转化后的地址
len:转化后值的大小
返回值:成功0、出错-1

inet_aton은 문자열 IP 주소를 32 비트 네트워크 직렬 IP 주소로 변환하는 개선 된 방법입니다.

1 입력 매개 변수 문자열에는 ASCII IP 주소가 포함됩니다.
2 출력 매개 변수 addr는 새 IP 주소로 업데이트 할 구조입니다.

이 함수가 성공하면 함수의 반환 값은 0이 아닙니다. 입력 주소가 올바르지 않으면 0이 반환됩니다. 이 함수를 사용할 때 errno에 저장된 오류 코드가 없으므로 해당 값은 무시됩니다.

예:

struct sockaddr_in socket_server_addr;

socket_server_addr.sin_family   = AF_INET;
/*主机字节序转换为网络字节序*/
socket_server_addr.sin_port     = htons(SERVER_PORT);
if (inet_aton(“192.168.1.100”, &socket_server_addr.sin_addr) == 0)
{
    printf("invalid server ip\n");
    return -1;
}

(4) 이름 주소 변환

일반적으로 사람들은 사용 중에 긴 IP 주소를 기억하는 것을 꺼려합니다. 특히 IPv6에서 주소 길이는 최대 128 비트이므로 이러한 긴 IP 주소를 반복해서 기억하는 것은 훨씬 더 불가능합니다. 따라서 호스트 이름을 사용하는 것이 좋습니다. Linux에는 호스트 이름과 주소의 변환을 실현할 수있는 기능도 있으며, 가장 일반적인 기능은 gethostbyname (), gethostbyaddr () 및 getaddrinfo () 등으로 모두 IPv4와 IPv6 간의 변환을 실현할 수 있습니다. 주소 및 호스트 이름. 이 중 gethostbyname ()은 호스트 이름을 IP 주소로 변환하고, gethostbyaddr ()는 IP 주소를 호스트 이름으로 변환하는 역 연산입니다. 또한 getaddrinfo ()는 IPv4 주소와 IPv6 주소를 자동으로 식별 할 수 있습니다.

struct hostent
{
    char *h_name;/*正式主机名*/
    char **h_aliases;/*主机别名*/
    int h_addrtype;/*地址类型*/
    int h_length;/*地址字节长度*/
    char **h_addr_list;/*指向 IPv4 或 IPv6 的地址指针数组*/
}

struct addrinfo
{
    int ai_flags;/*AI_PASSIVE, AI_CANONNAME;*/
    int ai_family;/*地址族*/
    int ai_socktype;/*socket 类型*/
    int ai_protocol;/*协议类型*/
    size_t ai_addrlen;/*地址字节长度*/
    char *ai_canonname;/*主机名*/
    struct sockaddr *ai_addr;/*socket 结构体*/
    struct addrinfo *ai_next;/*下一个指针链表*/
}

ai_flags{
    AI_PASSIVE:该套接口是用作被动地打开
    AI_CANONNAME:通知 getaddrinfo 函数返回主机的名字
}
ai_family{
    AF_INET:IPv4 协议
    AF_INET6:IPv6 协议
    AF_UNSPEC:IPv4 或 IPv6 均可
}
ai_socktype {
    SOCK_STREAM:字节流套接字 socket(TCP)
    SOCK_DGRAM:数据报套接字 socket(UDP)
}
ai_protocol{
    IPPROTO_IP:IP 协议
    IPPROTO_IPV4:IPv4 协议 4 IPv4
    IPPROTO_IPV6:IPv6 协议
    IPPROTO_UDP:UDP
    IPPROTO_TCP:TCP
}
#include <netdb.h>

struct hostent *gethostbyname(const char *hostname)
hostname:主机名
返回值:成功返回hostent类型指针、错误-1

int getaddrinfo(const char *node, const char *service, const struct addrinfo *hints,struct addrinfo **result)
node:网络地址或者网络主机名
service:服务名或十进制的端口号字符串
hints:服务线索
result:返回结果
返回值:成功0、错误-1

(1) 일반적으로 서버 측에서 getaddrinfo ()를 호출하기 전에 ai_flags는 bind () 함수 (나중에 설명 할 포트와 주소를 바인딩하는 데 사용됨)에 대해 AI_PASSIVE를 설정하고 호스트 이름 nodename은 일반적으로 NULL로 설정됩니다. .
(2) 클라이언트가 getaddrinfo ()를 호출 할 때 ai_flags는 일반적으로 AI_PASSIVE를 설정하지 않지만 호스트 이름 nodename과 서비스 이름 servname (port)은 비워 둘 수 없습니다.
(3) ai_flags가 AI_PASSIVE로 설정되어 있지 않아도 검색된 주소를 바인딩 할 수 있습니다. 많은 프로그램에서 ai_flags가 직접 0으로 설정되어있는 즉, 3 개의 플래그 비트가 설정되지 않습니다.이 경우 호스트 이름 만 있으면됩니다. 및 servname이 문제없이 설정되었습니다. 올바르게 바인딩 될 수 있습니다.

예:

/* getaddrinfo.c */
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <netdb.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <sys/socket.h>
int main()
{
    struct addrinfo hints, *res = NULL;
    int rc;
    memset(&hints, 0, sizeof(hints));

    /*设置 addrinfo 结构体中各参数 */
    hints.ai_flags = AI_CANONNAME;
    hints.ai_family = AF_UNSPEC;
    hints.ai_socktype = SOCK_DGRAM;
    hints.ai_protocol = IPPROTO_UDP;
    /*调用 getaddinfo 函数*/
    rc = getaddrinfo("localhost", NULL, &hints, &res);
    if (rc != 0)
    {
        perror("getaddrinfo");
        exit(1);
    }
    else
    {
        printf("Host name is %s\n", res->ai_canonname);
    }
    exit(0);
}

2. 소켓 기본 프로그래밍

(1) 소켓 설명자

소켓 만들기 :

#include <sys/socket.h>
int socket(int domain, int type,int protocol);

domain:确定通信的特性,包括地址格式
        AF_INET:IPv4因特网域
        AF_INET6:IPv6因特网域
        AF_UNIX:UNIX域
        AF_UPSPEC:未指定
type:确定套接字类型
        SOCK_DGRAM:固定长度的,无连接的,不可靠的报文传递(UDP)
        SOCK_RAW:IP协议的数据报接口
        SOCK_SEQPACKET:固定长度的,有序的,可靠的,面向连接的报文传递
        SOCK_STREAM:有序的,可靠的,双向的,面向连接的字节流(TCP)
protocol:通常为0,表示为给定的域和套接字类型选择默认协议

소켓 닫기 :

#include <sys/socket.h>
int shutdown(int sockfd, int how);

how:SHUT_RD(关闭读端),无法从套接字读取数据
     SHUT_WR(关闭写端),无法从套接字发送数据
     SHUT_RDWR,无法读取和发送数据

(2) 서버 관련 소켓 및 주소

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

示例:
struct sockaddr_in socket_server_addr;
duty_socket = socket(AF_INET, SOCK_STREAM, 0);
/* 服务器端填充 sockaddr_in结构 */
socket_server_addr.sin_family   = AF_INET;
/*端口号转换为网络字节序*/
socket_server_addr.sin_port     = htons(SERVER_PORT);
/*接收本机所有网口的数据*/
socket_server_addr.sin_addr.s_addr  = INADDR_ANY;
memset(socket_server_addr.sin_zero, 0, 8);

/* 捆绑sockfd描述符 */
ret = bind(duty_socket, (const struct sockaddr *)&socket_server_addr, sizeof(struct sockaddr));

(3) 서버는 연결 요청을 수락하기 위해 listen 함수를 호출합니다.

int listen(int sockfd,int backlog);

ret = listen(duty_socket, C_QUEUE);

sockfd是bind后的文件描述符。
backlog设置请求排队的最大长度。当有多个客户端程序和服务端相连时,使用这个表示可以介绍的排队长度。
listen函数将bind的文件描述符变为监听套接字,返回的情况和bind一样。

(4) 서버가 연결 요청을 받고 연결을 설정합니다.

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


int customer_socket;
customer_socket = accept(duty_socket, (struct sockaddr *)&socket_client_addr, &addr_len);

sockfd是listen后的文件描述符。
addr,addrlen是用来给客户端的程序填写的,服务器端只要传递指针就可以了, bind,listen和accept是服务器端用的函数。
accept调用时,服务器端的程序会一直阻塞到有一个客户程序发出了连接。 accept成功时返回最后的服务器端的文件描述符,这个时候服务器端可以向该描述符写信息了,失败时返回-1 。

(5) 클라이언트가 연결을 설정합니다.

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

ret = connect(client_socket, (const struct sockaddr *)&socket_server_addr, sizeof(struct sockaddr));


可以用connect建立一个连接,在connect中所指定的地址是想与之通信的服务器的地址。
sockfd是socket函数返回的文件描述符。
serv_addr储存了服务器端的连接信息,其中sin_add是服务端的地址。
addrlen是serv_addr的长度 
connect函数是客户端用来同服务端连接的.成功时返回0,sockfd是同服务端通讯的文件描述符,失败时返回-1。

(6) 데이터 보내기

ssize_t send(int sockfd, const void *buf, size_t len, int flags);

sockfd 指定发送端套接字描述符;
buf 指明一个存放应用程序要发送数据的缓冲区;
len 指明实际要发送的数据的字节数;
flags 一般置0。
客户或者服务器应用程序都用send函数来向TCP连接的另一端发送数据


ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,
                      const struct sockaddr *dest_addr, socklen_t addrlen);
sendto和send相似,区别在于sendto允许在无连接的套接字上指定一个目标地址。
dest_addr 表示目地机的IP地址和端口号信息,
addrlen 常常被赋值为sizeof (struct sockaddr)。
sendto 函数也返回实际发送的数据字节长度或在出现发送错误时返回-1。

(7) 데이터 수락

ssize_t recv(int sockfd, void *buf, size_t len, int flags);

sockfd 指定接收端套接字描述符;
buf 指明一个缓冲区,该缓冲区用来存放recv函数接收到的数据;
len 指明buf的长度;
flags 一般置0。
客户或者服务器应用程序都用recv函数从TCP连接的另一端接收数据。


ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,
                        struct sockaddr *src_addr, socklen_t *addrlen);


recvfrom通常用于无连接套接字,因为此函数可以获得发送者的地址。
src_addr 是一个struct sockaddr类型的变量,该变量保存源机的IP地址及端口号。
addrlen 常置为sizeof (struct sockaddr)。

TCP 예 :

인스턴스는 클라이언트와 서버의 두 부분으로 나뉩니다. 서버는 먼저 소켓을 설정 한 다음 로컬 포트에 바인딩 한 다음 클라이언트로부터 연결 요청을 수신하고 연결을 설정 한 다음 수신합니다. client 보낸 메시지입니다. 클라이언트는 소켓이 설정된 후 연결을 설정하기 위해 connect () 함수를 호출합니다.

섬기는 사람:

/*server.c*/
#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>
#include <netinet/in.h>
#define PORT 4321
#define BUFFER_SIZE 1024
#define MAX_QUE_CONN_NM 5
int main()
{
    struct sockaddr_in server_sockaddr,client_sockaddr;
    int sin_size,recvbytes;
    int sockfd, client_fd;
    char buf[BUFFER_SIZE];
    /*建立 socket 连接*/
    if ((sockfd = socket(AF_INET,SOCK_STREAM,0))== -1)
    {
        perror("socket");
        exit(1);
    }
    printf("Socket id = %d\n",sockfd);
    /*设置 sockaddr_in 结构体中相关参数*/
    server_sockaddr.sin_family = AF_INET;
    server_sockaddr.sin_port = htons(PORT);
    server_sockaddr.sin_addr.s_addr = INADDR_ANY;
    bzero(&(server_sockaddr.sin_zero), 8);
    int i = 1;/* 允许重复使用本地地址与套接字进行绑定 */
    setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &i, sizeof(i));
    /*绑定函数 bind()*/
    if (bind(sockfd, (struct sockaddr *)&server_sockaddr,
    sizeof(struct sockaddr)) == -1)
    {
        perror("bind");
        exit(1);
    }
    printf("Bind success!\n");
    /*调用 listen()函数,创建未处理请求的队列*/
    if (listen(sockfd, MAX_QUE_CONN_NM) == -1)
    {
        perror("listen");
        exit(1);
    }
    printf("Listening....\n");
    /*调用 accept()函数,等待客户端的连接*/
    if ((client_fd = accept(sockfd,(struct sockaddr *)&client_sockaddr, &sin_size)) == -1)
    {
        perror("accept");
        exit(1);
    }
    /*调用 recv()函数接收客户端的请求*/
    memset(buf , 0, sizeof(buf));
    if ((recvbytes = recv(client_fd, buf, BUFFER_SIZE, 0)) == -1)
    {
        perror("recv");
        exit(1);
    }
    printf("Received a message: %s\n", buf);
    close(sockfd);
    exit(0);
}

고객:

/*client.c*/
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <netdb.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <sys/socket.h>
#define PORT 4321
#define BUFFER_SIZE 1024
int main(int argc, char *argv[])
{
    int sockfd,sendbytes;
    char buf[BUFFER_SIZE];
    struct hostent *host;
    struct sockaddr_in serv_addr;
    if(argc < 3)
    {
        fprintf(stderr,"USAGE: ./client Hostname(or ip address) Text\n");
        exit(1);
    }
    /*地址解析函数*/
    if ((host = gethostbyname(argv[1])) == NULL)
    {
        perror("gethostbyname");
        exit(1);
    }
    memset(buf, 0, sizeof(buf));
    sprintf(buf, "%s", argv[2]);
    /*创建 socket*/
    if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1)
    {
        perror("socket");
        exit(1);
    }
    /*设置 sockaddr_in 结构体中相关参数*/
    serv_addr.sin_family = AF_INET;
    serv_addr.sin_port = htons(PORT);
    serv_addr.sin_addr = *((struct in_addr *)host->h_addr);
    bzero(&(serv_addr.sin_zero), 8);
    /*调用 connect 函数主动发起对服务器端的连接*/
    if(connect(sockfd,(struct sockaddr *)&serv_addr,
    sizeof(struct sockaddr))== -1)
    {
        perror("connect");
        exit(1);
    }
    /*发送消息给服务器端*/
    if ((sendbytes = send(sockfd, buf, strlen(buf), 0)) == -1)
    {
        perror("send");
        exit(1);
    }
    close(sockfd);
    exit(0);
}

결과:

$ ./server
Socket id = 3
Bind success!
Listening....
Received a message: Hello,Server!
$ ./client localhost(或者输入 IP 地址) Hello,Server!

3. 고급 네트워크 프로그래밍

실제 상황에서 사람들은 종종 여러 클라이언트가 서버에 연결되는 상황에 직면합니다. 앞서 소개 한 connet (), recv (), send ()는 모두 차단 기능이므로 리소스가 준비되지 않은 경우이 함수를 호출하는 프로세스가 절전 상태가되어 I / O 멀티플렉싱을 처리 할 수없는 상황 사용. 이 섹션에서는 I / O 멀티플렉싱을 해결하는 두 가지 솔루션을 제공합니다.이 두 함수는 이전에 배운 fcntl () 및 select ()입니다.

(1) fcntl ()

fcntl () 함수는 소켓 프로그래밍을 위해 다음과 같은 프로그래밍 기능을 제공합니다.

  • 비 블로킹 I / O : cmd를 F_SETFL로 설정하고 O_NONBLOCK으로 잠글 수 있습니다.
  • 비동기 I / O : cmd를 F_SETFL로 설정하고 O_ASYNC로 잠글 수 있습니다.
/* net_fcntl.c */
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/wait.h>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <sys/un.h>
#include <sys/time.h>
#include <sys/ioctl.h>
#include <unistd.h>
#include <netinet/in.h>
#include <fcntl.h>
#define PORT 1234
#define MAX_QUE_CONN_NM 5
#define BUFFER_SIZE 1024
int main()
{
    struct sockaddr_in server_sockaddr, client_sockaddr;
    int sin_size, recvbytes, flags;
    int sockfd, client_fd;
    char buf[BUFFER_SIZE];
    if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1)
    {
        perror("socket");
        exit(1);
    }
    server_sockaddr.sin_family = AF_INET;
    server_sockaddr.sin_port = htons(PORT);
    server_sockaddr.sin_addr.s_addr = INADDR_ANY;
    bzero(&(server_sockaddr.sin_zero), 8);
    int i = 1;/* 允许重复使用本地地址与套接字进行绑定 */
    setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &i, sizeof(i));
    if (bind(sockfd, (struct sockaddr *)&server_sockaddr,sizeof(struct sockaddr)) == -1)
    {
        perror("bind");
        exit(1);
    }
    if(listen(sockfd,MAX_QUE_CONN_NM) == -1)
    {
        perror("listen");
        exit(1);
    }
    printf("Listening....\n");
    /* 调用 fcntl()函数给套接字设置非阻塞属性 */
    flags = fcntl(sockfd, F_GETFL);
    if (flags < 0 || fcntl(sockfd, F_SETFL, flags|O_NONBLOCK) < 0)
    {
        perror("fcntl");
        exit(1);
    }
    while(1)
    {
        sin_size = sizeof(struct sockaddr_in);
        if ((client_fd = accept(sockfd,(struct sockaddr*)&client_sockaddr, &sin_size)) < 0)
        {
            perror("accept");
            exit(1);
        }
        if ((recvbytes = recv(client_fd, buf, BUFFER_SIZE, 0)) < 0)
        {
            perror("recv");
            exit(1);
        }
        printf("Received a message: %s\n", buf);
    } /*while*/
    close(client_fd);
    exit(1);
}



$ ./net_fcntl
Listening....
accept: Resource temporarily unavailable
可以看到,当 accept()的资源不可用(没有任何未处理的等待连接的请求)时,程序就会自动返回。

(2) select ()

fcntl () 함수는 non-blocking I / O 또는 신호 기반 I / O를 구현하는 데 사용할 수 있지만 실제로는 리소스가 순환 할 준비가되었는지 여부를 테스트하여 불필요한 CPU 리소스의 소비를 크게 증가시킵니다. 여기서 select () 함수를 사용하여이 문제를 해결할 수 있고 동시에 select () 함수를 사용하여 대기 시간을 설정하면 더 강력하다고 할 수 있습니다.

/* net_select.c */
#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/time.h>
#include <sys/ioctl.h>
#include <unistd.h>
#include <netinet/in.h>
#define PORT 4321
#define MAX_QUE_CONN_NM 5
#define MAX_SOCK_FD FD_SETSIZE
#define BUFFER_SIZE 1024
int main()
{
    struct sockaddr_in server_sockaddr, client_sockaddr;
    int sin_size, count;
    fd_set inset, tmp_inset;
    int sockfd, client_fd, fd;
    char buf[BUFFER_SIZE];
    if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1)
    {
        perror("socket");
        exit(1);
    }
    server_sockaddr.sin_family = AF_INET;
    server_sockaddr.sin_port = htons(PORT);
    server_sockaddr.sin_addr.s_addr = INADDR_ANY;
    bzero(&(server_sockaddr.sin_zero), 8);
    int i = 1;/* 允许重复使用本地地址与套接字进行绑定 */
    setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &i, sizeof(i));
    if (bind(sockfd, (struct sockaddr *)&server_sockaddr,sizeof(struct sockaddr)) == -1)
    {
        perror("bind");
        exit(1);
    }
    if(listen(sockfd, MAX_QUE_CONN_NM) == -1)
    {
        perror("listen");
        exit(1);
    }
    printf("listening....\n");
    /*将调用 socket()函数的描述符作为文件描述符*/
    FD_ZERO(&inset);
    FD_SET(sockfd, &inset);
    while(1)
    {
        tmp_inset = inset;
        sin_size=sizeof(struct sockaddr_in);
        memset(buf, 0, sizeof(buf));
        /*调用 select()函数*/
        if (!(select(MAX_SOCK_FD, &tmp_inset, NULL, NULL, NULL) > 0))
        {
            perror("select");
        }
        for (fd = 0; fd < MAX_SOCK_FD; fd++)
        {
            if (FD_ISSET(fd, &tmp_inset) > 0)
            {
                if (fd == sockfd)
                { /* 服务端接收客户端的连接请求 */
                    if ((client_fd = accept(sockfd,(struct sockaddr *)&client_sockaddr, &sin_size))== -1)
                    {
                        perror("accept");
                        exit(1);
                    }
                    FD_SET(client_fd, &inset);
                    printf("New connection from %d(socket)\n", client_fd);
                }
                else /* 处理从客户端发来的消息 */
                {
                    if ((count = recv(client_fd, buf, BUFFER_SIZE, 0)) > 0)
                    {
                        printf("Received a message from %d: %s\n",
                        client_fd, buf);
                    }
                    else
                    {
                        close(fd);
                        FD_CLR(fd, &inset);
                        printf("Client %d(socket) has left\n", fd);
                    }
                }
            } /* end of if FD_ISSET*/
        } /* end of for fd*/
    } /* end if while while*/
    close(sockfd);
    exit(0);
}


$ ./server
listening....
New connection from 4(socket) /* 接受第一个客户端的连接请求*/
Received a message from 4: Hello,First! /* 接收第一个客户端发送的数据*/
New connection from 5(socket) /* 接受第二个客户端的连接请求*/
Received a message from 5: Hello,Second! /* 接收第二个客户端发送的数据*/
Client 4(socket) has left /* 检测到第一个客户端离线了*/
Client 5(socket) has left /* 检测到第二个客户端离线了*/
$ ./client localhost Hello,First! & ./client localhost Hello,Second

 

추천

출처blog.csdn.net/qq_34968572/article/details/107513609