服务器编程入门:TinyHttpd项目详解

本文介绍了TinyHttpd项目,这是一个适合C++初学者的后端开发入门实践。作者具备一定的C++和Linux基础知识,计划通过项目学习深入理解。文章详细讲述了Socket编程基础,包括如何确定通信进程、通信方式以及通信实例,分别展示了客户端和服务端的通信流程。此外,还提到了项目来源及其教育价值,旨在帮助读者理解HTTP/socket编程和UNIX系统调用。
摘要由CSDN通过智能技术生成

服务器编程入门:TinyHttpd项目详解

对于C++的学习已经进入实战阶段,目标岗位是后端开发。当下我具备的基础知识有:计算机基础四大件,C++基本语法以及C++11新特性、内存管理、设计模式,Linux基本用法、makefile等等,读过《Essential C++》《STL源码剖析》、《程序员的自我修养》、《Effective C++》等书籍。阅读书籍和资料学习的方法,我打算先告一段落,转而以项目为导向进行学习,遇到看不懂的函数再查阅相关书籍进行学习。我找到了一个非常经典的学习项目,即标题提到的TinyHttpd,项目介绍我将在下文引用。

我将阅读项目源码,对项目的整体功能、流程进行描述,并讲解理解本项目需要具备的知识点,尽量做到不留疑点。

下文来自于TinyHttpd原作者:

This software is copyright 1999 by J. David Blackstone. Permission is granted to redistribute and modify this software under the terms of the GNU General Public License, available at http://www.gnu.org/ .
If you use this software or examine the code, I would appreciate knowing and would be overjoyed to hear about it at jdavidb@sourceforge.net .
This software is not production quality. It comes with no warranty of any kind, not even an implied warranty of fitness for a particular purpose. I am not responsible for the damage that will likely result if you use this software on your computer system.
I wrote this webserver for an assignment in my networking class in 1999. We were told that at a bare minimum the server had to serve pages, and told that we would get extra credit for doing “extras.” Perl had introduced me to a whole lot of UNIX functionality (I learned sockets and fork from Perl!), and O’Reilly’s lion book on UNIX system calls plus O’Reilly’s books on CGI and writing web clients in Perl got me thinking and I realized I could make my webserver support CGI with little trouble.
Now, if you’re a member of the Apache core group, you might not be impressed. But my professor was blown over. Try the color.cgi sample script and type in “chartreuse.” Made me seem smarter than I am, at any rate. 😃
Apache it’s not. But I do hope that this program is a good educational tool for those interested in http/socket programming, as well as UNIX system calls. (There’s some textbook uses of pipes, environment variables, forks, and so on.)
One last thing: if you look at my webserver or (are you out of mind?!?) use it, I would just be overjoyed to hear about it. Please email me. I probably won’t really be releasing major updates, but if I help you learn something, I’d love to know!

Happy hacking!
J. David Blackstone

Socket编程基础

服务器是通过互联网与其它计算机进行通信的,在进入项目源码之前,有必要了解计算机通讯的基本原理及对应的编程接口。

学习编程接口时,阅读C语言中文网:1天玩转Socket通信技术,跟着动手写写例子,基本就可以掌握了。如果想了解原理,可以阅读 小林coding:图解计算机网络,在里面阅读TCP三次握手,四次挥手的相关内容。

原理部分我不做过多赘述,下文主要记录一下我理解的Socket编程接口的使用框架。

如何确定两个相互通信的进程?

答:套接字,即IP地址和进程监听的端口号。
在代码上,用一个结构体sockaddr_in来存储,可以简单理解它有如下三个成员变量(这个不是源码!!而是简单理解):

struct sockaddr_in{
	unsigned int sin_family;
	unsigned int sin_port;
	struct sin_addr;
}
struct sin_addr{
	unsigned int s_addr;
}

其中,sin_family指明了使用的协议,一般填写AF_INET,sin_port是端口号 ,sin_addr.s_addr是IP地址,依次给这三个成员变量赋值就可以了,赋值的时候要注意大小端的转换。

通信双方之间通过什么来发送或接收内容?

答:编程接口int socket(int, int, int)函数返回的文件描述符,我们也称之为socket,搭配read(int, void*,size_t)write(int, void*,size_t)分别进行接收和发送信息,发送和接收的信息都存在自己创建的缓冲区:char buffer[BufSize]中。

通信实例

下文代码用到的每一个函数的具体的定义,可以查阅我上文提到的资料进行细节了解,我这里只做总体描述。通信双方总体操作如下图所示,可以看完代码解释后再回头看看这张图。
通信双方流程(图源小林coding)

客户端
//client.cpp
#include<iostream>
#include<string.h>
#include<stdlib.h>
#include<unistd.h>
#include<arpa/inet.h>
#include<sys/socket.h>
int main(){
    //创建自己的套接字
    int sock = socket(AF_INET,SOCK_STREAM,0);  //返回文件描述符
    
    //设置服务器的套接字
    struct sockaddr_in serve_addr;
    memset(&serve_addr,0,sizeof(serve_addr));
    serve_addr.sin_family = AF_INET;
    serve_addr.sin_addr.s_addr = inet_addr("127.0.0.1");
    serve_addr.sin_port = htons(1234);

    //让当前的sock连接到服务器的套接字
    //会自动放松当前计算机的套接字给对方
    connect(sock,(struct sockaddr*)&serve_addr,sizeof(serve_addr));

    char buffer[40];
    read(sock,buffer,sizeof(buffer)-1);

    std::cout<<buffer<<std::endl;
    
    close(sock);
    return 0;
}

以下是整体流程框架:

socket()函数建立客户端的socket–>填写需要连接的服务器的套接字–>connect()函数将客户端的socket连接至服务端–>开始收发数据

值得注意的是,connect连接成功以后,客户端的socket(代码中的sock变量)就可以当作unix环境下的一个文件描述符,调用read()write()函数分别接受和发送信息,如果对方没给你发信息,那么read()函数会阻塞,直至收到对方的消息。

服务端
server.cpp
#include<iostream>
#include<string.h>
#include<stdlib.h>
#include<unistd.h>
#include<arpa/inet.h>
#include<sys/socket.h>
#include<netinet/in.h>


int main(){
    //创建服务端套接字
    int server_sock = socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);
    //创建地址对象
    struct sockaddr_in server_addr; 
    memset(&server_addr,0,sizeof(sockaddr_in));

    server_addr.sin_family = AF_INET;   //IPv4地址
    server_addr.sin_port = htons(1234);
    server_addr.sin_addr.s_addr = inet_addr("127.0.0.1");
    
    //绑定服务器和IP地址和端口,IP地址必须是本机上的IP 
    bind(server_sock,(struct sockaddr*)&server_addr,sizeof(sockaddr_in));
    
    //把socket的状态改为监听,并设置请求队列的大小。
    listen(server_sock,20);  
    
    //用于接受客户的地址和端口信息
    struct sockaddr_in client_addr;
    socklen_t  ClientAddrLen = sizeof(client_addr);
    int ClientSock = accept(server_sock,(struct sockaddr*)&client_addr,&ClientAddrLen);
    
    //收到客户端的访问以后,直接往客户端发送数据
    char content[] = "here is my message";
   
    write(ClientSock,content,sizeof(content));
    
    close(ClientSock);
    close(server_sock);
    return 0;
}

服务端的流程大体如下:

socket()函数建立服务端监听socket–>填写服务端的套接字–>bind()函数将服务端套接字与监听socket进行绑定–>listen()函数把socket模式改为监听–>accept()函数返回与客户端进行通信的socket–>开始与客户端收发消息

需要注意的是,socket()返回的是用于监听的socket,accept()返回的socket是用于通信的socket,这两个socket是不一样的。用于监听的socket可以继续被accept()函数使用,通信socket将用于read()write()函数进行通信。

没有客户端连接服务端时,accept()函数进入阻塞,直至有请求到来。


更新中…

引用文章

  1. C语言中文网:1天玩转Socket通信技术
  2. 小林coding:图解计算机网络
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值