vlambda博客
学习文章列表

C语言实现简单Web服务器(一)

我们这次要完成的最终结果如上图所示 

前置知识

  • C语言
  • Linux Socket编程
  • 基本的网络知识
  • Unix/Linux 基本知识
C语言实现简单Web服务器(一)
Socket通信模式

一图胜千言,可以看出Socket编程主要分为这7个步骤,这次我们主要编写服务器端的代码,客户端由浏览器代理。

创建一个socket

int socket(int domain, int type, int protocol);

这是创建socket的函数原型

domain:中文意思为域,可传的值为AF_UNIXAF_LOCALAF_INETAF意为Adress Family。前两个为本机操作,最后一个为IPv4的网络操作,所以为AF_INET

type:类型,可传值为SOCK_STREAMSOCK_DGRAMSOCK_PACKETSOCK_STREAM 使用 TCP 协议传输数据,SOCK_DGRAM 使用 UDP 协议传输数据,我们要做的是Web服务器,肯定是选择面向连接的可靠的TCP协议,所以这个值传SOCK_STREAM

protocol: 所用的协议,有IPPROTO_TCPIPPTOTO_UDPIPPROTO_SCTP,传0为自动选择协议,所以我们传0

返回值:返回一个socket描述符(socket descriptor),它唯一标识一个socket,这个socket描述字跟文件描述字一样。

int server_socket = socket(AF_INET, SOCK_STREAM, 0);

将socket和地址绑定

int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

sockfd:socket文件描述符,socket()函数的返回值,也就是server_socket

struct sockaddr_in { __uint8_t sin_len; sa_family_t sin_family; in_port_t sin_port; struct in_addr sin_addr; char sin_zero[8];};

传递的时候需要做强制类型转换

struct sockaddr_in server_addr;memset(&server_addr, 0, sizeof(server_addr));server_addr.sin_family = AF_INET;server_addr.sin_addr.s_addr = htonl(INADDR_ANY);server_addr.sin_port = htons(PORT);

memset函数初始化server_addr各个字节为0,防止有未初始化的垃圾值存在

addrlen: 结构体的长度,由于在函数内部无法获取到结构体长度(因为传递的是指针,参考数组),所以需要把长度传入

返回值: 绑定成功或者失败的消息码,暂时不作处理

bind(server_socket, (struct sockaddr *)&server_addr, sizeof(server_addr));

监听

int listen(int sockfd, int backlog);

sockfd:socket文件描述符,socket()函数的返回值,也就是server_socket

backlog:socket待连接队列的最大个数,一般为5

返回值: 绑定成功或者失败的消息码,暂时不作处理

listen(server_socket, 5);

与客户端建立连接

int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);

sockfd:socket文件描述符,socket()函数的返回值,也就是server_socket

返回值:socket文件描述符,在与客户端建立连接后,accpet还是会生成一个专门用于和当前客户端通信的socket,而原来那个socket照常负责和其他等待建立连接的客户端建立通信

int client_socket = accept(server_socket, NULL, NULL);

从浏览器读取请求内容

ssize_t read(int fd, void *buf, size_t count);

fd: 文件描述符,从哪个文件读buf:读的内容存到buf中count: 共读多少个字节

char buf[1024];read(client_socket, buf, 1024);

记住,在Linux,一切皆文件,网络接口、甚至鼠标键盘显示器都是文件

往浏览器写响应内容

ssize_t write(int fd, const void *buf, size_t count);
char status[] = "HTTP/1.0 200 OK\r\n";char header[] = "Server: DWBServer\r\nContent-Type: text/html;charset=utf-8\r\n\r\n";char body[] = "<html><head><title>C语言构建小型Web服务器</title></head><body><h2>欢迎</h2><p>Hello,World</p></body></html>";
write(client_socket, status, sizeof(status));write(client_socket, header, sizeof(header));write(client_socket, body, sizeof(body));

写的格式是按HTTP协议响应报文的格式写的,响应报文的格式为响应行+响应首部+响应体,注意响应首部响应体之间有一个空行

在浏览器中输入http://localhost:8080/, 就会出现C语言实现简单Web服务器(一)

用Charles抓包

请求
响应

关闭连接

int close(int fd);
close(client_socket);close(server_socket);

最后把两个socket全部关闭

完整代码

#include <sys/socket.h>#include <sys/un.h>#include <arpa/inet.h>#include <unistd.h>#include <string.h>#include <stdio.h>
#define PORT 8080 // 服务器监听端口
int main(){ int server_socket = socket(AF_INET, SOCK_STREAM, 0); struct sockaddr_in server_addr; memset(&server_addr, 0, sizeof(server_addr)); server_addr.sin_family = AF_INET; server_addr.sin_addr.s_addr = htonl(INADDR_ANY); server_addr.sin_port = htons(PORT); bind(server_socket, (struct sockaddr *)&server_addr, sizeof(server_addr)); listen(server_socket, 5); int client_socket = accept(server_socket, NULL, NULL); char buf[1024]; read(client_socket, buf, 1024); printf("%s",buf);
char status[] = "HTTP/1.0 200 OK\r\n"; char header[] = "Server: DWBServer\r\nContent-Type: text/html;charset=utf-8\r\n\r\n"; char body[] = "<html><head><title>C语言构建小型Web服务器</title></head><body><h2>欢迎</h2><p>Hello,World</p></body></html>";
write(client_socket, status, sizeof(status)); write(client_socket, header, sizeof(header)); write(client_socket, body, sizeof(body));
close(client_socket); close(server_socket);
return 0;}

结语

至此,我们已经用socket实现了一个最简单的Web服务器(其实还算不上,只是一个浏览器充当clientsocket小程序),下一篇继续完善这个Web服务器,加入处理Get请求的逻辑,进一步实现HTTP协议