《Unix/Linux系统编程》13章学习笔记

网络编程简介

  1. TCP/IP协议
  2. UDP和TCP协议
  3. 服务器—客户机计算
  4. HTTP和Web页面
  5. 动态Web页面的PHP和CGI编程

TCP/IP协议

TCP/IP是互联网的基础,TCP代表传输控制协议,IP代表互联网协议。
两个版本的IP:ipv4(32位)、ipv6(128位)
TCP/IP的组织结构分为几个层级,通常称为TCP/IP堆栈。

顶层时使用TCP/IP的应用程序
进程与主机之间的传输层或其上方的数据传输只是逻辑传输,实际传输发生在互联网和链路层

IP主机和IP地址

每个主机由一个32位的IP地址来标识,主机也可以用主机名表示
IP地址分为两部分:NetworkID和HostID

IP协议

IP协议用于在IP主机之间发送/接收数据包

IP数据包格式

IP数据包由IP头、发送方IP地址和接收方IP地址以及数据组成
IP头中含有:数据包的总长度、数据包使用TCP还是UDP、生存时间(TTL)计数】错误检测的校验和

路由器

通常不可能从一个主机直接向另一个主机直接发送数据包。
一个数据包可能会经过许多路由器,或者跳跃到达某个目的地。
IP包在IP报头中都有一个8位生存时间(TTL)计数,其最大值为225.
每个路由器上,TTL会减小1,如果TTL减小到0,包还没有到达目的地,则可以直接丢弃它,防止任何数据包在IP网络中无限循环。

路由器是接收和转发数据包的特殊IP主机

UDP

UDP(用户数据报协议)在IP上运行,用于发送/接收数据报。
UDP不能保证可靠性,但是快速高效
用户可以使用ping命令探测目标主机
Ping是一个向目标主机发送带时间戳UDP包的应用程序,接收到一个pinging数据包后,目标主机将带有时间戳的UDP回送给发送者。
TTL减小为0时,路由器将会丢弃pinging UDP数据包

TCP

TCP(传输控制协议)是一种面向连接的协议,用于发送/接收数据流。
TCP也可以在IP上运行。
TCP类似于电话连接

端口编号

多个应用程序可以同时使用TCP/UDP。
每个应用程序有三个组成部分唯一标识

应用程序=(主机IP,协议,端口号)

协议是TCP/IP,端口号是分配给应用程序的唯一无符号短整数
要使用UDP或TCP,应用程序必须先选择或选择一个端口号
前1024个端口号已被预留

网络和主机字节序

计算机可以使用大端字节序,也可以使用小端字节序。
例如:PC中的端口号1234按主机字节序(小端)是无符号短整数。
必须先通过htons(1234)把它转换成网络序。

大端/小端

TCP/IP网络中的数据流


数据——>传输层(添加TCP或UDP报头)——>IP网络层(添加包含IP地址的IP报头来标识发送和接受主机)——>网络链路层(分成多个帧,并添加发送和接受网络的地址,用于在物理网络之间传输)。

网络编程

所有Unix/Linux系统都为网络编程提供TCP/IP支持
用于网络编程的平台和服务器—客户机计算模型

网络编程平台

  1. 服务器上的用户账户
  2. 单独PC或笔记本电脑

服务器—客户机计算模型

大多数网络编程任务基于服务器—客户机计算模型
首先在服务器主机上运行服务器进程
然后从客户机运行客户机

套接字编程

TCP/IP的用户界面是通过一系列C语言库函数和系统调用来实现的,这些函数和系统调用统称为套接字API
netdb.h 和sys/socket.h中有套接字地址结构的定义

套接字地址

struct sockaddr_in {
sa_family_t sin_family; // AF_INET for TCP/IP
in_port_t sin_port; // port number
struct in_addr sin_addr; // IP address
};
struct in_addr { // internet address
uint32_t s_addr; // IP address in network byte order
};
  • TCP/IP网络的sin_family时钟设置为AF_INET
  • sim_port包含按网络字节顺序排列的端口号
  • sin_addr是按网络字节顺序排列的主机IP地址

套接字API

服务器必须创建一个套接字,并将其与包含服务器Ip地址和端口号的套接字地址绑定
socket()系统调用,它创建一个套接字并返回一个文件描述符

  1. int套接字(int域, int类型, int协议)int socket(int domain, int type, int protocol)

int udp_sock = socket(AF_INET, SOCK_DGRAM, 0);
创建一个用于发送那个/接收UDP数据包的套接字
int tcp_sock = socket(AF_INET, SOCK_STREAM, 0);
创建一个用于发送/接受数据流的面向连接的TCP套接字
2. int bind(intsockfd, struct sockaddr *addr, socklen_taddrlen);

  1. UDP套接字
    ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,
    const struct sockaddr *dest_addr, socklen_t addrlen);

ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,
struct sockaddr *src_addr, socklen_t *addrlen);
sendto()将缓冲区中的len字节数据发送到由dest_addr标识的目标主机,该目标包含目标主机IP和端口号。
4. TCP Sockets
intlisten(int sockfd, int backlog);
intaccept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
intconnect(intsockfd, const struct sockaddr *addr, socklen_taddrlen);

  1. send()/read()或recv/write()

UDP回显服务器—客户机程序



/* C13.1.a: UDP server.c file */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/ip.h>
#include <arpa/inet.h>
#define BUFLEN 256 // max length of buffer
#define PORT 1234 // fixed server port number
char line[BUFLEN];
struct sockaddr_in me, client;
int sock, rlen, clen = sizeof(client);
int main()
{
printf("1. create a UDP socket\n");
sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
printf("2. fill me with server address and port number\n");
memset((char *)&me, 0, sizeof(me));
me.sin_family = AF_INET;
me.sin_port = htons(PORT);
me.sin_addr.s_addr = htonl(INADDR_ANY); // use localhost
printf("3. bind socket to server IP and port\n");
bind(sock, (struct sockaddr*)&me, sizeof(me));
printf("4. wait for datagram\n");
while(1){
memset(line, 0, BUFLEN);
printf("UDP server: waiting for datagram\n");
// recvfrom() gets client IP, port in sockaddr_in clinet
rlen=recvfrom(sock,line,BUFLEN,0,(struct sockaddr *)&client,&clen);
printf("received a datagram from [host:port] = [%s:%d]\n",inet_ntoa(client.sin_addr), ntohs(client.sin_port));
printf("rlen=%d: line=%s\n", rlen, line);
printf("send reply\n");
sendto(sock, line, rlen, 0, (struct sockaddr*)&client, clen);
}
}
/***** C13.1.b: UDP client.c file *****/
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<sys/socket.h>
#include <netinet/ip.h>
#include<arpa/inet.h>
#define SERVER_HOST "127.0.0.1" // default server IP: localhost
#define SERVER_PORT 1234 // fixed server port number
#define BUFLEN 256 // max length of buffer
char line[BUFLEN];
struct sockaddr_in server;
int sock, rlen, slen=sizeof(server);
int main()
{
printf("1. create a UDP socket\n");
sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
printf("2. fill in server address and port number\n");
memset((char *) &server, 0, sizeof(server));
server.sin_family = AF_INET;
server.sin_port = htons(SERVER_PORT);
inet_aton(SERVER_HOST, &server.sin_addr);
while(1){
printf("Enter a line : ");
fgets(line, BUFLEN, stdin);
line[strlen(line)-1] = 0;
printf("send line to server\n");
sendto(sock,line,strlen(line),0,(struct sockaddr *)&server,slen);
memset(line, 0, BUFLEN);
printf("try to receive a line from server\n");
rlen=recvfrom(sock,line,BUFLEN,0,(struct sockaddr*)&server,&slen);
printf("rlen=%d: line=%s\n", rlen, line);
}
}

TCP回显服务器—客户机程序



/******** C13.2.b: TCPclient.c file TCP********/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <netdb.h>
#define MAX 256
#define SERVER_HOST "localhost"
#define SERVER_PORT 1234
#include <unistd.h>
struct sockaddr_in server_addr;
int sock, r;
int client_init()
{
printf("======= clinet init ==========\n");
printf("1 : create a TCPsocket\n");
sock = socket(AF_INET, SOCK_STREAM, 0);
if (sock<0){
printf("socket call failed\n"); exit(1);
}
printf("2 : fill server_addr with server's IP and PORT#\n");
server_addr.sin_family = AF_INET;
server_addr.sin_addr.s_addr = htonl(INADDR_ANY); // localhost
server_addr.sin_port = htons(SERVER_PORT); // server port number
printf("3 : connecting to server ....\n");
r = connect(sock,(struct sockaddr*)&server_addr, sizeof(server_addr));
if (r < 0){
printf("connect failed\n"); exit(3);
}
printf("4 : connected OK to\n");
printf("-------------------------------------------------------\n");
printf("Server hostname=%s PORT=%d\n", SERVER_HOST, SERVER_PORT);
printf("-------------------------------------------------------\n");
printf("========= init done ==========\n");
}
int main()
{
 int n;
 char line[MAX], ans[MAX];
client_init();
printf("******** processing loop *********\n");
while (1){
printf("input a line : ");
bzero(line, MAX); // zero out line[ ]
fgets(line, MAX, stdin); // get a line from stdin
line[strlen(line)-1] = 0; // kill \n at end
if (line[0]==0) // exit if NULL line
exit(0);
// Send line to server
n = write(sock, line, MAX);
printf("client: wrote n=%d bytes; line=%s\n", n, line);
// Read a line from sock and show it
n = read(sock, ans, MAX);
printf("client: read n=%d bytes; echo=%s\n", n, ans);
}
}
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <netdb.h>
#define MAX 256
#define SERVER_HOST "localhost"
#define SERVER_IP "127.0.0.1"
#define SERVER_PORT 1234
#include <arpa/inet.h>
#include <unistd.h>
struct sockaddr_in server_addr, client_addr;
int mysock, csock; // socket descriptors
int r, len, n; // help variables
int server_init()
{
printf("================== server init ======================\n");
// create a TCP socket by socket() syscall
printf("1 : create a TCP STREAM socket\n");
mysock = socket(AF_INET, SOCK_STREAM, 0);
if (mysock < 0){
printf("socket call failed\n"); exit(1);
}
printf("2 : fill server_addr with host IP and PORT# info\n");
// initialize the server_addr structure
server_addr.sin_family = AF_INET; // for TCP/IP
server_addr.sin_addr.s_addr = htonl(INADDR_ANY); // This HOST IP
server_addr.sin_port = htons(SERVER_PORT); // port number 1234
printf("3 : bind socket to server address\n");
r = bind(mysock,(struct sockaddr*)&server_addr,sizeof(server_addr));
if (r < 0){
printf("bind failed\n"); exit(3);
}
printf(" hostname = %s port = %d\n", SERVER_HOST, SERVER_PORT);
printf("4 : server is listening ....\n");
listen(mysock, 5); // queue length = 5
printf("=================== init done =======================\n");
}
int main()
{
char line[MAX];
server_init();
while(1){ // Try to accept a client request
printf("server: accepting new connection ....\n");
// Try to accept a client connection as descriptor newsock
len = sizeof(client_addr);
csock = accept(mysock, (struct sockaddr *)&client_addr, &len);
if (csock < 0){
printf("server: accept error\n"); exit(1);
}
printf("server: accepted a client connection from\n");
printf("-----------------------------------------------\n");
printf("Clinet: IP=%s port=%d\n",
  inet_ntoa(client_addr.sin_addr),
ntohs(client_addr.sin_port));
printf("-----------------------------------------------\n");
// Processing loop: client_sock <== data ==> client
while(1){
n = read(csock, line, MAX);
if (n==0){
printf("server: client died, server loops\n");
close(csock);
break;
}
// show the line string
printf("server: read n=%d bytes; line=%s\n", n, line);
// echo line to client
n = write(csock, line, MAX);
printf("server: wrote n=%d bytes; ECHO=%s\n", n, line);
printf("server: ready for next request\n");
}
}
}

主机名和ip地址

web和CGI编程

万维网或Web是互联网上的资源和用户组合,它使用超文本传输协议进行信息交换

HTTP编程模型

http://hostname[/filename]

web页面

Web页面使用HTML标记语言编写的文件

托管Web页面

  1. 与商业Web托管服务提供商签约
  2. 机构或部门服务器上的用户账户
  3. 独立PC或笔记本电脑

为Web页面配置HTTPD

动态Web页面

标准HTML:静态

JavaScript:动态

PHP

一种用于创建服务器端动态Web页面的脚本语言

CGI编程

CGI代表通用网关接口

配置CGI的HTTPD

在HTTPD中,CGI程序的默认目录是/srv/httpd/cgi-bin
苏格拉底挑战