C/C++项目,实现一个简单的tcp服务器程序


要使用网络编程,要包含系统提供的头文件winsock2.h,同时还需要加载连接库文件(#pragma comment(lib,“ws2_32.lib”)),实现通信程序的管理。实现简单的http服务器程序,需要有以下7个步骤:

1.初始化网络库

其代码如下:

void initSocket() {
    WORD version = MAKEWORD(2, 2);
    WSADATA wsadata;
    if (0 != WSAStartup(version, &wsadata)) {// 参数1.指定socket版本 参数2.是一个传出参数
        error_die("WSAStartup");
    }
}

在c++中,BYTE、WORD和DWORD本质上都是一种无符号整型,它们在WINDEF.H中被定义,定义如下:

typedef unsigned char BYTE;
typedef unsigned short WORD;
typedef unsigned long DWORD;

在socket编程中,声明调用不同的Winsock版本MAKEWORD(2, 2)就是调用2.2版本,MAKEWORD(1, 1)就是调用1.1版本,不同的版本是有区别的,例如1.1版只支持TCP/IP协议,而2.2版本可以支持多协议。2.0版本有良好的向后兼容性。
WSAStartup主要就是进行相应的socket库绑定。使用socket的程序在使用之前必须调用WSAStartup函数。当一个应用程序调用WSAStartup函数时,操作系统根据请求socket版本来搜索相应的socket库,然后绑定找到的socket库到该应用程序中。以后应用程序就可以调用所请求的socket库中其他的socket函数。该函数执行成功后返回0。
WSADATA结构被用来保存函数WSAStartup返回的Windows sockets初始化信息。

2.创建socket

其代码如下:

//2.创建socket
    //参数1.指定IP协议 ipv4(AF_INET) ipv6(AF_INET6)
    //参数2.数据传输格式,常见的有两种,流式传输和数据报传输 
    //参数3.传输协议,                   TCP    和    UDP
    SOCKET serfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    if (serfd == INVALID_SOCKET) {
        error_die("socket");
    }

3.绑定IP地址和端口号

其代码如下:

    struct sockaddr_in serAddr;
    /*
    typedef struct sockaddr_in {
        USHORT sin_port;
        IN_ADDR sin_addr;
        CHAR sin_zero[8];
    } SOCKADDR_IN, *PSOCKADDR_IN;
    */
    serAddr.sin_family = AF_INET;//必须和创建socket的时候一样
    //大端存储--一般网络和小端存储--一般计算机,htons把本地字节序转为网络字节序
    serAddr.sin_port = htons(80);       //[0-65535) 0-1024是系统保留端口号,一般不用,80是http服务器专用
    serAddr.sin_addr.S_un.S_addr = INADDR_ANY;//绑定本机任意服务器
    if (SOCKET_ERROR == bind(serfd, (struct sockaddr*)&serAddr, sizeof(serAddr))) {
        error_die("bind");
    }

int bind(int socket,const struct *sockaddr,socklen_t address_len);将address指向的sockaddr结构体中的描述的一些属性(IP地址、端口号、地址蔟)于socket套接字进行绑定,调用后,就为socket套接字关联了一个相应的地址和端口号,即发送到地址值该端口的数据可通过socket读取和使用。当然也可以通过该socket发送数据到指定目的。
难点:(struct sockaddr*)&serAddr为什么要强制转换?
答:struct sockaddr是通用的套接字地址,而struct sockaddr_in则是因特网环境下的套接字的地址形式,二者长度一样,都是16字节。一般情况下,需要把sockaddr_in结构强制转换成sockaddr结构再传入到系统调用函数中。

4.监听

其代码如下:

listen(serfd, 10);

连接请求只能由客户端发起,此时服务端的listen函数是将服务端的主动描述符转换为被动描述符,否则无法用于监听客户端的连接。参数1表示socket创建的套接字文件描述符;参数2指定队列的容量。

5.接受连接

其代码如下:

SOCKET clifd = accept(serfd, (struct sockaddr*)&cliAddr, &len);//阻塞的
        //SOCKET clifd = accept(serfd, NULL, NULL);
        if (clifd == INVALID_SOCKET) {
            error_die("accept");
        }
        printf("have a new connect...\n");

accept()系统调用主要用在基于连接的套接字类型,它提取所监听套接字的等待连接队列中的第一个连接请求,创建一个新的套接字的文件描述符。新建立的套接字准备发送send()和接收数据recv()。其定义如下:int accept(int sockfd,struct sockaddr *addr,socklen_t *addrlen)。

6.处理连接请求

其代码如下:

void accept_request(SOCKET clifd) {
    //从clifd接受数据
    char recvBuf[1024] = "";
    if (recv(clifd, recvBuf, sizeof(recvBuf), 0)<=0) {
        error_die("recv offline");
    }
    printf("recvBuf:%s\n", recvBuf);
    //直接给客户端发送文本
    char filePath[128] = "";
    //随机选择一个网页发送
    strcpy(filePath,rand() % 2 ? "htmlFiles/index.html" : "htmlFiles/css.html");
    //判断文件是否存在
    if (access(filePath,0)==0) {//0代表文件是否存在 如果存在返回0,否则返回-1
        //发送网页
        sendHtml(clifd, filePath);

    }
    else {
        //发送404 not found网页
        notFound(clifd);
    }
}
void sendHtml(SOCKET clifd, const char * filePath) {
    FILE* pr = fopen(filePath, "r");
    if (pr==NULL) {
        error_die("fopen");
    }
    char data[1024] = "";
    do {
        fgets(data, 1024, pr);
        send(clifd, data, strlen(data), 0);
    } while (!feof(pr));
    fclose(pr);
}
void notFound(SOCKET clifd) {
    /*
    * 1.发送404错误 HTTP/1.0 404  not found
    */
    char sendData[1024] = "<html><body><h1>zhinen丶</h1></body></html>";
    send(clifd, sendData, strlen(sendData), 0);
    char sendBuf[1024];
    sprintf(sendBuf, "HTTP/1.0 404  not found\r\n");
    send(clifd, sendBuf, strlen(sendBuf), 0);
}

其解释如下:

strcpy(filePath,rand() % 2 ? "htmlFiles/index.html" : "htmlFiles/css.html");

在本地创建两个网页代码,利用随机函数和三元运算符来随机选择其中一个网页。
access()函数有四种方式,00:用来判断指定的文件或目录是否仅存在;04:已存在的文件或目录是否有仅读;02:已存在的文件或目录是否有仅写;06:已存在的文件或目录是否有既读又可写。

7.关闭连接,清理网络库

其代码如下:

closesocket(serfd);
    WSACleanup();

8.完整代码

视频讲解地址:https://www.bilibili.com/video/BV1Xv411b7vH?from=search&seid=1982437677755267168&spm_id_from=333.337.0.0
其代码如下:

// httpServer.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//

#include <stdio.h>
#include<string.h>
#include<io.h>
//要使用网络编程,要包含系统给我们提供的头文件
#include<winsock2.h>
#pragma comment(lib,"ws2_32.lib")
void sendHtml(SOCKET clifd, const char* filePath);
void notFound(SOCKET clifd);
//输出错误代码,并返回输出码
void error_die(const char* str) {
    printf("[hint]%s failed:%d", str,WSAGetLastError());//获取错误码
    exit(-1);
}
//初始化
void initSocket() {
    // 参数1.指定socket版本 参数2.是一个传出参数
    WORD version = MAKEWORD(2, 2);
    WSADATA wsadata;
    if (0 != WSAStartup(version, &wsadata)) {
        error_die("WSAStartup");
    }
}
SOCKET listenClient(){
    //2.创建socket
    //参数1.指定IP协议 ipv4(AF_INET) ipv6(AF_INET6)
    //参数2.数据传输格式,常见的有两种,流式传输和数据报传输 
    //参数3.传输协议,                   TCP    和    UDP
    SOCKET serfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    if (serfd == INVALID_SOCKET) {
        error_die("socket");
    }
    //3.绑定IP地址和端口号
    //参数1.指定的socket
    //参数2.IP地址和端口号
    //参数3.
    struct sockaddr_in serAddr;
    /*
    typedef struct sockaddr_in {
        USHORT sin_port;
        IN_ADDR sin_addr;
        CHAR sin_zero[8];
    } SOCKADDR_IN, *PSOCKADDR_IN;
    */
    serAddr.sin_family = AF_INET;//必须和创建socket的时候一样
    //大端存储--一般网络和小端存储--一般计算机,htons把本地字节序转为网络字节序
    serAddr.sin_port = htons(80);       //[0-65535) 0-1024是系统保留端口号,一般不用,80是http服务器专用
    serAddr.sin_addr.S_un.S_addr = INADDR_ANY;//绑定本机任意服务器
    if (SOCKET_ERROR == bind(serfd, (struct sockaddr*)&serAddr, sizeof(serAddr))) {
        error_die("bind");
    }
    //4.监听
    listen(serfd, 10);
    return serfd;
}
void accept_request(SOCKET clifd) {
    //从clifd接受数据
    char recvBuf[1024] = "";
    if (recv(clifd, recvBuf, sizeof(recvBuf), 0)<=0) {
        error_die("recv offline");
    }
    printf("recvBuf:%s\n", recvBuf);
    //直接给客户端发送文本
    char filePath[128] = "";
    //随机选择一个网页发送
    strcpy(filePath,rand() % 2 ? "htmlFiles/index.html" : "htmlFiles/css.html");
    //判断文件是否存在
    if (access(filePath,0)==0) {//0代表文件是否存在 如果存在返回0,否则返回-1
        //发送网页
        sendHtml(clifd, filePath);

    }
    else {
        //发送404 not found网页
        notFound(clifd);
    }
}
void sendHtml(SOCKET clifd, const char * filePath) {
    FILE* pr = fopen(filePath, "r");
    if (pr==NULL) {
        error_die("fopen");
    }
    char data[1024] = "";
    do {
        fgets(data, 1024, pr);
        send(clifd, data, strlen(data), 0);
    } while (!feof(pr));
    fclose(pr);
}
void notFound(SOCKET clifd) {
    /*
    * 1.发送404错误 HTTP/1.0 404  not found
    */
    char sendData[1024] = "<html><body><h1>zhinen丶</h1></body></html>";
    send(clifd, sendData, strlen(sendData), 0);
    char sendBuf[1024];
    sprintf(sendBuf, "HTTP/1.0 404  not found\r\n");
    send(clifd, sendBuf, strlen(sendBuf), 0);
}
int main()
{
    //1.初始化网络库wsa windows socket ansyc->windows异步套接字
    initSocket();
    SOCKET serfd = listenClient();
    printf("zhinen丶的http服务器!\n");
    //5.接收链接
    struct sockaddr_in cliAddr;
    int len = sizeof(cliAddr);
    while (1) {
        SOCKET clifd = accept(serfd, (struct sockaddr*)&cliAddr, &len);//阻塞的
        //SOCKET clifd = accept(serfd, NULL, NULL);
        if (clifd == INVALID_SOCKET) {
            error_die("accept");
        }
        printf("have a new connect...\n");
        //6.处理链接请求
        //直接给客户端发送文本
        //char sendData[1024] = "<html><body><h1>zhinen丶</h1></body></html>";
        //send(clifd, sendData, strlen(sendData), 0);
        accept_request(clifd);
        closesocket(clifd);//发送完直接关闭,http是无连接的
    }
    //7.关闭链接,清理网络库
    closesocket(serfd);
    WSACleanup();
    return 0;
}

  • 2
    点赞
  • 37
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

ZhInen丶

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值