字节序:小端字节序指内存高位地址存放数值高位,大端字节序指内存高位存放数值低位。PC一般采用小端字节序,网络一般采用大端字节序,因此需要API转换字节序,htonl()将主机长整型转换位网络长整型,htons(),ntohl(),ntohs()将网络短整型转换为主机短整型,长整型函数一般用于转换IP地址,短整型用来转换端口
Socket 可以被定义描述为两个应用通信通道的端点。一个 Socket 端点可以用 Socket 地址来描述, Socket 地址结构由 IP 地址,端口和使用协议组成
socket地址结构体sockaddr其成员有sa_family(地址族类型AF_UNIX本地协议族AF_INET为IPv4协议族AF_INET6是IPv6协议族)和sa_data是socket地址值,linux下新的socket地址结构体sockaddr_storage能够容纳更多的协议族并且内存对齐
专用socket地址:本地域地址结构体sockaddr_un;IPv4的socket地址结构体sockaddr_in内嵌IPv4的IP地址结构体in_addr;IPv6的socket地址结构体sockaddr_in6内嵌IPv6的IP地址结构体in6_addr。但在实际使用中专业socket地址结构体必须强制转换为sockaddr因为socket编程接口使用的参数是sockaddr
IP地址转换函数:
IPv4点分十进制和网络字节整数之间的转换:inet_addr(将点分十进制转为网络字节序整数),inet_aton(同前一个但是结果存入参数inp指向的地址结构中),inet_ntoa(网络字节序整数转为点分十进制该函数内部用一个静态变量存储转换结果并返回该静态内存所以是不可重入的)
同时适用于IPv4和IPv6的转换函数:inet_pton将用字符串表示的IP地址src(IPv4是点分十进制,IPv6是十六进制字符串)转换成网络字节序整数,参数af指定地址族;inet_ntop进行反向转换比前面的函数多一个参数cnt用于指定目标存储单元dst的大小,有宏INET_ADDRSTRLEN,INET6_ADDRSTRLEN指定这个大小(分别用于IPv4和IPv6)
socket(int domain,int type,int protocol)其中domain表示使用的协议族PF_INET、IP_INET6、PF_UNIX;type指定服务类型SOCK_STREAM使用TCP协议,SOCK_DGRAM使用UDP协议,想与的类型SOCK_NONBLOCK(非阻塞)和SOCK_CLOEXEC(fork的子进程中关闭该socket);protocol设置为0表示默认协议
命名socket:创建socket后还需要指定使用地址族中的哪个具体socket地址,将一个socket与socket地址绑定的过程成为socket命名,通常服务器需要命名socket以使客户端知道如何连接它,而客户端一般不需要命名socket即使用操作系统自动分配的socket。bind(int sockfd,const struct sockaddr* my_addr,socklen_t addrlen)将my_addr所指的socket地址分配给未命名的sockfd文件描述符,addrlen表示该socket地址长度,成功返回0,失败返回-1及errno常见的两种errno:EACCESS被绑定地址为受保护地址常为普通用户欲使用知名服务端口号(0-1023),EADDRINUSE被绑定的地址正在使用中比如处于TIME_WAIT状态
监听socket:socket被命名后还需要监听才能接受客户连接,listen(int sockfd,int backlog)其中sockfd是指被监听的socket,backlog监听队列的最大长度典型值为5,超过该值服务器不受理新的客户连接客户端收到ECONNREFUSED错误信息,成功返回0失败返回-1
接受连接:accept(int sockfd,struct sockaddr* addr,socklen_t* addrlen)其中sockfd是执行过listen监听的socket,addr用来获取被接受连接的远端socket地址,addrlen指出该地址长度。成功时返回一个新的连接socket该socket唯一的标识了被接受的这个连接,服务器可通过读写该socket来与被接受的连接对应的客户端通信,失败返回-1.accept只是从监听队列中取出连接而不论连接处于何种状态,更不关心任何网络状况的变化
发起连接:connect(int sockfd,const struct sockaddr* serv_addr,socklen_t addrlen)其中sockfd由socket系统调用返回一个socket,serv_addr是服务器监听的socket地址,addrlen指定这个地址长度。成功返回0否则返回-1,sockfd唯一的标识了这个连接客户端就可以通过读写sockfd来与服务器通信,失败两个常见errno:ECONNREFFUSED目的端口不存在被拒绝,ETIMEOUT连接超时。//Sockfd是目的服务器的sockt描述符;serv_addr是包含目的机IP地址和端口号的指针
关闭连接:close(int fd)只是将fd引用计数减1(fork的子进程引用计数加1)当应用计数为0时关闭连接;shutdown(int sockfd,int howto)以howto方式关闭sockfd,howto参数可选值:SHUT_RD关闭sockfd上读的一半程序不再针对该socketfd执行读操作缓冲区数据丢弃,SHUT_WR关闭socketfd写的一半缓冲区会在连接关闭之前将数据发送出去不再执行写操作,SHUT_RDWR同时关闭读和写(缓冲区数据?),成功返回0失败返回-1
TCP数据读或写:read,write可以,但是有专门的系统调用recv(int sockfd,void *buf,size_t len,int flags);send(int sockfd,const void* buf,size_t len,int flags)其中buf和len分别指定缓冲区的位置和大小,flags有很多选项通常设置为0,recv返回0表示对方关闭连接
Socket()函数返回一个整型的Socket描述符,随后的连接建立、数据传输等操作都是通过该Socket实现的;应该将该socket与你本机上的一个端口相关联(往往当你在设计服务器端程序时需要调用该函数,随后你就可以在该端口监听服务请求,而客户端一般无须调用该函数),Bind函数;Listen()——监听是否有服务请求,在服务器端程序中,当socket与某一端口捆绑以后,就需要监听该端口,以便对到达的服务请求加以处理(服务端被动接受连接);accept()——连接端口的服务请求,当某个客户端试图与服务器监听的端口连接时,该连接请求将排队等待服务器accept()它,通过调用accept()函数为其建立一个连接,accept()函数将返回一个新的socket描述符,来供这个新连接来使用,而服务器可以继续在以前的那个socket上监听;Connect()函数客户端主动与远端服务器建立一个TCP连接;Send()和recv()——数据传输这两个函数是用于面向连接的socket上进行数据传输;Sendto()和recvfrom()——利用数据报方式进行数据传输;Close()和shutdown()——结束数据传输 。服务器首先创建一个Socket,然后将该Socket与本地地址/端口号捆绑,成功之后就在相应的socket上监听,当accpet捕捉到一个连接服务请求时,就生成一个新的socket,并通过这个新的socket向客户端发送,然后关闭该socket。客户端代码相对来说要简单一些,首先通过服务器域名获得其IP地址,然后创建一个socket,调用connect函数与服务器建立连接,连接成功之后接收从服务器发送过来的数据,最后关闭socket,结束程序。阻塞(blocking)的概念和select()函数当服务器运行到accept语句时,而没有客户连接服务请求到来,那么会发生什么情况?这时服务器就会停止在accept语句上等待连接服务请求的到来;同样,当程序运行到接收数据语句时,如果没有数据可以读取,则程序同样会停止在接收语句上。这种情况称为blocking。但如果你希望服务器仅仅注意检查是否有客户在等待连接,有就接受连接;否则就继续做其他事情,则可以通过将Socke设置为非阻塞方式来实现:非阻塞socket在没有客户在等待时就使accept调用立即返回。
只要发送了紧急数据接收端在紧急数据前后的数据被截断不能一次recv读走
UDP读写:recvfrom(int sockfd,void* buf,size_t len,int flags,struct sockaddr* src_addr,socklen_t* addrlen)其中读取sockfd上的数据,buf和len指定缓冲区位置和大小,无连接因此每次读取需要获取发送端的socket地址(该地址封装了IP地址端口和协议等内容),addrlen是该地址长度
sendto(int sockfd,const void* buf,size_t len,int flags,const struct sockaddr* dest_addr,socklen_t addrlen)往sockfd写数据,dest_addr指定接收端的socket地址,flags参数和send/recv一致,recvfrom/sendto将最后两个参数置为NULL就可以用与面向连接(不需要socket地址)
通用数据读写函数:recvmsg(int sockfd,struct msghdr* msg,int flags)
sendmsg(int sockfd,struct msghdr* msg,int flags)sockfd是socket,msg是msghdr结构体指针(msghdr内成员:msg_name指向一个socket地址当是TCP必须为NULL,msg_iov是iovec结构体类型指针(可以是数组名则是多块内存)该结构体封装了一块内存的起始位置和长度读端是分散读,写端是集中写,msg_control和msg_controllen用于辅助数据的传送实现进程间传递文件描述符,msg_flags直接复制recvmsg/sendmsg的flags参数不用指定且会自动更新)
内核通知应用程序带外数据到达的两种方式:I/O复用产生的异常事件和SIGURG信号。但是应用程序还需要知道带外数据的具体位置
sockatmark(int sockfd)判断sockfd是否处于带外标记若是返回1此时就可以利用带MSG_OOB标志的recv调用来接收带外数据
获取连接socket地址:getsockname(int sockfd,struct sockaddr* address,socklen_t* address_len)获取本端socket地址
getpeername(int sockfd,struct sockaddr* address,socklen_t* address_len)获取连接对端socket地址,获取的地址存放于长度为ddress_len的address中若address_len小于实际socket地址大小则被截断,成功返回0
读取或设置socket文件描述符属性,类似与fcntl
getsockopt(int sockfd,int level,int option_name.void* option_value,socklen_t* restrict option_len)
setsockopt(int sockfd,int level,int option_name,const void* option_value,socklen_t option_len)其中sockfd是socket,level指定要操作哪个协议的选项(属性)比如IPv4或TCP等,option_name指定选项的名字,option_value和option_len指定被操作选项(注意选项的适用范围比如有些选项只能三次握手期间的同步报文中使用)的值和长度。成功返回0
关键字restrict只用于限定指针;该关键字用于告知编译器,所有修改该指针所指向内容的操作全部都是基于(base on)该指针的,即不存在其它进行修改操作的途径;这样的后果是帮助编译器进行更好的代码优化,生成更有效率的汇编代码。
SO_REUSEADDR选项用来强制使用处于TIME_WAIT状态的socket地址,也可以修改内核参数/proc/sys/net/ipv4/tcp_tw_recycle立即回收被关闭的socket
SO_RECVUF/SO_SNDBUF设置TCP接收和发送缓冲区大小但系统一般都会将该值加倍已达到不小于某个最小值(且不同的系统调用有不同的默认最小值),可以修改内核参数/proc/sys/net/ipv4/tcp_rmem和/proc/sys/net/ipv4/tcp_wmem来强制缓冲区没有最小值限制
SO_RCVLOWAT/SO_SNDLOWAT表示接收和发送缓冲区的低水位标志一般被IO复用通知应用程序,当接收缓冲区小于低水平位则通知应用程序读取数据,后者是当发送缓冲区空余大于低水平位通知应用程序写,一般低水平位默认1B
SO_LINGER控制close关闭TCP的行为,默认情况close关闭TCP立即返回且将残留数据发送,在setsockopt设置该选项时需要给setsockopt传递一个linger结构体,根据这个结构体不同,close有不同的行为。linger内成员l_onoff为0则该选项无效;l_onoff!=0&&l_linger=0丢弃发送缓冲区残留数据close立即返回同时给对方发送一个复位(RET)报文,这提供了一个异常终止连接的方法;l_onoff!=0&&l_linger>0时close取决于两个条件是否有残留数据和socket是否为阻塞,对于阻塞socket则close等待l_linger时间若此期间仍没有发送完残留数据或没有得到对方确认close返回-1并置errno为EWOULDBLOCK,若socket是非阻塞的close立即返回只有根据返回值和errno确定是否有残留数据
struct* hostent* gethostbyname(const char* name)通过主机名name获取主机的完整信息,先查询/ect/host没找到再查询DNS服务器
struct* hostent* gethostbyaddr(const void* addr,size_t len,int type)通过IP地址获取主机完整信息,addr指定目标主机的IP地址,len指定IP地址长度,type可选为AF_INET/AF_INET6指定addr所指IP地址的类型,返回hostent结构指针
struct servent* getservbyname(const char* name,const char* proto)通过服务名称获取服务的完整信心
struct servent* getservbyport(int port,const char* proto)通过端口号获取服务完整信息,name是服务的名字,port是端口号,proto是服务类型若为tcp表示获取流服务若为udo获取数据报服务若为NULL获取一切服务,都是读取/etc/service获取服务信息,都返回servent结构体指针。值得注意的是上面四个函数都是不可重入的但在netdb.h下有后缀_r的可重入版本
getaddrinfo可以通过主机名获取IP地址也可以通过服务名获取端口号,就是封装了下上面的函数,且调用它后必须调用freeaddrinfo释放对内存
getnameinfo可以通过socket地址获取字符串表示的主机名和端口号