C++ Win32网络编程

 |
总阅读量


最近计算机网络课需要我们做一个图片传输程序,在客户端上传输图片至服务器,要求服务器能过滤文件并展示客户端上传的图片,我带着亲爱的“C++”,开着“百度”,开始畅游“网络”,并在这里写下总结。

TCP与UDP都是OSI(Open System Interconnection 开放式系统互联)参考模型下第四层——传输层协议,在这里总结一下它们的特点。

TCP

TCP(Transmission Control Protocol 传输控制协议),是面向连接的。交互双方都建立一个流式套接字,服务器需要等待客户端连接,一旦连接上就立即返回一个新的套接字描述符,通过描述符调用数据传输函数与客户端进行数据的收发。

  • 优点:可靠、稳定。
  • 缺点:速度慢、效率低、占用系统资源多、易被攻击。
  • 适合场景:网络通讯质量要求高的地方。

UDP

UDP(User Datagram Protocol 用户数据报协议),是面向无连接的。双方建立的都是数据报套接字,服务器与客户端在进行传描数据之前不需要进行连接的申请和建立,可随时向对方发送消息。

  • 优点:速度快、比TCP稍安全
  • 缺点:不可靠,不稳定
  • 使用场合:网络通讯质量要求不高、速度快的场所。

TCP/IP三次握手

这里有一段生动地描述,我摘抄如下:

主机A向主机B发出连接请求数据包:“我想给你发数据,可以吗?”,这是第一次对话;主机B向主机A发送同意连接和要求同步(同步就是两台主机一个在发送,一个在接收,协调工作)的数据包:“可以,你什么时候发?”,这是第二次对话;主机A再发出一个数据包确认主机B的要求同步:“我现在就发,你接着吧!”,这是第三次对话。三次“对话”的目的是使数据包的发送和接收同步,经过三次“对话”之后,主机A才向主机B正式发送数据。

摘自百度百科TCP/UDP协议。经过这样三次对话,TCP才能建立稳定、可靠的连接,保证发送数据的正确性。

C++实现TCP/IP三次握手,并在传输文件后关闭连接

运行环境: VS 2017

服务器端

#include "pch.h"
#include <iostream>
#include <WinSock2.h>
#include <string>
#include <fstream>
#define _WINSOCK_DEPRECATED_NO_WARNINGS
#pragma comment(lib, "ws2_32.lib")        // 导入动态链接库

SOCKET sockSrv;
void RecvFile();

int main(int argc, CHAR* argv[])

{
    WSADATA wsaData;
    int port = 8800;
    // 加载套接字
    if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)
    {
        std::cout<<"加载套接字失败: "<< WSAGetLastError()<<"......\n";
        return 1;
    } 
    sockSrv = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);    //    TCP/IP协议 
    // 初始化IP和端口信息
    SOCKADDR_IN addrSrv;
    addrSrv.sin_family = AF_INET;
    addrSrv.sin_port = htons(port);                            // 1024以上的端口号
    addrSrv.sin_addr.S_un.S_addr = INADDR_ANY;

    // bind()
    if (bind(sockSrv, (LPSOCKADDR)&addrSrv, sizeof(SOCKADDR_IN)) == SOCKET_ERROR)
    {
        std::cout<<"套接字绑定失败:"<< WSAGetLastError()<<"......\n";
        return 1;
    }
    // listen()
    if (listen(sockSrv, 1) == SOCKET_ERROR)
    {
        std::cout<<"套接字监听失败:"<<WSAGetLastError()<<"......\n";
        return 1;
    }
    // 客户端信息
    SOCKADDR_IN addrClient;
    int len = sizeof(SOCKADDR);
    // 开始监听
    std::cout<<"服务端启动成功......开始监听...\n";
    while (1)
    {
        // 等待客户请求到来  
        sockSrv = accept(sockSrv, (SOCKADDR *)&addrClient, &len);
        if (sockSrv == SOCKET_ERROR) {
            std::cout<<"建立连接失败:"<<WSAGetLastError()<<"......\n";
            break;
        }
        std::cout<<"与客户端建立连接......IP:"<<inet_ntoa(addrClient.sin_addr)<<std::endl;
        std::cout << "开始接受数据..." << std::endl;
        RecvFile();
        std::cout << "接受完毕" << std::endl;
        // 关闭套接字
        closesocket(sockSrv);
    }
    WSACleanup();
    system("pause");
    return 0;
}
void RecvFile() {
    const int bufferSize = 1024;
    char buffer[bufferSize] = { 0 };
    int readLen = 0;
    std::string desFileName = "E:\\demo.jpg";   // 打开我的E盘的一个图片文件
    std::ofstream desFile;
    desFile.open(desFileName.c_str(), std::ios::binary);    // 以二进制文件方式,不然图片会乱码
    do {                    
        readLen = recv(sockSrv, buffer, bufferSize, 0);
        if (readLen == 0)                            // 循环接受文件,直到发送的文件为 EOF
        {
            break;
        }
        else
        {
            desFile.write(buffer, readLen);
        }
    } while (true);
    desFile.close();                    // 莫忘关闭文件
}

客户端

#include "pch.h"
#include <iostream>
#include <WinSock2.h>
#include <string>
#include <fstream>
#pragma comment(lib, "ws2_32.lib")

SOCKET sockClient;
void SendFile() {
    std::streamsize haveSend = 0;
    const int bufferSize = 1024;
    char buffer[bufferSize] = {0};
    std::streamsize readLen=0;
    std::string srcFileName = "E:\\upload\\universe.jpg";        // 打开E盘的一个图片文件
    std::ifstream srcFile;
    srcFile.open(srcFileName.c_str(), std::ios::binary);        // 以二进制文件打开
    while (!srcFile.eof()) {                                    // 当读到结尾时会多循环一次,以作为发送结束标志
        srcFile.read(buffer, bufferSize);
        readLen = srcFile.gcount();
        send(sockClient, buffer, readLen, 0);
        haveSend += readLen;
    }
    srcFile.close();
}

int main(int argc, CHAR* argv[])

{
    WSADATA wsaData;
    int port = 8800;
    // 加载套接字
    if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)
    {
        std::cout << "加载套接字失败:" << WSAGetLastError() << "......"<<std::endl;
        system("pause");
        return 1;
    }
    // 初始化IP和端口信息
    SOCKADDR_IN addrSrv;
    addrSrv.sin_family = AF_INET;
    addrSrv.sin_port = htons(port);
    addrSrv.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
    // socket()
    sockClient = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    if (SOCKET_ERROR == sockClient) 
    {
        std::cout << "创建套接字失败:" << WSAGetLastError() << "......" << std::endl;
        system("pause");
        return 2;
    }
    // 向服务器发出连接请求
    if (connect(sockClient, (struct sockaddr*)&addrSrv, sizeof(addrSrv)) == INVALID_SOCKET)
    {
        std::cout << "连接服务器失败:" << WSAGetLastError() << "......" << std::endl;
        system("pause");
        return 3;
    }
    else
    {
        std::cout << "连接成功!\n\n" << std::endl;
        std::cout << "开始发送" << std::endl;
        SendFile();
        std::cout << "\n发送成功!" << std::endl;
    }
    // 关闭套接字
    closesocket(sockClient);
    WSACleanup();
    system("pause");
    return 0;
}

考虑在发送图片文件之前,给服务器发送一些该文件的信息,如名称,大小等,这样服务器可以精确的保存图片文件,诸如此类。
参考资料:
windows环境下使用C++&Socket实现文件传输


雪林于

抛砖引玉 亦或 他山之石,可以攻玉。