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