在实际开发中经常用到web框架,比如Servlet,SpringBoot等,这些开发框架提高了我们的开发效率,节省了开发时间。但是这会令我们技术人员处于浮云之上,看不到其本质。说实话,Java语言那么流行,其本质是运行在JRE虚拟机上的,而JRE是用C/C++语言开发的。与其说java跨平台,不如说是因为在不同平台上各自实现JRE,从而屏蔽了java语言直接与不同平台打交道。http协议广泛应用,也是基于TCP协议之上的封装。本节博主将带领大家用C语言在Linux环境下开发HTTP服务器,支持浏览器下载和浏览文件。另外还使用TCP协议开发了服务端和客户端来实现服务端监听客户端连接,然后向其发送一首唐诗。
目录
1.HTTP服务器
1.1 源码
1.2 效果
2.TCP服务器和客户端
2.1 源码
2.1 效果
1.HTTP服务器
1.1 源码
头文件:
#pragma once
#include <pthread.h>
//线程参数结构
struct ThreadParam {
pthread_t tid;
int fd;
int epfd;
};
int initListenFd(unsigned short port);
int epoolRun(int lfd);
void* acceptClient(void *);
void* recvHttpRequest(void *);
int parseRequestLine(int cfd, const char *line);
int sendFile(int cfd, const char *fileName);
int sendHeadMsg(int cfd, int status, const char *descr, const char *type, int length);
const char *getFileType(const char *name);
int sendDir(int cfd, const char*dirName);
int hex2dec (char c);
char dec2hex (short int c);
void urlDecode(char* from, char *to);
源文件
#include "HttpServer.h"
#include <arpa/inet.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/epoll.h>
#include <fcntl.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <assert.h>
#include <sys/sendfile.h>
#include <dirent.h>
#include <ctype.h>
int initListenFd(unsigned short port) {
int lfd = socket(AF_INET, SOCK_STREAM, 0);
if (-1 == lfd) {
perror("scoket");
return -1;
}
int opt = -1;
int ret = setsockopt(lfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
if (-1 == ret) {
perror("setsockopt");
return -1;
}
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons(port);
addr.sin_addr.s_addr = INADDR_ANY;
ret = bind(lfd, (const struct sockaddr *)&addr, sizeof(addr));
if (-1 == ret) {
perror("bind");
return -1;
}
ret = listen(lfd, 128);
if (-1 == ret) {
perror("listen");
return -1;
}
return lfd;
}
int epoolRun(int lfd) {
int epfd = epoll_create(1);
if (-1 == epfd) {
perror("epoll_create");
return -1;
}
struct epoll_event ev;
ev.data.fd = lfd;
ev.events = EPOLLIN;
int ret = epoll_ctl(epfd, EPOLL_CTL_ADD, lfd, &ev);
if (-1 == ret) {
perror("epoll_ctl");
return -1;
}
struct epoll_event evs[2048];
int maxwait = sizeof(evs) / sizeof(struct epoll_event);
while (1) {
int num = epoll_wait(epfd, evs, maxwait, -1);
for (int i = 0; i <= num; i++) {
int fd = evs[i].data.fd;
pthread_t tid;
struct ThreadParam *param =
(struct ThreadParam *)malloc(sizeof(struct ThreadParam));
param->fd = fd;
param->epfd = epfd;
param->tid = tid;
if (fd == lfd) {
pthread_create(&tid, NULL, acceptClient, param);
pthread_detach(tid);
}
else {
pthread_create(&tid, NULL, recvHttpRequest, param);
pthread_detach(tid);
}
}
}
return 0;
}
void* acceptClient(void *arg) {
struct ThreadParam *param = (struct ThreadParam *)arg;
if (!param) {
return NULL;
}
int lfd = param->fd;
int epfd = param->epfd;
int cfd = accept(lfd, NULL, NULL);
if (-1 == cfd) {
perror("accept");
return NULL;
}
int flag = fcntl(cfd, F_GETFL);
flag |= O_NONBLOCK;
fcntl(cfd, F_SETFL, flag);
struct epoll_event ev;
ev.data.fd = cfd;
ev.events = EPOLLIN | EPOLLET; //cfd边缘非阻塞模式,效率最高
int ret = epoll_ctl(epfd, EPOLL_CTL_ADD, cfd, &ev);
if (-1 == cfd) {
perror("epoll_ctl");
}
free(param);
param = NULL;
printf("accept thread %ld\n", pthread_self());
return NULL;
}
void* recvHttpRequest(void *arg) {
struct ThreadParam *param = (struct ThreadParam *)arg;
char buf[8192] = {0};
int len = 0;
int total = 0;
char tmpBuf[1024] = {0};
if (!param) {
return NULL;
}
int cfd = param->fd;
int epfd = param->epfd;
while ((len = recv(cfd, tmpBuf, sizeof(tmpBuf), 0)) > 0) {
if (total + len < sizeof(buf)) {
memcpy(buf + total, tmpBuf, len);
}
total += len;
}
if (-1 == len && errno == EAGAIN && total > 0) { //接收数据完毕
//解析http协议
char *pt = strstr(buf, "\r\n");
int reqLen = pt - buf;
buf[reqLen] = '\0';
parseRequestLine(cfd, buf);
}
else if (0 == len) { //客户端断开了连接
epoll_ctl(epfd, EPOLL_CTL_DEL, cfd, NULL);
close(cfd);
}
else {
perror("recv");
}
free(param);
param = NULL;
printf("client thread %ld\n", pthread_self());
return NULL;
}
int parseRequestLine(int cfd, const char *line) {
char method[32] = {0};
char path[2048] = {0};
char decodePath[1024] = {0};
char protocol[128] = {0};
//sscanf解析格式化字符串
sscanf(line, "%[^ ] %[^ ] %s", method, path, protocol);
printf("method: %s, path: %s protocol: %s\n", method, path, protocol);
if (0 != strcasecmp(method, "get")) {
return -1;
}
urlDecode(path, decodePath);
//http中/代表服务端工作的资源根目录
char *file = NULL;
if (0 == strcmp(decodePath, "/")) {
file = ".";
}
else {
file = decodePath + 1;
}
struct stat st;
int ret = stat(file, &st);
if (-1 == ret) {
//回复404页面
sendHeadMsg(cfd, 404, "Not Found", getFileType(".html"), -1); //-1表示不知道长度,让浏览器自己解析去
sendFile(cfd, "404.html");
return 0;
}
if (S_ISDIR(st.st_mode)) {
sendHeadMsg(cfd, 200, "OK", getFileType(".html"), -1);
sendDir(cfd, file);
}
else {
sendHeadMsg(cfd, 200, "OK", getFileType(file), st.st_size);
sendFile(cfd, file);
}
return 0;
}
int sendFile(int cfd, const char *fileName) {
//读一部分数据,发送一部分数据,因为tcp是面向连接的流式的
int fd = open(fileName, O_RDONLY);
assert(fd > 0);
#if 0
while (1) {
char buf[1024];
int len = read(fd, buf, sizeof(buf));
if (len > 0) {
send(cfd, buf, len, 0);
usleep(20); //减轻接收端压力
}
else if (0 == len) {
break;
}
else {
perror("read");
}
}
#endif
#if 1
off_t len = 0;
int size = lseek(fd, 0, SEEK_END);
lseek(fd, 0, SEEK_SET);
while (len < size) {
int ret = sendfile(cfd, fd, &len, size - len);
printf("ret value %d \n", ret);
if (-1 == ret) {
if (EAGAIN == errno) {
printf("no data\n");
perror("sendfile");
}
else {
printf("client quit \n");
break;
}
}
}
#endif
return 0;
}
int sendHeadMsg(int cfd, int status, const char *descr, const char *type, int length) {
char buf[8192] = {0};
int offset = 0;
int ret = sprintf(buf + offset, "http/1.1 %d %s\r\n", status, descr);
offset += ret;
ret = sprintf(buf + offset, "content-type: %s\r\n", type);
offset += ret;
ret = sprintf(buf + offset, "content-length: %d\r\n\r\n", length);
offset += ret;
send(cfd, buf, offset, 0);
return 0;
}
const char *getFileType(const char *name) {
const char* dot = strrchr(name, '.');
if (NULL == dot) {
return "text/palin; charset=utf-8";
}
if (0 == strcasecmp(dot, ".html")) {
return "text/html; charset=utf-8";
}
if (0 == strcasecmp(dot, ".png")) {
return "image/png; charset=utf-8";
}
if (0 == strcasecmp(dot, ".txt")) {
return "text/palin; charset=utf-8";
}
// ...
return "application/octet-stream; charset=utf-8";
}
int sendDir(int cfd, const char*dirName) {
char buf[2048] = {0};
int len = 0;
int ret = sprintf(buf + len, "<html><head><title>%s</title><body><table>", dirName);
len += ret;
struct dirent** namelist = NULL;
int num = scandir(dirName, &namelist, NULL, alphasort);
for (int i = 0; i < num; i++) {
char * name = namelist[i]->d_name;
struct stat st;
char path[1024] = {0};
sprintf(path, "%s/%s", dirName, name);
stat(path, &st);
if (S_ISDIR(st.st_mode)) {
if (!strcmp(".", name) || !strcmp("..", name)) {
continue;
}
ret = sprintf(buf + len, "<tr><td><a href=\"%s/\" style=\"font-size:20px\">%s</a> </td><td>%ld</td></tr>", \
name, name, st.st_size);
}
else {
ret = sprintf(buf + len, "<tr><td><a href=\"%s\" style=\"font-size:20px\">%s</a> </td><td>%ld</td></tr>",
name, name, st.st_size);
}
len += ret;
send(cfd, buf, len, 0);
len = 0;
memset(buf, 0x00, sizeof(buf));
free(namelist[i]);
}
len = sprintf(buf, "</table></head></body></html>");
send(cfd, buf, len, 0);
free(namelist);
return 0;
}
int hex2dec (char c) {
if ('0' <= c && c <= '9') return c - '0';
else if ('a' <= c && c <= 'f') return c - 'a' + 10;
else if ('A' <= c && c <= 'F') return c - 'A' + 10;
return 0;
}
char dec2hex (short int c) {
if (0 <= c && c <= 9) return c + '0';
else if (10 <= c && c <= 15) return c + 'A' - 10;
return 0;
}
void urlDecode(char* org, char *obj) {
if (!org || !obj) {
return;
}
//isxdigit:判断字符是否是十六进制字符
for (; *org != '\0'; ++org, ++obj) {
if ('%' == org[0] && isxdigit(org[1]) && isxdigit(org[2])) {
*obj = hex2dec(org[1]) * 16 + hex2dec(org[2]);
org += 2;
}
else {
*obj = *org;
}
}
}
主程序:
#include <stdio.h>
#include <unistd.h>
#include "HttpServer.h"
int main(int argc, char** argv) {
unsigned short int port;
if (argc < 3) {
printf("program {port} {path}\n");
return -1;
}
sscanf(argv[1], "%hu", &port);
printf("begin start http server, listen %d ...\n", port);
//修改进程的工作目录
chdir(argv[2]);
int lfd = initListenFd(port);
epoolRun(lfd);
return 0;
}
Makefile 编译脚本:
app: httpServer tcpServer tcpClient
#说明:$^代表依赖项
httpServer: CommonUtil.c HttpServer.c main.c
gcc -g $^ -o httpServer -lpthread
tcpServer: CommonUtil.c TcpServer.c
gcc -g $^ -o tcpServer
tcpClient: CommonUtil.c TcpClient.c
gcc -g $^ -o tcpClient
clean:
-rm httpServer tcpServer tcpClient -f
1.2 效果
2.TCP服务器和客户端
2.1 源码
头文件:
#pragma once
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
int isLittleEndian();
int readn(int fd, char* buf, int len);
int writen(int fd, char *buf, int len);
源文件
#include <strings.h>
#include <arpa/inet.h>
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <assert.h>
#include "CommonUtil.h"
static const char* _s_showMsg [] = {
"次北固山下",
"【作者】王湾 【朝代】唐",
"客路青山外,行舟绿水前。",
"潮平两岸阔,风正一帆悬。",
"海日生残夜,江春入旧年。",
"乡书何处达?归雁洛阳边。"
};
static void sendMsg(int fd, const char *data, int len) {
char *buf = (char *)malloc(sizeof(int) + len);
int nlen = len;
if (isLittleEndian()) {
nlen = htonl(len);
}
memcpy(buf, &nlen, sizeof(int));
memcpy(buf + sizeof(int), data, len);
printf("发送数据长度:: [%d] 内容:: [%s]\n", len, data);
writen(fd, buf, sizeof(int) + len);
if (buf) {
free(buf);
}
}
static int startTcpServer(unsigned short port)
{
int lfd = socket(AF_INET, SOCK_STREAM, 0);
if (-1 == lfd) {
perror("scoket");
return -1;
}
int opt = -1;
int ret = setsockopt(lfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
if (-1 == ret) {
perror("setsockopt");
return -1;
}
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons(port);
addr.sin_addr.s_addr = INADDR_ANY;
ret = bind(lfd, (const struct sockaddr *)&addr, sizeof(addr));
if (-1 == ret) {
perror("bind");
return -1;
}
ret = listen(lfd, 128);
if (-1 == ret) {
perror("listen");
return -1;
}
struct sockaddr_in client;
socklen_t client_addrlen = sizeof(client);
printf("port %d, wait client accept ...\n", port);
int connfd = accept(lfd, (struct sockaddr*)(&client), &client_addrlen);
if (connfd < 0) {
perror("accept");
return -1;
}
printf("connect client info: addr = %s, port = %d\n",
inet_ntoa(client.sin_addr), ntohs(client.sin_port));
int lineNum = sizeof(_s_showMsg) / sizeof(_s_showMsg[0]);
for (int i = 0; i < lineNum; i++) {
sendMsg(connfd, _s_showMsg[i], strlen(_s_showMsg[i]));
usleep(1000 * 100); //此处为了减轻客户端压力
}
printf("我活干完了,数据已经全部发送到客户端!\n");
getchar();
close(connfd);
close(lfd);
return 1;
}
int main(int argc, char**argv) {
if (argc < 2) {
printf("a.out {port}\n");
return -1;
}
unsigned short port;
sscanf(argv[1], "%hu", &port);
startTcpServer(port);
return 0;
}
#include <arpa/inet.h>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>
#include <assert.h>
#include "CommonUtil.h"
static void recvMsg(int fd, char **data, int *len) {
int nlen = 0;
int ret = readn(fd, (char*)&nlen, sizeof(int));
*len = nlen;
if (isLittleEndian()) {
*len = ntohl(nlen);
}
char *tmp = (char *)malloc(*len + 1);
readn(fd, tmp, *len);
tmp[*len] = '\0';
*data = tmp;
}
static int startTcpClient(unsigned short port)
{
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
assert(-1 != sockfd);
//指定服务器的ip和端口
struct sockaddr_in saddr;
memset(&saddr, 0, sizeof(saddr));
saddr.sin_family = AF_INET;
saddr.sin_port = htons(port);
//saddr.sin_addr.s_addr = inet_addr("127.0.0.1");
inet_pton(sockfd, "127.0.0.1", &saddr.sin_addr.s_addr);
//作为客户端不需要指定端口,系统自动给客户端设置端口,连接上后直接收发数据
printf("begin connect port %d\n", port);
int ret = connect(sockfd, (struct sockaddr*)&saddr, sizeof(saddr));
if (-1 == ret) {
perror("connect");
return 0;
}
while(1)
{
char *buff = NULL;
int ret = 0;
recvMsg(sockfd, &buff, &ret);
if (-1 == ret) {
perror("read");
break;
}
else if (0 == ret) {
perror("server quit ");
break;
}
else {
if (buff) {
printf("接收数据长度:: [%d] 内容:: [%s]\n", ret, buff);
free(buff);
}
}
printf("\r\n------------------------------\n\r");
sleep(rand() % 10);
}
close(sockfd);
exit(0);
}
int main(int argc, char**argv) {
if (argc < 2) {
printf("a.out {port}\n");
return -1;
}
unsigned short port;
sscanf(argv[1], "%hu", &port);
startTcpClient(port);
return 0;
}
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include "CommonUtil.h"
int isLittleEndian() {
static unsigned short data = 0x1234;
if (*((char*)&data) == 0x34) {
return 1;
}
else {
return 0;
}
}
int readn(int fd, char* buf, int len) {
int nleft = len;
int nread = 0;
char *pbuf = buf;
while (nleft > 0) {
nread = read(fd, pbuf, nleft);
if (-1 == nread) {
perror("read");
return -1;
}
else if (0 == nread) {
return len - nleft;
}
pbuf += nread;
nleft -= nread;
}
return len;
}
int writen(int fd, char *buf, int len) {
int nleft = len;
int nwrite = 0;
char *pbuf = buf;
while (nleft > 0) {
nwrite = write(fd, pbuf, nleft);
if (-1 == nwrite) {
perror("write");
return -1;
}
else if (0 == nwrite) {
continue;
}
pbuf += nwrite;
nleft -= nwrite;
}
return len;
}
编译脚本:
app: httpServer tcpServer tcpClient
#说明:$^代表依赖项
httpServer: CommonUtil.c HttpServer.c main.c
gcc -g $^ -o httpServer -lpthread
tcpServer: CommonUtil.c TcpServer.c
gcc -g $^ -o tcpServer
tcpClient: CommonUtil.c TcpClient.c
gcc -g $^ -o tcpClient
clean:
-rm httpServer tcpServer tcpClient -f
2.1 效果
源码下载路径如下:
https://download.csdn.net/download/hsy12342611/87183336
只有平时多接触底层编程才能体会到一些技术的本质,做技术不能被表面的虚幻所迷惑,要从本质上去理解一些东西,好的,今天就到这里了,该去休息了。