Linux c 网络编程--中篇
1.字节顺序与转换函数
1.1字节顺序
大端模式:高字节数据存放在低地址,低字节数据存放在高地址。
小端模式:低字节数据存放在低地址,高字节数据存放在高地址。
数据0x04030201存放如下
注意
1.对于char类型数据由于只占一个字节,因此不存在大小端的问题
2.对于IP地址、端口等非char类型数据,必须在数据发送到网络之前转换为大端模式;在接收端接收后转为相应的主机端模式
TCP/IP协议规定网络上必须使用网络字节顺序即大端模式
1.2Linux系统提供的转换函数
linux下 shell下输入 man byteorder可获得函数原型
#include <arpa/inet.h>
uint32_t htonl(uint32_t hostlong);//主机unsigned int转换为网络顺序 host to network long
uint16_t htons(uint16_t hostshort);//主机unsigned short转换为网络顺序
uint32_t ntohl(uint32_t netlong);
uint16_t ntohs(uint16_t netshort);
linux下 shell下输入 man inet可获得inet系统函数原型
因为我们习惯于使用字符串形式的网络地址"192.168.0.1",然而网络上数据传输时使用的是二进制形式且大端模式的IP地址。因此有了inet系列。
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
1.字符串IP(cp) ==>二进制网络字节顺序IP(inp);
成功返回非0值,参数无效返回0
int inet_aton(const char *cp, struct in_addr *inp);
2.类似于函数1,已过时,推荐使用1
in_addr_t inet_addr(const char *cp);
3.字符串IP ==> 主机字节顺序形式的二进制IP地址;
成功返回转换后结果,参数无效返回-1;
in_addr_t inet_network(const char *cp);
4.网络字节顺序二进制IP转换为"."分十进制的字符串IP
成功返回字符指针,参数无效返回NULL
char *inet_ntoa(struct in_addr in);
5.将主机字节顺序的二进制网络号+主机字节顺序的二进制主机号组合为网络字节顺序的二进制网络地址
struct in_addr inet_makeaddr(int net, int host);
6.从参数中提取主机号
成功返回主机字节顺序的主机号(二进制)
in_addr_t inet_lnaof(struct in_addr in);
7.从参数中提取网络号
成功返回主机字节顺序的网络号(二进制)
in_addr_t inet_netof(struct in_addr in);
在这里插入代码片
2.获取和设置套接字属性
获取或设置套接字的工作方式
2.1获取套接字属性
man getsocketopt
#include<sys/types.h>
#include<sys/socket.h>
int getsockopt(int s,int level,int optname,void *optval,socklen_t optlen);
参数:
s:套接字
level:进行套接字选项操作层次如下等
SOL_SOCKET:通用套接字
IPPROTO_IP:IP层套接字
IPPROTO_TCP:TCP层套接字
一般选SOL_SOCKET来进行与特定协议不相关的操作。
optname:套接字选项名称
optval:用来存放获得的套接字选项,调用函数前optlen是optval指向的空间大小,调用之后其值为optval所保存的结果的实际大小。
返回值:成功0失败-1 错误码存入errno
2.2设置套接字属性
man setsocketopt
#include<sys/types.h>
#include<sys/socket.h>
int setsockopt(int s,int level,int optname,const void *optval,socklen_t optlen);
参数:
s:套接字
level:进行套接字选项操作层次
optname:套接字选项名称
optval:待设置的套接字选项的值,optlen是该选项的长度。
返回值:成功0失败-1 错误码存入errno
带设置的套接字选项optval可选值
SO_KEEPALIVE
系统检测该连接是否还有效
服务端使用此选项则某客户端一段时间内没有反应则关闭该连接。
服务端未使用此选择则某客户端非正常端口连接,服务器进程将一直被阻塞等待。
SO_RCVLOVAT和SO_SNDLOWAT
接收缓冲区的下限:只有接收缓冲区中数据超过下限才会被送给上层应用程序
发送缓冲区的下限:只有发送缓冲区中数据超过下限才会将数据发送出去。
注意:Linux下这两个值均为1且不能更改,即只要有数据就发送和接收;这两个选项只能使用getsocketopt获取而不能使用setsocketopt更改
SO_RCVTIMEO和SO_SNDTIMEO
设置对套接字读或者写的超时时间
struct timeval{
long tv_sec;//秒数
long tv_usec;//微秒数
}
超时时间为二者和,在某套接字连接上,若读写超时,则认为接收或发送数据失败。
SO_BINDTODEVICE
将套接字绑定到特定的网络接口,此后只有该网络接口上的数据才会被套接字处理。若选项设置为空字符串或者选项长度设置为0时则将取消绑定。
SO_DEBUG
该选项只能对TCP套接字使用,设置了该选项后系统将保存TCP发送和接收的所有数据的相关信息,以便调试程序。
SO_REUSEADDR
Linux系统中若socket绑定了一个端口,当该socket正常关闭或程序异常退出后,一段时间内,该端口依然维持原绑定状态,其他程序无法绑定该端口,若设置此参数这不存在此问题。
SO_TYPE
用于获取套接字的类型,该选项只能被函数getsocketopt用来获取套接类型,而不能使用setsockopt改变套接字类型。
SO_ACCEPTCONN
该选项用于检测套接字是否处于监听状态
0-非监听状态 ;1- 正在监听
该选项只能被getsockopt函数用来获取监听状态信息。
SO_DONTROUTE
设置该选项表示在发送IP数据包时不使用路由表来寻找路由。
SO_BROADCAST
该选项表示套接字是否能够在网络上刚播数据
实际应用中要在网络中广播数据必须硬件支持且使用SOCK_DGRAM套接字,系统默认不支持广播。
SO_SNDBUF和SORCVBUF
用于设置套接字的发送和接收缓冲区大小
对于TCP类型的套接字,缓冲区太小会影响TCP流量控制
对于UDP类型的套接字,若数据缓冲区满则后续数据将被丢弃,实际应用中应选择合适大小。
SO_SERROR
获取套接字内部的错误变量so_error
当套接字上发生了异步错误时,系统将设置套接字的so_error。
异步错误是指错误的发生和错误被发现的时间不一致。通常在目的主机非正常关闭时发生这种错误。该选项只能被getsockopt函数用来获取so_error
注意:调用完getsockeopt完成后,so_error的值将自动被重新初始化。
3.多路复用select()
多路复用:服务器端同时处理多个客户端的连接请求。
实现方法一:采用非阻塞方式套接字,服务器端不断查询各个套接字的状态,若有数据到达则读出数据,没有则查询下一个套接字(这种方式浪费大量的CUP,效率低)
实现方法二:服务器不主动查询套接字状态,而是向系统登记希望监听的套接字,然后阻塞。当套接字上有事件发生时则系统通知服务器进程对应套接字有什么事件,由服务器进程查询对应套接字并进行处理。
使用select()函数可以实现方式2的多路复用。
man select()
#include<sys/select.h>
#include<sys/time.h>
#include<sys/types.h>
#include<unistd.h>
int select(int n,fd_set *readfds,fd_set* writefds,fd_set* execptfds,struct timeval* timeout);
参数:
n:需要监视的文件描述符数,要监视的文件描述符数值为0~n-1
readfds:需要监视的可读文件描述符集合,当这个集合的一个描述符有数据到达时,系统将通知select函数的程序。
writefds:需要监视的可写文件描述符集合,当集合中某个描述符可发送数据时,程序将收到通知。
execptfds:需要监视的异常文件描述符,当集合中某描述符发送异常时,程序将收到通知。
timeout:阻塞时间,若这段时间内监视的文件描述符都没有事件发生,则select函数返回0;若timeout设置为NULL则select函数将一直阻塞,直到某个文件描述符发生事件。若timeout设置为0,则select函数非阻塞模式,函数select查询完文件描述符集合状态后立即返回。
注意:这里的文件描述符既可以是普通文件描述符也可以是套接字描述符。
struct timeval{
long tv_sec;
long tv_usec;
}
系统为文件描述符集合提供了一系列的宏以便操作
FD_CLR(int fd,fd_set* set);//集合中删除fd文件描述符
FD_ISSET(int fd,fd_set* set);//集合中是否存在fd文件描述符
FD_SET(int fd,fd_set* set);//集合中增加fd文件描述符
FD_ZERO(fd_set* set);//集合中文件描述符清空
若select()设定的要监控的文件描述符集合中有描述符发生了事件,则select返回发生事件的文件描述符的个数