1. 字节序转换函数
头文件<netinet/ih.h>
uint16_t htons(uint16_t host16bitvalue);
uint32_t htonl(uint32_t host32bitvalue);
uint16_t ntohs(uint16_t net16bitvalue);
uint32_t ntohl(uint32_t net32bitvalue);
2. 地址转换函数
头文件<arpa/inet.h>
2.1. IPv4定义的地址转换函数
//将ASCII字符串表示的点分十进制地址字符串转换为32-bit的网络字节序地址
//即转换后的地址即是网络字节序(big-endian)的32-bit整数
int inet_aton(const char *strptr,struct in_addr *addrptr);
//同inet_aton函数,区别在于255.255.255.255这个地址从编址上来说是一个
//合法的地址,但此函数返回错误,认为不合法
in_addr_t inet_addr(const char *strptr);
//功能与inet_aton正好相反
char* inet_ntoa(struct int_addr* inaddr);
2.2 IPv6定义的地址转换函数,兼容IPv4
//将ASCII字符串表示的点分十进制地址字符串转换为32-bit的网络字节序地址
int inet_pton(int family,const char* strptr,void *addrptr);
转换成功返回1,失败返回0
//与inet_pton正好相反
const char *inet_ntop(int family,const void *addrptr,char* strptr,size_t len);
注意strptr长度,建议为IPv4长度16,IPv6长度46,外部分配空间。如果成功,
返回值为strptr
其中"p"意指presentation的意思,"n"意指numericf
family为AF_INET或AF_INET6
3. 基本TCP套接字函数
头文件<sys/socket.h>
3.1 套接字对象创建函数socket(UDP也使用)
int socket(int family,int type,int protocol);
family:地址族,IPv4下编程用AF_INET
type:数据报类型
protocol:传输层协议类型:IPPROTO_TCP、IPPROTO_UDP、IPPROTO_SCTP
如果protocol设置为0,则将根据family和type的类型默认一个值,如
family为AF_INET,type为SOCK_STREAM,则默认为TCP协议,若type为SOCK_DGRAM,
则默认为UDP协议,注意:family与type的组合一定要是有效的,并非所有组合都是有效的。
如果创建成功,其返回一个很小的非负整数值,即大于等于0的数。
3.2 客户端connect函数
int connect(int sockfd,struct sockaddr* servaddr,socklen_t addrlen);
返回0表示成功,返回-1表失败,函数在连接成功,或者出现连接错后才返回。
TCP的三次握手在此函数中完成,函数成功返回0则已建立连接。如果从某个路由器回应了
一个"主机不可达"的ICMP错误,说明服务器没监听,或者地址有错,在有些异步连接的
情况下,函数立即返回,就需要根据具体的错误号作处理。
3.3 bind函数,绑定一个协议地址到一个套接字。
协议地址由一个IPv4地址或IPv6地址和一个公开的端口号组成。
int bind(int sockfd,const struct sockaddr *myaddr,socklen_t addrlen);
如果客户端或服务端没有调用此函数,则在客户端在调用connect或服务端调用listen
函数的时候,内核会自动分配一个临时端口号。一般来说,客户端无需调用,而由服务端调用。
如果在调用bind函数时,公开端口号设置为0,则内核分配一个临时端口号,如果IP地址写为0,
则由内核分配IP地址。
3.4 listen函数
int listen(int sockfd,int backlog);
主要是对backlog的理解,内核维持连接数队列的最大值。
内核维持着两个队列,一个是未完成连接的队列,即收到了SYN请求,但未完成三次握手,
另一个是已完成了三次握手的队列,即正式建立连接的队列,其状态均为ESTABLISHED。
注意不要把backlog设置为0,因为不同的系统对此值的实现是不一样的,由于历史原因,
有的代码设置为5,因为早期的网络应用并发量不大,可以满足需求,为了满足大并发量的需求,
如果新的操作系统或内核支持,应将backlog设置为更大,比如187-192之间。如果设置的值大于
内核支持的最大值,则系统自动切掉越出的值而不会报错,至于究竟要设置为多大没有一个标准。
如果两个队列已满(任何一个),则新来的SYN请求将被忽略而不发出任何回应,客户端只有超时重试。
如果还没有调用accept取出连接,但连接队列里的连接已有数据到来,则系统应该提供一个队列存放
到处来的数据,直到队列缓冲区满,此时,由于滑动窗口的作用,客户端不再发送数据,直到服务端
处理数据后可以接收数据。
3.5 accept函数
int accept(int sockfd,struct sockaddr * cliaddr,socklen_t *addrlen);
setsockopt(srv_socket->fd, SOL_TCP, TCP_DEFER_ACCEPT, &val, sizeof(val)) ;
里面 val 的单位是秒,注意如果打开这个功能,kernel 在 val 秒之内还没有收到数据,不会继续唤醒进程,而是直接丢弃连接。
如果服务器设置TCP_DEFER_ACCEPT选项后,服务器受到一个CONNECT请求后,三次握手之后,新的socket状态依然为SYN_RECV,而不是ESTABLISHED,操作系统不会Accept。
由于设置TCP_DEFER_ACCEPT选项之后,三次握手后状态没有达到ESTABLISHED,而是SYN_RECV。这个时候,如果客户端一直没有发送"数据"报文,服务器将重传SYN/ACK报文,
重传次数受net.ipv4.tcp_synack_retries参数控制,达到重传次数之后,才会再次进行setsockopt中设置的超时值,因此会出现SYN_RECV生存时间比设置值大一些的情况。
3.6 close函数
3.6 close函数
头文件<unistd.h>
int close(int sockfd);
调用close后,默认的做法是将套接字标记为关闭并立即返回,此后套接字标识符视为无效不再使用。
因TCP底层试图发送任何放在缓冲区的未发出数据,在close调用后,TCP连接就进入了四次挥手的中止环节。
如果要改变这种默认做法,必须要设置SO_LINGER选项。注意,调用close一次,其实是其引用记数减1,只
有当其引用记数减为0时,才真正的关闭套接字。关闭套接字后,未发送的数据继续发送到对方,但未读取的数据
将丢弃。
3.7 shutdown函数
头文件<sys/socket.h>
int shutdown(int sockfd,int howto);
正常中止一个连接是调用close函数,但是,调用shutdown函数可以避免调用close带来的两个限制。
1) 调用close只是减少了文件描述符的引用次数,仅当引用次数为零时才真正关闭连接,调用shutdown
可以立即初始化TCP连接的正常中止序列而不用考虑引用计数;
2) 调用close只能同时中止双向的连接,此时,如果对端还有数据需要发送过来,我们也无法接收。
shutdown函数的行为取决于howto参数
SHUT_RD:关闭读操作,任何调用读的操作都会失败,仍然保留到socket缓冲区中的数据将被丢弃。
SHUT_WR:关闭写操作,任何调用写的操作都会失败,但保留到socket缓冲区中未发出的数据将被
发送出去,并在最后一包数据的后面加上正常连接中止序列,调用此方法后无错误,则可确保数据
全部发送到对端。
SHUT_RDWR:相当于调用了两次shudown,一次SHUT_RD,一次SHUT_WR。
注意事项:在调用shutdown函数后,对于发送端(暂定为客户端),只能保证数据发送到了对端(暂订为服务端)并收到了应答,并不表明服务端已调用了read函数从缓冲区中读出了数据,如果需要明确知道服务端已读取了数据,需要在两端做一个简单的处理,即客户端在调用shutdown时,使用SHUT_WR参数以确保数据全部发出,但不关闭接收端,在shutdown后再调用read函数等待服务端正常关闭,当服务端read函数后,正常关闭连接,此时我们的客户端read会返回0,表明服务端读取了数据并处理,这实际上是利用了TCP协议的特点从应用的角度解决我们的问题。
头文件<netinet/ih.h>
uint16_t htons(uint16_t host16bitvalue);
uint32_t htonl(uint32_t host32bitvalue);
uint16_t ntohs(uint16_t net16bitvalue);
uint32_t ntohl(uint32_t net32bitvalue);
2. 地址转换函数
头文件<arpa/inet.h>
2.1. IPv4定义的地址转换函数
//将ASCII字符串表示的点分十进制地址字符串转换为32-bit的网络字节序地址
//即转换后的地址即是网络字节序(big-endian)的32-bit整数
int inet_aton(const char *strptr,struct in_addr *addrptr);
//同inet_aton函数,区别在于255.255.255.255这个地址从编址上来说是一个
//合法的地址,但此函数返回错误,认为不合法
in_addr_t inet_addr(const char *strptr);
//功能与inet_aton正好相反
char* inet_ntoa(struct int_addr* inaddr);
2.2 IPv6定义的地址转换函数,兼容IPv4
//将ASCII字符串表示的点分十进制地址字符串转换为32-bit的网络字节序地址
int inet_pton(int family,const char* strptr,void *addrptr);
转换成功返回1,失败返回0
//与inet_pton正好相反
const char *inet_ntop(int family,const void *addrptr,char* strptr,size_t len);
注意strptr长度,建议为IPv4长度16,IPv6长度46,外部分配空间。如果成功,
返回值为strptr
其中"p"意指presentation的意思,"n"意指numericf
family为AF_INET或AF_INET6
3. 基本TCP套接字函数
头文件<sys/socket.h>
3.1 套接字对象创建函数socket(UDP也使用)
int socket(int family,int type,int protocol);
family:地址族,IPv4下编程用AF_INET
type:数据报类型
protocol:传输层协议类型:IPPROTO_TCP、IPPROTO_UDP、IPPROTO_SCTP
如果protocol设置为0,则将根据family和type的类型默认一个值,如
family为AF_INET,type为SOCK_STREAM,则默认为TCP协议,若type为SOCK_DGRAM,
则默认为UDP协议,注意:family与type的组合一定要是有效的,并非所有组合都是有效的。
如果创建成功,其返回一个很小的非负整数值,即大于等于0的数。
3.2 客户端connect函数
int connect(int sockfd,struct sockaddr* servaddr,socklen_t addrlen);
返回0表示成功,返回-1表失败,函数在连接成功,或者出现连接错后才返回。
TCP的三次握手在此函数中完成,函数成功返回0则已建立连接。如果从某个路由器回应了
一个"主机不可达"的ICMP错误,说明服务器没监听,或者地址有错,在有些异步连接的
情况下,函数立即返回,就需要根据具体的错误号作处理。
3.3 bind函数,绑定一个协议地址到一个套接字。
协议地址由一个IPv4地址或IPv6地址和一个公开的端口号组成。
int bind(int sockfd,const struct sockaddr *myaddr,socklen_t addrlen);
如果客户端或服务端没有调用此函数,则在客户端在调用connect或服务端调用listen
函数的时候,内核会自动分配一个临时端口号。一般来说,客户端无需调用,而由服务端调用。
如果在调用bind函数时,公开端口号设置为0,则内核分配一个临时端口号,如果IP地址写为0,
则由内核分配IP地址。
3.4 listen函数
int listen(int sockfd,int backlog);
主要是对backlog的理解,内核维持连接数队列的最大值。
内核维持着两个队列,一个是未完成连接的队列,即收到了SYN请求,但未完成三次握手,
另一个是已完成了三次握手的队列,即正式建立连接的队列,其状态均为ESTABLISHED。
注意不要把backlog设置为0,因为不同的系统对此值的实现是不一样的,由于历史原因,
有的代码设置为5,因为早期的网络应用并发量不大,可以满足需求,为了满足大并发量的需求,
如果新的操作系统或内核支持,应将backlog设置为更大,比如187-192之间。如果设置的值大于
内核支持的最大值,则系统自动切掉越出的值而不会报错,至于究竟要设置为多大没有一个标准。
如果两个队列已满(任何一个),则新来的SYN请求将被忽略而不发出任何回应,客户端只有超时重试。
如果还没有调用accept取出连接,但连接队列里的连接已有数据到来,则系统应该提供一个队列存放
到处来的数据,直到队列缓冲区满,此时,由于滑动窗口的作用,客户端不再发送数据,直到服务端
处理数据后可以接收数据。
3.5 accept函数
int accept(int sockfd,struct sockaddr * cliaddr,socklen_t *addrlen);
其中sockfd为监听套接字描述符,struct cliaddr为客户端地址如果对客户端地址不感兴趣,则将后面两参数设置为NULL,返回值为客户端套接字描述符。注意一个SOCKET选项TCP_DEFER_ACCEPT(linux中的选项)(FreeBSD中为SO_ACCEPTFILTER)defer accept,从字面上理解是推迟accept,实际上是当接收到第一个数据之后,才会创建连接。对于像HTTP等非交互式的服务器,这个很有意义,可以用来防御空连接攻击(只是建立连接,但是不发送任何数据)。
使用方法如下:
val = 5;setsockopt(srv_socket->fd, SOL_TCP, TCP_DEFER_ACCEPT, &val, sizeof(val)) ;
里面 val 的单位是秒,注意如果打开这个功能,kernel 在 val 秒之内还没有收到数据,不会继续唤醒进程,而是直接丢弃连接。
如果服务器设置TCP_DEFER_ACCEPT选项后,服务器受到一个CONNECT请求后,三次握手之后,新的socket状态依然为SYN_RECV,而不是ESTABLISHED,操作系统不会Accept。
由于设置TCP_DEFER_ACCEPT选项之后,三次握手后状态没有达到ESTABLISHED,而是SYN_RECV。这个时候,如果客户端一直没有发送"数据"报文,服务器将重传SYN/ACK报文,
重传次数受net.ipv4.tcp_synack_retries参数控制,达到重传次数之后,才会再次进行setsockopt中设置的超时值,因此会出现SYN_RECV生存时间比设置值大一些的情况。
3.6 close函数
3.6 close函数
头文件<unistd.h>
int close(int sockfd);
调用close后,默认的做法是将套接字标记为关闭并立即返回,此后套接字标识符视为无效不再使用。
因TCP底层试图发送任何放在缓冲区的未发出数据,在close调用后,TCP连接就进入了四次挥手的中止环节。
如果要改变这种默认做法,必须要设置SO_LINGER选项。注意,调用close一次,其实是其引用记数减1,只
有当其引用记数减为0时,才真正的关闭套接字。关闭套接字后,未发送的数据继续发送到对方,但未读取的数据
将丢弃。
3.7 shutdown函数
头文件<sys/socket.h>
int shutdown(int sockfd,int howto);
正常中止一个连接是调用close函数,但是,调用shutdown函数可以避免调用close带来的两个限制。
1) 调用close只是减少了文件描述符的引用次数,仅当引用次数为零时才真正关闭连接,调用shutdown
可以立即初始化TCP连接的正常中止序列而不用考虑引用计数;
2) 调用close只能同时中止双向的连接,此时,如果对端还有数据需要发送过来,我们也无法接收。
shutdown函数的行为取决于howto参数
SHUT_RD:关闭读操作,任何调用读的操作都会失败,仍然保留到socket缓冲区中的数据将被丢弃。
SHUT_WR:关闭写操作,任何调用写的操作都会失败,但保留到socket缓冲区中未发出的数据将被
发送出去,并在最后一包数据的后面加上正常连接中止序列,调用此方法后无错误,则可确保数据
全部发送到对端。
SHUT_RDWR:相当于调用了两次shudown,一次SHUT_RD,一次SHUT_WR。
注意事项:在调用shutdown函数后,对于发送端(暂定为客户端),只能保证数据发送到了对端(暂订为服务端)并收到了应答,并不表明服务端已调用了read函数从缓冲区中读出了数据,如果需要明确知道服务端已读取了数据,需要在两端做一个简单的处理,即客户端在调用shutdown时,使用SHUT_WR参数以确保数据全部发出,但不关闭接收端,在shutdown后再调用read函数等待服务端正常关闭,当服务端read函数后,正常关闭连接,此时我们的客户端read会返回0,表明服务端读取了数据并处理,这实际上是利用了TCP协议的特点从应用的角度解决我们的问题。