[Computer Network] Interpretation of Network Programming Interface Socket API (1)

        Socket is an API exposed by the network protocol stack to programmers. Compared with complex computer network protocols, API abstracts key operations and configuration data, which simplifies programming.

        The socket content described in this article comes from the man tool on the Linux distribution centos 9, and there will be some differences between other platforms (such as os-x and different versions). This article mainly introduces each API in detail to better understand socket programming.

1.socket() 

Comply with POSIX.1-2001, POSIX.1-2008, 4.4BSD

1.Library

标准 c 库,libc, -lc

2.Header file

<sys/socket.h>

3.Interface definition

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

4. Interface description

        Create a communication endpoint (each end of the communication can become an endpoint) and return the file descriptor pointing to the endpoint. socket() is the prerequisite for all other operations, such as creating a socket before further setting up and using the network.

5. Parameters

  • domain
    socket 的网络协议簇,通常包括:
    AF_INET ipv4     网络协议
    AF_INET6 ipv6    网络协议(有时也会兼容 ipv4)
    AF_UNIX          本地socket,也就是我们常说的 domain socket
  • type 
    SOCK_STREAM      (TCP)有序、可靠、双向的基于连接的字节流,可选支持带外数据传输
    SOCK_DGRAM       (UDP)数据报文传输,非连接的、不可靠的、有固定最大长度
    SOCK_SEQPACKET   (不粘包的 TCP)有序、可靠、双向具有最大数据报长度的传输
    SOCK_RAW          提供原始网络协议访问
    SOCK_RDM          提供可靠的数据报传输层,但是不保证报文顺序
    

Note: Some types may not implement the protocol family

Out-of-band data transmission refers to the logic of TCP in emergency situations by adjusting the position of the message in the send/receive buffer and adding an emergency mark to the data packet. 

  • protocol
    指定具体的传输协议,比如 IPPROTO_TCP, IPPROTO_SCTP, IPPROTO_UDP, IPPROTO_DCCP,在netinet/in.h 中定义

6.Return value

        Returns -1 when an error occurs, sets errno to indicate the error code, otherwise returns a newly created integer file descriptor.

        Possible error codes include:

error code meaning
EACCES No permission to create the corresponding socket
EAFNOSUPPORT The implementation does not support the specified AF_ address family
SINGLE CHOICE Unknown protocol or address family is not available
SINGLE CHOICE type parameter is illegal
DEAD Process file descriptor reaches maximum limit
PUT ON System file descriptor limit reached
ENOBUFS or ENOMEM Not enough storage
EPROTONOSUPPORT domain does not support the specified protocol type

2. bind

Comply with POSIX.1-2008

1.Library

标准 c 库,libc, -lc

2.Header file

<sys/socket.h>

3.Interface definition

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

4. Interface description

        After a socket is created through the socket() interface, the socket only exists in the name space and no actual address is assigned to it. The bind interface assigns the IP address specified by addr to the socket specified by the file descriptor sockfd. addrlen specifies the byte length of the address structure pointed to by the addr pointer. Previously we performed this operation to assign a name to the socket.

        Usually before a TCP_STREAM socket accepts a connection, a local address needs to be assigned to the socket through bind.

        Name binding rules vary with address families.

         The data structure of addr also changes with the change of address family. The sockaddr structure is defined similarly:

 struct sockaddr {
      sa_family_t sa_family;
      char        sa_data[14];
 }

         This structure definition is mainly to prevent the compiler from reporting errors, and it mainly performs forced conversion of various address structures.

5. Return value

        Returns -1 when an error occurs, sets errno to indicate the error code, otherwise returns a newly created integer file descriptor.

        Possible error codes include:

error code meaning
EACCES The address is a protected address and the user is not a superuser
EADDRINUS The specified address is already in use
EADDRINUS

For domain socket, the port number is in the address structure

Specified as 0, but when trying to bind to the ephemeral port, there is no free ephemeral port.

EBADF sockfd is not a valid file descriptor
SINGLE CHOICE The socket has been bound to an address
SINGLE CHOICE addrlen error, or addr is not a usable domain address
ENOTSOCK The file descriptor does not point to any socket
UNIX domain (AF_UNIX) specific error code
EACCESS No search permission under path prefix
EADDRNOTAVAIL The requested interface does not exist or is not a local interface
EFAULT addr points to an address space that is inaccessible to the user
ELOOP Too many symbolic links encountered while resolving address
ENAMETOOLONG Address is too long
ENOENT The specified path does not exist
ENOMEM Out of kernel memory
ENOTDIR path prefix is ​​not a directory
EROFS The socket inode is located in a read-only file system

6. Sample code

       #include <stdio.h>
       #include <stdlib.h>
       #include <string.h>
       #include <sys/socket.h>
       #include <sys/un.h>
       #include <unistd.h>

       #define MY_SOCK_PATH "/somepath"
       #define LISTEN_BACKLOG 50

       #define handle_error(msg) \
           do { perror(msg); exit(EXIT_FAILURE); } while (0)

       int
       main(void)
       {
           int                 sfd, cfd;
           socklen_t           peer_addr_size;
           struct sockaddr_un  my_addr, peer_addr;

           sfd = socket(AF_UNIX, SOCK_STREAM, 0);
           if (sfd == -1)
               handle_error("socket");

           memset(&my_addr, 0, sizeof(my_addr));
           my_addr.sun_family = AF_UNIX;
           strncpy(my_addr.sun_path, MY_SOCK_PATH,
                   sizeof(my_addr.sun_path) - 1);

           if (bind(sfd, (struct sockaddr *) &my_addr,
                    sizeof(my_addr)) == -1)
               handle_error("bind");

           if (listen(sfd, LISTEN_BACKLOG) == -1)
               handle_error("listen");

           /* Now we can accept incoming connections one
              at a time using accept(2). */

           peer_addr_size = sizeof(peer_addr);
           cfd = accept(sfd, (struct sockaddr *) &peer_addr,
                        &peer_addr_size);
           if (cfd == -1)
               handle_error("accept");

           /* Code to deal with incoming connection(s)... */

           if (close(sfd) == -1)
               handle_error("close");

           if (unlink(MY_SOCK_PATH) == -1)
               handle_error("unlink");
       }

 3. accept

1.Library

标准 c 库,libc, -lc

2.Header file

<sys/socket.h>

3.Interface definition

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

#define _GNU_SOURCE             /* See feature_test_macros(7) */
#include <sys/socket.h>

int accept4(int sockfd, struct sockaddr *_Nullable restrict addr,
                  socklen_t *_Nullable restrict addrlen, int flags);

4. Interface description

        The accept system call is used for connection-oriented sockets (SOCK_STREAM, SOCK_SEQPACKET). It will get the first connection request from the waiting connection queue of the listening socket (sockfd), create a new connection socket, and return a new The file description points to this new socket. The newly created socket is not in the listening state, and the original socket (sockfd) will not be affected in any way.

        sockfd is created by sockect(), bound to the local address through bind, and listens for connections through listen.

        addr is a pointer to the sockaddr structure, and this address is filled with the address of the peer socket. The structure type of the returned addr varies according to the socket address family. When addr is NULL, the bottom layer will not fill it. In this case, addrlen is useless and should also be NULL.

        The addrlen parameter is an input and output parameter. The caller must initialize it with the size of the structure pointed to by addr, and when returning, it will be filled with the actual size of the peer address.

        If the supplied buffer is too small, the returned address will be truncated, in which case addrlen will return a value larger than the supplied value.

        If there are no pending connection requests in the current waiting connection queue and the socket is not set to non-blocking, accept() will always block. If the socket is set to non-blocking, accept() will report an error of EAGAIN or EWOULDBLOCK.

        In order to obtain a connection request on the socket, we need to use select, poll, and epoll. When a new connection comes, a readable event will be generated, and we can use accept to continue to obtain a socket from the connection.

        We can also set the SIGIO signal to be sent when there is a connection on the socket.

        If flag is 0, then accept4() is equivalent to accept(). flag can be one of the following configured values ​​to achieve different behaviors:

  • SOCK_NONBLOCK

                 Set the O_NONBLOCK attribute of the new file descriptor

  • SOCK_CLOEXEC

                 Set the FD_CLEXEC attribute of the new file descriptor.

5.Return value

        On success, returns the file descriptor (non-negative value) of the newly received socket.

        When an error occurs, -1 is returned, errno is set as the error code, and addrlen will not be modified.

  • Error handling

        Linux's accept() will also return a value for existing network errors. This behavior is different from the behavior of other BSD socket implementations. For reliability, we should handle accept returning network errors, which are protocol-related. For example, EAGAIN means retransmission. In the TCP/IP scenario, there are also ENETDOWN, EPROTO, ENOPROTOOPT, EHOSTDOWN, ENONET, EHOSTUNREACH, EOPNOTSUPP, ENETUNREACH, etc. that need to be processed.

       Possible error codes include:

error code meaning
EAGAIN or EWOULDBLOCK The socket is set to non-blocking and there are currently no available connections. POSIX.1-2001 and POSIX.1-2008 allow any error in the scope, and they do not have the same value, so for portability, they need to be judged separately.
ECONNABORTED The connection has been interrupted
EFAULT

addr is not a writable address in the user address space

EBADF sockfd is not an open file descriptor
SINGLE CHOICE The socket is not listening for connections or the addrlen is invalid.
SINGLE CHOICE accept4, flags value is illegal
ENOTSOCK The file descriptor does not point to any socket
EINTR The system call was interrupted by a signal before the connection was reached.
DEAD The number of process descriptors reaches the upper limit
EFAULT addr points to an address space that is inaccessible to the user
PUT ON System file descriptor limit reached
ENAMETOOLONG 地址太长
ENOENT 指定路径不存在
ENOMEM或ENOBUFS 内核内存不足
EOPNOTSUPP socket 不 SOCK_STREAM 类型
EPERM 防火墙禁止连接
EPROTO 协议错误

Linux 上,新创建的 socket 并不会从监听 socket 上继承 O_NONBLOCK 和 O_AYSNC 属性,这点和 canonical BSD socket 实现的行为不同。所以,实现可移植的程序不应该依赖这些行为。

值得注意的是,有时在我们收到 SIGIO 或者通过select、poll、epoll 获得到一个刻度的事件时,并不一定就会有一个连接等待连接,这是因为连接很可能会被异步网络错误或者其他线程通过 accept() 拿走了。在这种情况下,就会导致 accept 阻塞,直到下一个连接到达。为了保证 accept 永远不会阻塞,传来的 socketfd 需要有 O_NONBLOCK 属性。

在最初的 BSD socket 实现中,accept 的第三个参数是 int *,在 POSIX.1g 草稿版标准想把它改成 size_t *C,后来 POSIX 标准和glibc 2.x 定为 socket_t *。

遵循:

accept()   POSIX.1-2008

accept4    Linux 

Guess you like

Origin blog.csdn.net/BillyThe/article/details/132729143