注意!!注意!!注意!!
本篇博客是:
- 网络编程的理论基础
- 是一个服务器开发程序员的重要基本功
- 是整个Linux课程中的重点和难点
- 也是各大公司面试笔试的核心考点
应用层
我们程序员写的一个个解决我们实际问题,满足我们日常需求的网络程序,都在应用层。
再谈“协议”
协议是一种 “约定”. socket api的接口, 在读写数据时, 都是按 “字符串” 的方式来发送接收的. 如果我们要传输一些 “结构化的数据” 怎么办呢?
网络版计算机
例如, 我们需要实现一个服务器版的加法器. 我们需要客户端把要计算的两个加数发过去, 然后由服务器进行计算, 后再把结果返回给客户端。
约定方案一:
- 客户端发送一个形如“1+1”的字符串
- 这个字符串有两个操作符,都是整型
- 两个数字之间会有一个字符是运算符, 运算符只能是 + ;
- 数字和运算符之间没有空格
约定方案二:
- 定义结构体来表示我们需要交互的信息;
- 发送数据时将这个结构体按照一个规则转换成字符串, 接收到数据的时候再按照相同的规则把字符串转 化回结构体;
- 这个过程叫做 “序列化” 和 "反序列化
//proto.h定义通信的结构体
typedef struct Request
{
int a;
int b;
}Request;
typedef struct Response
{
int sum;
};
//client.c 客户端核心代码
Request request;
Response response;
scanf("%d %d", &request.a, &response.b);
write(fd, request, sizeof(Request));
read(fd, response sizeof(Response));
//server.c 服务器核心代码
Request request;
read(client_fd, &request, sizeof(request));
Response response;
response.sum = request.a + request.b;
write(client_fd, &response, sizeof(response));
无论我们采用方案一, 还是方案二, 还是其他的方案, 只要保证, 一端发送时构造的数据, 在另一端能够正确的进行解 析, 就是ok的. 这种约定, 就是 应用层协议
HTTP协议(超文本传输协议)
- 无状态的
- 应用层
- 可靠
- 面向字节流
- 底层是TCP协议,正常通信前要进行三次握手
认识URL
平时我们俗称的“网址”,其实就是说的URL
urlencode和urldecode
像 / ? : 等这样的字符, 已经被url当做特殊意义理解了. 因此这些字符不能随意出现.
比如, 某个参数中需要带有这些特殊字符, 就必须先对特殊字符进行转义.
转义的规则如下:
将需要转码的字符转为16进制,然后从右到左,取4位(不足4位直接处理),每2位做一位,前面加上%,编码成%XY 格式 ,"+" 被转义成了 “%2B”
HTTP协议格式
HTTP的方法
其中最常用的我就是GET和POST方法
GET和POST的区别?
- GET在url传参,POST在正文传参
- url的长度是有限制的,正文传参是无限制的
- GET在传参时,把数据暴露在外边,而POST不会把信息暴露在外边,POST比GET更私密
HTTP的状态码
最常见的状态码,比如200(ok),404(Not Found0),403(Forbidden),302(Rediret,重定向),504(Bad Gateway)
HTTP常见的Header
- Content-Type:数据类型(text/html等)
- Content-Length:Body的长度(读取有效载荷)
- Host:客户端告知服务器,所请求的资源是在哪个主机的哪个端口上
- User-Agent:声明用户的操作系统和浏览器版本信息
- referer:当前页面是从哪个页面跳转过来的
- location:搭配3xx状态码使用, 告诉客户端接下来要去哪里访问
- Cookie(重点讲):用于在客户端存储少量信息. 通常用于实现会话(session)的功能
比如,你在百度上访问了一个网站,网站记住了你的用户名和密码,你下次登录就方便了,方便了用户,但是不能滥用
最简单的HTTP服务器
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
void Usage()
{
printf("usage: ./server [ip] [port]\n");
}
int main(int argc, char* argv[])
{
if (argc != 3)
{
Usage();
return 1;
}
}
int fd = socket(AF_INET, SOCK_STREAM, 0);
if (fd < 0) {
perror("socket");
return 1;
}
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = inet_addr(argv[1]);
addr.sin_port = htons(atoi(argv[2]));
int ret = bind(fd, (struct sockaddr*)&addr, sizeof(addr));
if (ret < 0) {
perror("bind");
return 1;
}
ret = listen(fd, 10);
if (ret < 0) {
perror("listen");
return 1;
}
for (;;) {
struct sockaddr_in client_addr;
socklen_t len;
int client_fd = accept(fd, (struct sockaddr*)&client_addr, &len);
if (client_fd < 0) {
perror("accept");
continue;
}
char input_buf[1024 * 10] = {0};
// 用一个足够大的缓冲区直接把数据读完.
ssize_t read_size = read(client_fd, input_buf, sizeof(input_buf) - 1);
if (read_size < 0) {
return 1;
}
printf("[Request] %s", input_buf);
char buf[1024] = {0};
const char* hello = "<h1>hello world</h1>";
sprintf(buf, "HTTP/1.0 200 OK\nContent-Length:%lu\n\n%s", strlen(hello), hello);
write(client_fd, buf, strlen(buf));
}
return 0;
}