Chapter 11 网络编程
11.1 客户端-服务器编程模型
基本操作:事务transaction
事务组成:
(1)客户端需要服务,向服务器发送请求,发起一个事务
(2)服务器收到请求并解释,并操作它的资源
(3)服务器给客户端发送一个响应,等待下一个请求
(4)客户端收到响应并处理它。
11.2 网络
介绍了封装思想和具体实现,建议是看计算机网络调理一下
11.3 全球IP因特网
因特网的客户端和服务器混合使用套接字接口函数和Unix I/O函数来进行通信。套接字函数典型地是作为会陷入内核的系统调用来实现的,并调用各种内核模式的TCP/IP函数。
11.3.1 IP地址
/*Internet address structure*/
struct in_addr {
unsigned int s_addr; /*Network byte order (big-endian)*/
};
IP地址结构,由于原本的实现是这样,后期虽然觉得不好但已经大量使用,所以不改了。
TCP/IP为任意整数数据项定义了统一的网络字节顺序(大端),所以对于不同主机,可能需要实现网络和主机字节顺序间实现转换:
#include<netinet/in.h>
/*返回:按照网络字节顺序的值*/
unsigned long int htonl(unsigned long int hostlong);
unsigned short int htons(unsigned short int hostshort);
/*返回:按照主机字节顺序的值*/
unsigned long int ntohl(unsigned long int netlong);
unsigned short int ntohs(unsigned short int netlong);
因特网程序使用inet_aton和inet_ntoa来实现IP地址与点分十进制的转换
a: appliction n:network
#include<arpa/inet.h>
/*将点分十进制转化为网络字节顺序的IP地址,成功了返回1,否则为0*/
int inet_aton(const char* cp, struct in_addr *inp);
/*将网络字节顺序的IP地址转化为点分十进制,返回指向点分十进制字符串的指针*/
char *inet_ntoa(struct in_addr in);
练习题11.1
十六进制地址 | 点分十进制地址 |
---|---|
0x0 | 0.0.0.0 |
0xffffffff | 255.255.255.255 |
0x7f000001 | 127.0.0.1 |
0xcdbca079 | 205.188.160.121 |
0x400c950d | 64.12.149.13 |
0xcdbc9217 | 205.188.146.23 |
练习题乱写的。。。
练习题11.2
/*hex2dd.c*/
#include<stdio.h>
int hexto(char i)
{
if (i <= '9' && i >= '0') {
return i - '0';
} else if (i <= 'f' && i >= 'a'){
return i - 'a' + 10;
} else {
printf("FORMAT ERROR\n");
return -1;
}
}
void getip(int ip[])
{
int i = 0;
for (; i < 3; i++)
printf("%d.", ip[i]);
printf("%d\n", ip[i]);
}
int main(int argv, char*argc[])
{
int i = 1;
if (argv < 2) {
printf("missing.\n");
} else {
for (; i < argv; i ++) {
int ip[4];
int cnt = 0;
int pos = 2;/*跳过16进制标识0x,直接从开始读取数据*/
while (pos < 10) {
int h = hexto(argc[i][pos]);
int l = hexto(argc[i][pos+1]);
if (h != -1 && l != -1) {
ip[cnt] = h*16 + l;
cnt ++;
}
pos = pos + 2;
}
getip(ip);
}
}
return 0;
}
练习题11.3
#include<stdio.h>
#include<stdlib.h>
char gethex(int z)
{
if (z >= 0 &&z <= 9)NS系统维护IP到域名的映射
return z+'0';
else if (z >= 10 && z <= 15)
return z-10 +'a';
else
return '\0';
}
char* ddtohex(int dd)
{
char *hex = (char*)malloc(3*sizeof(char));
hex[0] = gethex(dd/16);
hex[1] = gethex(dd%16);
if(hex[0] != '\0' && hex[1] != '\0')
return hex;
else
return NULL;
}
void getip(char *ip[])
{
printf("0x");
int i = 0;
for(; i < 3; i++)
printf("%s", ip[i]);
printf("%s\n", ip[i]);
}
int main(int argv, char*argc[])
{
int i = 1;
if (argv < 2) {
printf("missing.\n");
} else {
for (; i < argv; i ++) {
/*以点为分界读取转换*/
int j = 0;
char *ip[4];
int cnt = 0;
while (argc[i][j] != '\0') {
int dd = 0;
for (; argc[i][j] != '.' && argc[i][j] != '\0'; j++)
dd = dd*10 + (argc[i][j] - '0');
ip[cnt] = ddtohex(dd);
cnt ++;
if (argc[i][j] == '.')
j++;
}
getip(ip);
}
}
return 0;
}
11.3.2 因特尔域名
域名集合形成了一个层次结构,每个域名编码了它在这个层次中的位置。层次结构可以表示为一棵树。树的节点表示域名,反向到根的路径形成了域名。
DNS系统维护IP到域名的映射
struct hostent
{
char *h_name; /* Official name of host. */
char **h_aliases; /* Alias list. */
int h_addrtype; /* Host address type. */
int h_length; /* Length of address. */
char **h_addr_list; /* List of addresses from name server. */
};
因特尔应用程序通过gethostbyname和gethostbyaddr函数从DNS数据库中搜索任意的主机条目
#include<netdb.h>
struct hostent *gethostbyname(const char* name);
struct hostent *gethostbyaddr(const char* name);
/*出错返回NULL,且同时设置h_errno*/
#include<stdio.h>
#include<netdb.h>
#include<arpa/inet.h>
#include<stdlib.h>
int main(int argc, char **argv)
{
char **pp;
struct in_addr addr;/*IP地址*/
struct hostent *hostp;/*DNS主机条目结构*/
if (argc != 2) {
fprintf(stderr, "usage: %s <domain name or dotted-decNS系统维护IP到域名的映射imal>\n", argv[0]);
exit(0);
}
if (inet_aton(argv[1], &addr) != 0)
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);
}
练习题11.4
DNS轮转:返回地址的不同顺序,对一个大量使用的域名请求做负载平衡。
11.3.3 因特尔连接
一个套接字是连接的一个端点。每个套接字都有相应的套接字地址,是由因特网地址和一个16位整数端口组成的。一个连接是由它两端的套接字地址唯一确定的。这对套接字地址叫做套接字对。(cliaddr:cliport, servaddr:servport)
11.4 套接字接口
11.4.1 套接字地址结构
/*Generic aocket address structure (for connect, bind, and accept)*/
struct sockaddr {
unsigned short sa_family; /*Protocol family*/
char sa_data[14]; /*Address data*/
};
NS系统维护IP到域名的映射
/*Internet-style socket address structure _in来源*/
struct sockaddr_in {
unsigned short sin_family; /*Address family (always AF_INET)*/
unsigned short sin_port; /*Port number in network byte order*/
struct in_addr sin_addr; /*IP address in network byte order*/
unsigned char sin_zero[8]; /*Pad to sizeof(struct sockaddr)*/
};
11.4.2 socket函数
#include<sys/types.h>
#include<sys/socket.h>
int socket(int domain, int type, int protocol);
/*domain: AF_INET 因特网; type: SOCK_STREAM 因特网连接的一个端点*/
11.4.3 connect函数
客户端通过调用connect函数建立与服务器的连接,connect返回套接字描述符给客户端的时候,客户端就可以立即开始用UnixI/O和服务器通信了!
#include<sys/socket.h>
int connect(int sockfd, struct sockaddr *serv_addr, int addrlen);
/*成功则为0,出错则为1*/
11.4.4 open_clientfd函数
可以将socket函数和connect函数包装成一个叫open_clientfd的辅助函数,客户端利用它和服务器进行链接。
int open_clientfd(char *hostname, int port);
/*返回:成功-描述符,Unix出错- -1,DNS出错- -2 */
剩余的bind、listen和accept被服务器用来和客户端建立连接
11.4.5 bind函数
#include<sys/socket.h>
int bind(int sockfd, struct sockaddr *my_addr, int addrlen);
告诉内核将my_addr中的服务器套接字地址和套接字描述符sockfd联系起来。
11.4.6 listen函数
#include<sys/socket.h>
int listen(int sockfd, int backlog);
listen函数将sockfd从一个主动套接字转化为一个监听套接字,用来接受客户端的连接请求。
11.4.7 open_listenfd函数
我们将socket,bind和listen结合为这个函数,服务器用它简单地创建一个监听套接字
int open_listenfd(int port);
11.4.8 accept函数
服务器用这个函数来等待来自客户端的连接请求
#include<sys/socket.h>
int accept(int listenfd, struct sockaddr *addr, int *addrlen);
监听描述符是只被创建一次,并存在于服务器的整个生命周期,已连接描述符则是客户端与服务器之间已经建立起来的连接的一个端点。区分两者,有利于我们建立并发服务器,同时处理多个客户端连接