Linux下C语言实现HTTP文件服务器和TCP协议实现网络数据传输

2023-05-16

       在实际开发中经常用到web框架,比如Servlet,SpringBoot等,这些开发框架提高了我们的开发效率,节省了开发时间。但是这会令我们技术人员处于浮云之上,看不到其本质。说实话,Java语言那么流行,其本质是运行在JRE虚拟机上的,而JRE是用C/C++语言开发的。与其说java跨平台,不如说是因为在不同平台上各自实现JRE,从而屏蔽了java语言直接与不同平台打交道。http协议广泛应用,也是基于TCP协议之上的封装。本节博主将带领大家用C语言在Linux环境下开发HTTP服务器,支持浏览器下载和浏览文件。另外还使用TCP协议开发了服务端和客户端来实现服务端监听客户端连接,然后向其发送一首唐诗。

目录

1. 关键函数说明

1.1 IP地址字节序转换

1.2 端口字节序转换

1.3 socket

1.4 bind

1.5 listen

1.6 accept

1.7 接收数据

1.8 发送数据

1.9 connect

2. HTTP服务器

2.1 源码

2.2 效果

3.TCP服务器和客户端

3.1 源码

3.2 效果


1. 关键函数说明

1.1 IP地址字节序转换

  IP 地址本质是整数,但是为了方便,在使用的过程中都是用字符串来描述,超过两个字节的数据单元,在跨网络传输时候就需要考虑本地字节序和网络字节序的转换,Linux下主要使用api如下:

1)本地字节序转网络字节序
int inet_pton(int af, const char *src, void *dst); 
参数:
af: 地址族协议,IPV4或者IPV6
AF_INET: IPV4  地址
AF_INET6: IPV6地址
src: 点分十进制的 ip 地址,例如192.168.1.2
dst: 传出参数,存放大端整形IP地址
返回值:成功返回 1,失败返回 0 或者 - 1

2)网络字节序转本机字节序    
const char *inet_ntop(int af, const void *src, char *dst, socklen_t size);

参数
af: 地址族协议,IPV4或者IPV6
AF_INET: IPV4  地址
AF_INET6: IPV6地址
src: 大端的整形 IP 地址
dst: 存储转换得到的小端的点分十进制的IP地址
size: dst内存中占用字节数
返回值:
成功:指针指向第三个参数对应的内存地址,通过返回值可以直接取出转换得到的IP字符串
失败: NULL

只能处理IPV4地址的api
(1)点分十进制IP转大端整形
in_addr_t inet_addr (const char *cp);

(2)大端整形转点分十进制IP
char* inet_ntoa(struct in_addr in);

1.2 端口字节序转换

1)主机字节序转网络字节序
uint16_t htons(uint16_t hostshort);    
uint32_t htonl(uint32_t hostlong);    

2)网络字节序转主机字节序
uint16_t ntohs(uint16_t netshort)
uint32_t ntohl(uint32_t netlong);

1.3 socket

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

作用:创建socket文件描述符,通过该文件描述符可以操作内核中的某一块内存,用来进行网络通信

参数:
domain: 使用的地址族协议
AF_INET:  IPv4 格式的 ip 地址
AF_INET6:  IPv6 格式的 ip 地址
type:
SOCK_STREAM: 流式传输协议
SOCK_DGRAM: 报式 (报文) 传输协议
protocol: 一般写 0 ,表示使用默认的协议
SOCK_STREAM: 流式传输默认是 tcp
SOCK_DGRAM: 报式传输默认是udp
返回值:
成功:可用于套接字通信的文件描述符
失败: -1

1.4 bind

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

作用:将文件描述符和本地的IP与端口进行绑定   

参数:
sockfd: 监听的文件描述符,通过 socket () 调用得到
addr: 要绑定的 IP 和端口信息需要初始化到这个结构体中,IP和端口要转换为网络字节序
addrlen: 参数 addr 指向的内存大小
返回值:成功返回 0,失败返回 - 1

1.5 listen

作用:监听套接字
int listen(int sockfd, int backlog);
参数:
sockfd: 文件描述符,调用 socket () 得到
backlog: 同时能处理的最大连接,最大值为 128
返回值:成功返回 0,失败返回 -1

1.6 accept

int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
作用:等待并接受客户端的连接请求, 建立新的连接, 得到一个通信的文件描述符,函数是一个阻塞函数,当没有新的客户端连接请求的时候,该函数阻塞。
当检测到有新的客户端连接时,解除阻塞,得到的描述符就可以和客户端通信。
参数:
sockfd: 监听的文件描述符
addr: 建立连接的客户端的地址信息
addrlen:  addr 指向的内存大小
返回值:函数调用成功,得到一个文件描述符,调用失败返回 -1

1.7 接收数据

ssize_t read(int sockfd, void *buf, size_t size);
ssize_t recv(int sockfd, void *buf, size_t size, int flags);

参数:
sockfd: accept 或者connect函数的返回值
buf: 接收数据内存
size: 参数 buf 指向的内存的容量
flags: 一般不使用,指定为 0
返回值:
大于 0:实际接收的字节数
等于 0:对方断开了连接
-1:接收数据失败了
如果连接没有断开,接收不到数据,会阻塞等待数据到达,数据到达后函数解除阻塞,开始接收数据,当发送端断开连接,接收端无法接收到任何数据,时候就不会阻塞了,直接返回0。

1.8 发送数据

ssize_t write(int fd, const void *buf, size_t len);
ssize_t send(int fd, const void *buf, size_t len, int flags);
参数:
fd: accept 或者connect函数的返回值
buf: 发送的数据
len: 要发送数据长度
flags: 一般不使用,指定为 0
返回值:
大于 0:实际发送的字节数,等于参数 len
-1:发送数据失败了

1.9 connect

int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
作用:与服务器建立连接
参数:
sockfd: socket 返回值
addr: 要连接的服务器的 iP 和 端口,这个 IP 和端口都是大端的
addrlen: addr内存大小
返回值:连接成功返回 0,连接失败返回 - 1

2. HTTP服务器

http服务器实现建立在tcp协议之上,采用epoll和多线程的方式接收和处理客户端。解析从TCP传过来的数据,解析请求行,找到需要访问的资源。如果是目录则使用html的a标签href属性进行重定位,进入目录。如果是文件,txt和png文件浏览器在线预览,其他文件则让浏览器进行下载。

2.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

2.2 效果

 

 展示web服务器的根目录:

在线浏览文件:

下载文件:

3.TCP服务器和客户端

tcp协议作用途广泛,它是面向连接的流式协议,三次握手建立连接,四次挥手断开连接,其通信流程如下:

3.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

3.2 效果

源码下载路径如下:
https://download.csdn.net/download/hsy12342611/87183336

只有平时多接触底层编程才能体会到一些技术的本质,做技术不能被表面的虚幻所迷惑,要从本质上去理解一些东西,好的,今天就到这里了,该去休息了。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Linux下C语言实现HTTP文件服务器和TCP协议实现网络数据传输 的相关文章

  • vscode配置C++调试

    vscode打开一个目录后生成 vscode目录 xff0c 如果配置有问题 xff0c 可以把 vscode删掉 调试首先点击左侧按钮进入run and debug xff0c 这里面默认是什么都没有的 xff0c 点击选择C 43 43
  • Java利用技巧——通过JNI加载dll

    0x00 前言 Java可以通过JNI接口访问本地的动态连接库 xff0c 从而扩展Java的功能 本文将以Tomcat环境为例 xff0c 介绍通过jsp加载dll的方法 xff0c 开源代码 xff0c 记录细节 0x01 简介 本文将
  • LeetCode-两数之和

    给定一个整数数组 nums 和一个整数目标值 target xff0c 请你在该数组中找出 和为目标值 target 的那 两个 整数 xff0c 并返回它们的数组下标 你可以假设每种输入只会对应一个答案 但是 xff0c 数组中同一个元素
  • LeetCode-子数组的最小值之和

    给定一个整数数组 arr xff0c 找到 min b 的总和 xff0c 其中 b 的范围为 arr 的每个 xff08 连续 xff09 子数组 由于答案可能很大 xff0c 因此 返回答案模 10 9 43 7 实例1 输入 xff1
  • LeetCode-只出现一次的数字

    给定一个非空整数数组 xff0c 除了某个元素只出现一次以外 xff0c 其余每个元素均出现两次 找出那个只出现了一次的元素 说明 xff1a 你的算法应该具有线性时间复杂度 你可以不使用额外空间来实现吗 xff1f 输入 2 2 1 输出
  • 关于结构体成员变量用 . 还是 ->

    之前一直认为引用结构体成员 xff0c 指针的话用 gt xff0c 成员变量的话用 但是一直都没去深究过 xff0c 今天遇到一个复杂的 xff0c 决心去了解一下 typedef struct IPC ISP CFG DATA IPC
  • 顺序表的建立 基本输入输出

    输入数据的个数n 输入n个数 然后输出 input 5 1 2 3 4 5 output 1 2 3 4 5 以下是代码 xff1a include lt stdio h gt include lt stdlib h gt define l
  • 离散题目18--求传递闭包

    离散题目18 Time Limit 1000MS Memory Limit 65536KB Submit Statistic Problem Description 给出一个集合A和A上的关系R xff0c 求关系R的传递闭包 例如 xff
  • 基于MSP430红外循迹小车

    2021 个人公众号 高瞻猿瞩 xff0c 会在其中发布一些有趣或是实用的编程内容 xff0c 大部分都是些轻松加愉快的内容 xff0c 欢迎大家前来看看 xff01 可在公众号恢复关键词 430 51 树莓派 循迹小车测试视频 可以获取相
  • 基于51单片机的简单方波发生器

    一个按键可以调整频率的简易方波信号发生器 xff0c 当频率超出范围时LED亮 xff0c 频率范围是100 1000hz include lt reg52 h gt sbit PWMOUT 61 P1 0 sbit LED1 61 P2
  • matlab绘制垂线(x轴或y轴)

    使用line函数就可以绘制垂线 1 绘制垂直于x轴的垂线 line xvalue xvalue y1 y2 xff1b 比如绘制x 61 5 y取值为 0 10 xff1b line 5 5 0 10 2 绘制垂直于y轴的垂线 line x
  • ubuntu解决中文乱码

    1 查看当前系统使用的字符编码 locale LANG 61 en US LANGUAGE 61 en US LC CTYPE 61 34 en US 34 LC NUMERIC 61 34 en US 34 LC TIME 61 34 e
  • ROS 在工作空间中创建python程序

    ROS 在工作空间中创建python程序 基于ros xff0c 在工作空间catkin ws中创建pkg和python程序 xff0c 并进行编译使其可以用rosrun进行运行 xff0c 参考 xff1a 参考 默认前面已经创建了cat
  • 关于AD15铺铜的注意事项

    1 在铺好信号线和电源线后再考虑是否在铺地之前先手动连接地线 因为在铺地时有一个间距问题 xff0c 如果有限地线的间距太小 xff0c 那么在铺地时就会不成功 xff0c 导致墨迹个地没有被接进去 还有就是要考虑敷铜时的间距 xff0c
  • AD15 建立铺铜间距规则

    系统默认的普通间距就是系统的clearance 10mil xff0c 可是默认普通出来的话间距太小了 xff0c 于是乎可以建立一个普通规则 xff0c 但是要注意你所建立的铺铜规则优先级永远在默认优先级之前 xff08 比如你的poly
  • AD15 修改铺铜(去除自己不想要的铺铜区域)

    1 点击place gt polygon pour cutout 2 出现光标 xff0c 然后把你想要去除的那个区域选中 xff0c 如图一所示 xff0c 单击右键退出选择模式 3 双击没有被选中的区域 xff0c 软件提示重新铺铜 x
  • 串口缓冲区管理分析

    一 概述 xff1a 串口使用时一般包含两个缓冲区 xff0c 即发送缓冲区和接收缓冲区 发送数据时 xff0c 先将数据存在发送缓冲区 xff0c 然后通过串口发送 xff1b 接收数据时 xff0c 先将接收的数据存在接收缓冲区 xff
  • JLINK给STM32下载的两种模式--jtag & sw连线及配置

    jtag线就不说了 xff0c 将jlink的Vref GND TMS TCK分别接至SW接口 对于STM32F103RCT6来说 xff1a TMS PA12 xff0c TCK PA14 关于KEIL MDK中的设置如下图所示就可以了
  • 3.3V过压保护电路

    好久没写了 xff0c 今天就写一些工作中用到的一个电路 3 3V过压保护电路 通常一个电路中给单片机等对电压信息敏感的器件供电时都会小心翼翼 xff0c 严防前级降压电路出问题 xff0c 我就碰到过12V转5V的1117奔溃记过加在ST
  • eagle使用注意点

    使用eagle也有快一年时间了 xff0c 刚开始很不习惯 xff0c 后来习惯了也还可以 xff0c 这里我举出几个设计中经常出错的地方 xff1a 1 PCB翻转问题 xff1a 在翻转PCB文件时一定要打开torigin borigi

随机推荐

  • 自制pixhawk电脑不识别com口

    在原版pix上面进行改版很方便 xff0c 可以去除很多不必要的电路 笔者将电源管理芯片去除 xff0c 5V来源于变压器输出或者是连接电脑时的USB供电 xff0c 并将它们并联起来 xff0c 但是板子做回来焊接后发现问题如下 xff1
  • eagle pcb v8.2 便捷性大大提升

    eagle pcb在被Autodesk收购之前是7 x版本 xff0c 但是却有一些一直被吐槽的东西 xff0c 说实话这些东西确实增加了布线难度 xff0c 增加了布板时间 xff1a 1 real time DRC xff1a 在7 x
  • Ubuntu firefox 显示在运行无法打开,如何在终端关闭进程

    用top命令找不到firfox的进程 xff0c 查看某个用户运行的进程 xff1a ps u username grep eclipse 查看用户名为 xff1a username 的用户是否运行了eclipse 查看用户当前运行fire
  • 【万字详解】cJSON解析

    目录 1 通过README文件 xff0c 初步了解cJSON xff1a 1 1 头文件的开头和结尾 xff1a 1 2 头文件关于cJSON类型的宏定义 1 3 头文件中的extern 2 阅读并且分析cJSON源码 2 1 结构体st
  • VINS-mono 解析 新特征

    在17 12 29 xff0c VINS更新了代码加入了新的特征 xff0c 包括map merge 地图合并 pose graph reuse 位姿图重利用 online temporal calibration function 在线时
  • VINS-mono 位姿图 重利用测试

    在前一篇博文里介绍了VINS mono pose graph reuse功能的使用 xff0c 这里接着贴出一些延伸的测试 xff0c 并进行一些探讨 延伸测试 一般来说 xff0c 加载地图是进行非GPS定位必要的一步 这里根据新的VIN
  • 2022年全国大学生电子设计大赛省赛A题

    2022年全国大学生电子设计大赛省赛A题 交流电子负载 文章目录 2022年全国大学生电子设计大赛省赛A题 交流电子负载 前言一 总体思路二 模块设计1 半桥模块2 测量模块3 辅助电源模块 三 主电路搭建总结 前言 2022年全国大学生电
  • linux下使用shell发送http请求

    一 curl 1 get请求 curl命令默认下就是使用get方式发送http请求 curl www baidu com 2 post请求 使用 d参数 xff0c 形式如下 xff1a curl d 34 param1 61 value1
  • 网络摄像头 接口协议 ONVIF,PSIA,CGI,ISAPI

    ONVIF致力于通过全球性的开放接口标准来推进网络视频在安防市场的应用 xff0c 这一接口标准将确保不同厂商生产的网络视频产品具有互通性 2008年11月 xff0c 论坛正式发布了ONVIF第一版规范 ONVIF核心规范1 0 随着视频
  • VLC架构及流程分析

    注明 xff1a 此文为转载 原文地址 xff1a https jiya io archives vlc learn 2 html 由于本人之前由于在工作中需要对VLC进行二次开发 因此进行了相关工作的开发 xff08 由于工作原因 目前暂
  • 学习、使用C++开发是不是过时了?

    C 43 43 在开发过程中真心很尴尬 1 拿相同薪水使用不同语言的程序员 xff0c 开发大多数相同的常见业务需求 xff0c C 43 43 总是进度较慢 xff08 不考虑时 空复杂性及效率 xff09 2 扩展性 跨平台 资源 内存
  • strcat()函数的用法

    这几天的一次程序练习中用到了strcat 函数 xff0c 但也想到了一些问题 我们都知道strcat str ptr 是将字符串ptr内容连接到字符串str后 xff0c 然后得到一个组合后的字符串str xff0c 比如 str字符串内
  • libQtCore.so.4 undefined symbol :g_main_context_push_thread_default

    开发板终端执行qt程序 qtDemo qws 报错 xff1a libQtCore so 4 undefined symbol g main context push thread default 解决方案 xff1a cd DVSDK p
  • curl时设置Expect的必要性

    curl 在项目中使用频率较高 xff0c 比如内部接口 第三方 api 图片存储服务等 xff0c 但是我们在使用 curl 时可能并没有注意到 Expect 这个请求头信息 xff0c 而 Expect 设置不正确 xff0c 会导致不
  • 奇偶校验原理

    奇校验 xff1a 求一个字节8位中 1 的个数 xff0c 添加一位校验位 xff0c 使9位中 1 的个数为奇数 xff1b 偶校验同理 奇校验就是让原有数据序列中 xff08 和要加上的一位 xff09 1的个数为奇数 如010001
  • CreateMutex函数函数用来实现进程互斥

    CreateMutex函数 正常情况下 xff0c 一个进程的运行一般是不会影响到其他正在运行的进程的 但是对于某些有特殊要求的如以独占方式使用串行口等硬件设备的程序就要求在其进程运行期间不允许其他试图使用此端口设备的程序运行的 xff0c
  • C++与QML交互总结

    一直对于QT的理解和使用都停留在主窗口程序和控制台程序 xff0c 虽然QT的新东西QML听过也接触过 xff0c 但是基本上没梳理过调用流程 趁着旧项目要使用QML技术 xff0c 现在就将C 43 43 和QML交互进行总结 目录 一
  • QT下TCP协议实现数据网络传输

    QT开发框架以其跨平台的优势 xff0c 在全世界IT界如雷贯耳 其封装了功能齐全的各种类 xff0c 大大的提高了开发者的效率 本篇内容将介绍如何使用QT 6 4 1框架开发服务器和客户端程序 xff0c 让两端能够首发消息 xff0c
  • 从零实现vins-mono+fast-planner+M100无人机实验在现实场景中的应用

    版权声明 本文为博主原创文章 未经博主允许不能随意转载 本文链接 https blog csdn net AnChenliang 1002 article details 109535355 最近由于科研的需要 要将VINS mono与fa
  • Linux下C语言实现HTTP文件服务器和TCP协议实现网络数据传输

    在实际开发中经常用到web框架 xff0c 比如Servlet xff0c SpringBoot等 xff0c 这些开发框架提高了我们的开发效率 xff0c 节省了开发时间 但是这会令我们技术人员处于浮云之上 xff0c 看不到其本质 说实