【网络编程】Linux网络编程基础API


一、socket 地址API

1.1 大端、小端字节序

对于同一组数据 0x12 34 56 78
大端字节序:0x12 34 56 78 -------> 网络字节序(为了避免不同字节序的主机间传递信息,统一在网络中使用大端字节序)
小端字节序:0x78 56 34 12-------->主机字节序(由于现代PC采用较多)

对应的转换api有

#include<netline/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);//将短整型的网络字节序转换为主机字节序

1.2 通用 socket 地址结构

  所有的socket编程接口使用的地址结构都是以下两个

#include<bits/socket.h>
struct sockadd{
	sa_family_t sa_family;//表示这个地址属于什么协议,例如ipv4等
	char sa_data[14];//具体的地址数据
}

//这个结构体是在上面的基础上可以存放更长的地址,并且可以做到内存对齐
struct sockaddr_storage{
	sa_family_t sa_family;//表示这个地址属于什么协议,例如ipv4等
	unsigned long int __ss_align;//用来内存对齐
	char __ss_padding[128-sizeof(__ss_align)];//具体的地址数据
}

1.3 专用 socket 地址结构

#include<sys/un.h>
//UNIX本地协议族使用的socket结构体
struct sockaddr_un{
	sa_family_t sin_family;  //地址族:AF_UNIX
	char sun_path[108];      //地址路径
};
//ipv4使用的socket结构体
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地址,用网络字节序表示
};

//ipv6使用的socket结构体
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的地址结构体
};
struct in6_addr{
	unsigned char sa_addr[16]; //ipv6地址,用网络字节序表示
}

  所有的专用socket结构在使用的时候,都需要去转换成通用地址结构sockaddr(直接强制转换),这是由于所有的socket编程接口使用的地址参数都是sockaddr。

1.4 IP地址转换函数

  下面的函数主要用于点分十进制和网络字节序的转换
  ipv4地址的函数

#include <arpa/inet.h>
//inet_addr 函数将点分十进制转换为网络字节序,失败返回INADDR_NONE
in_addr_t inet_addr(const char* strptr);
//inet_aton 函数将点分十进制转换为网络字节序存储在inp中,成功返回1,失败放回0
int inet_aton(const char* cp, struct in_addr* inp);
//inet_ntoa 函数将网络字节序转化为点分十进制输出
//函数内部使用静态变量存储结果,所以具有不可重入性
char* inet_ntoa(struct in_addr in);

  ipv6地址的函数(ipv4也可以用)

#include <arpa/inet.h>
//inet_pton 表示将点分十进制转化为网络字节序表示的ip地址
//af指定地址族,src是点分十进制的地址,dst表示转化后的网络字节序表示的地址
//成功返回1,失败返回0并设置error
int inet_pton(int af, const char* src, void* dst);
//inet_ntop 进行相反的转换,成功放回目标存储单元的地址,失败返回NULL并设置error
const char* inet_ntop(int af, const void* src, char* dst, socklen_t cnt);
//cnt是使用宏来设置的,如下
#include <netinet/in.h>
#define INET_ADDRSTRLEN 16
#define INET6_ADDRSTRLEN 64

二、创建socket

  创建socket主要使用以下方式:

#include <sys/types.h>
#include <sys/socket.h>
int socket(int domain, int type, int protocol);
//domain 指出使用的是哪个底层协议族,有PE_INET(ipv4),PF_INET6(ipv6),PE_UNIX
//type 指出服务类型,主要有SOCK_STREAM流服务(TCP)和SOCK_UGRAM数据报(UDP)
//protocol几乎所有情况下设置为0
//创建成功返回一个socket文件描述符,失败返回-1并设置error

三、命名socket

  socket命名指的是将socket与socket地址绑定
  在服务器中需要命名socket,而在客户端中一般采用匿名的方式,所以不用命名socket

#include <sys/types.h>
#include <sys/socket.h>
//函数主要将my_addr所指向的地址分配给当前的socket文件描述符sockfd,addrlen表示该socket地址的长度
int bind(int sockfd, const struct sockaddr* my_addr,socklen_t addrlen);
//bind成功返回0,失败返回-1,并设置error,两种常见的error如下
//EACCES,普通用户将socket绑定到受保护的地址上了。
//EADDRINUSE,被绑定的地址正在使用中

四、监听socket

  监听队列用来存放待处理的客户连接

#include <sys/socket.h>
int listen(int sockfd, int backlog);
//sockfd用来指定被监听的socket句柄
//backlog提示的是内核监听队列的最大长度
//listen失败返回-1并设置error,成功返回0

五、接受连接

#include <sys/types>
#include <sys/socket.h>
int accept(int sockfd, struct sockaddr* addr, socklen_t* addrlen);
//sockfd表示执行过listen的socket句柄
//addr用来获取被接受连接的远端socket地址
//addrlen用来接收addr的长度
//失败返回-1并设置error,成功返回0
问题:如果监听队列中处于ESTABLISHD状态的连接对应的客户端出现网络异常或者提前退出,那么服务器对这个连接执行的accept调用是否成功?

  accept只是从监听队列中取出连接,不关心连接处于何种状态,更不关心任何网络状况的变化,所以不管是出现网络异常还是提前退出,accept都会成功。

六、发起连接

  客户端侧用来与服务器建立连接

#include <sys/types.h>
#include <sys/socket.h>
int connect(int sockfd, const struct sockaddr* serv_addr, socklen_t addrlen);
//sockfd是系统调用返回的一个socket
//serv_addr是服务器监听的socket地址
//addrlen指定这个地址的长度
//连接成功返回0,失败返回-1并设置error

七、关闭连接

#include <unistd.h>
int close(int fd);
//fd是待关闭的socket句柄,成功返回0,失败返回-1
//close并不是真正关闭一个socket连接,而是将其引用计数减一,引用计数变成0时才会真正关闭连接

#include <sys/socket.h>
int shutdown(int sockfd, int howto);
//sockfd是待关闭的fd,howto决定了shutdown的方法,有SHUT_RD,SHUT_WR,SHUT_RDWR
//shutdown能够立刻关闭,而不是close那种计数减一

八、数据读写

8.1 TCP数据读写

#include <sys/types.h>
#include <sys/socket.h>
ssize_t recv(int sockfd, void* buf, size_t len, int flags);
//recv读取sockfd上的数据,buf和len分别代表缓冲区和缓冲区长度,返回0表示对方关闭连接,-1表示recv出错并设置error
ssize_t send(int sockfd, const void* buf, size_t len, int flags); 
//send往sockfd上写数据,buf和len指定缓冲区大小以及长度,成功返回实际写入的长度,失败返回-1并设置error
//flag为数据收发设置额外的控制

8.2 UDP数据读写

  也可以用于有连接的数据传输(addrlen写为NULL即可)

#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 recvto(int sockfd, const void* buf, size_t len, int flags, const struct sockaddr*
                 src_addr, socklen_t* addrlen);
//读/写socket上的数据,buf和len表示指定缓冲区的位置以及大小,src_addr表示发送端/接收端的socket地址,addrlen表示该地址的长度

8.3 通用数据读写函数

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

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;
};
//msg_name用于指定对方的socket地址,对于TCP连接没有意义,必须设置为NULL
//msg_control和msg_controllen用于实现进程间传递文件描述符
//msg_flags无需设定,他会复制recvmsg和sengmsg的对应内容

struct iovec{
	void* iov_base;  //内存起始地址
	size_t iov_len;  //内存长度
};

  对于recvmsg来说,数据将被读取并保存在msg_iovlen块分散的内存中,这称为分散读。
  对于sendmsg来说,msg_iovlen块分散在内存中的数据将被统一发送,这称为集中写。

9、带外标记

#include <sys/socket.h>
int sockatmark(int sockfd);
//sockatmark判断fd是否处在带外标记,即下一个被读取到的数据是否是带外数据
//是的话,sockatmark返回1,并且可以利用带MSG_OOB标志的recv调用来接收带外数据
//不是的话,sockatmark返回0

10、地址信息函数

#include <sys/socket.h>
int getsockname(int sockfd, struct sockaddr* address, socklen_t* address_len);
//获取本端sockfd的地址存在address中,address_len表示本端地址长度
int getpeername(int sockfd, struct sockaddr* address, socklen_t* address_len);
//获取远端sockfd的地址存在address中,address_len表示远端地址长度

  如果实际socket地址大于address的长度,socket地址将会被截断,上面两个函数成功时会返回0,失败返回-1并设置error

11、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);
//sockfd指定了被操作的目标socket,level指定要操作那个协议的选项,
//option_name指定选项的名字,有SOL_SOCKET,IPPROTO_IP,IPPROTO_IPV6,IPPROTO_TCP
//option_value和option_len表示被操作选项的值和长度
1.SO_REUSEADDR选项

该选项可以重用本地地址,即使得处于TIME_WAIT状态的连接占用的socket地址被强制重用

2.SO_RCVBUF选项和SO_SNDBUF选项

  SO_RCVBUF选项和SO_SNDBUF选项分别表示TCP接收缓冲区和发送缓冲区的大小,但是使用setsockopt来设置,系统会给缓冲区设置两倍于我们设置值的大小,例如我们设置2000B的缓冲区,系统实际分配4000B的缓冲区。
  但是,接收缓冲区的最小值为256B,发送缓冲区的最小值为2048B,小于这两个值的话,用户的设置便会无效,例如我们将发送缓冲区设置为50B,则系统会无视我们的设置,发送缓冲区仍然为256B。

3.SO_RCVLOWAT选项和SO_SNDLOWAT选项

  SO_RCVLOWAT选项和SO_SNDLOWAT选项分别表示TCP接收缓冲区以及发送缓冲区的低水位标志,一般被I\O复用系统调用来判断socket是否可读或者可写。
  默认情况下,TCP接收缓冲区以及发送缓冲区的低水位标志均为1B。

3.SO_LINGER选项

若有数据要发送,则延迟关闭socket,设置SO_LINGER的值时,涉及一个结构体

#include <sys/socket.h>
struct linger{
	int l_onoff; //是否开启该选项
	int l_linger;//滞留时间
};
  • l_onoff=0,SO_LINGER不起作用,close默认关闭socket
  • l_onoff≠0,l_linger=0,TCP丢弃该socket的发送缓冲区中残留的数据,同时给对方发送一个复位报文段
  • l_onoff≠0,l_linger>0,如果socket阻塞,close将等待长为l_linger的时间直到TCP发送完所有的残余数据并且得到对方的确认,如果这段时间没有发送完数据并且得到确认,则close系统调用将返回-1并设置error为EWOULDBLOCK,如果socket非阻塞,close将立即返回。

12、网络信息api

12.1 gethostbyname和gethostbyaddr

#include <netdb.h>
struct hostent* gethostbyname(const char* name);//根据主机名获取主机完整信息
struct hostent* gethostbyaddr(const void* addr, size_t len, int type);//根据主机地址获取主机完整信息

struct hostent{
	char* h_name;      //主机名
	char** h_aliases;  //主机别名
	int h_addrtype;    //地址类型
	int h_length;      //地址长度
	char** h_addr_list;//按网络字节序列出的主机IP地址列表
};

12.2 getservbyname和getservbyport

#include <netdb.h>
struct servnet* getservbyname(const char* name, const char* proto);//根据名称获取整个服务的完整信息
struct servnet* getservbyport(int port, const char* proto);//根据端口号获取整个服务的完整信息
//proto表示服务类型,有tcp,udp,NULL(tcp+udp)

struct servent{
	char* s_name;     //服务名称
	char** s_aliases; //服务的别名
	int s_port;       //端口号
	char* s_proto;    //服务类型,通常是tcp或者udp
};

12.3 getaddrinfo

  既能使用主机名获取IP地址(内部使用gethostbyname),也能使用服务名获取端口号(内部使用getservbyport)

#include<netdb.h>
int getaddrinfo(const char* hostname, const char* service, const struct addrinfo* hints, struct addrinfo** result);
//hostname可以接收主机名,也可以接收字符串表示的IP地址
//service可以接收服务名,也可以接收十进制端口号          
//hints可以设置为NULL,以接收认可有用的反馈
//result指向一个链表,存储getaddrinfo的反馈

struct addrinfo{
	int ai_flags;              //
	int ai_family;             //地址族
	int ai_socktype;           //服务类型,SOCK_STREAM或者SOCK_DGRAM
	int ai_ptotocol;           //具体的网络协议
	socklen_t ai_addrlen;      //socket地址ai_addr的长度
	char* ai_canonname;        //主机别名
	struct sockaddr* ai_addr;  //指向socket地址
	struct addrinfo* ai_next;  //指向下一个addrinfo结构
};

12.4 getnameinfo

  既能使用socket地址获取以字符串表示的主机名(内部使用gethostbyaddr),也能使用socket地址获取以字符串表示的服务名(内部使用getservbyport)

#include<netdb.h>
int getnameinfo(const struct sockaddr* sockaddr, socklen_t addrlen, char* host, socklen_t hostlen, 
                char* serv, socklen_t servlen, int flags);
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Rockict_z

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值