如果你的网页是纯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. //1、用于端口号
  2. // 主机字节序 -> 网络字节序 hton函数
  3. u_short htons(u_short hostshort );
  4. u_long htonl(u_long hostlong);
  5. // 网络字节序 -> 主机字节序 ntoh函数
  6. u_short ntohs (u_short netshort );
  7. u_long ntohl ( u_long netlong);
  8. //2、用于IP地址
  9. // linux里面的函数, window上没有这两个函数
  10. inet_ntop(int af, const char *src, void *dst);
  11. // dst: 传出参数,函数调用完成,转换得到的大端整形 IP 被写入到这块内存中
  12. inet_pton(int af, const void *src, char *dst, socklen_t size);
  13. // dst: 传出参数,存储转换得到的小端的点分十进制的 IP 地址
  14. // windows 和 linux 都使用, 只能处理ipv4的ip地址
  15. // 点分十进制IP -> 大端整形
  16. unsigned long inet_addr (const char FAR * cp); // windows
  17. in_addr_t inet_addr (const char *cp); // linux
  18. // 大端整形 -> 点分十进制IP
  19. // window, linux相同
  20. char* inet_ntoa(struct in_addr in);

1.2 文件描述符

在 tcp 的服务器端,有两类文件描述符:一类用于监听的,一类用于通信的
1、监听的只需要有一个,它不负责和客户端通信,负责检测客户端的连接请求,检测到之后调用 accept 函数就可以建立新的连接。
2、用于通信的可以创建多个描述符,如果有 N 个客户端和服务器建立了新的连接,通信的文件描述符就有 N 个,每个客户端和服务器都对应一个通信的文件描述符

1.3 HTTP表头文件形式

请求消息:有两种
GET:安全且幂等,不会破坏服务器上的资源,多次执行相同操作结果都是一样的。
POST:不安全、不幂等
image.png
响应消息:
image.png
状态码,状态信息:
200 OK、404 Not Found等
content-Type:image.png

1.4 传输控制层(基于tcp的套接字通信流程)

TCP是一个传输层的协议,TCP 是一个面向连接的,可靠的流式传输协议

  1. 面向连接:是一个双向连接,通过三次握手完成,断开连接需要通过四次挥手完成。

连接完成会在双方内存中开辟相应的资源(接收队列、发送队列)
网络基础 - 图4

网络基础 - 图5

  1. 安全:tcp 通信过程中,会对发送的每一数据包都会进行校验,如果发现数据丢失,会自动重传
  2. 流式传输:发送端和接收端处理数据的速度,数据的量都可以不一致

实时抓包:-nn参数是为了IP和端口号都以数字形式显示
image.png

流程:
image.png
下面通过QT实现服务器端和客户端的tcp网络通信
因为TcpServer中没有任何有关读写操作的成员函数,所以读写数据的操作全权交由TcpSocket处理。

1.2.1 服务器端

  1. int main()
  2. {
  3. QTcpServer* tcpServer = new QTcpServer(this); //实例化服务器
  4. QTcpSocket* currentClient; //客户端
  5. // 用于记录当前连接的客户端
  6. currentClient = new QTcpSocket(this);

1.2.2 客户端

1.5 linux实现HTTP通信

1.5.1 服务端的实现

  1. //响应消息的发送头
  2. void http_respond_head (int cfd, char* type){
  3. char buf[1024];
  4. //状态行
  5. sprintf(buf"http/1.1 200 OK\r\n");
  6. write(cfd, buf, strlen(buf));
  7. //消息报头
  8. sprintf(buf"Content-Type: %s\r\n", type);
  9. write(cfd, buf, strlen(buf));
  10. //空行
  11. write(cfd, "\r\n", 2);
  12. }
  13. void main(string& path)
  14. {
  15. //修改当前进程的工作目录
  16. chdir(path);
  17. //1、创建监听的套接字
  18. int lfd = socket(af_inet, sock_stream, 0);
  19. // 参数af_inet指定的是IPv4格式的IP地址,如果是本地套接字通信的可以为AF_LOCAL
  20. // sock_stream是流式传输协议(tcp),还有sock_dgram报式传输协议(udp)
  21. //2、绑定
  22. struct sockaddr_in addr;
  23. // 初始化addr(该结构体中有三个成员)
  24. addr.family = af_inet;
  25. // hton 将小端转为大端进行网络传输
  26. addr.port = htons(80); //随便给个端口
  27. addr.addr.s_addr = INADDR_ANY; //初始化任意IP地址:0.0.0.0
  28. bind(lfd, (struct sockaddr*)&addr, sizeof(addr));
  29. // bind()
  30. // 绑定函数参数一是文件描述符,就是调用套接字函数的返回值
  31. // 参数二serv是一个sockaddr(sockaddr_in)结构体,主要用于存储IP和端口号
  32. // len是serv的内存大小
  33. //3、监听
  34. listen(lfd, 128);
  35. //4、阻塞并等待客户端的连接
  36. int cfd = accept(); //cfd是一个文件描述符
  37. // int accept(int sockefd, struct sockaddr * addr, len * addrlen);
  38. // 接收函数的参数二不是一个初始化参数,而是对应要连接的客户端的IP和端口号,是一个传出参数
  39. // accept函数的返回值用于通信的文件描述符,基于这个描述符客户端就可以和服务器通信了
  40. //5、读数据,获取客户端的http请求消息(read或者recv函数)
  41. read(cfd, buf, sizeof(buf));
  42. //先将buf中的请求行拿出来 // GET /hello.c http/1.1
  43. char method[12], path[1024], protocol[12];
  44. char* file = path+1; //得到文件名,因为path第一个字符是/
  45. //打开文件(记得在函数开头将路径更改到当前的资源目录,现在就可以直接打开hello.c)
  46. int fdd = open(file, O_RDONLY);
  47. int len = 0;
  48. //在后续给客户端发数据需要遵守的http协议格式(响应消息头)
  49. http_respond_head(cfd , "text/plain");
  50. //由于栈区内存不够,故循环分片读数据(write或者send函数)
  51. while( (len=read(fddbufsizeof(buf))) > 0)
  52. {
  53. // 数据发送给浏览器
  54. write(fdd,const buf, len);
  55. }
  56. close(fd);
  57. close(cfd);
  58. }

1.5.2 客户端的实现

对于客户端的绑定操作,用于通信的客户端也是需要绑定一个端口的,但并没有像服务器那样通过bind函数进行绑定,而是系统通过调用connect函数之后随机绑定一个没有被占用的端口,服务器不会去主动连接客户端的。

  1. int connect(int sockefd, const struct sockaddr * addr, len * addrlen )
  2. // 连接函数第一个参数sockefd是客户端创建用于通信的套接字
  3. // 第二个参数是传入参数,给addr初始化相关的IP和端口号
  1. void main(string& path)
  2. {
  3. //1、创建通信的套接字
  4. int fd = socket(af_inet, sock_stream, 0);
  5. //2、连接服务器的IP和端口号
  6. struct sockaddr_in addr;
  7. // 初始化addr(该结构体中有三个成员)
  8. addr.family = af_inet;
  9. // hton 将小端转为大端进行网络传输
  10. addr.port = htons(8989); //绑定服务器的端口
  11. addr.addr.s_addr = "换成本机IP地址";
  12. int cfd = accept();
  13. // int accept(int sockefd, struct sockaddr * addr, len * addrlen);
  14. // 接收函数的参数二不是一个初始化参数,而是对应要连接的客户端的IP和端口号,是一个传出参数
  15. // accept函数的返回值用于通信的文件描述符,基于这个描述符客户端就可以和服务器通信了
  16. //3、通信
  17. // 客户端先发送数据
  18. char buf[1024];
  19. send(fd, buf, sizeof(buf));
  20. read(fdbufsizeof(buf));
  21. close(fd);
  22. }

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