htons、ntohs、htonl和ntohl函数
Linux提供了4个函数来完成主机字节序和网络字节序之间的转换
#include <netinet/in.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);
inet_aton、inet_addr和inet_ntoa函数
#include <arpa/inet.h> int inet_aton(const char *strptr, struct in_addr *addrptr); 返回:若字符有效则为1,否则为0 in_addr_t inet_addr(const char *strptr); 返回:若字符串有效则为32位二进制网络字节序地址,否则为INADDR_NONE char *inet_ntoa(struct in_addr inaddr); 返回:指向一个点分十进制数串的地址
inet_aton将strptr所指向的字符串转换为32网络字节序二进制值,并通过addrptr来存储,成功返回1,失败返回0。inet_addr进行与inet_aton相同的转换,inet_addr不能处理点分十进制字符串255.255.255.255,因为它的二进制值被用来只是函数返回失败,但是,有的编译器的INADDR_NONE不一定是这种。
注意,这3个函数操作的in_addr结构是网络字节序的。进行网络二进制和点分字符串转换最好用inet_pton和inet_ntop函数,它们是与协议无关的。
inet_pton和inet_ntop函数#include <arpa/inet.h> int inet_pton(int family, const char *strptr, void *addrptr); 返回:成功为1,输入不是有效表达式返回0,出错为-1 const char *inet_ntop(int family, const void *addrptr, char *strptr, size_t len); 返回:成功为指向结果的指针,出错为NULL
这两个函数对于IPv4和IPv6都适用,p代表表达式(presentation)和数值(numeric)。第一个函数尝试转化有strptr指针所指的字符串,通过addptr指针存放二进制结果,成功返回1,如果对指定的family而言输入的不是有效的表达格式,那么返回0
inet_ntop进行相反的操作,如果len的值太小,不足以存放表达式结果,则返回一个空指针,并置error为ENOSPC
socket和connect函数#include <sys/socket.h> int socket(int family, int type, int protocol); 返回:成功返回非负描述符,出错返回-1
socket函数指定期望的通信协议类型(比如使用IPv4的TCP、使用IPv6的UDP、Unix域字节流协议)和套接字字类型(字节流、数据报或原始套接字)
----socket函数的family常值------------
family 说明
AF_INET IPv4协议
AF_INET6 IPv4协议
AF_LOCAL Unix协议域
AF_ROUTE 路由套接字
AF_KEY 秘钥套接字
----------------------------------------------------
----socket函数的type常值----------------
SOCK_STREAM 字节流套接字
SOCK_DGRAM 数据报套接字
SOCK_SEQPACKET 有序分组套接字
SOCK_RAW 原始套接字
----------------------------------------------------
----socket函数的protocal常值----------
IPPROTO_CP TCP传输协议
IPPROTO_UDP UDP传输协议
IPPROTO_SCTP SCTP传输协议
----------------------------------------------------
#include <sys/socket.h> int connect(int sockfd, const struct sockaddr* servaddr, socklen_t addrlen); 返回:成功为0,出错-1
TCP客户用connect函数来建立一个与TCP服务器连接,sockfd是由socket函数返回的套接字描述符,第二个、第三个参数分别是指向一个套接字地址结构的指针和该结构的大小,套接字结构必须含有服务器的IP地址和端口号。如果connect失败后,就必须close当前的套接字描述符并重新调用socket。
bind和listen函数
#include <sys/socket.h> int bind(int sockfd, const struct sockaddr *myaddr, socklen_t addrlen); 返回:成功为0,出错-1
bind函数把一个本地协议地址赋予一个套接字,它只是把一个协议地址赋予一个套接字,至于协议地址的含义则取决于协议本身。第二个参数指向协议地址结构的指针,第三个参数是协议地址的长度,对于TCP,调用bind函数可以指定一个端口号,或指定一个IP地址,或两者都指定,也可以两者都不指定。
#include <sys/socket.h> int listen(int sockfd, int backlog); 返回:成功返回0,出错-1
socket创建一个套接字时,它被假设为一个主动套接字,也就是说,它是一个将调用connect发起连接的一个客户套接字。listen函数把一个未连接的套接字转换为一个被动套接字,指示内核应接受指向该套接字的连接请求,调用listen函数将导致套接字从CLOSEE状态转换到LISTEN状态。第二个参数规定了内核应为相应套接字排队的最大连接个数。
(1)、未完成连接队列:每一个这样的SYN分节对应其中一项:已由某个客户发出并到达服务器,而服务器正在等待完成相应的TCP三路握手过程。这些套接字处于SYN_RCVD状态。
(2)、已完成连接队列:每个完成TCP三路握手过程的客户对应其中一项,这些套接字处于ESTABLISHED状态。
accept函数
#include <sys/socket.h> int accept(int sockfd, struct sockaddr *cliaddr, socklen_t *addrlen); 返回:成功返回已连接描述符(非负),出错-1
如果accept成功,那么其返回值是由内核自动生成的一个全新套接字,代表与返回客户的TCP连接,函数的第一个参数为监听套接字,返回值为已连接套接字
服务器、客户端程序流程图
服务器客户端简单交互程序见:http://blog.csdn.net/u012796139/article/details/44984879
TCP状态转换图
recv和send函数
TCP流数据读写
#include <sys/socket.h> ssize recv(int sockfd, void *buff, size_t nbytes, int flags); ssize send(int sockfd, void *buff, size_t nbytes, int flags); 返回:成功为读入或写入的字节数,出错为-1
MSG_OOB 对于send,表明将要发送带外数据,TCP连接上只有一个字节可以作为带外数据发送,对于recv,本标志表明即将要读入的是带外数据而不是普通数据。
MSG_PEEK 该标志适用于recv和recvfrom,它允许我们查看已可读取的数据,而且在系统不在recv和recvfrom返回丢弃其这些数据
注意的是,flags参数只对send和recv的当前调用有效,当然也可以通过setsockopt系统调用永久性地修 改socket的某些属性
recvfrom和sendto函数
#include <sys/socket.h> ssize_t recvfrom(int sockfd, void *buf, size_t nbytes, int flags, struct sockaddr *from, socklen_t *addrlen); ssize_t recvto(int sockfd, void *buf, size_t nbytes, int flags, struct sockaddr *to, socklen_t addrlen); 返回:成功为读或写的字节数,失败为-1
前3个参数sockfd、buf、nbytes等同于read和write函数的3个参数:描述符、指向读入或写出缓冲区的指针和读写字节数
recvmsg和sendmsg函数
#include <sys/socket.h> ssize_t recvmsg(int sockfd, struct msghdr *msg, int flags); ssize_t sendmsg(int sockfd, struct msghdr *msg, int flags); 返回:成功则为读入或写出字节数,出错-1
msg参数是msghdr结构体类型指针
struct msghdr { void *msg_name; //socket地址 socklen_t msg_namelen; //socket地址长度 struct iovec *msg_iov; //分散的内存块 int iovlen; //分散内存块数量 void *msg_control; //指向辅助数据的起始地址 socklen_t msg_controllen; //辅助数据大小 int msg_flags; //复制函数中的flags参数,并在调用过程中更新 }; struct iovec { void *iov_base; //内存起始地址 size_t iov_len; //这块内存长度 }
iovec结构体封装了一块内存的起始地址和长度,msg_iovlen指定了这样的iovec结构体有多少个,对于recvmsg而言,数据将被读取并放在msg_iovlen块分离的内存中,这些内存的位置和长度则由msg_iov指向的数组指定,这成为分散读(scatter read);对于sendmsg而言,msg_iovlen块分散内存中的数据将被一块发送,这成为集中写(gather write)
msg_control和msg_controllen用于辅助数据的发送。
msg_flags成员无需设定,它会复制recvmsg/sendmsg的flags参数的内容以影响数据读写过程。recvmsg还会在调用结束前,将某些更新后的标志设置到msg_flags中。recvmsg/sendmsg的flags参数以及返回值的含义均与send/recv的flags参数返回值相同。
getsockname和getpeername函数#include <sys/socket.h> int getsockname(int sockfd, struct sockaddr *localaddr, &addrlen); int getpeername(int sockfd, struct sockaddr *peeraddr, &addrlen); 返回:成功为0, 出错为-1getsockname获取sockfd对应的本端socket地址,并将其存储于address参数指定的内存地址,该socket长度存储于addrlen指向的变量中。getpeername获取远端的socket地址。
getsockope和setsockopt函数
#include <sys/socket.h> int getsockopt(int sockfd, int level, int optname, void *optval, socklen_t *optlen); int setsockopt(int sockfd, int level, int optname, void *optval, socklen_t optlen); 返回:成功为0,出错-1
sockfd指向一个打开的套接字描述符,level(级别)指定系统中解释选项的代码或为通用套接字代码,或为某个特定于协议的代码(例如IPv4、IPv6、TCP或SCTP)。optval是一个指向某个变量(*optval)的指针,setsockopt从*optval的大小有最后一个参数指定,它对于setsockopt是一个值参数,对于getsockopt是一个值-结果参数。
注意:套接字选项粗分为两大类型:一是启用或禁止某个特性的二元选项(成为标志选项),二是取得并返回我们可以设置或检查的特定值的选项(称为值选项)。标有 “标志”的列指出一个选项是否是标志选项,当给这些标志选项调用getsockopt函数时,*optval是一个整数,*optval中返回的为0表示相应选项被禁止,不为0表示相应选项被启动。类似的,setsockopt需要一个不为0的*optval来启动选项,一个为0的*optval来禁止选项。如果标志中没有”.”,那么相应选项用于在用户进程和系统之间传递所指定的数据类型的值。SO_RCVBUF和SO_SNDBUF套接字选项
每个套接字都有一个发送缓冲区和一个接收缓冲区,接收缓冲区被TCP、UDP和SCTP用来保存收到的数据,直到由应用进程来读取,对于TCP来说,套接字接收缓冲区中可用的空间大小限制了TCP通告窗口的大小。TCP套接字缓冲区不可能溢出,因为不允许对端发出超过该窗口大小的数据,本端TCP将丢弃他们。对于UDP是没有流量控制的:较快的发送端可以很容易地淹没较慢得接收端,导致接收端的UDP丢弃数据,事实上较快的发送端甚至可以淹没本机的网络接口,导致数据报被本机丢弃。
当设置TCP套接字接收缓冲区的大小时,函数调用的顺序很重要,因为TCP窗口规模选项是在建立连接时用SYN分节与对端交换得到的。对于客户端,这意味着SO_RCVBUF选项必须在调用connect之前设置;对于服务器,这意味着该选项必须在调用listen之前对套接字进行设置。对已连接的套接字设置该选项对可能存在的窗口规模选项没有任何影响,因为accept直到TCP三次握手完成才会创建并返回已连接套接字,这就是必须给监听套接字进行该选项设置的原因,套接字缓冲区大小是由新创建的已连接套接字从监听套接字继承来的。
gethostbyname函数#include <netdb.h> struct hostent *gethostbyname(const char *hostname); 返回:成功为非空指针,出错为NULL并设置h_errno
函数返回的非空指针结构如下的hostent结构
struct hostent { char *h_name; char **h_aliases; int h_addrtype; int h_length; /* length of address: 4 */ char **h_addr_list; }
按照DNS的说法,gethostbyname执行的是A记录的查询(DNS的资源记录有A、AAAA、PTR、MX、CNAME等),它只能返回IPv4地址,注意,该函数返回的是struct hostent *结构
gethostbyaddr函数#include <netdb.h> struct hostent *gethostbyaddr(const char *addr, socklen_t len, int fanily); 返回:成功为非空指针,出错为NULL并置h_errnoaddr参数其实并不是char *结构,而是一个指向存放IPv4地址的in_addr结构的指针,len参数是这个结构的大小,对于IPv4,family参数为AF_INET
getservbyname和getservbyport函数
#include <netdb.h> struct servent *getservbyneme(const char *servname, const char *protoname); struct servent *getservbyport(int port, const char *protoname); 返回:成功为非空指针,出错为NULL函数返回的非空指针指向如下结构:
struct servent { char *s_name; char *s_aliases; /* alias list */ int s_port; char *s_proto; }从名字到端口号的映射关系一般保存在一个文件中(通常是/etc/services)
参考:
1、《UNIX网络编程》第4章 第11章
2、《Linux高性能服务器编程》第5章 Linux网络编程基础API
3、服务器、客户端简单交互程序 http://blog.csdn.net/u012796139/article/details/44984879
原文地址:http://blog.csdn.net/u012796139/article/details/46358379