1. Receive an HTTP message

This article explains how to use Winsock2 to receive an HTTP message from a browser and echo it.

The core content of the TCP/IP protocol is encapsulated in the operating system. When programmers want to use the TCP/IP protocol to realize their own functions, they can only realize it through the interface provided by the system. This interface is called the socket interface.

TCP is a connection-oriented protocol at the transport layer. Before the communication parties communicate, they must be connected. And when it comes to connection, we use a five-tuple to describe who is connecting with whom (because it is end-to-end, there must be a port number):

<protocol, local ip address, local port number, remote ip address, remote port number>

What we are programming is actually: filling the five-tuple, so that the communication requester (client) can send the communication request to the correct location, and the communication receiver (server) needs to receive the client’s request at the correct location. request, and respond to connection requests.

If we just want to be a web server, we only need to pay attention to the left half of the above figure, which describes how the server receives the connection request from the client and connects in socket programming. Of course, studying the right half will give you a better grasp of the entire communication process. (Look through it first, and really experience this picture in the following code implementation)

Class declaration

This file declares the class HttpServer. If you want to know what these variables do, it is better to look directly at the class implementation part.

//
// Created by Chen Haonan on 2023/3/23.
//

#ifndef HTTPSERVER_HTTPSERVER_H
#define HTTPSERVER_HTTPSERVER_H

#include <Winsock2.h>
#include <iostream>
#include <string>
#pragma comment(lib,"ws2_32.lib")
const int BUFF_LEN(2048);
class HttpServer
{
private:
    //Used to listen to a client requesting a certain port on the server
    std::string serverIP;
    unsigned short serverPort;
    //Used to receive the HTTP message sent by the client
    char receiveBuff[BUFF_LEN];
    // Some intermediate quantities required by Winsock
    WSAData wsaData;
    SOCKET listenSocket, acceptSocket;
    struct sockaddr_in addrServer,addrClient; //Client is used to save client information when receiving a connection
    //The length of socketaddr
    int len;
    //Pending connection queue length
    int backlog;
    //intermediate function
    int receiveMessage();
public:
    HttpServer(const std::string serverIP, const unsigned short serverPort);
    int start();
};


#endif //HTTPSERVER_HTTPSERVER_H

Class implementation

Constructor

In this function, we receive the local ip and local listening port passed in by the user and assign them to class variables, that is, get the two elements in the five-tuple.

Then we stored the local ip and listening port in a structure, and specified the protocol family AF_INET, which means ipv4.

Note that here we use a inet_addr function, which can convert the dotted decimal ip address into a long integer (string → long) of the network byte sequence. When storing the port, a htons function is used, which converts the port number of the host byte sequence to the network byte sequence.

In computer networking, different computers and operating systems use different endianness (that is, the order in which data is stored in memory). Host byte order (also known as local byte order) refers to the byte order used in the memory of the current system, while network byte order (also known as big-endian byte order) is a standard byte order, It is used to transfer data over the network.

In network communication, since different computers and operating systems use different endianness, if conversion is not performed, data exchange between the sender and receiver may be wrong. To solve this problem, the sender must convert the data to network byte order, and the receiver converts it back to host byte order after receiving the data.

The bottom two lines are not important, so leave them alone.

HttpServer::HttpServer(const std::string serverIP, const unsigned short serverPort)
{
    this->serverIP=serverIP;
    this->serverPort=serverPort;

    addrServer.sin_addr.S_un.S_addr= inet_addr(serverIP.c_str());
    addrServer.sin_family=AF_INET;
    addrServer.sin_port = htons(serverPort);

    len=sizeof(sockaddr);
    backlog=1;
}

start function

This function is used to establish a TCP connection and receive a message from the client.

This part of the operation can correspond to the big picture above.

First, check the protocol stack, that is, check the installation of the system protocol stack. Socket programming requires you to pass the check before continuing to other operations.

if (SOCKET_ERROR== WSAStartup(MAKEWORD(2,2), & amp;wsaData))
{
    cout<<"WSAStartup error"<<endl;
    return -1;
}

Obtain a socket interface from the operating system,

The first parameter is to specify what communication protocol to use, here is the ipv4 protocol.

There are many types of sockets. The second parameter is to specify what type of socket is required. We are using stream sockets here.

The third parameter is to specify a specific type of protocol. Since the TCP protocol we use has only one protocol field, this parameter can be filled with 0.

if (INVALID_SOCKET==(listenSocket= socket(AF_INET,SOCK_STREAM,0)))
{
    cout<<"socket failed"<<endl;
    WSACleanup();
    return -1;
}

Bind the ip address, protocol family, and port number to listenSocket. The first parameter is the socket to be bound, the second parameter is a structure sockaddr, and the third parameter is the length of this structure. (This is the same as when we pass an array into a function, we always have to pass the length of the array into it)

if (INVALID_SOCKET== bind(listenSocket,(sockaddr *) & amp;addrServer,len))
{
    cout<<"bind failed"<<endl;
    closesocket(listenSocket);
    WSACleanup();
    return -1;
}

The listening port. The first parameter is the socket used for listening, and the second parameter is the length of the waiting queue. Suppose there are now 100 client thread requests to connect to our port, and we can only handle 20, then we will put the first 20 connection requests in the “waiting for processing” queue, and the remaining 80 requests will be Caused a WSAECONNREFUSED error. And the ” defined here can only handle 20 ” where 20 is the backlog.

if (INVALID_SOCKET== listen(listenSocket,backlog))
{
    cout<<"listen failed"<<endl;
    closesocket(listenSocket);
    WSACleanup();
    return -1;
}

Receive connection. listen is a blocking function. When it finishes running, it means that a connection request is successfully sent to the listening port. We just need to receive the connection under listen.

Note that there is another socket (acceptSocket) here. This is because in the TCP protocol, the server needs to listen to a specific port first and wait for the connection request from the client. Once a client connection request arrives, the server needs to use the accept function to accept the connection and establish a new socket ( acceptSocket) to communicate with the client.

When the server uses the socket function to create a listening socket (listenSocket), the socket is only used to accept client connection requests, and it is not directly used for actual data transmission. Once a client connection request arrives, the server needs to use the accept function to accept the connection and establish a new socket (acceptSocket) to communicate with the client. This new socket is the socket that actually transmits data between the server and the client, and the server’s listening socket continues to wait for the next client connection request.

if (SOCKET_ERROR==(acceptSocket=accept(listenSocket,(sockaddr *) & amp;addrClient, & amp;len)))
{
    cout<<"accept failed"<<endl;
    closesocket(listenSocket);
    WSACleanup();
    return -1;
}

Then receive an HTTP message from the client, and at this point we have achieved all the things we have to do.

Finally, we need to close the open socket, freeing the Windows DLL resources.

receiveMessage();
closesocket(acceptSocket);
closesocket(listenSocket);
WSACleanup();
return 0;

receiveMessage function

This function receives a message from the client and echoes it back.

First use the memset function to clear the receive buffer.

Then use the recv function to receive the segment from the client into the receive buffer.

Finally, print out the content in the receive buffer.

int HttpServer::receiveMessage()
{
    memset(receiveBuff,0,BUFF_LEN);
    int ret=recv(acceptSocket,receiveBuff,BUFF_LEN-1,0);
    if (SOCKET_ERROR==ret)
    {
        cout<<"recv error"<<endl;
        return -1;
    }
    else
    {
        cout<<"The client sent a request:"<<endl;
        cout<<receiveBuff<<endl;
        return 0;
    }
}

Result presentation

Since I am listening on port 81, when I type the following content in the browser and press Enter, my program will receive an HTTP request message from the browser and echo it.

localhost:81

like this:

References

  • [0] TCP/IP Protocol and Network Programming Ren Taiming

  • [1] chatgpt3.5

Complete code

HttpServer.h

//
// Created by Chen Haonan on 2023/3/23.
//

#ifndef HTTPSERVER_HTTPSERVER_H
#define HTTPSERVER_HTTPSERVER_H

#include <Winsock2.h>
#include <iostream>
#include <string>
#pragma comment(lib,"ws2_32.lib")
const int BUFF_LEN(2048);
class HttpServer
{
private:
    //Used to listen to a client requesting a certain port on the server
    std::string serverIP;
    unsigned short serverPort;
    //Used to receive the HTTP message sent by the client
    char receiveBuff[BUFF_LEN];
    // Some intermediate quantities required by Winsock
    WSAData wsaData;
    SOCKET listenSocket, acceptSocket;
    struct sockaddr_in addrServer,addrClient; //Client is used to save client information when receiving a connection
    //The length of socketaddr
    int len;
    //Pending connection queue length
    int backlog;
    //intermediate function
    int receiveMessage();
public:
    HttpServer(const std::string serverIP, const unsigned short serverPort);
    int start();
};


#endif //HTTPSERVER_HTTPSERVER_H

HttpServer.cpp

//
// Created by Chen Haonan on 2023/3/23.
//

#include "HttpServer.h"
using std::cout;
using std::endl;

HttpServer::HttpServer(const std::string serverIP, const unsigned short serverPort)
{
    this->serverIP=serverIP;
    this->serverPort=serverPort;

    addrServer.sin_addr.S_un.S_addr= inet_addr(serverIP.c_str());
    addrServer.sin_family=AF_INET;
    addrServer.sin_port = htons(serverPort);

    len=sizeof(sockaddr);
    backlog=1;
}
int HttpServer::start()
{
    // The console displays garbled characters to correct, removing them will not affect the operation (source network)
    system("chcp 65001"); //Set the character set (use SetConsoleCP(65001) to set invalid, the reason is unknown)
    CONSOLE_FONT_INFOEX info = { 0 };
    info.cbSize = sizeof(info);
    info.dwFontSize.Y = 16; // leave X as zero
    info.FontWeight = FW_NORMAL;
    wcscpy(info.FaceName, L"Consolas");
    SetCurrentConsoleFontEx(GetStdHandle(STD_OUTPUT_HANDLE), NULL, &info);

    //Check whether this Window socket version (version 2.2) can be used (that is, check the installation of the system protocol stack), and only if it can be used can continue to call other Windows sockets
    if (SOCKET_ERROR== WSAStartup(MAKEWORD(2,2), & amp;wsaData))
    {
        cout<<"WSAStartup error"<

main.cpp

#include "HttpServer.h"
int main()
{
    HttpServer httpServer("127.0.0.1",81);
    httpServer.start();
    return 0;
}
BUFF_LEN-1,0);
    if (SOCKET_ERROR==ret)
    {
        cout<<"recv error"<<endl;
        return -1;
    }
    else
    {
        cout<<"The client sent a request:"<<endl;
        cout<<receiveBuff<<endl;
        return 0;
    }
}

main.cpp

#include "HttpServer.h"
int main()
{
    HttpServer httpServer("127.0.0.1",81);
    httpServer.start();
    return 0;
}