Linux网络编程的基础API总结

1. socket API

先理解主机字节序网络字节序
我们都知道不同的操作系统分为大端地址(高字节存低地址)和小端地址(高字节存高地址,低字节存低地址)。
小端字节序:主机字节序
大端字节序:网络字节序

区分大端和小端code:

void byteorder() {
		union{
			int value;
			char union_bytes[sizeof(int)]; 
		}test;
		test.value = 0x0102;
		
		if((test.union_bytes[0]==1) && (test.union_bytes[1]==2))
			printf("big endian\n");
		else 
			printf("little endian\n");
}

Linux 提供了4个函数来完成主机字节序和网络字节序的转换:

#include <netinet/in.h>
unsigned long int htonl(unsigned long int hostlong);
unsigned short int htons(unsigned short int hostshort);
unsigned long int ntohl(unsigned long int netlong);
unsigned short int ntohs(unsigned short int netshort);

htonl和htons 表示:host to net 转换, htonl 一般用来转换IP地址,htons一般用来转换端口号。ntohl和ntohs 相反。

sockaddr的结构体:

TCP IPv4:

#include <bits/socket.h>
struct sockaddr_in
{
	sa_family_t sin_family;   //地址族: AF_INET
	u_int16_t sin_port;       // 端口号,使用网络字节序
	struct in_addr sin_addr;  //IPV4地址结构体
};

sa_family_t 表述地址族类型:
在这里插入图片描述我们经常看到PF和AF, 含义相同。

地址结构体:

struct in_addr
{
	u_int32_t s_addr;          //IPV4地址,要用网络字节序表示
};

TCP IPv6结构体:

struct sockaddr_in16
{
	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_scope_in;    //scope ID
}
struct in6_addr
{
	unsigned char sa_addr[16];  //IPV6地址,要用网络字节序表示
}

UDP的结构体:

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

IP地址转换函数:

#include <arpa/inet.h>
in_addr_t inet_addr(const char* strptr);
int inet_pton(int af, const char* src, void *dst);
char* inet_ntoa(struct in_addr in);
const char* inet_ntop(int af, const void * src, char* dst, socklen_t cnt);

inet_addr:将十进制字符串表示的IPV4地址转换为网络字节序整数表示的IPV4地址。
inet_pton:用字符串表示的src转换成用网络字节序表示的IP地址,并将转换结果存储在dst指向的内存中,af: 地址族(AF_INET),成功时返回1,失败返回0并设置errno。
inet_ntoa:将网络字节序转换为十进制表示的字符串。
inet_ntop: 和inet_pton进行相反的转换。cnt: 指定目标存储单元的大小。成功时返回目标单元的地址,失败返回MULL并设置errno。可以用下面的两个宏进行指定:

#include <netinet/in.h>
#define INET_ADDRSTRLEN 16
#define INET6_ADDRSTRLEN 46

2. 创建socket

#include <sys/types.h>
#include <sys/socket.h>
int socket(int domain, int type, int protocol);

domain:协议族,TCP 设置为AF_INET, UDP设置为PF_UNIX;
type:指服务类型。SOCK_STREAM 流服务, SOCK_UGRAM 数据报服务。
protol: 表示协议, 设置为0,让操作系统自己选择。
调用成功返回一个描述符,失败返回-1并设置errno;

3 bind

将一个socket与socket地址进行绑定。

#include <sys/types.h>
#include <sys/socket.h>
int bind(int sockfd, const struct sockaddr* my_addr, socklen_t addrlen)

sockfd: 创建socket的fd
my_addr: sockaddr_in的结构体
addrlen: sizeof(struct sockaddr_in)的大小

4. listen

#include <sys/socket.h>
int listen(int sockfd, int backlog);

sockfd: 创建socket的fd
backlog: 是指所有处于半连接状态(三次握手 SYN_RCVD)和完全连接状态(ESTABLISHED)的socket 的上限。自内核2.2之后,它只表示处于完全连接状态的socket的上限,由/proc/sys/net/ipv4/tcp_max_syn_backlog内核参数定义。backlog参数的典型值是5。
函数成功返回0, 失败则返回-1并设置errno.

5. accept

#include <sys/socket.h>
#include <sys/types.h>
int accept(int sockfd, struct sockaddr* addr, socklen_t *addrlen);

sockfd: 创建socket的fd.
addr: struct sockaddr_in 结构体地址
addrlen: sizeof(struct sockaddr_in)
accept成功时返回一个新的连接socket, 失败时返回-1

6. connect

客户端需要调用connect来主动与服务器建立连接。

#include <sys/socket.h>
#include <sys/types.h>
int connect(int sockfd, const struct sockaddr* serv_addr, socklen_t addrlen);

sockfd: 创建socket的fd.
serv_addr: 服务器监听的socket地址
addrlen: 指定这个地址的长度

7. close

关闭对应的socket

#include <unistd.h>
int close(int fd);

fd参数是待关闭的socket。 不过,close系统调用并非总是立即关闭一个连接,而是将fd的引用计数减1.只有当fd的引用计数为0时,才真正关闭连接。

如果无论如何都要立即终止连接(不是将socket的引用计数减1),可以使用如下的shutdown系统调用。

#include <sys/socket.h>
int shutdown(int sockfd, int howto)

sockfd 是待关闭的socket。 howto参数决定了shutdown的行为。值可取下表
在这里插入图片描述

8. 数据读写

TCP流数据的读写系统调用是:

#include <sys/types.h>
#include <sys/socket.h>
ssize_t recv(int sockfd, void *buf, size_t len, int flags);
ssize_t send(int sockfd, const void *buf, size_t len, int flags);

buf和len分别是读写缓冲区的大小和长度,flags通常设置为0. recv成功返回实际读取到的数据的长度,它可能小于我们的期望长度len。因此我们可能需要多次调用recv,才能读取到完整的数据。recv可能返回0,意味着通信的对方已经关闭连接了。recv出错时返回-1并设置errno。send成功时返回实际写入的数据的长度,失败则返回-1并设置errno。flags为数据提供了额外的控制,通常设为0.
在这里插入图片描述
UDP数据读写:

#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, const void *buf, size_t len, int flags, struct sockaddr* dst_addr, socklen_t* addrlen);

因为UDP通信没有连接的概念,所以我们每次读取数据都需要获取发送端的socket地址,即参数src_addr所指的内容,addrlen参数则指定该地址的长度。也可用于TCP,只需要把最后两个参数设置为NULL,因为TCP已经知道了对端的地址。

通用的数据读写:

#include <sys/socket.h>
ssize_t recvmsg(int sockfd, struct msghdr* msg, int flags);
ssize_t sendmsg(int sockfd, struct msghdr* msg, int flags);

msg参数是msghdr结构体类型的指针,定义如下:

struct msghdr
{
	void* msg_name;               //socket地址
	socklen_t  msg_namelen;       //socket
	struct iovec*msg_iov;         //分散的内存块
	void *msg_control;           //分散的内存块数量
	socklen_t msg_controllen;     //指向辅助数据的起始位置
	int msg_flags;               //复制函数中的flags参数, 并在调用过程中更新
};
struct iovec
{
	void *iov_base;     //内存的起始地址
	size_t iov_len;     //这块内存的长度
};

recvmsg数据被读取并存放在msg_iovlen块分散的内存中,这些内存的位置和导航度则由msg_iov指向的数组指定,这称为分散读。sendmsg, msg_iovlen块分散内存中的数据一并发送,这称为集中写。

9. 带外标记

#include <sys/socket.h>
int sockatmark(int sockfd);

sockatmark判断sockfd是否处于带外标记,如果是,sockatmark返回1, 可以利用带MSG_OOB标志的recv调用来接收带外数据。如果不是,则sockatmark返回0。

10. socket选项

#include <sys/socket.h>
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 option_len);

level指定要操作哪个协议的选项。option_value和option_len分别是被操作选项的值和长度。具体如下:
在这里插入图片描述
函数成功时返回0,失败时返回-1并设置errno。

这个需要注意几个参数比较重要:
SO_REUSEADDR
TCP连接的TIME_WAIT状态,服务器可以通过设置socket选项SO_REUSEADDR来强制使用被处于TIME_WAIT状态的连接占用的socket地址。具体如下:

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

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));

SO_RCVBUF和SO_SNDBUF
这两个参数分别表示TCP的接收缓冲区和发送缓冲区的大小。但是,当我们用setsockopt来设置TCP的接收缓冲区和发送缓冲区的大小时,系统都会将其值加倍,并且不得小于某个最小值。TCP的接收缓冲区的最小值是256字节,而发送缓冲区最小值是2048个字节。这就是为什么需要多次接收的原因。系统这样做的目的,主要是确保一个TCP连接拥有足够的控线缓冲区来处理拥塞。我们可以修改内核参数/proc/sys/net/ipv4/tcp_rmem和/proc/sys/net/ipv4/tcp_wmem来强制修改。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值