笔记
http服务器
一.何为HTTP
1> Hyper Text Transfer Protocol(超文本传输协议),用于万维网(WWW:world wide web)进行超文本(html文档)信息的传送协议
2> 该协议属于应用层协议,传输层是使用TCP实现的
3> http是基于BS模型,即浏览器服务器模型,主要完成的是客户端请求端和服务器相应端
二.超文本文档HTML
1> Hypertext Markup Language 超文本标记语言,属于标签式语言,能够被浏览器所识别
2> 案例文档
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>欢迎来到我的主页</title>
</head>
<body align="center">
<h1 >欢迎欢迎来的我的主页</h1>
姓名:<input type="text">
年龄:<input type="password"><br><br>
留言 <textarea name="" id=""></textarea><br>
<input type="button" value="提交">
<input type="button" value="取消">
</body>
</html>
三.客户端请求
3.1请求首部
1> 任何一个http请求都由三部分组成:请求首部、请求主体、请求数据
2> 对于客户端请求协议包而言,一般没有请求数据
3> 方法:
GET:表示发送请求数据(常用)
POST:应答数据(常用)
DELETE:删除文档
PUT:上传文件
4> 需要对发来的请求进行解析,需要获取请求头部部分的方法以及url以便于相应
#include<myhead.h>
#define SER_PORT 80 //服务器端口号
#define SER_IP "192.168.0.133" //服务器ip地址
int get_one_line(int newfd, char msg[])
{
char buf = '\0'; //读取消息
int i = 0; //填充数组
while(1)
{
int res = recv(newfd, &buf, 1, 0);
if(res == 0)
{
break;
}else if (res== 1)
{
if(buf=='\n')
{
break;
}else
{
msg[i] = buf;
i++; //填充下一个
}
}else
{
perror("recv error");
return -1;
}
}
msg[i] = '\0';
}
int main(int argc, const char *argv[])
{
//1、创建套接字
int sfd = socket(AF_INET, SOCK_STREAM, 0);
//参数1:表示ipv4的网络通信
//参数2:表示使用的是TCP通信方式
//参数3:表示默认使用一个协议
if(sfd == -1)
{
perror("socket error");
return -1;
}
printf("socket success, sfd = %d\n", sfd); //3
//将端口号快速重用
int reuse = 1;
if(setsockopt(sfd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse))==-1)
{
perror("setsockopt error");
return -1;
}
printf("端口号快速重用成功\n");
//2、为套接字绑定ip地址和端口号
//2.1 填充地址信息结构体
struct sockaddr_in sin;
sin.sin_family = AF_INET; //通信域
sin.sin_port = htons(SER_PORT); //端口号
sin.sin_addr.s_addr = inet_addr(SER_IP); //ip地址
//2.2 绑定工作
if(bind(sfd, (struct sockaddr*)&sin, sizeof(sin)) == -1)
{
perror("bind error");
return -1;
}
printf("bind success\n");
//3、将套接字设置成被动监听状态
if(listen(sfd, 128)==-1)
{
perror("listen error");
return -1;
}
printf("listen success\n");
//4、阻塞等待客户端的连接请求
//4.1 定义变量用于接收客户端的信息
struct sockaddr_in cin; //用于接收地址信息
socklen_t addrlen = sizeof(cin); //用于接收长度
while(1)
{
//4.2 接收连接
int newfd = accept(sfd, (struct sockaddr*)&cin, &addrlen) ;
if(newfd == -1)
{
perror("accept error");
return -1;
}
printf("[%s:%d]: 已成功连接一个客户端\n", \
inet_ntoa(cin.sin_addr), ntohs(cin.sin_port));
//5、数据收发
char buf[128] = "";
//接收请求头部
get_one_line(newfd, buf); //获取到请求行
//printf("buf = %s\n", buf);
//获取请求请求方法
char mathod[5] = "";
strcpy(mathod, strtok(buf, " ")); //将分割下的第一个串放入到mathod中
char url[50] = "";
strcpy(url, strtok(NULL, " "));
//printf("mathod = %s, url = %s\n", mathod, url);
//判断是否为GET方法
if(strcmp(mathod, "GET") == 0)
{
//说明要进行get请求,完成响应工作
//重组url
snprintf(url, "./htmls%s", url);
//判断要访问的文件是否存在
if(access(url, F_OK) ==-1)
{
//返回一个响应为NOT FOND的网页
//do_notfond(newfd);
return -1;
}else
{
//给出响应
//do_response(newfd);
}
}else
{
//非法请求
//do_illigal(newfd);
}
}
//6、关闭监听
close(sfd);
return 0;
}
3.2 响应首部
1> 响应首部也是由三部分组成,分别是响应头、响应主体、响应数据
2> 对于响应首部而言,必须由响应数据
3> 常见的响应代号
http的响应代号:1XX (信息状态错误) 2XX(成功) 、3XX(重定向状态码)、4XX(客户端错误)、5XX(服务器出错)
#include<myhead.h>
#define SER_PORT 80 //服务器端口号
#define SER_IP "192.168.0.172" //服务器ip地址
int get_one_line(int newfd, char msg[])
{
char buf = '\0'; //读取消息
int i = 0; //填充数组
while(1)
{
int res = recv(newfd, &buf, 1, 0);
if(res == 0)
{
break;
}else if (res== 1)
{
if(buf=='\n')
{
break;
}else
{
msg[i] = buf;
i++; //填充下一个
}
}else
{
perror("recv error");
return -1;
}
}
msg[i] = '\0';
}
//定义发送头部函数
int send_head(int newfd, int file_size)
{
char *head = "HTTP/1.1 200 OK\r\n\
Content-Type:text/html\r\n";
char new_head[512] = "";
//将要发送的数据长度连接
snprintf(new_head, sizeof(new_head), "%sContent-Length:%d\r\n\r\n", head, file_size);
printf("new_head = %s\n", new_head);
//将头部发送给客户端
int res = send(newfd, new_head, strlen(new_head), 0);
return res;
}
//定义发送文件的函数
void send_html(int newfd, int fd)
{
//定义搬运工
char buf[128] = "";
while(1)
{
//从文件中读取数据
int res = read(fd, buf, sizeof(buf));
if(res == -1)
{
//服务器内部错误
//do_server_error(newfd);
return ;
}else if(res == 0)
{
break;
}
//将数据发送给客户端
send(newfd, buf, res, 0);
}
}
//定义服务器内部出差函数
void do_server_error(int newfd)
{
char *msg = "HTTP/1.1 502 SERVER_ERROR\r\n\
Content-Type:text/html\r\n\r\n";
//发送给客户端
send(newfd, msg, strlen(msg), 0);
//定义要发送的数据
char *m = "<!DOCTYPE html>\n\
<html lang=\"en\"> \n\
<head>\n\
<meta charset=\"UTF-8\" />\n\
<title>Document</title>\n\
</head>\n\
<body align=\"center\">\n\
<h3>服务器内部出错</h3>\n\
<p>文件描述符打开出错</p>\n\
</body>\n\
</html>";
//发送给服务器
send(newfd, m, strlen(m), 0);
}
/*********************************响应客户端函数********************************/
void do_response(int newfd, const char *url)
{
//1、以只读的形式打开文件
int fd = open(url, O_RDONLY);
if(fd == -1)
{
//服务器内部错误
do_server_error(newfd);
return ;
}
//2、获取文件的长度
int file_size = lseek(fd, 0, SEEK_END);
//3、封装响应头部发送给客户端
int res = send_head(newfd, file_size);
if(res != 0)
{
//4、发送html文档给客户端
lseek(fd, 0, SEEK_SET);
send_html(newfd, fd);
}
printf("响应成功\n");
close(fd); //关闭文件
}
//定义notfount函数
void do_notfond(int newfd)
{
char *msg = "HTTP/1.1 404 NOT FOUND\r\n\
Content-Type:text/html\r\n\r\n\
<!DOCTYPE html>\n\
<html lang=\"en\"> \n\
<head>\n\
<meta charset=\"UTF-8\" />\n\
<title>Document</title>\n\
</head>\n\
<body align=\"center\">\n\
<h3>404 NOT FOUND</h3>\n\
<p>您要访问的网页不存在</p>\n\
</body>\n\
</html>";
//发送给服务器
send(newfd, msg, strlen(msg), 0);
}
/***************************************主程序***********************************/
int main(int argc, const char *argv[])
{
//1、创建套接字
int sfd = socket(AF_INET, SOCK_STREAM, 0);
//参数1:表示ipv4的网络通信
//参数2:表示使用的是TCP通信方式
//参数3:表示默认使用一个协议
if(sfd == -1)
{
perror("socket error");
return -1;
}
printf("socket success, sfd = %d\n", sfd); //3
//将端口号快速重用
int reuse = 1;
if(setsockopt(sfd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse))==-1)
{
perror("setsockopt error");
return -1;
}
printf("端口号快速重用成功\n");
//2、为套接字绑定ip地址和端口号
//2.1 填充地址信息结构体
struct sockaddr_in sin;
sin.sin_family = AF_INET; //通信域
sin.sin_port = htons(SER_PORT); //端口号
sin.sin_addr.s_addr = inet_addr(SER_IP); //ip地址
//2.2 绑定工作
if(bind(sfd, (struct sockaddr*)&sin, sizeof(sin)) == -1)
{
perror("bind error");
return -1;
}
printf("bind success\n");
//3、将套接字设置成被动监听状态
if(listen(sfd, 128)==-1)
{
perror("listen error");
return -1;
}
printf("listen success\n");
//4、阻塞等待客户端的连接请求
//4.1 定义变量用于接收客户端的信息
struct sockaddr_in cin; //用于接收地址信息
socklen_t addrlen = sizeof(cin); //用于接收长度
while(1)
{
//4.2 接收连接
int newfd = accept(sfd, (struct sockaddr*)&cin, &addrlen) ;
if(newfd == -1)
{
perror("accept error");
return -1;
}
printf("[%s:%d]: 已成功连接一个客户端\n", \
inet_ntoa(cin.sin_addr), ntohs(cin.sin_port));
//5、数据收发
char buf[128] = "";
//接收请求头部
get_one_line(newfd, buf); //获取到请求行
//printf("buf = %s\n", buf);
//获取请求请求方法
char mathod[5] = "";
strcpy(mathod, strtok(buf, " ")); //将分割下的第一个串放入到mathod中
char url[50] = "";
char new_url[50] = "";
strcpy(url, strtok(NULL, " "));
//printf("mathod = %s, url = %s\n", mathod, url);
//判断是否为GET方法
if(strcmp(mathod, "GET") == 0)
{
//说明要进行get请求,完成响应工作
//重组url
snprintf(new_url, sizeof(new_url), "./htmls%s", url);
printf("url = %s\n", new_url);
//判断要访问的文件是否存在
if(access(new_url, F_OK) ==-1)
{
//返回一个响应为NOT FOND的网页
do_notfond(newfd);
}else
{
//给出响应
do_response(newfd, new_url);
}
}else
{
//非法请求
//do_illigal(newfd);
}
}
//6、关闭监听
close(sfd);
return 0;
}
3.3 使用多线程完成http并发服务器
#include<myhead.h>
#define SER_PORT 80 //服务器端口号
#define SER_IP "192.168.0.172" //服务器ip地址
int get_one_line(int newfd, char msg[])
{
char buf = '\0'; //读取消息
int i = 0; //填充数组
while(1)
{
int res = recv(newfd, &buf, 1, 0);
if(res == 0)
{
break;
}else if (res== 1)
{
if(buf=='\n')
{
break;
}else
{
msg[i] = buf;
i++; //填充下一个
}
}else
{
perror("recv error");
return -1;
}
}
msg[i] = '\0';
return strlen(msg);
}
//定义发送头部函数
int send_head(int newfd, int file_size)
{
char *head = "HTTP/1.1 200 OK\r\n\
Content-Type:text/html\r\n";
char new_head[512] = "";
//将要发送的数据长度连接
snprintf(new_head, sizeof(new_head), "%sContent-Length:%d\r\n\r\n", head, file_size);
printf("new_head = %s\n", new_head);
//将头部发送给客户端
int res = send(newfd, new_head, strlen(new_head), 0);
return res;
}
//定义发送文件的函数
void send_html(int newfd, int fd)
{
//定义搬运工
char buf[128] = "";
while(1)
{
//从文件中读取数据
int res = read(fd, buf, sizeof(buf));
if(res == -1)
{
//服务器内部错误
//do_server_error(newfd);
return ;
}else if(res == 0)
{
break;
}
//将数据发送给客户端
send(newfd, buf, res, 0);
}
}
//定义服务器内部出差函数
void do_server_error(int newfd)
{
char *msg = "HTTP/1.1 502 SERVER_ERROR\r\n\
Content-Type:text/html\r\n\r\n";
//发送给客户端
send(newfd, msg, strlen(msg), 0);
//定义要发送的数据
char *m = "<!DOCTYPE html>\n\
<html lang=\"en\"> \n\
<head>\n\
<meta charset=\"UTF-8\" />\n\
<title>Document</title>\n\
</head>\n\
<body align=\"center\">\n\
<h3>服务器内部出错</h3>\n\
<p>文件描述符打开出错</p>\n\
</body>\n\
</html>";
//发送给服务器
send(newfd, m, strlen(m), 0);
}
/*********************************响应客户端函数********************************/
void do_response(int newfd, const char *url)
{
//1、以只读的形式打开文件
int fd = open(url, O_RDONLY);
if(fd == -1)
{
//服务器内部错误
do_server_error(newfd);
return ;
}
//2、获取文件的长度
int file_size = lseek(fd, 0, SEEK_END);
//3、封装响应头部发送给客户端
int res = send_head(newfd, file_size);
if(res != 0)
{
//4、发送html文档给客户端
lseek(fd, 0, SEEK_SET);
send_html(newfd, fd);
}
printf("响应成功\n");
close(fd); //关闭文件
}
//定义notfount函数
void do_notfond(int newfd)
{
char *msg = "HTTP/1.1 404 NOT FOUND\r\n\
Content-Type:text/html\r\n\r\n\
<!DOCTYPE html>\n\
<html lang=\"en\"> \n\
<head>\n\
<meta charset=\"UTF-8\" />\n\
<title>Document</title>\n\
</head>\n\
<body align=\"center\">\n\
<h3>404 NOT FOUND</h3>\n\
<p>您要访问的网页不存在</p>\n\
</body>\n\
</html>";
//发送给服务器
send(newfd, msg, strlen(msg), 0);
}
//定义有关数据传输的结构体
struct Info
{
int newfd;
struct sockaddr_in cin;
};
//定义线程处理函数
void *deal_cli_msg(void *arg)
{
//解析主线程传过来的数据
int newfd = (*(struct Info*)arg).newfd;
struct sockaddr_in cin = (*(struct Info*)arg).cin;
while(1)
{
//5、数据收发
char buf[128] = "";
//接收请求头部
int res = get_one_line(newfd, buf); //获取到请求行
//printf("buf = %s\n", buf);
if(res == 0)
{
break;
}
//获取请求请求方法
char mathod[5] = "";
strcpy(mathod, strtok(buf, " ")); //将分割下的第一个串放入到mathod中
char url[50] = "";
char new_url[50] = "";
strcpy(url, strtok(NULL, " "));
//printf("mathod = %s, url = %s\n", mathod, url);
//判断是否为GET方法
if(strcmp(mathod, "GET") == 0)
{
//说明要进行get请求,完成响应工作
//重组url
snprintf(new_url, sizeof(new_url), "./htmls%s", url);
printf("url = %s\n", new_url);
//判断要访问的文件是否存在
if(access(new_url, F_OK) ==-1)
{
//返回一个响应为NOT FOND的网页
do_notfond(newfd);
}else
{
//给出响应
do_response(newfd, new_url);
}
}else
{
//非法请求
//do_illigal(newfd);
}
//将剩余数据读取出来
while(res != 0)
{
res = get_one_line(newfd, buf); //获取到请求行
}
}
//退出线程
pthread_exit(NULL);
}
/***************************************主程序***********************************/
int main(int argc, const char *argv[])
{
//1、创建套接字
int sfd = socket(AF_INET, SOCK_STREAM, 0);
//参数1:表示ipv4的网络通信
//参数2:表示使用的是TCP通信方式
//参数3:表示默认使用一个协议
if(sfd == -1)
{
perror("socket error");
return -1;
}
printf("socket success, sfd = %d\n", sfd); //3
//将端口号快速重用
int reuse = 1;
if(setsockopt(sfd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse))==-1)
{
perror("setsockopt error");
return -1;
}
printf("端口号快速重用成功\n");
//2、为套接字绑定ip地址和端口号
//2.1 填充地址信息结构体
struct sockaddr_in sin;
sin.sin_family = AF_INET; //通信域
sin.sin_port = htons(SER_PORT); //端口号
sin.sin_addr.s_addr = inet_addr(SER_IP); //ip地址
//2.2 绑定工作
if(bind(sfd, (struct sockaddr*)&sin, sizeof(sin)) == -1)
{
perror("bind error");
return -1;
}
printf("bind success\n");
//3、将套接字设置成被动监听状态
if(listen(sfd, 128)==-1)
{
perror("listen error");
return -1;
}
printf("listen success\n");
//4、阻塞等待客户端的连接请求
//4.1 定义变量用于接收客户端的信息
struct sockaddr_in cin; //用于接收地址信息
socklen_t addrlen = sizeof(cin); //用于接收长度
while(1)
{
//4.2 接收连接
int newfd = accept(sfd, (struct sockaddr*)&cin, &addrlen) ;
if(newfd == -1)
{
perror("accept error");
return -1;
}
printf("[%s:%d]: 已成功连接一个客户端\n", \
inet_ntoa(cin.sin_addr), ntohs(cin.sin_port));
//创建一个分支线程用于处理客户端
struct Info cli_info = {newfd, cin};
pthread_t tid = -1;
if(pthread_create(&tid, NULL, deal_cli_msg, &cli_info) != 0)
{
printf("tid create error\n");
return -1;
}
//将分支线程设置成分离太
pthread_detach(tid);
}
//6、关闭监听
close(sfd);
return 0;
}
动态库和静态库
一.linux系统中的库
库在linux系统中是一个二进制文件,它是由XXX.c(不包含main函数)文件编译而来的,分为静态库和动态库。
库在系统中的内容是不可见的,是一个二进制乱码
当程序需要使用库中的相关函数时,直接链接对应的库即可,无需跟对应的 源文件一起联合编译
作用:能够保证源代码的安全性
库在不同的系统中的表现方式也是不同的
windows下:
静态库:XXX.lib
动态库:XXX.dll
linux下
静态库:libXXX.a
动态库:libXXX.so
二.静态库
2.1 概念
所谓静态库,就是将一个XXX.c的源文件,编译生成一个libXXX.a的二进制文件。
当程序需要使用源文件中的函数时,只需要链接上该源文件生成的静态库文件即可直接调用相关函数,无需跟源文件联合编译
静态体现在:静态库在跟主程序一起生成可执行程序时,会将静态库完整的放入可执行程序中,每个可执行程序中都独立拥有整个静态库。函数调用时,效率比较高,但是,可执行程序的体积比较大。
2.2 静态库的制作
1> 准备文件
/********************************test.h**************************/
#define TEST_H
#include<myhead.h>
//声明求和函数
int qiuhe(int m, int n);
//声明求最值函数
int qiuzuida(int m, int n);
#endif
/********************************test.c**************************/
//声明求和函数
int qiuhe(int m, int n)
{
return m+n;
}
//声明求最值函数
int qiuzuida(int m, int n)
{
return m>n?m:n;
}
/********************************main.c**************************/
#include"test.h"
int main(int argc, const char *argv[])
{
printf("sum = %d\n", qiuhe(3,5));
printf("max = %d\n", qiuzuida(3,5));
printf("%lf\n", pow(2,3));
return 0;
}
2> 编译生成静态库
1、gcc -c XXX.c -o XXX.o //只编译不链接生成二进制文件
2、ar -crs libXXX.a XXX.o //将XXX.o的二进制文件编译生成libXXX.a的静态库
一个静态库依赖多个.o文件:ar -crs libXXX.a XXX.o AAA.o BBB.o //将XXX.o的二进制文件编译生成libXXX.a的静态库
ar :指令表示要编译生成静态库
-c:create,表示创建静态库
-r:replace,表示如果XXX.o文件已经存在,则被新的文件替换
-s:重置静态库索引
3> 静态库的使用
gcc main.c -L 库的路径 -I 头文件路径 -l库名 -o 可执行程序
三.动态库
3.1 概念
所谓动态库,就是将一个XXX.c的源文件,编译生成一个libXXX.so的二进制文件。
当程序需要使用源文件中的函数时,只需要链接上该源文件生成的动态库文件即可直接调用相关函数,无需跟源文件联合编译
动态体现在:动态库在跟主程序一起生成可执行程序时,只是将相关函数的入口地址列表编译到可执行程序中,当执行到对应的函数调用时,会根据该函数的入口地址,找到对应的动态库,执行该库中的函数。不会将整个库封装到可执行程序中。多个可执行程序可以连接同一个动态库,所以动态库也称为共享库。动态库的体积较小,但是,执行效率没有静态库高。
3.2 动态库的制作
1、gcc -fPIC -c XXX.c -o XXX.o //只编译不链接生成二进制文件, -fPIC表示忽略文件位置进行编译
2、gcc -shared XXX.o -o libXXX.so //编译生成动态库
多个程序共同生成动态库:gcc -shared XXX.o AAA.o BBB.o -o libXXX.so
s也可以将上面的两个操作合成一个
gcc -fPIC -shared XXX.c -o libXXX.so
3.3 动态库的使用
gcc main.c -L 库的路径 -I 头文件路径 -l库名 -o 可执行程序