如果你的网页是纯html的,浏览器就可以直接解释查看效果
但是如果你的网页是.jsp .asp .php 等的动态网页时,浏览器自己就无法解释了,常见的jsp网站需要用到tomcat服务器,asp网站用到微软的IIS服务器,php网站用apache服务器进行解释。
0、引言
网络应用程序设计模式:
1、CS模式,优点是协议选择灵活,可缓存数据,但不安全
2、BS模式,优点是可以跨平台,但只能使用http协议
1、套接字-Socket
套接字可以理解为一套网络通信的接口。
本文笔记是基于Linux的接口介绍Socket,Win平台和Linux套接字函数几乎大同小异,但更加麻烦,在进行套接字通信之前需要加载套接字库,最后还要释放套接字库资源,Linux完全不需要。
1、对于服务端进行的优化:
如果客户端过多,服务器需要进行并发处理,第一种方式可以通过多线程处理,得到一个线程池。第二种是使用多进程进行处理,得到一个进程池;第三种是通过IO多路转接,使用slider/epho进行处理,在单线程的情况下处理多个客户端的请求等等。
2、若客户端的业务比较复杂,需要对客户端进行优化:
为了提高效率可以与服务器建立多个连接,如在客户端内部建立一个套接字连接池,每个连接对应一个要处理的业务流程,搭配多线程/线程池使用可以大大提高效率。
对于客户端,可能是通过浏览器访问服务端,这种模式称为B/S模式,如果是必须在本机上安装客户端软件,通过软件访问服务端称为C/S模式。
1.1 字节序
对于通信数据,并不是直接就从一台主机发送到另一台主机,而是在发送端经过层层协议封装好之后先存到一块内存中,然后通过网络进行发送。接收端将接收到的数据先存储到一块内存中,然后经过层层协议进行解析。因此在套接字通信时,需要将发送的数据从小端转为大端进行发送,接收到数据后将大端转为小端进行存储。
Little-Endian -> 主机字节序 (小端) 数据的低位字节存储到内存的低地址位 , 数据的高位字节存储到内存的高地址位 我们使用的 PC 机,数据的存储默认使用的是小端 Big-Endian -> 网络字节序 (大端) 据的低位字节存储到内存的高地址位 , 数据的高位字节存储到内存的低地址位 套接字通信过程中操作的数据都是大端存储的,包括:接收/发送的数据、IP地址、端口
服务器和客户端之间网络通信经常提到的三个概念:IP、端口号、通信数据
//1、用于端口号
// 主机字节序 -> 网络字节序 hton函数
u_short htons(u_short hostshort );
u_long htonl(u_long hostlong);
// 网络字节序 -> 主机字节序 ntoh函数
u_short ntohs (u_short netshort );
u_long ntohl ( u_long netlong);
//2、用于IP地址
// linux里面的函数, window上没有这两个函数
inet_ntop(int af, const char *src, void *dst);
// dst: 传出参数,函数调用完成,转换得到的大端整形 IP 被写入到这块内存中
inet_pton(int af, const void *src, char *dst, socklen_t size);
// dst: 传出参数,存储转换得到的小端的点分十进制的 IP 地址
// windows 和 linux 都使用, 只能处理ipv4的ip地址
// 点分十进制IP -> 大端整形
unsigned long inet_addr (const char FAR * cp); // windows
in_addr_t inet_addr (const char *cp); // linux
// 大端整形 -> 点分十进制IP
// window, linux相同
char* inet_ntoa(struct in_addr in);
1.2 文件描述符
在 tcp 的服务器端,有两类文件描述符:一类用于监听的,一类用于通信的
1、监听的只需要有一个,它不负责和客户端通信,负责检测客户端的连接请求,检测到之后调用 accept 函数就可以建立新的连接。
2、用于通信的可以创建多个描述符,如果有 N 个客户端和服务器建立了新的连接,通信的文件描述符就有 N 个,每个客户端和服务器都对应一个通信的文件描述符
1.3 HTTP表头文件形式
请求消息:有两种
GET:安全且幂等,不会破坏服务器上的资源,多次执行相同操作结果都是一样的。
POST:不安全、不幂等
响应消息:
状态码,状态信息:
200 OK、404 Not Found等
content-Type:
1.4 传输控制层(基于tcp的套接字通信流程)
TCP是一个传输层的协议,TCP 是一个面向连接的,可靠的流式传输协议
- 面向连接:是一个双向连接,通过三次握手完成,断开连接需要通过四次挥手完成。
连接完成会在双方内存中开辟相应的资源(接收队列、发送队列)
- 安全:tcp 通信过程中,会对发送的每一数据包都会进行校验,如果发现数据丢失,会自动重传
- 流式传输:发送端和接收端处理数据的速度,数据的量都可以不一致
实时抓包:-nn参数是为了IP和端口号都以数字形式显示
流程:
下面通过QT实现服务器端和客户端的tcp网络通信
因为TcpServer中没有任何有关读写操作的成员函数,所以读写数据的操作全权交由TcpSocket处理。
1.2.1 服务器端
int main()
{
QTcpServer* tcpServer = new QTcpServer(this); //实例化服务器
QTcpSocket* currentClient; //客户端
// 用于记录当前连接的客户端
currentClient = new QTcpSocket(this);
1.2.2 客户端
1.5 linux实现HTTP通信
1.5.1 服务端的实现
//响应消息的发送头
void http_respond_head (int cfd, char* type){
char buf[1024];
//状态行
sprintf(buf,"http/1.1 200 OK\r\n");
write(cfd, buf, strlen(buf));
//消息报头
sprintf(buf,"Content-Type: %s\r\n", type);
write(cfd, buf, strlen(buf));
//空行
write(cfd, "\r\n", 2);
}
void main(string& path)
{
//修改当前进程的工作目录
chdir(path);
//1、创建监听的套接字
int lfd = socket(af_inet, sock_stream, 0);
// 参数af_inet指定的是IPv4格式的IP地址,如果是本地套接字通信的可以为AF_LOCAL
// sock_stream是流式传输协议(tcp),还有sock_dgram报式传输协议(udp)
//2、绑定
struct sockaddr_in addr;
// 初始化addr(该结构体中有三个成员)
addr.family = af_inet;
// hton 将小端转为大端进行网络传输
addr.port = htons(80); //随便给个端口
addr.addr.s_addr = INADDR_ANY; //初始化任意IP地址:0.0.0.0
bind(lfd, (struct sockaddr*)&addr, sizeof(addr));
// bind()
// 绑定函数参数一是文件描述符,就是调用套接字函数的返回值
// 参数二serv是一个sockaddr(sockaddr_in)结构体,主要用于存储IP和端口号
// len是serv的内存大小
//3、监听
listen(lfd, 128);
//4、阻塞并等待客户端的连接
int cfd = accept(); //cfd是一个文件描述符
// int accept(int sockefd, struct sockaddr * addr, len * addrlen);
// 接收函数的参数二不是一个初始化参数,而是对应要连接的客户端的IP和端口号,是一个传出参数
// accept函数的返回值用于通信的文件描述符,基于这个描述符客户端就可以和服务器通信了
//5、读数据,获取客户端的http请求消息(read或者recv函数)
read(cfd, buf, sizeof(buf));
//先将buf中的请求行拿出来 // GET /hello.c http/1.1
char method[12], path[1024], protocol[12];
char* file = path+1; //得到文件名,因为path第一个字符是/
//打开文件(记得在函数开头将路径更改到当前的资源目录,现在就可以直接打开hello.c)
int fdd = open(file, O_RDONLY);
int len = 0;
//在后续给客户端发数据需要遵守的http协议格式(响应消息头)
http_respond_head(cfd , "text/plain");
//由于栈区内存不够,故循环分片读数据(write或者send函数)
while( (len=read(fdd,buf,sizeof(buf))) > 0)
{
// 数据发送给浏览器
write(fdd,const buf, len);
}
close(fd);
close(cfd);
}
1.5.2 客户端的实现
对于客户端的绑定操作,用于通信的客户端也是需要绑定一个端口的,但并没有像服务器那样通过bind函数进行绑定,而是系统通过调用connect函数之后随机绑定一个没有被占用的端口,服务器不会去主动连接客户端的。
int connect(int sockefd, const struct sockaddr * addr, len * addrlen )
// 连接函数第一个参数sockefd是客户端创建用于通信的套接字
// 第二个参数是传入参数,给addr初始化相关的IP和端口号
void main(string& path)
{
//1、创建通信的套接字
int fd = socket(af_inet, sock_stream, 0);
//2、连接服务器的IP和端口号
struct sockaddr_in addr;
// 初始化addr(该结构体中有三个成员)
addr.family = af_inet;
// hton 将小端转为大端进行网络传输
addr.port = htons(8989); //绑定服务器的端口
addr.addr.s_addr = "换成本机IP地址";
int cfd = accept();
// int accept(int sockefd, struct sockaddr * addr, len * addrlen);
// 接收函数的参数二不是一个初始化参数,而是对应要连接的客户端的IP和端口号,是一个传出参数
// accept函数的返回值用于通信的文件描述符,基于这个描述符客户端就可以和服务器通信了
//3、通信
// 客户端先发送数据
char buf[1024];
send(fd, buf, sizeof(buf));
read(fd,buf,sizeof(buf));
close(fd);
}
1.6 网络层
路由表:route
IP地址:
子网掩码:
网络号(网关的ip):通过IP与子网掩码按位与可以得到网络号
如果是局域网内的通信,就不需要走下一跳的网关,直接从网卡就发出去了
如果是与外网通信,需要走路由器(网关)下一跳发出去。
1.7 链路层
arp
2、
exec 8<> /dev/tcp/www.baidu.com/80 建立连接
echo -e "GET /HTTP/1.0\n" >& 8 给服务器发东西需要[按照HTTP协议]去组织字符串的格式
cat <& 8