之前一直都没有做过网络编程的相关学习,昨天突然翻了翻《深入浅出计算机系统》,发现是个入门的好方法,因此在此做一下学习笔记,同时学习了linux相关命令,何乐而不为。
客户端-服务器编程模型
所有的网络应用都是基于相同的基本编程模型,有着相似的整体逻辑结构,并且依赖相同的编程接口。
一个应用由一个服务器进程或者多个客户端进程组成,要记住这里的客户端和服务器指的都是进程,而不是常常提到的机器或者主机,这是非常重要的。
全球IP因特网
TCP/IP协议(Transmission Control Protocol/Internet Protocol,传输控制协议/互联网络协议)
- UDP (Unreliable Datagram Protocol,不可靠数据报协议)稍微扩展了IP协议,使得包可以在进程间而不是在主机间传送。
- TCP是构建在IP之上的复杂协议,提供进程间可靠的全双公(双向的)连接
IP地址
IP地址结构:
struct in_addr{
unsigned
int s_addr;
};
网络-主机地址翻译
不同的主机可以有不同的主机字节顺序,因此TCP/IP为任意整数项定义了同意的网络字节顺序(大端字节顺序),因此便产生了以下函数
#include <netinet/
in.h>
//
将32位整数由主机字节顺序转换为网络字节顺序
unsigned
long
int htonl(unsigned
long
int hostlong);
unsigned
short
int htons(unsigned
short
int hostshort);
//
将32位整数从网络字节顺序转换为主机字节
unsigned
long
int ntohl(unsigned
long
int netlong);
unsigned
short
int ntohs(unsigned
short
int netshort);
IP地址和点分十进制转换
学到这里,我感觉网络编程,其实就是在记忆各种系统提供的函数调用,学的全是函数呵呵。
#include<arpa/inet.h>
//
将点分十进制串(cp)转换为网络字节顺序的IP地址(inp),传递的是指向IP结构的指针
int inet_aton(
const
char*cp,
struct in_addr* inp);
//
将网络字节顺序的IP地址转换为对应的点分十进制串
char* inet_ntoa(
struct in_addr
in);
//
其中,n表示network,a表示application,to表示转换
IP地址结构/网络主机地址翻译/IP与点分十进制转换的2个例子
例子1:
#include <stdio.h>
#include <arpa/inet.h>
#include <netinet/
in.h>
//
将十六进制的字符串转换成整数
int htoi(
char s[])
{
int i;
int n =
0;
if (s[
0] ==
'
0
' && (s[
1]==
'
x
' || s[
1]==
'
X
'))
{
i =
2;
}
else {
i =
0;
}
for (; (s[i] >=
'
0
' && s[i] <=
'
9
') || (s[i] >=
'
a
' && s[i] <=
'
z
') || (s[i] >=
'
A
' && s[i] <=
'
Z
');++i)
{
if (tolower(s[i]) >
'
9
')
{
n =
16 * n + (
10 + tolower(s[i]) -
'
a
');
}
else {
n =
16 * n + (tolower(s[i]) -
'
0
');
}
}
return n;
}
int main(
int argc,
char** argv){
if(argc<
2)
return
0;
struct in_addr
in;
//
第一步,将十六进制字符串转换成十进制
//
第二步,将网络字节顺序,转换成主机字节顺序
//
第三步,利用inet_ntoa函数将网络字节顺序地址,转换成点分十进制串
in.s_addr = ntohl(htoi(argv[
1]));
printf(
"
%s \n
", inet_ntoa(
in));
return
0;
}
运行结果:
crazyant@crazyant-virtual-machine:~/CPPWorkBench$ ./hex2dd 0x8002c2f2
128.2.194.242
例子2:
/*
* 将点分十进制串转换成十六进制串并打印出结果
*/
#include <arpa/inet.h>
#include <netinet/
in.h>
#include <stdio.h>
int main(
int argc,
char** argv){
struct in_addr inp;
if(argc<
2)
return
0;
//
int inet_aton(const char*cp, struct in_addr *inp);
//
将一个点分十进制串(cp)转换为一个网络字节顺序的IP地址(inp)
inet_aton(argv[
1],&inp);
//
unsigned long int htonl(unsigned long int hostlong);
//
将32位整数由网络字节顺序转换为主机自己顺序,这样输出才不会相反
printf(
"
%x \n
", ntohl(inp.s_addr));
return
0;
}
运行结果:
crazyant@crazyant-virtual-machine:~/CPPWorkBench$ ./dd2hex 128.2.194.242
8002c2f2
因特网域名
域名组成了层次结构,从上到下依次是.com,自定义,www 从下到上,依次连接变成了最终的域名
DNS数据库由大量的如下的主机条目结构,其中每条定义了一组域名(一个官方名字和一组别名)和一组IP地址之间的映射,其实每个主机条目就是一个域名和IP地址的等价类
/*
DNS主机条目结构
*/
struct hostent{
char *h_name;
//
官方的主机名字
char **h_aliases;
//
主机名字数组
int h_addrtype;
//
主机地址类型
int h_length;
//
主机地址长度,按位计算
char **h_addr_list;
//
网络地址结构体数组
};
因特网应用程序,可以通过两个函数对DNS进行检索:
#include<netdb.h>
//
返回和域名name相关的主机条目
struct hostent *gethostbyname(
const
char* name);
//
返回和IP地址addr相关联的主机条目
,第二个参数给出了IP地址的字节长度,当前都为4,第三个参数一直是0
struct hostent *gethostbyaddr(
const
char* addr,
int len,
0);
DNS结构体/获取域名的DNS信息例子
#include
"
csapp.h
"
#include <netdb.h>
#include <stdio.h>
int main(
int argc,
char** argv){
char **pp;
struct in_addr addr;
//
ip地址结构体
struct hostent *hostp;
//
dns上的主机条目的结构体
if(argc!=
2){
//
参数必须为域名或者IP地址
fprintf(stderr,
"
usage: %s <domain name or dotted-decimal>\n
", argv[
0] );
exit(
0);
}
if(inet_aton(argv[
1],&addr)!=
0){
//
说明参数是一个IP地址
hostp = gethostbyaddr((
const
char*)&addr,
sizeof(addr), AF_INET);
}
else {
hostp = gethostbyname(argv[
1]);
//
说明是域名
}
printf(
"
official hostname:%s \n
", hostp->h_name);
for( pp=hostp->h_aliases; *pp!=NULL; pp++){
printf(
"
alias: %s\n
",*pp);
}
for(pp=hostp->h_addr_list; *pp!=NULL; pp++){
addr.s_addr=((
struct in_addr*)*pp)->s_addr;
printf(
"
address: %s\n
", inet_ntoa(addr));
}
exit(
0);
}
运行结果:
crazyant@crazyant-virtual-machine:~/CPPWorkBench$ ./hostinfo google.com
official hostname:google.com
address: 74.125.128.100
address: 74.125.128.138
address: 74.125.128.113
address: 74.125.128.102
address: 74.125.128.139
address: 74.125.128.101
crazyant@crazyant-virtual-machine:~/CPPWorkBench$ ./hostinfo google.com
official hostname:google.com
address: 74.125.128.101
address: 74.125.128.139
address: 74.125.128.102
address: 74.125.128.113
address: 74.125.128.138
address: 74.125.128.100
crazyant@crazyant-virtual-machine:~/CPPWorkBench$ ./hostinfo google.com
official hostname:google.com
address: 74.125.128.138
address: 74.125.128.100
address: 74.125.128.101
address: 74.125.128.139
address: 74.125.128.102
address: 74.125.128.113
运行了三次,大家仔细观察会发现,google.com的地址列表每次以一种不同的/轮转(round robin)的顺序返回,这可以用来对一个大量使用的域名的请求做
负载平衡
因特网连接
因特网上的数据是双向/全双工进行发送和接受的,每个主机可以用一个套接字来标识
套接字 = IP地址:端口号
客户端的套接字和服务器段的套接字共同组成了一个套接字对,它标识了唯一的一个连接
一个连接 = (客户端IP:客户端端口, 服务器IP:服务器端口)
小结
本次主要是整理了客户端服务器模型/IP地址结构/网络主机地址翻译/IP和16进制转换/DNS条目结构体与检索/套接子概念等内容,下次将会学习创建网络应用的套接子接口(又一套函数)。