1. 概述
服务器的开发不容易,尤其是开发高性能、稳定性好服务器,更加不容易,因此人们尝试更好简单的方式来开发软件。
在服务器方面,使用Web服务器,采用HTTP协议来代替底层的socket,是常见的选择。采用HTTP协议更加除了能得到稳定的服务器支持外,更加可以兼容各种客户端(手机、PC、浏览器)等等。这样实现了一个服务器之后,多个客户端可以通用。
2.通信过程
HTTP 协议采用请求/响应模型。客户端向服务器发送一个请求报文,服务器以一个状态作为响应。
HTTP 请求/响应的步骤:
- 客户端连接到web服务器:HTTP 客户端与web服务器建立一个 TCP 连接;
- 客户端向服务器发起 HTTP 请求:通过已建立的TCP 连接,客户端向服务器发送一个请求报文;
- 服务器接收 HTTP 请求并返回 HTTP 响应:服务器解析请求,定位请求资源,服务器将资源副本写到 TCP 连接,由客户端读取;
- 释放 TCP 连接:若connection 模式为close,则服务器主动关闭TCP 连接,客户端被动关闭连接,释放TCP 连接;若connection 模式为keepalive,则该连接会保持一段时间,在该时间内可以继续接收请求;
- 客户端浏览器解析HTML内容:客户端将服务器响应的 html 文本解析并显示。
3. 测试代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
int main()
{
// 创建通信端点:套接字
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
if(sockfd < 0)
{
perror("socket");
return -1;
}
// 设置本地地址结构体
struct sockaddr_in my_addr;
bzero(&my_addr, sizeof(my_addr)); // 清空
my_addr.sin_family = AF_INET; // ipv4
my_addr.sin_port = htons(8000); // 端口
my_addr.sin_addr.s_addr = htonl(INADDR_ANY); // ip
// 绑定
int err_log = bind(sockfd, (struct sockaddr*)&my_addr, sizeof(my_addr));
if( err_log != 0)
{
perror("binding");
close(sockfd);
return -1;
}
err_log = listen(sockfd, 10); // 监听,监听套接字改为被动
if(err_log != 0)
{
perror("listen");
close(sockfd);
return -1;
}
printf("listen client @port=%d...\n", 8000);
int connfd;
connfd = accept(sockfd, NULL, NULL); // 等待连接
char recv_buf[8*1024] = {0};
read(connfd, recv_buf, sizeof(recv_buf));
printf("%s", recv_buf);
//获取客户端需要的网页内容
char filename[200] = {0};
sscanf(recv_buf, "GET /%[^ ]", filename); //获取文件名字
printf("filename = %s\n", filename);
int fd;
fd = open(filename, O_RDONLY);//只读方式打开
if(fd < 0)//打开文件失败
{
//HTTP 响应报文由状态行、响应头部、空行、响应包体4个部分组成
char err[]= "HTTP/1.1 404 Not Found\r\n" //状态行
"Content-Type: text/html\r\n" //响应头部
"\r\n" //空行
"<HTML><BODY>File not found</BODY></HTML>"; //响应包体
perror("open");
send(connfd, err, strlen(err), 0);//发送失败的响应报文头
close(connfd);
return -1;
}
//HTTP 响应报文由状态行、响应头部、空行、响应包体4个部分组成
char head[] = "HTTP/1.1 200 OK\r\n" //状态行
"Content-Type: text/html\r\n" //响应头部
"\r\n"; //空行
send(connfd, head, strlen(head), 0); //发送成功的响应报文头
//发送响应包体
int len;
char file_buf[4 * 1024];
//循环读取并发送文件,读多少,发多少
while((len = read(fd, file_buf, sizeof(file_buf))) > 0)
{
send(connfd, file_buf, len, 0);
}
close(fd);
close(connfd);
while(1)
{
NULL;
}
return 0;
}
终端运行服务器程序:
浏览器请求服务,得到相应内容:
4. 简单版Web服务器
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
/************************************************************************
函数名称: int main(int argc, char *argv[])
函数功能: 通过进程创建webserver
函数参数: int argc, char *argv[]
函数返回: 无
************************************************************************/
int main(int argc, char *argv[])
{
unsigned short port = 8000; //设置默认端口号
if(argc > 1)
{
port = atoi(argv[1]); //将参数2赋值给端口号变量
}
//创建TCP套接字
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
if( sockfd < 0)
{
perror("socket");
exit(-1);
}
//服务器套接字地址变量赋值
struct sockaddr_in my_addr;
bzero(&my_addr, sizeof(my_addr));
my_addr.sin_family = AF_INET; //IPV4族
my_addr.sin_port = htons(port); //将端口号转换成网络字节序
my_addr.sin_addr.s_addr = htonl(INADDR_ANY); //本机IP地址
//绑定TCP套接字
if( bind(sockfd, (struct sockaddr*)&my_addr, sizeof(my_addr)) != 0)
{
perror("bind");
close(sockfd);
exit(-1);
}
//监听
if( listen(sockfd, 10) != 0)
{
perror("listen");
close(sockfd);
exit(-1);
}
printf("Listenning at port=%d\n",port); //打印端口号信息
printf("Usage: http://127.0.0.1:%d/html/index.html\n", port);
while(1)
{
char cli_ip[INET_ADDRSTRLEN] = {0}; //存放客户端点分十进制IP地址
struct sockaddr_in client_addr;
socklen_t cliaddr_len = sizeof(client_addr);
//等待客户端连接
int connfd = accept(sockfd, (struct sockaddr*)&client_addr, &cliaddr_len);
printf("connfd=%d\n",connfd); //打印已连接套接字
if(connfd > 0)
{
if(fork() == 0) //创建进程并判断返回值
{
close(sockfd);
//子进程执行
int fd = 0;
int len = 0;
char buf[1024] = "";
char filename[50] = "";
//将网络字节序转换成点分十进制形式存放在cli_ip中
inet_ntop(AF_INET, &client_addr.sin_addr, cli_ip, INET_ADDRSTRLEN);
printf("connected form %s\n\r", cli_ip); //打印点分十进制形式的客户端IP地址
recv(connfd, buf, sizeof(buf), 0); //接收客户端发送的请求内容
sscanf(buf, "GET /%[^ ]", filename); //解析客户端发送请求字符串
printf("filename=*%s*\n", filename);
fd = open(filename, O_RDONLY); //以只读方式打开文件
if( fd < 0) //如果打开文件失败
{
//HTTP失败头部
char err[]= "HTTP/1.1 404 Not Found\r\n"
"Content-Type: text/html\r\n"
"\r\n"
"<HTML><BODY>File not found</BODY></HTML>";
perror("open error");
send(connfd, err, strlen(err), 0);
close(connfd); //关闭已连接套接字
exit(0); //子进程退出
}
//打开文件成功后
//接收成功时返回的头部
char head[]="HTTP/1.1 200 OK\r\n"
"Content-Type: text/html\r\n"
"\r\n";
send(connfd, head, strlen(head), 0); //发送HTTP请求成功头部
while( (len = read(fd, buf, sizeof(buf))) > 0) //循环读取文件内容
{
send(connfd, buf, len, 0); //将读得的数据发送给客户端
}
close(fd); //成功后关闭文件
close(connfd); //关闭已连接套接字
exit(0); //子进程退出
}
}
close(connfd); //父进程关闭连接套接字
}
close(sockfd);
printf("exit main!\n");
return 0;
}
本教程示例代码下载请点此链接:http://download.csdn.net/detail/tennysonsky
github下载路径:https://github.com/mikejiangsky/http_communication.git
时间: 2024-10-10 00:19:07