Networking - CS model

Computer Networking: A Top-Down Approach 课程中,我们学习了数据是如何从 application layer 经由 transport layer 和 network layer 的两层封装,将数据传送到另一台主机上的特定端口号进程上的。在这个过程中,底层的网络如同一道道传送门,我们开发人员是不需要知道其中的实现细节的。当我们要将数据传送给另一台主机上的 application layer 进程时。我们只需要告诉网络目标主机的IP地址和端口号就能够使用网络进行数据的传输了。

1. TCP in C/S Model: The Prototype

在TCP实现的C/S原型模型中,我们实现的功能极为简单:服务器进程一直检测来自客户端进程的连接,一旦检测到有客户端“敲门”,就创建客户端Socket并给客户端发送"hello world"字符串,然后客户端回发,服务器显示。

//server
#include <iostream>
#include <WinSock2.h>	//声明
#include <WS2tcpip.h>
#include <thread>
#include <chrono>       //时间头函数
#pragma comment(lib, "ws2_32.lib")	//实现

const u_int BACKLOG = 128;
u_int link_count = 0;
const char* msg = "hello world";

//若当前socket连接数量小于BACKLOG,则每隔一秒打印一次打印监听信息。
void printListeningStatus() {
    while (link_count < BACKLOG) {
        std::cout << "Server is listening on port 2345...\tWe now can connect " 
                  <<BACKLOG - link_count<<" hosts." << std::endl;
        std::this_thread::sleep_for(std::chrono::seconds(2));
    }
}
//管理客户端进程
int clientHandle(SOCKET* servSock, SOCKET*clntSock) {

    // 打印客户端信息
    link_count++;
    sockaddr_in clntAddr;
    int clntAddrSize = sizeof(clntAddr);
    getpeername(*clntSock, (sockaddr*)&clntAddr, &clntAddrSize);
    char clntIP[INET_ADDRSTRLEN];
    inet_ntop(AF_INET, &clntAddr.sin_addr, clntIP, INET_ADDRSTRLEN);
    std::cout << "Client connected from IP: " << clntIP << " and port: " 
              << ntohs(clntAddr.sin_port) << std::endl;
    
    //处理与client的会话
    char recvBuf[128]{};
    if (send(*clntSock, msg, strlen(msg), 0) == -1) {
        std::cout << "Sending fail!\tError number is:" << WSAGetLastError() << std::endl;
        closesocket(*servSock);
        closesocket(*clntSock);
        WSACleanup();
        return -1;
    }
    int recvRet = recv(*clntSock, recvBuf, 128, 0);
    if (recvRet == 0) {
        std::cout << "Client drop the connection gracefully." << std::endl;
    }
    else if (recvRet < 0) {
        int ErrNum = WSAGetLastError();
        if (ErrNum == 10054) {
            std::cout << "Client drop the connection forcefully." << std::endl;
        }
        else {
            std::cout << "Receiving fail!\tError number is:" << ErrNum << std::endl;
            closesocket(*servSock);
            closesocket(*clntSock);
            WSACleanup();
            return -1;
        }
    }
    std::cout << recvBuf << std::endl;
    closesocket(*clntSock);
    link_count--;
    std::cout << "Client from IP: " << clntIP << " Port:" << ntohs(clntAddr.sin_port) 
              <<" has been disconnected."<< std::endl;
    return 0;
}
int main() {
    //初始化WSADATA结构。
    WSADATA data{};
    if (WSAStartup(MAKEWORD(2, 2), &data) == SOCKET_ERROR) {
        std::cout << "WSAStart failed! \tError number is:" << WSAGetLastError() << std::endl;
        WSACleanup();
        return -1;
    }
    //为服务器进程创建用于TCP/IP通信的套接字。
    SOCKET servSock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    if (servSock == INVALID_SOCKET) {
        std::cout << "Server socket creation fail!\tError number is:" << WSAGetLastError() << std::endl;
        WSACleanup();
        return -1;
    }
    /*定义一个sockaddr_in(Socket address internet)的结构体变量用来存储IP地址、端口号信息。
    之后将进程的“地址信息”一律转换成网络字节序(大端序)。*/
    sockaddr_in servAddr;
    servAddr.sin_family = AF_INET;      //设置地址族为IPv4
    servAddr.sin_port = htons(2345);	//将port号转换成网络字节序
    inet_pton(AF_INET, "127.0.0.1", &servAddr.sin_addr.S_un.S_addr);//将IP地址转换成网络字节序

    //将服务器进程和特定的端口号进行绑定
    if (bind(servSock, (sockaddr*)&servAddr, sizeof(sockAddr)) == SOCKET_ERROR) {
        std::cout << "Binding fail!\tError number is:" << WSAGetLastError() << std::endl;
        closesocket(servSock);
        WSACleanup();
        return -1;
    }

    //服务器socket状态从closed转换成listening。
    if (listen(servSock, BACKLOG) == -1) {
        std::cout << "Listen function fail!\tError number is:" << WSAGetLastError() << std::endl;
        closesocket(servSock);
        WSACleanup();
        return -1;
    }

    // 启动一个线程来打印监听状态
    std::thread statusThread(printListeningStatus);

    //当有client进程“敲门”,服务器进程就创建一个client socket线程来与客户端进程进行通信。
    while(link_count < BACKLOG){
        SOCKET clntSock = accept(servSock, nullptr, nullptr);
        if (clntSock == INVALID_SOCKET) {
            std::cout << "Client socket creation fail!\tError number is:" << WSAGetLastError() << std::endl;
            closesocket(servSock);
            WSACleanup();
            return -1;
        }
        // 创建一个线程来处理客户端通信
        std::thread clientThread(clientHandle, &servSock, &clntSock);
        //分离线程,让线程单独执行
        clientThread.detach();
    }

    // 等待线程结束
    statusThread.join(); 
    
    closesocket(servSock);

    WSACleanup();

    system("pause");
    return 0;
}
// client
#include <WinSock2.h>
#include <WS2tcpip.h>
#include <iostream>
#pragma comment(lib, "ws2_32.lib")

int main() {
    // 初始化 WSADATA 结构。
    WSADATA data{};
    if (WSAStartup(MAKEWORD(2, 2), &data) == SOCKET_ERROR) {
        std::cout << "WSAStartup failed! \tError number is:" << WSAGetLastError() << std::endl;
        return -1;
    }

    // 为客户端进程创建用于 TCP/IP 通信的套接字。
    SOCKET servSock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    if (servSock == INVALID_SOCKET) {
        std::cout << "Client socket creation failed!\tError number is:" << WSAGetLastError() << std::endl;
        WSACleanup();
        return -1;
    }

    // 定义一个 sockaddr_in 结构体变量用来存储服务器的 IP 地址和端口号信息。
    sockaddr_in servAddr;
    servAddr.sin_family = AF_INET;      // 设置地址族为 IPv4
    servAddr.sin_port = htons(2345);    // 将端口号转换成网络字节序
    inet_pton(AF_INET, "127.0.0.1", &servAddr.sin_addr.S_un.S_addr); // 将 IP 地址转换成网络字节序

    // 连接到服务器
    if (connect(servSock, (sockaddr*)&servAddr, sizeof(servAddr)) == SOCKET_ERROR) {
        std::cout << "Connection failed!\tError number is:" << WSAGetLastError() << std::endl;
        closesocket(servSock);
        WSACleanup();
        return -1;
    }

    // 接收服务器发送的数据
    char recvBuf[128]{};
    int recvRet = recv(servSock, recvBuf, 128, 0);
    if (recvRet == 0) {
        std::cout << "Server dropped the connection gracefully." << std::endl;
    }
    else if (recvRet < 0) {
        int ErrNum = WSAGetLastError();
        if (ErrNum == 10054) {
            std::cout << "Server dropped the connection forcefully." << std::endl;
        }
        else {
            std::cout << "Receiving failed!\tError number is:" << ErrNum << std::endl;
            closesocket(servSock);
            WSACleanup();
            return -1;
        }
    }

    // 发送数据到服务器
    if (send(servSock, recvBuf, recvRet, 0) == SOCKET_ERROR) {
        std::cout << "Sending failed!\tError number is:" << WSAGetLastError() << std::endl;
        closesocket(servSock);
        WSACleanup();
        return -1;
    }

    closesocket(servSock);
    WSACleanup();
    system("pause");
    return 0;
}

2. TCP in C/S Model: A Class Implementation


void fileDirMnagr::listDir(int depth = 0)
{
	DIR *dir = opendir(currDir.c_str());
	if (!dir)
	{
		formattedMesg == "Cannot open " + currDir + ".\n";
		perror("Fail to open directory");
		return;
	}
	dirent *pDirent = nullptr;
	while ((pDirent = readdir(dir)) != nullptr)
	{
		const std::string filename = pDirent->d_name;
		if (filename == "." || filename == "..")
		{
			continue;
		}
		for (int i = 0; i < depth; ++i)
		{
			formattedMesg += "    ";
		}
		formattedMesg += "|---" + filename;
		if (pDirent->d_type == DT_DIR)
		{
			formattedMesg += "/\n";
			std::string previousDir = currDir;
			currDir = currDir + "/" + filename;
			listDir(depth + 1);
			currDir = previousDir;
		}
		else
		{
			formattedMesg += "\n";
		}
	}
	if (closedir(dir) != 0)
	{
		perror("Fail to close directory");
	}
}