我这里设置的是1234
#define SERVER_PORT 1234
#include
#include
#include
#include
#include
#include
#include
#include
int main(){
//创建套接字
int serv_sock = socket(AF_INET, SOCK_STREAM, 0);
//将套接字和IP、端口绑定
struct sockaddr_in serv_addr;
//每个字节都用0填充
bzero(&serv_addr, sizeof(serv_addr));
serv_addr.sin_family = AF_INET; //使用IPv4地址
serv_addr.sin_addr.s_addr = htonl(INADDR_ANY); //监听本地所有IP地址
serv_addr.sin_port = htons(SERVER_PORT); //端口
//绑定
bind(serv_sock, (struct sockaddr*)&serv_addr, sizeof(serv_addr));
//进入监听状态,等待用户发起请求
listen(serv_sock, 20);
printf("wait client connect...\n");
return 0;
}
int main(){
//...
//printf("wait client connect...\n");
int done = 1;
while (done){
//接收客户端请求
struct sockaddr_in clnt_addr;
socklen_t clnt_addr_size = sizeof(clnt_addr);
int clnt_sock = accept(serv_sock, (struct sockaddr*)&clnt_addr, &clnt_addr_size);
char client_ip[64];
char buf[256];
//打印客户端ip地址和端口号
printf("client ip: %s\t port: %d\n",
inet_ntop(AF_INET, &clnt_addr.sin_addr.s_addr, client_ip, sizeof(client_ip)),
ntohs(clnt_addr.sin_port));
//关闭套接字
close(clnt_sock);
}
//关闭套接字
close(serv_sock);
return 0;
}
获取服务器的ip地址
ifconfig
gcc server.c -o server
./server
在浏览器上输入
http://ip:port
客户端请求消息
客户端发送一个HTTP请求到服务器的请求消息包括以下格式:请求行(request line)、请求头部(header)、空行和请求数据四个部分组成,下图给出了请求报文的一般格式。
定义一个get_http_line(int clnt_sock, char * buf, int size)的函数,读取socket缓冲区的一行数据,放回-1读取失败,否则返回这一行数据的字符个数。
int get_http_line(int clnt_sock, char * buf, int size){
int cnt = 0; //这一行数据的字符个数
char ch = '\0';
int len = 0;
while (cnt < size - 1){
//读取socket缓冲区的一个字符
len = read(clnt_sock, &ch, 1);
if (len == 1){ //读取成功
if (ch == '\r'){ //吸收回车符
continue;
}else if (ch == '\n'){
break;
}
buf[cnt++] = ch;
}else if (len == -1){ //读取失败
fprintf(stderr, "read failed\n");
cnt = -1;
break;
}else { //客户端主动关闭
fprintf(stderr, "client close\n");
cnt = -1;
break;
}
}
if (cnt >= 0){
buf[cnt] = '\0';
}
return cnt;
}
定义一个do_http_request(int clnt_sock)的函数,实现获取客户端http请求的数据
void do_http_request(int clnt_sock){
int len = 0;
char buf[256];
//打印数据
do{
len = get_http_line(clnt_sock, buf, sizeof(buf));
if (debug) printf("%s\n", buf);
}while (len > 0);
}
在main函数这个位置调用do_http_request函数
//获取客户端http请求数据
do_http_request(clnt_sock);
根据 HTTP 标准,HTTP 请求可以使用多种请求方法。
HTTP1.0 定义了三种请求方法: GET, POST 和 HEAD 方法。
HTTP1.1 新增了六种请求方法:OPTIONS、PUT、PATCH、DELETE、TRACE 和 CONNECT 方法。
这里我只完成了GET的请求方法,其他请求 暂不处理。
重写do_http_request(int clnt_sock)函数
#include
#include
struct stat st;
static int debug = 1;
static char parent_path[] = "."; //存放html文件的主目录,"."为相对路径
void do_http_request(int clnt_sock){
int len = 0;
char buf[256];
char method[16]; //请求方法
char url[256]; //请求方法
char path[260]; //请求的文件、路径
//获取请求行(request line)
len = get_http_line(clnt_sock, buf, sizeof(buf));
if (len > 0){
int i = 0, j = 0;
//获取请求方法
while (buf[j] != ' ' && i < sizeof(method) - 1){
method[i++] = buf[j++];
}
method[i] = '\0';
if (debug) printf("request method: %s\n", method);
if (strncasecmp(method, "GET", i) == 0){ //get请求
while (buf[j++] != ' ');
i = 0;
//获取请求url
while (buf[j] != ' ' && i < sizeof(url) - 1){
url[i++] = buf[j++];
}
url[i] = '\0';
if (debug) printf("request url: %s\n", url);
//TODO:获取请求参数
//去掉请求参数
char *last = strchr(url, '?');
if (last) *last = '\0';
if (debug) printf("real request url: %s\n", url);
//获取请求文件、路径
sprintf(path, "%s%s", parent_path, url);
if (debug) printf("request path: %s\n", path);
//判断文件是否存在
if (stat(path, &st) == -1){
fprintf(stderr, "stat %s find failed. reason: %s\n", path, strerror(errno));
}else {
if (S_ISDIR(st.st_mode)){ //如果是目录,获取对应目录的index.html文件
strcat(path, "index.html");
}
}
}else{ //其他请求 (暂不处理)
fprintf(stderr, "other request [%s]\n", method);
}
//打印数据
if (debug) printf("request body:\n%s\n", buf);
do{
len = get_http_line(clnt_sock, buf, sizeof(buf));
if (debug) printf("%s\n", buf);
}while (len > 0);
}else{ //请求格式有问题,出错处理
fprintf(stderr, "request format error\n");
}
}
HTTP 状态码
当浏览者访问一个网页时,浏览者的浏览器会向网页所在服务器发出请求。当浏览器接收并显示网页前,此网页所在的服务器会返回一个包含 HTTP 状态码的信息头(server header)用以响应浏览器的请求。
HTTP 状态码的英文为 HTTP Status Code。
下面是常见的 HTTP 状态码:
200 - 请求成功
301 - 资源(网页等)被永久转移到其它URL
404 - 请求的资源(网页等)不存在
500 - 内部服务器错误
HTTP 状态码分类
HTTP 状态码由三个十进制数字组成,第一个十进制数字定义了状态码的类型。响应分为五类:信息响应(100–199),成功响应(200–299),重定向(300–399),客户端错误(400–499)和服务器错误 (500–599):
HTTP响应也由四个部分组成,分别是:状态行、消息报头、空行和响应正文。
这里我只实现了200,400,404,500,501的状态码
200 OK 请求成功。一般用于GET与POST请求
400 Bad Request 客户端请求的语法错误,服务器无法理解
404 Not Found 服务器无法根据客户端的请求找到资源(网页)。通过此代码,网站设计人员可设置"您所请求的资源无法找到"的个性页面
500 Internal Server Error 服务器内部错误,无法完成请求
501 Not Implemented 服务器不支持请求的功能,无法完成请求
定义HTTP响应状态行全局变量
char z00[] = "HTTP/1.0 200 OK\r\n";
char f00[] = "HTTP/1.0 400 BAD REQUEST\r\n";
char f0f[] = "HTTP/1.0 404 NOT FOUND\r\n";
char b00[] = "HTTP/1.0 500 Internal Sever Error\r\n";
char b0l[] = "HTTP/1.0 501 Method Not Implemented\r\n";
定义一个do_http_response(int clnt_sock, const char *path)的函数,实现Http响应
void do_http_response(int clnt_sock, const char *path){
FILE *resourse = NULL;
resourse = fopen(path, "r");
if (resourse == NULL){
fprintf(stderr, "stat %s open failed. reason: %s\n", path, strerror(errno));
not_found(clnt_sock);
return;
}
//定义HTTP响应状态行
char *header = z00;
if (strncasecmp(path, "./error.html", 12) == 0){
header = f0f;
}else if (strncasecmp(path, "./inner.html", 12) == 0){
header = b00;
}else if (strncasecmp(path, "./unimplemented.html", 18) == 0){
header = b0l;
}else if (strncasecmp(path, "./bad_request.html", 16) == 0){
header = f00;
}
//发送头部
if (headers(clnt_sock, resourse, header) == 0){
//发送内容
tailers(clnt_sock, resourse);
}
fclose(resourse);
}
定义not_found、inner_error、unimplemented、bad_request函数来实现响应404、500、501、400状态。
void not_found(int clnt_sock){
do_http_response(clnt_sock, "./error.html");
}
void inner_error(int clnt_sock){
do_http_response(clnt_sock, "./inner.html");
}
void unimplemented(int clnt_sock){
do_http_response(clnt_sock, "./unimplemented.html");
}
void bad_request(int clnt_sock){
do_http_response(clnt_sock, "./bad_request.html");
}
在do_http_request函数这个位置调用它们
在do_http_request函数这个位置调用do_http_response函数
发送头部函数headers(int clnt_sock, FILE *resourse, const char *header),返回-1发送失败(文件已经打开但是访问文件出错),返回0发送成功
int headers(int clnt_sock, FILE *resourse, const char *header){
struct stat st;
int fileId = 0;
fileId = fileno(resourse); //获取已经打开的文件的文件描述符
if (fstat(fileId, &st) == -1){ //文件已经打开但是访问文件出错
fprintf(stderr, "inner error. reason: %s\n", strerror(errno));
inner_error(clnt_sock);
return -1;
}
char buf[1024] = {0};
char temp[64];
//状态行
strcpy(buf, header);
//消息报头
strcat(buf, "Server: Martin Server\r\n");
strcat(buf, "Content-Type: text/html\r\n");
strcat(buf, "Connection: Close\r\n");
int wc = st.st_size; //正文大小
sprintf(temp, "Content-Length: %d\r\n\r\n", wc);
strcat(buf, temp);
if (debug) printf("write:\n%s\n", buf);
if(send(clnt_sock, buf, strlen(buf), 0) < 0){
fprintf(stderr, "failed to send.reason:%s.buf:%s\n", strerror(errno), buf);
return -1;
}
return 0;
}
发送内容函数tailers(int clnt_sock, FILE *resourse)
void tailers(int clnt_sock, FILE *resourse){
char buf[1024];
do{ //边读边写
fgets(buf, sizeof(buf), resourse);
int len = write(clnt_sock, buf, strlen(buf));
if (len < 0){
fprintf(stderr, "send error, reason: %s\n", strerror(errno));
break;
}
if (debug) printf("%s", buf);
}while (!feof(resourse));
if (debug) printf("\n");
}
创建html文件,并放在存放html文件的主目录里面
index.html
DOCTYPE html>
<html style="font-size: 16px;">
<head>
<meta charset="UTF-8">
<title>indextitle>
head>
<body>
<h1><b>Hello worldb>h1>
<p>Hi worldp>
body>
html>
error.html
DOCTYPE html>
<html style="font-size: 16px;">
<head>
<meta charset="UTF-8">
<title>404title>
head>
<body>
<h1><b>404 Not Foundb>h1>
<p>未找到该页面p>
body>
html>
inner.html
DOCTYPE html>
<html style="font-size: 16px;">
<head>
<meta charset="UTF-8">
<title>500title>
head>
<body>
<h1><b>500 Inner Errorb>h1>
<p>服务器内部错误p>
body>
html>
unimplemented.html
DOCTYPE html>
<html style="font-size: 16px;">
<head>
<meta charset="UTF-8">
<title>501title>
head>
<body>
<h1><b>501 Method Not Implementedb>h1>
<p>服务器不支持的请求方法p>
body>
html>
bad_request.html
DOCTYPE html>
<html style="font-size: 16px;">
<head>
<meta charset="UTF-8">
<title>400title>
head>
<body>
<h1><b>400 Bad Requestb>h1>
<p>错误的请求p>
body>
html>
编译并运行程序,输出如下:
请求空行
在终端输入:
telnet ip port
POST请求
在终端输入:
telnet ip port
POST index.html http/1.0
串行缺点:客户端排成一队,一个客户端完成请求后服务器才能接受下一个客户端的请求,客户端等待时间长。
在终端输入:
telnet ip port
然后在浏览器输入
http://ip:port
就会发现浏览器一直在等待服务器的响应,因为在浏览器前面服务器在处理telnet的请求,然而telnet不发送请求,就会造成死锁的情况。
解决死锁情况方法:
- 设定一个limit_time时间,服务器与客户端http连接后如果超过这个limit_time时间就断开连接。
- 使用多线程,并行执行http请求。
我用多线程来实现。
pthread_create函数
创建一个新线程,并行的执行任务。
int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg);
返回值:成功:0; 失败:错误号。
参数:
pthread_t:当前Linux中可理解为:typedef unsigned long int pthread_t;
参数1:传出参数,保存系统为我们分配好的线程ID
参数2:通常传NULL,表示使用线程默认属性。若想使用具体属性也可以修改该参数。
参数3:函数指针,指向线程主函数(线程体),该函数运行结束,则线程结束。
参数4:线程主函数执行期间所使用的参数。
在一个线程中调用pthread_create()创建新的线程后,当前线程从pthread_create()返回继续往下执行,而新的线程所执行的代码由我们传给pthread_create的函数指针start_routine决定。start_routine函数接收一个参数,是通过pthread_create的arg参数传递给它的,该参数的类型为void *,这个指针按什么类型解释由调用者自己定义。start_routine的返回值类型也是void *,这个指针的含义同样由调用者自己定义。start_routine返回时,这个线程就退出了,其它线程可以调用pthread_join得到start_routine的返回值,以后再详细介绍pthread_join。
pthread_create成功返回后,新创建的线程的id被填写到thread参数所指向的内存单元。
attr参数表示线程属性,本节不深入讨论线程属性,所有代码例子都传NULL给attr参数,表示线程属性取缺省值。
包含
#include
将原来的void do_http_request(int clnt_sock)修改成void * do_http_request(void * pclnt_sock),因为pthread_create传的是void *的函数指针和void *的参数。
void * do_http_request(void * pclnt_sock){
int len = 0;
char buf[256];
char method[16];
char url[256];
char path[260];
int clnt_sock = *(int *)pclnt_sock; //新增
len = get_http_line(clnt_sock, buf, sizeof(buf));
if (len > 0){
int i = 0, j = 0;
//获取请求方法
while (buf[j] != ' ' && i < sizeof(method) - 1){
method[i++] = buf[j++];
}
method[i] = '\0';
if (debug) printf("request method: %s\n", method);
if (strncasecmp(method, "GET", i) == 0){ //get请求
while (buf[j++] != ' ');
i = 0;
//获取请求url
while (buf[j] != ' ' && i < sizeof(url) - 1){
url[i++] = buf[j++];
}
url[i] = '\0';
if (debug) printf("request url: %s\n", url);
//去掉请求参数
char *last = strchr(url, '?');
if (last) *last = '\0';
if (debug) printf("real request url: %s\n", url);
//获取请求文件、路径
sprintf(path, "%s%s", parent_path, url);
if (debug) printf("request path: %s\n", path);
//判断文件是否存在
if (stat(path, &st) == -1){
fprintf(stderr, "stat %s find failed. reason: %s\n", path, strerror(errno));
not_found(clnt_sock);
}else {
if (S_ISDIR(st.st_mode)){
strcat(path, "index.html");
}
do_http_response(clnt_sock, path);
}
}else{ //其他请求 (暂不处理)
fprintf(stderr, "other request [%s]\n", method);
unimplemented(clnt_sock);
}
if (debug) printf("request body:\n%s\n", buf);
do{
len = get_http_line(clnt_sock, buf, sizeof(buf));
if (debug) printf("%s\n", buf);
}while (len > 0);
}else{ //请求格式有问题,出错处理
fprintf(stderr, "request format error\n");
bad_request(clnt_sock);
}
//关闭套接字
close(clnt_sock);
//释放指针
if (pclnt_sock) free(pclnt_sock);
pclnt_sock = 0;
return NULL;
}
在main函数增加如下代码:
pthread_t p_id;
int * pclnt_sock = NULL;
pclnt_sock = (int *)malloc (sizeof(int));
*pclnt_sock = clnt_sock;
//开启线程
pthread_create(&p_id, NULL, do_http_request, (void *)pclnt_sock);
gcc server.c -o server -lpthread
./server
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define SERVER_PORT 1234
struct stat st;
static int debug = 1;
static char parent_path[] = ".";
char z00[] = "HTTP/1.0 200 OK\r\n";
char f00[] = "HTTP/1.0 400 BAD REQUEST\r\n";
char f0f[] = "HTTP/1.0 404 NOT FOUND\r\n";
char b00[] = "HTTP/1.0 500 Internal Sever Error\r\n";
char b0l[] = "HTTP/1.0 501 Method Not Implemented\r\n";
int get_http_line(int clnt_sock, char * buf, int size);
int headers(int clnt_sock, FILE *resourse, const char *header);
void tailers(int clnt_sock, FILE *resourse);
void not_found(int clnt_sock);
void inner_error(int clnt_sock);
void unimplemented(int clnt_sock);
void bad_request(int clnt_sock);
void do_http_response(int clnt_sock, const char *path);
//void do_http_request(int clnt_sock);
void * do_http_request(void * pclnt_sock);
int main(){
//创建套接字
int serv_sock = socket(AF_INET, SOCK_STREAM, 0);
//将套接字和IP、端口绑定
struct sockaddr_in serv_addr;
//每个字节都用0填充
bzero(&serv_addr, sizeof(serv_addr));
serv_addr.sin_family = AF_INET; //使用IPv4地址
serv_addr.sin_addr.s_addr = htonl(INADDR_ANY); //具体的IP地址
serv_addr.sin_port = htons(SERVER_PORT); //端口
bind(serv_sock, (struct sockaddr*)&serv_addr, sizeof(serv_addr));
//进入监听状态,等待用户发起请求
listen(serv_sock, 20);
printf("wait client connect...\n");
int done = 1;
while (done){
//接收客户端请求
struct sockaddr_in clnt_addr;
socklen_t clnt_addr_size = sizeof(clnt_addr);
int clnt_sock = accept(serv_sock, (struct sockaddr*)&clnt_addr, &clnt_addr_size);
char client_ip[64];
char buf[256];
pthread_t p_id;
int * pclnt_sock = NULL;
pclnt_sock = (int *)malloc (sizeof(int));
*pclnt_sock = clnt_sock;
//打印客户端ip地址和端口号
printf("client ip: %s\t port: %d\n",
inet_ntop(AF_INET, &clnt_addr.sin_addr.s_addr, client_ip, sizeof(client_ip)),
ntohs(clnt_addr.sin_port));
//获取客户端http请求数据
// do_http_request(clnt_sock);
//关闭套接字
// close(clnt_sock);
//开启线程
pthread_create(&p_id, NULL, do_http_request, (void *)pclnt_sock);
}
//关闭套接字
close(serv_sock);
return 0;
}
//void do_http_request(int clnt_sock){
void * do_http_request(void * pclnt_sock){
int len = 0;
char buf[256];
char method[16];
char url[256];
char path[260];
int clnt_sock = *(int *)pclnt_sock;
len = get_http_line(clnt_sock, buf, sizeof(buf));
if (len > 0){
int i = 0, j = 0;
//获取请求方法
while (buf[j] != ' ' && i < sizeof(method) - 1){
method[i++] = buf[j++];
}
method[i] = '\0';
if (debug) printf("request method: %s\n", method);
if (strncasecmp(method, "GET", i) == 0){ //get请求
while (buf[j++] != ' ');
i = 0;
//获取请求url
while (buf[j] != ' ' && i < sizeof(url) - 1){
url[i++] = buf[j++];
}
url[i] = '\0';
if (debug) printf("request url: %s\n", url);
//去掉请求参数
char *last = strchr(url, '?');
if (last) *last = '\0';
if (debug) printf("real request url: %s\n", url);
//获取请求文件、路径
sprintf(path, "%s%s", parent_path, url);
if (debug) printf("request path: %s\n", path);
//判断文件是否存在
if (stat(path, &st) == -1){
fprintf(stderr, "stat %s find failed. reason: %s\n", path, strerror(errno));
not_found(clnt_sock);
}else {
if (S_ISDIR(st.st_mode)){
strcat(path, "index.html");
}
do_http_response(clnt_sock, path);
}
}else{ //其他请求 (暂不处理)
fprintf(stderr, "other request [%s]\n", method);
unimplemented(clnt_sock);
}
if (debug) printf("request body:\n%s\n", buf);
do{
len = get_http_line(clnt_sock, buf, sizeof(buf));
if (debug) printf("%s\n", buf);
}while (len > 0);
}else{ //请求格式有问题,出错处理
fprintf(stderr, "request format error\n");
bad_request(clnt_sock);
}
//关闭套接字
close(clnt_sock);
//释放指针
if (pclnt_sock) free(pclnt_sock);
pclnt_sock = 0;
return NULL;
}
void do_http_response(int clnt_sock, const char *path){
FILE *resourse = NULL;
resourse = fopen(path, "r");
if (resourse == NULL){
fprintf(stderr, "stat %s open failed. reason: %s\n", path, strerror(errno));
not_found(clnt_sock);
return;
}
//定义HTTP响应状态行
char *header = z00;
if (strncasecmp(path, "./error.html", 12) == 0){
header = f0f;
}else if (strncasecmp(path, "./inner.html", 12) == 0){
header = b00;
}else if (strncasecmp(path, "./unimplemented.html", 18) == 0){
header = b0l;
}else if (strncasecmp(path, "./bad_request.html", 16) == 0){
header = f00;
}
//发送头部
if (headers(clnt_sock, resourse, header) == 0){
//发送内容
tailers(clnt_sock, resourse);
}
fclose(resourse);
}
int get_http_line(int clnt_sock, char * buf, int size){
int cnt = 0;
char ch = '\0';
int len = 0;
while (cnt < size - 1 && ch != '\n'){
len = read(clnt_sock, &ch, 1);
if (len == 1){
if (ch == '\r'){
continue;
}else if (ch == '\n'){
break;
}
}else if (len == -1){
fprintf(stderr, "read failed\n");
cnt = -1;
break;
}else {
fprintf(stderr, "client close\n");
cnt = -1;
break;
}
buf[cnt++] = ch;
}
if (cnt >= 0){
buf[cnt] = '\0';
}
return cnt;
}
void not_found(int clnt_sock){
do_http_response(clnt_sock, "./error.html");
}
void inner_error(int clnt_sock){
do_http_response(clnt_sock, "./inner.html");
}
void unimplemented(int clnt_sock){
do_http_response(clnt_sock, "./unimplemented.html");
}
void bad_request(int clnt_sock){
do_http_response(clnt_sock, "./bad_request.html");
}
int headers(int clnt_sock, FILE *resourse, const char *header){
struct stat st;
int fileId = 0;
fileId = fileno(resourse); //获取已经打开的文件的文件描述符
if (fstat(fileId, &st) == -1){ //文件已经打开但是访问文件出错
fprintf(stderr, "inner error. reason: %s\n", strerror(errno));
inner_error(clnt_sock);
return -1;
}
char buf[1024] = {0};
char temp[64];
//状态行
strcpy(buf, header);
//消息报头
strcat(buf, "Server: Martin Server\r\n");
strcat(buf, "Content-Type: text/html\r\n");
strcat(buf, "Connection: Close\r\n");
int wc = st.st_size; //正文大小
sprintf(temp, "Content-Length: %d\r\n\r\n", wc);
strcat(buf, temp);
if (debug) printf("write:\n%s\n", buf);
if(send(clnt_sock, buf, strlen(buf), 0) < 0){
fprintf(stderr, "failed to send.reason:%s.buf:%s\n", strerror(errno), buf);
return -1;
}
return 0;
}
void tailers(int clnt_sock, FILE *resourse){
char buf[1024];
do{
fgets(buf, sizeof(buf), resourse);
int len = write(clnt_sock, buf, strlen(buf));
if (len < 0){
fprintf(stderr, "send error, reason: %s\n", strerror(errno));
break;
}
if (debug) printf("%s", buf);
}while (!feof(resourse));
if (debug) printf("\n");
}