Linux 网络编程基础 API

本文是《Linux 高性能服务器编程》阅读记录,供以后查阅参考。推荐阅读原书。

主机字节序和网络字节序转换函数

1
2
3
4
5
6
#include <netinet/in.h>

uint32_t ntohl (uint32_t __netlong); // network to host long
uint16_t ntohs (uint16_t __netshort); // network to host short
uint32_t htonl (uint32_t __hostlong);
uint16_t htons (uint16_t __hostshort);

其中,int32 型函数通常用来转换 IP 地址;int16 型函数通常用来转换端口号。

IP 地址转换函数

1
2
3
4
5
6
7
8
9
10
11
12
13
#include <arpa/inet.h>

/* Convert Internet host address from numbers-and-dots notation in CP
into binary data in network byte order. */
in_addr_t inet_addr (const char *__cp); // 失败时返回 INADDR_NONE

/* Convert Internet host address from numbers-and-dots notation in CP
into binary data and store the result in the structure INP. */
int inet_aton (const char *__cp, struct in_addr *__inp);

/* Convert Internet number in IN to ASCII representation. The return value
is a pointer to an internal array containing the string. */
char *inet_ntoa (struct in_addr __in); // 不可重入(如需要记录点分十进制 IP 地址,需拷贝到另外内存地址)

创建 socket

1
2
3
4
5
6
7
8
9
10
#include <sys/types.h>
#include <sys/socket.h>

/* Create a new socket of type TYPE in domain DOMAIN, using
protocol PROTOCOL. If PROTOCOL is zero, one is chosen automatically.
Returns a file descriptor for the new socket, or -1 for errors. */
int socket (int __domain, int __type, int __protocol);
// domain 参数可选值: PF_INET 表示 IPv4;PF_INET6 表示 IPv6
// type 参数可选值:SOCK_STREAM 表示 TCP;SOCK_UGRAM 表示 UDP
// protocol 一般都设置为 0,表示使用默认协议

命名 socket

服务端需要调用 bind 函数以给 socket 命名地址(IP 地址、端口号等),定义如下:

1
2
3
4
5
#include <sys/types.h>
#include <sys/socket.h>

/* Give the socket FD the local address ADDR (which is LEN bytes long). */
int bind (int __fd, const struct sockaddr* __addr, socklen_t __len);

监听 socket

使用 listen 函数创建监听队列以存放待处理的客户端连接:

1
2
3
4
5
6
#include <sys/socket.h>

/* Prepare to accept connections on socket FD.
N connection requests will be queued before further requests are refused.
Returns 0 on success, -1 for errors. */
int listen (int __fd, int __n);

接受连接

1
2
3
4
5
6
7
8
9
10
#include <sys/types.h>
#include <sys/socket.h>

/* Await a connection on socket FD.
When a connection arrives, open a new socket to communicate with it,
set *ADDR (which is *ADDR_LEN bytes long) to the address of the connecting
peer and *ADDR_LEN to the address's actual length, and return the
new socket's descriptor, or -1 for errors.
*/
int accept (int __fd, struct sockaddr *__addr, socklen_t *__addr_len);

发起连接

客户端需要通过如下调用主动与服务器建立连接:

1
2
3
4
5
6
7
8
#include <sys/types.h>
#include <sys/socket.h>

/* Open a connection on socket FD to peer at ADDR (which LEN bytes long).
For connectionless socket types, just set the default address to send to
and the only address from which to accept transmissions.
Return 0 on success, -1 for errors. */
int connect (int __fd, const struct sockaddr *__addr, socklen_t __len);

关闭连接

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <unistd.h>

int close(int fd); // 实际上该函数将 fd 引用计数减 1,只有当 fd 引用计数为 0 时,才真正关闭连接

// 可使用如下 shutdown 函数立即终止连接,而不是将 socket 引用计数减 1

#include <sys/socket.h>

/* Shut down all or part of the connection open on socket FD.
HOW determines what to shut down:
SHUT_RD = No more receptions;
SHUT_WR = No more transmissions;
SHUT_RDWR = No more receptions or transmissions.
Returns 0 on success, -1 for errors. */
int shutdown (int __fd, int __how);

TCP 数据读写

1
2
3
4
5
6
7
8
9
10
11
#include <sys/types.h>
#include <sys/socket.h>

/* Read N bytes into BUF from socket FD.
Returns the number read or -1 for errors.
*/
ssize_t recv (int __fd, void *__buf, size_t __n, int __flags);

/* Send N bytes of BUF to socket FD. Returns the number sent or -1.
*/
ssize_t send (int __fd, const void *__buf, size_t __n, int __flags);

UDP 数据读写

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include <sys/types.h>
#include <sys/socket.h>


/* Read N bytes into BUF through socket FD.
If ADDR is not NULL, fill in *ADDR_LEN bytes of it with tha address of
the sender, and store the actual size of the address in *ADDR_LEN.
Returns the number of bytes read or -1 for errors.
*/
ssize_t recvfrom (int __fd, void *__buf, size_t __n,
int __flags, struct sockaddr* __addr,
socklen_t * __addr_len);


/* Send N bytes of BUF on socket FD to peer at address ADDR (which is
ADDR_LEN bytes long). Returns the number sent, or -1 for errors.
*/
ssize_t sendto (int __fd, const void *__buf, size_t __n,
int __flags, const struct sockaddr* __addr,
socklen_t __addr_len);

通用数据读写函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
#include <sys/socket.h>


/* Receive a message as described by MESSAGE from socket FD.
Returns the number of bytes read or -1 for errors.
*/
ssize_t recvmsg (int __fd, struct msghdr *__message, int __flags);


/* Send a message described MESSAGE on socket FD.
Returns the number of bytes sent, or -1 for errors.
*/
ssize_t sendmsg (int __fd, const struct msghdr *__message, int __flags);

// msghdr 结构体定义
/* Structure describing messages sent by
`sendmsg' and received by `recvmsg'. */
struct msghdr
{
void *msg_name; /* Address to send to/receive from. */
socklen_t msg_namelen; /* Length of address data. */

struct iovec *msg_iov; /* Vector of data to send/receive into. */
size_t msg_iovlen; /* Number of elements in the vector. */

void *msg_control; /* Ancillary data (eg BSD filedesc passing). */
size_t msg_controllen; /* Ancillary data buffer length.
!! The type should be socklen_t but the
definition of the kernel is incompatible
with this. */

int msg_flags; /* Flags on received message. */
};

// iovoc 结构体定义
/* Structure for scatter/gather I/O. */
struct iovec
{
void *iov_base; /* Pointer to data. */
size_t iov_len; /* Length of data. */
};

带外标记

1
2
3
4
5
#include <sys/socket.h>

/* Determine whether socket is at a out-of-band mark. */
int sockatmark (int __fd);
// 判断下一个被读取到的数据是否是带外数据

地址信息函数

1
2
3
4
5
6
7
8
#include <sys/socket.h>

/* Put the local address of FD into *ADDR and its length in *LEN. */
int getsockname (int __fd, struct sockaddr* __addr, socklen_t * __len);

/* Put the address of the peer connected to socket FD into *ADDR
(which is *LEN bytes long), and its actual length into *LEN. */
int getpeername (int __fd, struct sockaddr* __addr, socklen_t * __len);

socket 选项

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include <sys/socket.h>

/* Put the current value for socket FD's option OPTNAME at protocol level LEVEL
into OPTVAL (which is *OPTLEN bytes long), and set *OPTLEN to the value's
actual length. Returns 0 on success, -1 for errors. */
int getsockopt (int __fd, int __level, int __optname,
void * __optval,
socklen_t * __optlen);


/* Set socket FD's option OPTNAME at protocol level LEVEL
to *OPTVAL (which is OPTLEN bytes long).
Returns 0 on success, -1 for errors. */
int setsockopt (int __fd, int __level, int __optname,
const void *__optval, socklen_t __optlen);

// level 参数指定要操作哪个协议的选项,如 IPv4、IPv6、TCP 等

SO_REUSEADDR 选项

该选项可以让服务器程序强制使用被处于 TIME_WAIT 状态的连接占用的 socket 地址,例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <assert.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>

int main(int argc, char *argv[])
{
if (argc <= 2)
{
printf("usage: %s ip_address port_number\n", basename(argv[0]));
return 1;
}
const char *ip = argv[1];
int port = atoi(argv[2]);

int sock = socket(PF_INET, SOCK_STREAM, 0);
assert(sock >= 0);
int reuse = 1;
setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse)); // set SO_REUSEADDR

struct sockaddr_in address;
bzero(&address, sizeof(address));
address.sin_family = AF_INET;
inet_pton(AF_INET, ip, &address.sin_addr);
address.sin_port = htons(port);
int ret = bind(sock, (struct sockaddr *)&address, sizeof(address));
assert(ret != -1);

ret = listen(sock, 5);
assert(ret != -1);

struct sockaddr_in client;
socklen_t client_addrlength = sizeof(client);
int connfd = accept(sock, (struct sockaddr *)&client, &client_addrlength);
if (connfd < 0)
{
printf("errno is: %d\n", errno);
}
else
{
char remote[INET_ADDRSTRLEN];
printf("connected with ip: %s and port: %d\n",
inet_ntop(AF_INET, &client.sin_addr, remote, INET_ADDRSTRLEN), ntohs(client.sin_port));
close(connfd);
}

close(sock);
return 0;
}

网络信息 API

gethostbyname 和 gethostbyaddr

gethostbyname 根据主机名称获取主机完整信息。其首先在本地 /etc/hosts 配置文件查找主机,若没找到,访问 DNS 服务。

gethostbyaddr 根据 IP 地址获取主机的完整信息。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <netdb.h>

struct hostent* gethostbyname(const char* name);
struct hostent* gethostbyaddr(const void* addr, size_t len, int type);

/* Description of data base entry for a single host. */
struct hostent
{
char *h_name; /* Official name of host. */
char **h_aliases; /* Alias list. */
int h_addrtype; /* Host address type. */
int h_length; /* Length of address. */
char **h_addr_list; /* List of addresses from name server. */
};

getservbyname 和 getservbyport

getservbyname 根据名称获取某个服务完整信息;

getservbyport 根据端口号获取某个服务完整信息。

两个函数实际上都通过读取 /etc/services 文件来获取服务信息。

1
2
3
4
5
6
7
8
9
10
11
12
13
#include <netdb.h>

struct servent* getservbyname(const char* name, const char* proto);
struct servent* getservbyport(int port, const char* proto);

/* Description of data base entry for a single service. */
struct servent
{
char *s_name; /* Official service name. */
char **s_aliases; /* Alias list. */
int s_port; /* Port number. */
char *s_proto; /* Protocol to use. */
};

Linux 网络编程基础 API
https://arcsin2.cloud/2023/08/11/Linux 网络编程基础 API/
作者
arcsin2
发布于
2023年8月11日
许可协议