前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Linux网络编程API(一)

Linux网络编程API(一)

作者头像
doper
发布2022-09-26 17:39:05
1.3K0
发布2022-09-26 17:39:05
举报

相关API笔记(一)

Linux网络编程基础API

1. 主机字节序和网络字节序

代码语言:javascript
复制
#include <netinet/in.h>
unsigned long int htonl( unsigned long int hostlong );
unsigned short int htons( unsigned long int hostlong );
unsigned long int ntohl( unsigned long int netlong );
unsigned short int ntohs( unsigned long int netlong );

htonl即”host to network long”, 即长整型(32bit)的主机字节序转换未网络字节序数据。

长整型函数 (htonl,ntohl)通常用来转换IP地址

短整型函数 (htonl,ntohl)通常用来转换端口号

2. 通用socket地址

这个比较少用

socket网络编程接口中表示socket地址的是结构体sockaddr

代码语言:javascript
复制
#include <bits/socket.h>
struct sockaddr{
    sa_family_t	sa_family;	//地址族类型变量
    char	sa_data[14];	//存放socket地址值
}

地址族类型通常与协议族类型对应。

协议族

地址族

描述

PF_UNIX

AF_UNIX

UNIX本地域协议族

PF_INET

AF_INET

TCP/IPv4协议族

PF_INET6

AF_INET6

TCP/Ipv6协议族

宏PF_*和AF_*都定在bits/socket.h头文件中,且后者与前者有完全相同的值,所以二者通常混用

sa_data存放socket地址值,不同的协议族的地址具有不同的长度

协议族

地址值含义和长度

PF_UNIX

文件的路径名,长度可达到108字节

PF_INET

16bit端口号和32bit IPv4地址,共6字节

PF_INET6

16bit端口号,32bit流标识,128bitIpv6地址,32bit范围ID,共26字节

14字节的sa_data不能容纳协议族的地址值。定义如下新的通用socket地址结构体:

代码语言:javascript
复制
#include <bits/socket.h>
struct sockaddr_storage {
    sa_family_t		sa_family;	//地址族类型变量
    unsigned long int	__ss_align;	//用来内存对齐
    char		__ss_padding[128-sizeof(__ss_align)];	
}

3.专用socket地址

这个比较常用

UNIX本地域协议族使用如下专用socket地址结构体:

代码语言:javascript
复制
#include <sys/un.h>
struct sockaddr_un {
	sa_family_t	sin_family;	//地址族: AF_UNIX
	char		sun_path[108];	//文件路径名
}

TCP/IP协议族sockaddr_insocketaddr_in6,分别用于Ipv4Ipv6

代码语言:javascript
复制
struct sockaddr_in {
    sa_family_t		sin_family;			//地址族: AF_INET
    u_int16_t		sin_port;			//端口号,用网络字节序表示
    struct in_addr	sin_addr;			//IPv4地址结构体
};
struct in_addr {
    u_int32_t		s_addr;				//IPv4地址,要用网络字节序表示
}
代码语言:javascript
复制
struct sockaddr_in6 {
    sa_family_t		sin6_family;		//地址族: AF_INET6
    u_int16_t		sin6_port;			//端口号,用网络字节序表示
    u_int32_t		sin6_flowinfo;		//流信息,应设置为0
    struct in6_addr	sin6_addr;			//IPv6地址结构体
    u_int32_t		sin6_scopt_16;		//scope ID,尚处于实验阶段
};
struct in6_addr {
    unsigned char	sa_addr[16];		//IPv6地址,要用网络字节序表示
}

实际使用时(包括sockaddr_storage)都需要将其转化为通用的socket地址类型sockaddr(强制转换即可),所以的socket编程接口使用的类型都是sockaddr。

4. IP地址转换函数

我们习惯上都是使用点分十进制(Dotted Decimal Notation)表示IP地址,实际上使用的得把它们转换为整数(二进制数)

代码语言:javascript
复制
#include <arpa/inet.h>
in_addr_t inet_addr(const char* strptr);				
int inet_aton(const char* cp, struct in_addr* inp);
char* inet_ntoa(struct in_addr in);

参数:

inet_addr: 点分十进制表示的IPv4转换为网络字节序整数表示的IPv4地址,失败返回INADDR_NONE

inet_aton: 完成与inet_addr相同功能,结果保存在Inp数组中。成功返回1,失败返回0

inet_ntoa: 网络字节序整数表示的IPv4地址转换为点分十进制表示的IPv4。但是该函数内部使用一个静态变量保存结果的,函数的返回值是这个静态内存,多次调用会覆盖到之前调用产生的结果。

下面的更好用,适应于IPv6

代码语言:javascript
复制
#include <arpa/inet.h>
int inet_pton(int af, const char* src, void* dst);
const char* inet_ntop(int af, const void* src, char* dst, socketlen_t cnt);

inet_pton(IP地址src->网络字节序IP,成功返回1,失败返回0并设置errno)

参数:

af: 地址族,AF_INET或者AF_INET6

src: 点分十进制表示的IPv4地址或者十六进制表示的**IPv6地址

dst: 转换的结果指向dst指向的内存

inet_ntop:(与inet_pton相反,成功返回目标存储单元的地址,失败返回NULL并设置errno)

前三个参数与上述相同

cnt: 指定目标存储单元的大小,使用如下两个宏能指定这个大小

代码语言:javascript
复制
#include <netinet/in.h>
#define	INET_ADDRSTRLEN 	16
#define	INET6_ADDRSTRLEN	46

5. 创建socket

代码语言:javascript
复制
#include <sys/types.h>
#include <sys/socket.h>
//成功返回socket文件描述符,失败返回-1并设置errno
int socket(int domain, int type, int protocol);

参数:

domain: 使用哪个底层协议族,TCP/IP协议: PF_INET,PF_INET6, UNIX本地协议族: PF_UNIX

type: 服务类型,取值有SOCK_STREAMSOCK_DGRAM,在TCP/IP中,SOCK_STREAM表示使用TCP,SOCK_DGRAM表示使用UDP。同时也可以传入上述服务类型与下面两个标志的相与的值: SOCK_NONBLOCKSOCK_CLOEXEC。分别表示非阻塞,fork调用创建子进程后在子进程关闭该socket

protocol: 在前两个参数构成的协议集合下再选择一个具体协议。一般情况设置为0即可

6. 命名socket

创建了socket后,我们还需要将对应的地址与其绑定。

代码语言:javascript
复制
#include <sys/types.h>
#include <sys/socket.h>
//成功返回0,失败返回-1并设置errno
//errno的类型: EACCES,表示被绑定地址是受保护的
//			  EADDRINUSE, 被绑定地址正在使用中
int bind(int sockfd, const struct sockaddr* my_addr, socklen_t addrlen);

参数:

sockfd: 要绑定的socket文件描述符

my_addr: socket地址,一般来说为sockaddr_un, sockaddr_in, sockaddr_in6的地址,传入参数时要强制转换为sockaddr*指针类型。

addrlen: 第二个参数my_addr所指向的socket地址的长度。通常使用sizeof()来获取。

7. 监听socket

socket被命名,即绑定后要使用listen函数创建监听队列存放待处理的用户连接

代码语言:javascript
复制
#include <sys/socket.h>
//成功返回0,失败返回-1并设置errno
int listen(int sockfd, int backlog);

参数:

sockfd: 被监听的socket文件描述符。

backlog: 内核监听队列的最大长度

8. 接受连接

下面系统调用从listen监听队列中接受一个连接

代码语言:javascript
复制
#include <sys/types.h>
#include <sys/socket.h>
//成功返回一个新的socket文件描述符,用来唯一标识被接受的这个连接,服务器可以通过读写该socket来与被连接的客户端通信
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);

参数:

sockfd: 执行过listen系统调用的socket文件描述符。

addr: 获取被接受连接的远端socket地址

addrlen: 第二个参数my_addr所指向的socket地址的长度,可以提前声明一个socklen_t类型变量并赋值socket地址的长度,然后传入这个变量的地址。

代码语言:javascript
复制
//例
struct sockaddr_in client;
socklen_t client_addrlength = sizeof(client);
int connfd = accept(sock, (struct sockaddr*)&client, &client_addrlength);

9. 发起连接

服务器通过listen被动接受连接,那么客户端就需要通过connect来主动与服务器建立连接

代码语言:javascript
复制
#include <sys/types.h>
#include <sys/socket.h>
int connect(int sockfd, const struct sockaddr *serv_addr, socklen_t addrlen);

参数:

sockfd: 通过socket系统调用,唯一标识与服务器连接的socket文件描述符

serv_addr: 服务器监听的socket地址

addrlen: 参数二的地址长度

10. 关闭连接

代码语言:javascript
复制
#include <unistd.h>
int close(int fd);

参数:

fd: 要关闭的socket文件描述符

close并非立刻关闭一个连接,只是是把fd的引用计数减1,当fd的引用数完全减为0时,才算真正关闭连接。

多进程中,一个fork系统调用默认会让父进程中打开的socket文件描述符的引用数加一,因此得再父子进程中都调用close才能真正关闭一个连接。

下面系统调用可以立刻终止连接

代码语言:javascript
复制
#include <sys/socket.h>
//成功返回0,失败返回-1并设置errno
int shutdown(int sockfd, int howto);

参数:

sockfd: 要立刻关闭的socket文件描述符

howto: 决定了shutdown的行为,取值如下

可选值

含义

SHUT_RD

关闭sockfd读的这一半。应用程序不能再针对socket文件描述符执行读操作,并且该socket接收缓冲区中的数据都被丢弃

SHUT_WR

关闭sockfd写的这一半。sockfd的发送缓冲区中的数据会在真正关闭连接之前全部发送出去,应用程序不可再对该socket文件描述符执行写操作。这种情况下,连接处于半关闭状态。

SHUT_RDWR

同时关闭sockfd上的读和写

11. 数据读写

1. TCP数据读写

对文件的读写read和write同样适用于socket。下列系统调用专门用于对socket数据读写

代码语言:javascript
复制
#include <sys/types.h>
#include <sys/socket.h>
//函数返回实际读(写)的数据长度,返回0的话意味对方已关闭连接,出错时返回-1并设置errno
ssize_t recv(int sockfd, void *buf, size_t len, int flags);
ssize_t send(int sockfd, cons void *buf, size_t len, int flags);

参数:

sockfd: 要读或写的socker文件描述符

buf: 读写缓冲区的位置

len: 读写缓冲区的大小

flags: 控制参数,如下

选项名

含义

send

revc

MSG_CONFIRM

指示数据链路层协议持续监听对方的回应,直到得到发育。它仅能用于SOCK_DGRAM和SOCK_RAW类型的socket

Y

N

MSG_DONTROUTE

不查看路由表,直接将数据发送给本地局域网络内的主机。这表示发送者确切地知道目标主机就在本地网络上

Y

N

MSG_DONTWAIT

对socket的此次操作将是非阻塞的

Y

Y

MSG_MORE

告诉内核应用程序还有更多数据要发送,内核将超时等待新数据写入TCP发送缓冲区后一并发送。这样可以防止TCP发送过多小的报文段,从而提高传输效率

Y

N

MSG_WAITALL

读操作仅在读取到指定数量的字节后才返回

N

Y

MSG_PEEK

窥探读缓存中的数据,此次读操作不会导致这些数据被清楚

N

Y

MSG_OOB

发送或接收紧急数据

Y

Y

MSG_NOSIGNAL

往读端关闭的管道或者socket连接中写数据时不引发SIGPIPE信号

Y

N

2. UDP数据读写

代码语言:javascript
复制
#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);
ssize_t sendto(int sockfd, cons void *buf, size_t len, int flags, const struct sockaddr* dest_addr, socklen_t addrlen);

参数:

前四个同TCP读写函数一样

src_addr: UDP没有连接的概念,所以每次读数据都要获取发送端(接收端)的socket地址

addrlen: src_addr或dest_addr地址的长度

这两个函数也可用于面向连接(STREAM)的socket的数据读写,只需要把最后两个参数设置为NULL即可。

3. 通用数据读写函数

下面这组接口可以用于TCP流数据,也可用于UDP数据报

代码语言:javascript
复制
#include <sys/socket.h>
ssize_t recvmsg(int sockfd, struct msghdr* msg, int flags);
ssize_t sendmsg(int sockfd, struct msghdr* msg, int flags);

参数:

sockfd: 被操作的目标socket文件描述符

msg: msgaddr结构体类型指针

flags: 控制参数,具体取值同上

msgaddr结构体定义如下:

代码语言:javascript
复制
struct msghdr{
    void*		msg_name;				//socket地址
    socklen_t 		msg_namelen;				//socket地址的长度
    struct iovec* 	msg_iov;				//分散的内存块,见后文
    int 		msg_iovlen;				//分散内存块的数量
    void* 		msg_control;			//指向辅助数据的起使位置
    socklen_t 		msg_controllen;			//辅助数据的大小
    int 		msg_flags;				//复制函数中的flags参数,并在调用过程中更新
};``
//iovec封装了一块内存的起始位置和长度
struct iovec{
    void*		iov_base;				//内存起始地址
    size_t 		iov_len;				//这块内存的长度
};

msghdr成员变量:

msg_name: 指向一个socket地址结构变量,指定通信对方的socket地址。对于面向连接的TCP协议他必须设置为NULL。

msg_name: socket地址的长度

msg_iovlen: iovec结构对象的个数

msg_controlmsg_controllen: 用于辅助数据的传送

msg_flags: 会复制recvmsg和sendmsg的flags参数的内容。

12. 带外标记

内核通知应用程序带外数据到达的两种常见方法:I/O复用产生的异常事件,SIGURG信号。

应用程序得到了有带外数据需要接收的通知,还需要知道带外数据在数据流中的具体位置。

sockatmark判断sockfd是否处于带外标记,即下一个被读取到的数据是否是带外数据

代码语言:javascript
复制
#include <sys/socket.h>
//成功返回1,失败返回0
//若成功了,我们就可以利用带MSG_OOB标志的recv调用来接收带外数据
int sockatmark(int sockfd);

13. 地址信息函数

想知道连接socket的本端socket地址,以及远端的socket地址,可以使用如下函数

代码语言:javascript
复制
#include <sys/socket.h>
//获取sockfd对应的本端socket地址,成功返回1,失败返回-1并设置errno
int getsockname(int sockfd, struct sockaddr* address, socklen_t* address_len);
//获取sockfd对应的远端socket地址,成功返回1,失败返回-1并设置errno
int getpeername(int sockfd, struct sockaddr* address, socklen_t* address_len);

参数:

sockfd: socket文件描述符

address: 存储对应的socket地址

address_len: socket地址的长度,注意其要传入一个socklen_t的指针变量

如果实际socket的地址长度大于address所指内存区的大小,则地址会被截断。

14. socket选项

下面两个函数用来读取设置socket文件描述符属性

代码语言:javascript
复制
#include <sys/socket.h>
//两个函数都是成功返回1,失败返回0并设置errno
int getsockopt(int sockfd, int level, int option_name, void* option_value, socklen_t* restrict option_len);
int setsockopt(int sockfd, int level, int option_name, const void* option_value, socklen_t* restrict option_len);

参数:

sockfd: socket文件描述符

level: 要操作哪个协议的选项(属性),如IPv4,IPv6,TCP等

option_name: 指定选项的名字

option_value: 被操作的选项的值

option_len: 被操作的选项的长度

mmexport1615097154410.png
mmexport1615097154410.png

15. 网络信息API

  1. gethostbyname和gethostbyaddr
代码语言:javascript
复制
#include <netdb.h>
//根据主机名获取主机的完整信息
struct hostnet* gethostbyname(const char* name);
//根据IP地址获取主机的完整信息
struct hostnet* gethostbyaddr(const void* addr, size_t len, int type);

struct hostnet{
    char*	h_name;			//主机名
    char**	h_aliases;		//主机别名列表,可能有多个
    int		h_addrtype;		//地址类型(地址族)
    int		h_length;		//地址长度
    char**	h_addr_list;	//按网络字节序 列出的主机IP地址列表
}

参数:

name: 指定目标主机的主机名(如localhost)

addr: 指定目标主机的IP地址

len: addr所指IP地址的长度

type: addr所指IP地址的类型,包括AF_INETAF_INET6

  1. getservbyname和getservbyport
代码语言:javascript
复制
#include <netdb.h>
//根据服务名称获取某个服务的完整信息
struct servent* getservbyname(const char* name, const char* proto);
//根据服务端口号获取某个服务的完整信息
struct servent* getservbyport(int port, const char* proto);

struct servent{
    char*	s_name;			//服务名称
    char**	s_aliases;		//服务的别名列表,可能有多个
    int		s_port;			//服务对应的端口号
    char*	s_proto;		//服务的类型,通常是tcp或者udp
}

参数:

name: 目标服务的名字

proto: 指定服务类型,如传递”tcp“表示获取流服务,传递“udp”表示获取数据报服务,传递NULL表示获取所有类型的服务

port: 目标服务对应的端口号

  1. getaddrinfo

getaddrinfo函数能通过主机名获得IP地址(内部使用gethostbyname)也能通过服务名获得端口号(内部使用getservbyname)

代码语言:javascript
复制
#include <netdb.h>
int getaddrinfo(const char* hostname, const char* service, const struct addrinfo* hints, struct addrinfo** result);

//由于getaddrin隐式分配堆内存到result,因此需要使用这个函数释放内存
void freeaddrinfo(struct addrinfo* res);

struct addrinfo{
    int			ai_flags;		//见后文
    int			ai_family;		//地址族
    int			ai_socktype;	//服务类型,SOCK_STREAM或SOCK_DGRAM
    int			ai_protocal;	//见后文
    socklen_t		ai_addrlen;		//socket地址ai_addr的长度
    char*		ai_canonname;	//主机的别名
    struct sockaddr*	ai_addr;		//指向socket地址
    struct addrinfo*	ai_next;		//指向下一个socketinfo结构对象
}
  1. getnameinfo

getnameinfo函数能通过socket地址同时获得以字符串表示的主机名(内部使用gethostbyaddr)服务名(内部使用的是getservbyport)

代码语言:javascript
复制
#include <netdb.h>
int getnameinfo(const struct sockaddr* sockaddr, socklen_t addrlen, char* host, socklen_t hostlen, char* serv, socklen_t servlen, int flags);
本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2021-03-06,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 相关API笔记(一)
  • 1. 主机字节序和网络字节序
  • 2. 通用socket地址
  • 3.专用socket地址
  • 4. IP地址转换函数
  • 5. 创建socket
  • 6. 命名socket
  • 7. 监听socket
  • 8. 接受连接
  • 9. 发起连接
  • 10. 关闭连接
  • 11. 数据读写
    • 1. TCP数据读写
      • 2. UDP数据读写
        • 3. 通用数据读写函数
        • 12. 带外标记
        • 13. 地址信息函数
        • 14. socket选项
        • 15. 网络信息API
        相关产品与服务
        NAT 网关
        NAT 网关(NAT Gateway)提供 IP 地址转换服务,为腾讯云内资源提供高性能的 Internet 访问服务。通过 NAT 网关,在腾讯云上的资源可以更安全的访问 Internet,保护私有网络信息不直接暴露公网;您也可以通过 NAT 网关实现海量的公网访问,最大支持1000万以上的并发连接数;NAT 网关还支持 IP 级流量管控,可实时查看流量数据,帮助您快速定位异常流量,排查网络故障。
        领券
        问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档