1. socket
#include <sys/types.h>
#include <sys/socket.h>
int socket(int domain, int type, int protocol);
- 创建套接字。
- 成功时返回新建的套接字文件描述符;失败时返回 -1,并设置
errno
。 domain
参数指定了通信域,常用值包括:AF_INET
(IPv4)、AF_INET6
(IPv6)。type
参数指定了通信语义,常用值包括:SOCK_STREAM
(字节流)、SOCK_GRAM
(数据报)。- 自 Linux 2.6.27 以来,
type
参数还可以或运算:SOCK_NONBLOCK
(非阻塞模式)、SOCK_CLOEXEC
(设置 close-on-exec 标志)。 protocol
参数指定要使用何种协议,通常设为 0 即可。
2. bind
#include <sys/types.h>
#include <sys/socket.h>
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
-
将
addr
指定的地址(大小为addrlen
个字节)赋给sockfd
所指向的套接字。 -
成功时返回 0;失败时返回 -1,并设置
errno
。 -
通用地址格式:
struct sockaddr { sa_family_t sa_family; char sa_data[14]; }
-
IPv4 地址格式:
struct sockaddr_in { sa_family_t sin_family; /* address family: AF_INET */ in_port_t sin_port; /* port in network byte order */ struct in_addr sin_addr; /* internet address */ }; /* Internet address. */ struct in_addr { uint32_t s_addr; /* address in network byte order */ };
-
IPv6 地址格式:
struct sockaddr_in6 { sa_family_t sin6_family; /* AF_INET6 */ in_port_t sin6_port; /* port number */ uint32_t sin6_flowinfo; /* IPv6 flow information */ struct in6_addr sin6_addr; /* IPv6 address */ uint32_t sin6_scope_id; /* Scope ID (new in 2.4) */ }; struct in6_addr { unsigned char s6_addr[16]; /* IPv6 address */ };
3. listen
#include <sys/types.h>
#include <sys/socket.h>
int listen(int sockfd, int backlog);
- 将
sockfd
所指向的套接字标记为被动套接字,即后续可以通过accept()
接受到来的连接请求。 - 成功时返回 0;失败时返回 -1,并设置
errno
。 sockfd
所指向套接字的类型需为:SOCK_STREAM
或SOCK_SEQPACKET
。backlog
指定了连接等待队列的最大长度。
4. accept
#include <sys/types.h>
#include <sys/socket.h>
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
#define _GNU_SOURCE
#include <sys/socket.h>
int accept4(int sockfd, struct sockaddr *addr, socklen_t *addrlen, int flags);
- 从
sockfd
所关联的监听套接字的连接队列中提取第一个连接,并新建一个套接字文件描述符来指向该连接(对端套接字)。 - 成功时返回新建的套接字文件描述符;失败时返回 -1,并设置
errno
。 - 于
addr
中返回对端套接字的地址;addrlen
传入时指定addr
所指区域的大小,返回时更新为对端套接字的地址大小。 - 如果对对端套接字的信息不感兴趣,可将
addr, addrlen
设为NULL
。 - 如果连接队列为空,且
sockfd
为阻塞模式,则accept()
将阻塞住。 - 对于
accept4()
来说,如果flags
为 0,则等价于accept()
。 flags
可取值为:SOCK_NONBLOCK
(设置新建套接字为非阻塞模式),SOCK_CLOEXEC
(设置新建套接字的 close-on-exec 标志)。
5. connect
#include <sys/types.h>
#include <sys/socket.h>
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
- 将
sockfd
指向的套接字连接到addr
所指定的地址(大小为addrlen
字节)。 - 成功时返回 0;失败时返回 -1,并设置
errno
。 - 如果
sockfd
所指套接字的类型为SOCK_DGRAM
,则connect()
的作用是设置数据报的默认目的地址为addr
;可调用多次connect()
。如果想解除此设置,可将addr
的sa_family
设为AF_UNSPEC
。 - 如果
sockfd
所指套接字的类型为SOCK_STREAM
或SOCK_SEQPACKET
,则connect()
的作用是发送一个连接请求到addr
所绑定的套接字;通常只能调用一次connect()
。
6. 套接字选项
#include <sys/types.h>
#include <sys/socket.h>
int getsockopt(int sockfd, int level, int optname, void *optval, socklen_t *optlen);
int setsockopt(int sockfd, int level, int optname, const void *optval, socklen_t optlen);
- 获取、设置套接字选项。
- 成功时返回 0;失败时返回 -1,并设置
errno
。 sockfd
指定了要操作哪个套接字,level
指定了选项所处的级别。optname
指定了选项,optval
指定了选项值。optlen
指定了选项值的大小;对于getsockopt()
来说,它是一个值-结果参数,需要在调用时初始化,并在其中返回选项值的大小。- 具体包括哪些选项可查看手册:
man 7 socket
、man 7 tcp
、man 7 udp
。
7. inet_pton
#include <arpa/inet.h>
int inet_pton(int af, const char *src, void *dst);
- 将 IPv4/IPv6 地址字符串转换为二进制格式。
- 成功时返回 1;如果传入的地址字符串格式无效,则返回 0;否则失败返回 -1,并设置
errno
。 af
指定了地址格式,可以为:AF_INET
或AF_INET6
。src
指向地址字符串,dst
指向转换结果。
8. inet_ntop
#include <arpa/inet.h>
const char *inet_ntop(int af, const void *src, char *dst, socklen_t size);
- 将 二进制格式的 IPv4/IPv6 地址转换为地址字符串。
- 成功时返回
dst
,失败时返回NULL
,并设置errno
。 af
指定了地址格式,可以为:AF_INET
或AF_INET6
。src
指向二进制格式的地址,dst
指向转换结果,size
指定了dst
所指内存区域的大小。
9. 字节序转换
#include <arpa/inet.h>
uint32_t htonl(uint32_t hostlong);
uint16_t htons(uint16_t hostshort);
uint32_t ntohl(uint32_t netlong);
uint16_t ntohs(uint16_t netshort);
h
表示host
,即主机字节序;n
表示network
,即网络字节序。
10. recvfrom
#include <sys/types.h>
#include <sys/socket.h>
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,
struct sockaddr *src_addr, socklen_t *addrlen);
- 通过
sockfd
指向的套接字接收数据。 - 成功时返回接收到的字节数;失败时返回 -1,并设置
errno
。 - 将接收到的数据存到
buf
中,len
指定了buf
所指内存区域的大小。 - 在
src_addr
中返回对端地址,addrlen
是一个值-结果参数,需要初始化,并在返回后指明对端地址的大小。 flags
用于指定接收操作的选项,可将其设为 0。
11. sendto
#include <sys/types.h>
#include <sys/socket.h>
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,
const struct sockaddr *dest_addr, socklen_t addrlen);
- 通过
sockfd
指向的套接字发送数据。 - 成功时返回发送的字节数;失败时返回 -1,并设置
errno
。 - 将
buf
中的数据发送出去,len
指定了buf
所指内存区域的大小。 - 对端地址在
dest_addr
中指定,addrlen
为dest_addr
所指内存区域的大小。 flags
用于指定发送操作的选项,可将其设为 0。
12. 例子
注:为简便起见,此处省略了返回值检查。
echo server:
#include <sys/types.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <cstring>
#include <iostream>
#include <string>
static const uint16_t Port = 6666;
static const char* Ip = "127.0.0.1";
static constexpr size_t MsgLen = 16;
static char Buf[MsgLen] = {0};
int main() {
int listenSock = socket(AF_INET, SOCK_STREAM, 0);
int value = 1;
setsockopt(listenSock, SOL_SOCKET, SO_REUSEADDR, &value, sizeof(int));
struct sockaddr_in addr;
memset(&addr, 0, sizeof(addr));
addr.sin_port = htons(Port);
addr.sin_family = AF_INET;
inet_pton(AF_INET, Ip, &addr.sin_addr);
bind(listenSock, (const struct sockaddr*)&addr, sizeof(addr));
listen(listenSock, 4);
while(true) {
int peerSock = accept(listenSock, NULL, NULL);
if (peerSock == -1) {
perror("accept()");
continue;
}
ssize_t n = read(peerSock, Buf, sizeof(Buf));
write(peerSock, Buf, n);
close(peerSock);
}
}
echo client:
#include <sys/types.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <cstring>
#include <iostream>
#include <string>
static const uint16_t Port = 6666;
static const char* Ip = "127.0.0.1";
static const std::string Msg("Hello, World!");
static constexpr size_t MsgLen = 13;
int main() {
int sock = socket(AF_INET, SOCK_STREAM, 0);
struct sockaddr_in addr;
memset(&addr, 0, sizeof(addr));
addr.sin_port = htons(Port);
addr.sin_family = AF_INET;
inet_pton(AF_INET, Ip, &addr.sin_addr);
connect(sock, (const struct sockaddr*)&addr, sizeof(addr));
char buf[MsgLen+1] = {0};
ssize_t n = write(sock, Msg.c_str(), MsgLen);
read(sock, buf, n);
std::cout << "client: " << buf << '\n';
close(sock);
return 0;
}