您的位置:首页 > 理论基础 > 计算机网络

网络编程API-上 (基本API)

2016-01-16 12:12 489 查看
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函数来建立一个与TCPserver连接,sockfd是由socket函数返回的套接字描写叙述符,第二个、第三个參数各自是指向一个套接字地址结构的指针和该结构的大小,套接字结构必须含有server的IP地址和port号。假设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函数能够指定一个port号,或指定一个IP地址,或两者都指定,也能够两者都不指定。

#include <sys/socket.h>
int listen(int sockfd, int backlog);
返回:成功返回0,出错-1

socket创建一个套接字时,它被如果为一个主动套接字,也就是说,它是一个将调用connect发起连接的一个客户套接字。listen函数把一个未连接的套接字转换为一个被动套接字,指示内核应接受指向该套接字的连接请求,调用listen函数将导致套接字从CLOSEE状态转换到LISTEN状态。第二个參数规定了内核应为对应套接字排队的最大连接个数。

(1)、未完毕连接队列:每个这种SYN分节相应当中一项:已由某个客户发出并到达server,而server正在等待完毕相应的TCP三路握手过程。这些套接字处于SYN_RCVD状态。

(2)、已完毕连接队列:每一个完毕TCP三路握手过程的客户相应当中一项,这些套接字处于ESTABLISHED状态。





accept函数

#include <sys/socket.h>
int accept(int sockfd, struct sockaddr *cliaddr, socklen_t *addrlen);
返回:成功返回已连接描写叙述符(非负)。出错-1


假设accept成功,那么其返回值是由内核自己主动生成的一个全新套接字,代表与返回客户的TCP连接,函数的第一个參数为监听套接字。返回值为已连接套接字

server、client程序流程图

serverclient简单交互程序见:/article/1538660.html



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。 出错为-1
getsockname获取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是没有流量控制的:较快的发送端能够非常easy地淹没较慢得接收端,导致接收端的UDP丢弃数据。其实较快的发送端甚至能够淹没本机的网络接口,导致数据报被本机丢弃。

当设置TCP套接字接收缓冲区的大小时。函数调用的顺序非常重要,由于TCP窗体规模选项是在建立连接时用SYN分节与对端交换得到的。对于client,这意味着SO_RCVBUF选项必须在调用connect之前设置;对于server,这意味着该选项必须在调用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_errno
addr參数事实上并非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)

getservbyname的參数servname必须指定。假设同一时候指定了protoname(为非空指针),那么指定的服务必须和协议匹配。假设protoname未指定,而servname指向服务支持多个协议,那么返回那个port号取决于实现。通常没有关系,由于多个协议同样的服务一般有同样的TCP和UDPport号,但这点并没有保证

getservbyport的port參数必须是网络序

使用实例:

struct servent *pserv;

pserv = getservbyname(“ftp”, “tcp”);

pserv = getservbyport(htons(80), “tcp”);

參考:

1、《UNIX网络编程》第4章 第11章

2、《Linux高性能server编程》第5章 Linux网络编程基础API

3、server、client简单交互程序 /article/1538660.html
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: