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