从programmer的角度,我们们可以把因特网看成一个世界范围内的主机集合,满足以下要求:
主机集合被映射为一组32位的ip地址
这组ip地址被映射为一组称为因特网域名的标识符
主机上的进程能够通过连接和其他主机上的进程通信
1.ip地址
ip地址是一个32位无符号整数,网络程序将ip地址存储在如下结构中:
#include <netinet/in.h>
struct in_addr{
unsigned int s_addr; // big endian
}
注意,tcp/ip为任意整数数据定义了统一的网络字节顺序:大端字节顺序。并提供了以下方法来在网络和主机间进行字节顺序的转换:
#include <netinet/in.h>
/* 32位/16位大端转小端 */
unsigned long int htonl(unsigned long int hostlong);
unsigned short int htons(unsigned short int hostshort);
/* 32位/16位小端转大端 */
unsigned long int ntohl(unsigned long int netlong);
unsigned short int ntohs(unsigned short int netshort);
ip地址还可以使用点分十进制表示法,所以系统提供了以下函数实现无符号整数和点分十进制表示方法的转换:
#include <arpa/inet.h>
/* 点分表示->整数
* 若成功返回1,出错返回0
*/
int inet_aton(const char *cp,struct in_addr *inp);
/* 整数->点分表示
* 返回指向点分十进制字符串的指针
*/
char *inet_ntoa(struct in_addr in);
2.因特网域名
因特网定义了域名集合和ip地址集合之间的映射,这个映射是通过分布在世界范围内的数据库来维护的。从概念上讲,DNS数据库由上百万如下所示的主机条目结构组成,其中每条定义了一组域名和一组ip地址之间的映射。
#include <netdb.h>
struct hostent{
char *h_name; //主机的官方域名
char **h_aliases; //别名的字符串数组
int addrtype; //地址类型,一般是AF_INET
int h_length; //地址的字节长度
char **h_addr_list; //字符串数组
}
网络程序通过调用下面的函数,从DNS数据库检索主机条目(for myself csapp 622)
#include <netdb.h>
/* 通过域名检索
* 若失败,返回NULL,并设置h_errno
*/
struct hostent *gethostbyname(const char *name);
/* 通过ip地址检索,第二个参数为地址的字节长度
* 若失败返回NULL,并设置h_errno
*/
struct hostent *gethostbyaddr(const char *addr,int len,0);
3.连接 & 套接字结构
一个套接字是连接的一个端点,每个套接字都有相应的地址,是由一个因特网地址和一个16位的整数端口组成,一般用 “地址:端口”来表示。一般来说,服务器的套接字端口通常是某个知名端口,而客户端端口由内核自动分配。
一个连接是由两端的套接字地址唯一确定的。
套接字地址存放在如下所示的结构中:
#include <sys/socket.h>
#include <netinet/in.h>
/* 这个结构是为了函数 connect、bind、accept而设计的 */
struct sockaddr{
unsigned short sa_family; //protocol family
char sa_data[14]; //address data
}
/* 套接字地址结构 */
struct {
unsigned short sin_family; //address family 一般为AF_INET
unsigned short sin_port; //大端表示的接口
struct in_addr sin_addr; //大端表示的ip地址
unsigned char sin_zero[8]; //pad to sizeof(struct sockaddr)
}
4.socket 函数
客户端使用函数的顺序是 socket->connect
服务端使用函数的顺序是 socket->bind->listen->accept
#include <sys/types.h>
#include <sys/socket.h>
/* 创建套接字描述符,成功返回描述符,失败返回-1
* 一般总是这样使用:clientfd=socket(AF_INET,SOCK_STREAM,0);
*/
int socket(int domin,int type,int protocol);
socket返回的描述符还不能用于读写,在客户端和服务端要经过不同的处理,如前文的顺序所示。
#include <sys/socket.h>
/* 客户端使用该函数建立与服务器的连接
* 成功,返回0;失败,返回-1
*/
int connect(int sockfd,struct sockaddr *serv_addr,int addrlen);
/* 服务端使用该函数将服务器的地址和描述符连接起来
* addrlen是 sizeof(sockaddr_in)
* 成功返回0,否则-1
*/
int bind(int sockfd,struct sockaddr *my_addr,int addrlen);
/* 服务端使用该函数将一个主动套接字改为监听套接字
* 成功返回0,失败-1
*/
int listen(int sockfd,int backlog);