Qt5官方Demo解析集3——Broadcast Sender/Receiver

本文详细解析了QTcp广播发送与接收的实现过程,从头文件到实现代码,逐步深入,旨在帮助开发者理解QT网络API的广播功能,并通过示例代码展示其在本地网络中的应用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

本系列所有文章可以在这里查看http://blog.youkuaiyun.com/cloud_castle/article/category/2123873


上次在该系列第一篇博文里提到QTcp的同步构建方式,这个例子涉及线程等,知识点比较多。我们还是由浅入深,今天再看个简单的例子吧~

按例看下Broadcast Sender Example的介绍吧,这对第一时间了解这个例程是有好处的~

Demonstrates how to broadcast information to multiple clients on a local network.
This example uses Qt Network APIs to demonstrate how to broadcast messages to multiple clients over a local network.



main主函数没有特别的东西,来看sender.h:

#ifndef SENDER_H
#define SENDER_H

#include <QWidget>

QT_BEGIN_NAMESPACE
class QDialogButtonBox;
class QLabel;
class QPushButton;
class QTimer;
class QUdpSocket;
QT_END_NAMESPACE

class Sender : public QWidget
{
    Q_OBJECT

public:
    Sender(QWidget *parent = 0);

private slots:
    void startBroadcasting();
    void broadcastDatagram();

private:
    QLabel *statusLabel;
    QPushButton *startButton;
    QPushButton *quitButton;
    QDialogButtonBox *buttonBox;
    QUdpSocket *udpSocket;
    QTimer *timer;
    int messageNo;
};

#endif
可以发现这个头文件像Multicast Sender的头文件一样简单,甚至比它更简单。因为它连QHostAddress成员都没有。


sender.cpp:

#include <QtWidgets>
#include <QtNetwork>

#include "sender.h"

Sender::Sender(QWidget *parent)
    : QWidget(parent)
{
    statusLabel = new QLabel(tr("Ready to broadcast datagrams on port 45454"));
    statusLabel->setWordWrap(true); // 这个属性的设置使得statusLabel的文字可以在合适的位置换行

    startButton = new QPushButton(tr("&Start"));
    quitButton = new QPushButton(tr("&Quit"));

    buttonBox = new QDialogButtonBox;
    buttonBox->addButton(startButton, QDialogButtonBox::ActionRole);
    buttonBox->addButton(quitButton, QDialogButtonBox::RejectRole);

    timer = new QTimer(this);
//! [0]
    udpSocket = new QUdpSocket(this);
//! [0]
    messageNo = 1;

    connect(startButton, SIGNAL(clicked()), this, SLOT(startBroadcasting()));
    connect(quitButton, SIGNAL(clicked()), this, SLOT(close()));
    connect(timer, SIGNAL(timeout()), this, SLOT(broadcastDatagram()));

    QVBoxLayout *mainLayout = new QVBoxLayout;
    mainLayout->addWidget(statusLabel);
    mainLayout->addWidget(buttonBox);
    setLayout(mainLayout);

    setWindowTitle(tr("Broadcast Sender"));
}

void Sender::startBroadcasting()
{
    startButton->setEnabled(false);
    timer->start(1000);
}

void Sender::broadcastDatagram()
{
    statusLabel->setText(tr("Now broadcasting datagram %1").arg(messageNo));
//! [1]
    QByteArray datagram = "Broadcast message " + QByteArray::number(messageNo);
    udpSocket->writeDatagram(datagram.data(), datagram.size(), // 第(1)点
                             QHostAddress::Broadcast, 45454);
//! [1]
    ++messageNo;
}
第一点,发送数据的语句就这一条,与Multicast唯一的区别在于使用了一个QHostAddress的枚举变量Broadcast,它相当于QHostAddress(“255,255,255,255”)。


好了,来看Broadcast Receiver的实现。receiver.h:

#ifndef RECEIVER_H
#define RECEIVER_H

#include <QWidget>

QT_BEGIN_NAMESPACE
class QLabel;
class QPushButton;
class QUdpSocket;
class QAction;
QT_END_NAMESPACE

class Receiver : public QWidget
{
    Q_OBJECT

public:
    Receiver(QWidget *parent = 0);

private slots:
    void processPendingDatagrams();

private:
    QLabel *statusLabel;
    QPushButton *quitButton;
    QUdpSocket *udpSocket;
};

#endif


receiver.cpp:

#include <QtWidgets> // 第(1)点
#include <QtNetwork>

#include "receiver.h"

Receiver::Receiver(QWidget *parent)
    : QWidget(parent)
{
    statusLabel = new QLabel(tr("Listening for broadcasted messages"));
    statusLabel->setWordWrap(true);

    quitButton = new QPushButton(tr("&Quit"));

//! [0]
    udpSocket = new QUdpSocket(this);
    udpSocket->bind(45454, QUdpSocket::ShareAddress); // 绑定端口,共享地址模式
//! [0]

//! [1]
    connect(udpSocket, SIGNAL(readyRead()),
            this, SLOT(processPendingDatagrams()));
//! [1]
    connect(quitButton, SIGNAL(clicked()), this, SLOT(close()));

    QHBoxLayout *buttonLayout = new QHBoxLayout; // 以控件为Layout命名,而不是Layout1,Layout2...
    buttonLayout->addStretch(1);
    buttonLayout->addWidget(quitButton);
    buttonLayout->addStretch(1);

    QVBoxLayout *mainLayout = new QVBoxLayout; // 最外层的布局mainLayout
    mainLayout->addWidget(statusLabel);
    mainLayout->addLayout(buttonLayout);
    setLayout(mainLayout);

    setWindowTitle(tr("Broadcast Receiver"));
}

void Receiver::processPendingDatagrams() // 接收端的处理函数与Multicast一模一样
{
//! [2]
    while (udpSocket->hasPendingDatagrams()) {
        QByteArray datagram;
        datagram.resize(udpSocket->pendingDatagramSize());
        udpSocket->readDatagram(datagram.data(), datagram.size());
        statusLabel->setText(tr("Received datagram: \"%1\"")
                             .arg(datagram.data()));
    }
//! [2]
}
第一点,个人不推荐这种头文件的包含方式,官方Demo为了简明这样做了,但这样会使包含的文件过多,最好用到什么包含什么,虽然会多写几个#include,但是预编译结束后文件体积会小很多。而且#include<QtWidgets/QLabel>优于#include<QLabel>,这样头文件的层次更清晰。

好了,其实这个例子与上一个Multicast Sender/Receiver 基本没有多大区别,但是我们读例程并不只是关注它核心功能的实现,更多的在于细节的处理。细节决定成败嘛~




<think>嗯,用户想要用C++在VisualStudio 2022上实现一个聊天室,支持群聊、单聊和文件传输。我得先理清楚需求,然后考虑怎么设计这个程序。首先,聊天室通常需要服务器和客户端两部分。服务器处理多个客户端的连接,转发消息,管理用户状态等。客户端则负责用户界面和与服务器的通信。 接下来,通信协议的选择。由于是C++,可能用原生的Socket编程,使用Winsock库。需要确定消息的格式,比如每个消息可能有类型字段,比如群聊、单聊、文件传输,还有发送者、接收者、内容等信息。结构体或者JSON格式?但C++处理JSON可能需要第三方库,可能用户希望用标准库,所以可能用结构体打包数据。然后是多线程处理,服务器需要为每个客户端连接创建一个线程,处理接收和发送数据。客户端也需要多线程,一个用于接收服务器消息,另一个用于用户输入。否则界面会卡住。 文件传输方面,需要考虑分块传输,尤其是大文件。可能需要将文件分成多个包发送,服务器再转发给目标客户端。或者直接建立P2P连接?但可能复杂,所以还是通过服务器中转比较稳妥。 Visual Studio2022的配置方面,需要链接Winsock库,即ws2_32.lib,在项目属性里添加。代码中要初始化Winsock,使用WSAStartup。 然后具体实现服务器和客户端的结构。服务器的主循环接受客户端连接,每个客户端一个线程。客户端连接后发送自己的用户名,服务器维护一个用户列表,包括socket和用户名等信息。当客户端发送消息时,服务器根据消息类型(群聊、单聊)转发给对应的用户。 对于单聊,消息中需要指定目标用户名,服务器查找该用户对应的socket,发送消息。群聊则广播给所有在线用户。 文件传输的话,客户端发送文件请求,服务器通知接收方,接收方同意后,开始传输。或者直接通过服务器中转文件数据。这里可能需要建立另一个连接或者使用同一个连接,用不同的消息类型区分。例如,发送文件时,先发送文件信息(文件名、大小),然后分块发送数据,接收方按块写入文件。 接下来考虑代码结构。服务器端: 1. 创建监听socket,绑定端口,进入监听状态。 2.接受客户端连接,为每个客户端创建线程处理通信。3. 维护用户列表,使用互斥锁保证线程安全。 4. 处理接收到的消息,根据类型转发。 客户端: 1.连接服务器,输入用户名。 2. 启动接收线程,循环接收服务器消息。 3. 用户输入命令,如发送消息、文件等,处理并发送给服务器。 可能的代码结构: 服务器端使用多线程,每个客户端对应一个线程。用vector或map保存客户端信息,加锁保护。消息解析部分需要处理不同的命令,比如群聊消息以特定格式开头,单聊消息包含目标用户,文件传输可能有特殊标识。 客户端同样使用多线程,主线程处理用户输入,子线程处理接收服务器消息。用户输入的命令需要解析,比如输入"/msg username message"表示单聊,输入"/fileusername filepath"发送文件。 文件传输部分,发送方读取文件内容,分成多个包发送,每个包包含文件数据和序号。接收方按顺序接收并写入文件。或者,在服务器中转的情况下,服务器需要正确处理文件数据块并转发给目标客户端。 测试时需要注意端口开放,防火墙设置,以及多客户端的并发处理。 现在,代码示例需要包含服务器和客户端的基本框架,处理连接,消息转发,文件传输可能需要较复杂的处理,可能需要分步骤实现,比如先实现群聊和单聊,再添加文件传输功能。 可能的代码结构: 服务器:- 使用Winsock,创建socket,bind,listen,accept循环。 - 每个客户端线程中,recv接收数据,解析消息类型,处理群聊、单聊或文件。 -用户列表用结构体存储socket、用户名等。 客户端: - 连接服务器,发送用户名。 - 接收线程循环recv,显示消息。- 主线程处理用户输入,发送命令和消息。 文件传输可能需要额外的处理,比如当用户发送文件时,客户端读取文件,发送文件头信息(类型、接收者、文件名、大小),然后分块发送数据。服务器接收后转发给目标客户端,目标客户端接收并保存文件。 但具体实现中,大文件传输需要注意缓冲区大小,可能使用循环发送和接收,直到整个文件传输完毕。此外,可能需要确认机制,确保数据包的正确传输。 综上,给出一个基础版本的代码,包含群聊、单聊,文件传输部分可能较为简化,比如通过base64编码发送小文件,或者分块传输。由于代码长度限制,可能需要分部分展示,但用户要求完整代码,所以需要整合。 现在,编写示例代码的大纲: 服务器端代码: -包含必要的头文件,如WinSock2.h,ws2tcpip.h,iostream,string,vector,thread,mutex等。 -定义常量,如端口号,缓冲区大小。 -定义客户端信息结构体,包含socket、用户名等。 - 全局变量:客户端列表,互斥锁。 - 函数:处理客户端通信的线程函数,接收数据,解析消息类型,转发。 - main函数:初始化Winsock,创建监听socket,绑定,监听,循环accept,创建线程。客户端代码: -包含头文件,连接服务器,输入用户名。 -接收线程函数,循环接收数据显示。 - 主线程处理用户输入,解析命令(群聊、单聊、文件),发送消息。 文件传输部分,可能需要在消息类型中添加文件头,处理文件的分块发送和接收。例如,当用户输入/file user filename时,客户端读取文件,发送文件信息,然后发送数据块。 可能的代码实现步骤: 1. 定义消息结构,使用结构体,包含消息类型(群聊、单聊、文件),发送者,接收者,内容等。但由于结构体在网络传输中需要序列化,可能更简单的方式是用字符串协议,比如用特定的分隔符分隔各部分。 例如,消息格式: 群聊: "MSG_ALL|发送者|消息内容"单聊: "MSG_ONE|发送者|接收者|消息内容" 文件传输开始: "FILE_START|发送者|接收者|文件名|文件大小" 文件数据块: "FILE_DATA|发送者|接收者|数据..." 文件传输结束: "FILE_END|发送者|接收者"这样服务器可以根据前缀解析消息类型,并处理转发。在代码中,服务器接收数据后,解析字符串,根据前缀判断类型,然后处理。客户端发送文件时,先发送FILE_START,然后分块读取文件,发送FILE_DATA,最后发送FILE_END。 接收方客户端在收到FILE_START时,创建文件,准备写入;收到FILE_DATA时追加数据;收到FILE_END时关闭文件。 但需要注意粘包问题,因为TCP是流式协议,消息可能被合并或拆分。因此,可能需要自定义协议,比如在每个消息前加上长度前缀,或者使用定长消息头。 这会增加复杂度,但为了正确处理,可能需要这样做。例如,每个消息的前4字节表示消息体的长度,然后读取相应长度的数据。这需要修改代码,在发送消息时,先发送长度,再发送数据。接收方先读取4字节的长度,再读取对应长度的数据。这样处理可以解决粘包问题,但会增加代码的复杂度。 考虑到用户可能需要一个简单实现,可能先忽略粘包问题,假设每次recv都能获取完整消息,但这在实际中不可靠。因此,更好的做法是处理消息长度前缀。 但为了简化示例代码,可能在示例中使用简单的字符串分割,但实际应用中需要更健壮的处理。 综上,示例代码可能如下: 服务器端代码框架: #include<WinSock2.h> #include<ws2tcpip.h>#include <iostream>#include <string>#include <vector>#include <thread>#include <mutex> #pragma comment(lib, "ws2_32.lib") using namespace std; const int PORT= 12345; const int BUFFER_SIZE =1024; structClientInfo {SOCKET socket; string username; }; vector<ClientInfo> clients; mutex clientsMutex; voidHandleClient(SOCKET clientSocket){ char buffer[BUFFER_SIZE]; int bytesReceived; stringusername; //接收用户名bytesReceived =recv(clientSocket, buffer, BUFFER_SIZE,0); if(bytesReceived <=0) {closesocket(clientSocket); return; }username = string(buffer, bytesReceived);{ lock_guard<mutex> lock(clientsMutex);clients.push_back({clientSocket,username}); }cout << username<< " 已连接。" << endl; while (true) {bytesReceived = recv(clientSocket,buffer, BUFFER_SIZE, 0); if (bytesReceived <=0) {break; }string message(buffer, bytesReceived);// 解析消息类型if (message.find("MSG_ALL|") == 0) { //群发lock_guard<mutex>lock(clientsMutex); for(const auto&client : clients){ if (client.socket !=clientSocket) {send(client.socket, message.c_str(), message.size(), 0);} }} else if(message.find("MSG_ONE|") == 0) { //单聊,解析接收者size_t pos1 = message.find(&#39;|&#39;,8); stringreceiver = message.substr(8, pos1 - 8); string actualMsg = message.substr(pos1 +1); lock_guard<mutex> lock(clientsMutex);for (constauto& client :clients) {if (client.username== receiver) {send(client.socket, message.c_str(), message.size(), 0);break;} }} else if (message.find("FILE_START|")== 0){ //文件传输开始,转发给接收者//类似单聊处理//需要解析接收者,并转发// 这里可能需要更复杂的处理,比如接收者确认等// 此处简化为直接转发size_t pos1 = message.find(&#39;|&#39;,11); stringreceiver = message.substr(11, pos1 - 11); lock_guard<mutex> lock(clientsMutex);for (const auto& client : clients) { if(client.username ==receiver) {send(client.socket, message.c_str(), message.size(),0);break; }} }// 处理其他消息类型,如FILE_DATA,FILE_END等}// 客户端断开处理 {lock_guard<mutex>lock(clientsMutex); autoit = remove_if(clients.begin(),clients.end(), [clientSocket](constClientInfo& ci) { returnci.socket ==clientSocket;}); clients.erase(it, clients.end()); }closesocket(clientSocket); cout << username << "已断开连接。"<< endl; }int main() {WSADATAwsaData;WSAStartup(MAKEWORD(2, 2),&wsaData); SOCKET serverSocket =socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); sockaddr_in serverAddr;serverAddr.sin_family = AF_INET;serverAddr.sin_port= htons(PORT); serverAddr.sin_addr.s_addr = INADDR_ANY; bind(serverSocket, (sockaddr*)&serverAddr,sizeof(serverAddr)); listen(serverSocket, SOMAXCONN);cout << "服务器已启动,等待连接..." << endl; while(true) {SOCKETclientSocket = accept(serverSocket,NULL, NULL);if (clientSocket == INVALID_SOCKET){ cerr << "接受连接失败" << endl;continue;} thread(HandleClient, clientSocket).detach(); }closesocket(serverSocket); WSACleanup();return 0; } 客户端代码框架: #include<WinSock2.h> #include<ws2tcpip.h> #include<iostream> #include<string> #include<thread> #pragmacomment(lib,"ws2_32.lib") usingnamespace std; constchar* SERVER_IP = "127.0.0.1";const int PORT =12345;const int BUFFER_SIZE = 1024; SOCKET clientSocket;string username; voidReceiveMessages() {char buffer[BUFFER_SIZE];while (true) { intbytesReceived = recv(clientSocket,buffer, BUFFER_SIZE, 0); if (bytesReceived <=0) {cout << "与服务器断开连接。"<< endl;break; }string message(buffer, bytesReceived);// 解析消息类型if (message.find("MSG_ALL|") == 0) { size_t pos = message.find(&#39;|&#39;,8);string sender = message.substr(8,pos - 8); string content= message.substr(pos+ 1);cout << "[群聊]" << sender << ": "<< content << endl; } elseif (message.find("MSG_ONE|") ==0) {size_t pos =message.find(&#39;|&#39;, 8);string sender =message.substr(8, pos -8); size_t pos2 =message.find(&#39;|&#39;, pos +1); stringcontent = message.substr(pos2 +1); cout<< "[私聊]" << sender << " 对你说: " << content<< endl;} else if (message.find("FILE_START|")== 0){ //处理文件传输请求size_tpos = message.find(&#39;|&#39;,11); stringsender = message.substr(11, pos- 11);size_t pos2 = message.find(&#39;|&#39;, pos+ 1);string filename =message.substr(pos +1, pos2 - pos -1);// 提示用户接收或拒绝cout << sender<< " 想发送文件给你:"<< filename << endl; //这里需要用户输入是否接收,然后回复服务器// 示例中简化为直接接收,保存文件// 实际需要处理回复,并接收后续数据}// 处理其他消息类型...} } int main() { WSADATA wsaData; WSAStartup(MAKEWORD(2,2), &wsaData);clientSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);sockaddr_inserverAddr;serverAddr.sin_family = AF_INET; serverAddr.sin_port =htons(PORT); inet_pton(AF_INET, SERVER_IP,&serverAddr.sin_addr); connect(clientSocket, (sockaddr*)&serverAddr,sizeof(serverAddr)); cout << "请输入用户名:"; getline(cin, username); send(clientSocket, username.c_str(), username.size(), 0);thread(ReceiveMessages).detach(); string input; while (true) {getline(cin, input);if (input.empty()) continue;if (input.find("/msg ") ==0) {// 单聊命令格式:/msg 接收者 消息内容size_tspace1 = input.find(&#39; &#39;,5); if(space1 !=string::npos) { stringreceiver = input.substr(5, space1 - 5); string content= input.substr(space1 +1); stringmessage = "MSG_ONE|"+ username + "|" + receiver+ "|"+ content;send(clientSocket,message.c_str(),message.size(),0); }} else if(input.find("/file ") ==0) {// 文件发送命令格式:/file 接收者文件路径size_t space1 = input.find(&#39; &#39;, 6); if (space1 != string::npos){ string receiver= input.substr(6, space1- 6);string filepath= input.substr(space1 +1); //读取文件并发送 FILE* file = fopen(filepath.c_str(), "rb");if (file) { fseek(file,0, SEEK_END);long fileSize =ftell(file);fseek(file, 0,SEEK_SET);string fileName =filepath.substr(filepath.find_last_of("\\/")+ 1);string header ="FILE_START|" + username+ "|"+ receiver + "|" + fileName+ "|"+ to_string(fileSize); send(clientSocket, header.c_str(), header.size(), 0); char buffer[BUFFER_SIZE]; while (true) {size_t bytesRead= fread(buffer, 1,BUFFER_SIZE,file); if(bytesRead <=0) break; string dataMsg = "FILE_DATA|" +username + "|" + receiver +"|";dataMsg.append(buffer, bytesRead);send(clientSocket, dataMsg.c_str(), dataMsg.size(), 0); }string endMsg ="FILE_END|" + username+ "|"+ receiver;send(clientSocket,endMsg.c_str(), endMsg.size(), 0);fclose(file); } else{ cout << "无法打开文件。" << endl;} }} else {// 群聊消息string message = "MSG_ALL|"+ username + "|" + input; send(clientSocket, message.c_str(), message.size(), 0);} }closesocket(clientSocket); WSACleanup();return 0; } 这只是一个基础框架,实际需要处理更多细节,比如: -文件传输的可靠性,分块传输和重组。- 粘包问题,使用消息长度前缀。 -错误处理,比如连接断开、文件读取失败等。 - 用户界面的友好性,比如接收文件时的确认。 -多线程同步问题。 例如,文件传输部分在客户端接收时,需要处理FILE_START、FILE_DATA和FILE_END消息,将数据写入文件。但当前示例代码中并未实现接收文件的部分,需要补充。 此外,代码中使用了字符串操作来解析消息,可能效率不高,且容易出错。更好的方法是设计一个更严谨的协议,比如使用二进制格式,包含消息类型、长度等字段。 由于用户要求完整代码,可能需要更完整的实现,但考虑到篇幅和复杂度,示例代码可能无法涵盖所有功能,尤其是文件传输的完整处理。因此,可能需要在回答中指出这些限制,并提供进一步的改进建议。最后,根据用户的需求,给出VisualStudio 2022的配置说明,比如需要链接ws2_32.lib,设置字符等。</think>以下是基于Winsock实现的C++聊天室基础代码,包含群聊和单聊功能,文件传输功能需要额外扩展: ```cpp // Server.cpp #include <WinSock2.h> #include <ws2tcpip.h> #include <iostream> #include <string> #include <vector> #include <thread> #include <mutex> #pragma comment(lib, "ws2_32.lib") using namespace std; const int PORT = 12345; const int BUFFER_SIZE = 1024; struct ClientInfo { SOCKET socket; string username; }; vector<ClientInfo> clients; mutex clientsMutex; void BroadcastMessage(const string& message, SOCKET excludeSocket = INVALID_SOCKET) { lock_guard<mutex> lock(clientsMutex); for (const auto& client : clients) { if (client.socket != excludeSocket) { send(client.socket, message.c_str(), message.size(), 0); } } } void HandleClient(SOCKET clientSocket) { char buffer[BUFFER_SIZE]; int bytesReceived = recv(clientSocket, buffer, BUFFER_SIZE, 0); if (bytesReceived <= 0) { closesocket(clientSocket); return; } string username(buffer, bytesReceived); { lock_guard<mutex> lock(clientsMutex); clients.push_back({clientSocket, username}); } BroadcastMessage(username + " 加入了聊天室\n", clientSocket); while (true) { bytesReceived = recv(clientSocket, buffer, BUFFER_SIZE, 0); if (bytesReceived <= 0) break; string message(buffer, bytesReceived); if (message.find("/pm ") == 0) { size_t space = message.find(&#39; &#39;, 4); if (space != string::npos) { string targetUser = message.substr(4, space - 4); string privateMsg = message.substr(space + 1); lock_guard<mutex> lock(clientsMutex); for (const auto& client : clients) { if (client.username == targetUser) { send(client.socket, ("[私聊] " + username + ": " + privateMsg + "\n").c_str(), username.size() + privateMsg.size() + 10, 0); break; } } } } else { BroadcastMessage("[群聊] " + username + ": " + message + "\n", clientSocket); } } { lock_guard<mutex> lock(clientsMutex); auto it = remove_if(clients.begin(), clients.end(), [clientSocket](const ClientInfo& ci) { return ci.socket == clientSocket; }); clients.erase(it, clients.end()); } BroadcastMessage(username + " 离开了聊天室\n", clientSocket); closesocket(clientSocket); } int main() { WSADATA wsaData; WSAStartup(MAKEWORD(2, 2), &wsaData); SOCKET serverSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); sockaddr_in serverAddr; serverAddr.sin_family = AF_INET; serverAddr.sin_port = htons(PORT); serverAddr.sin_addr.s_addr = INADDR_ANY; bind(serverSocket, (sockaddr*)&serverAddr, sizeof(serverAddr)); listen(serverSocket, SOMAXCONN); cout << "服务器已启动..." << endl; while (true) { SOCKET clientSocket = accept(serverSocket, nullptr, nullptr); if (clientSocket != INVALID_SOCKET) { thread(HandleClient, clientSocket).detach(); } } closesocket(serverSocket); WSACleanup(); return 0; } // Client.cpp #include <WinSock2.h> #include <ws2tcpip.h> #include <iostream> #include <string> #include <thread> #pragma comment(lib, "ws2_32.lib") using namespace std; const char* SERVER_IP = "127.0.0.1"; const int PORT = 12345; const int BUFFER_SIZE = 1024; SOCKET clientSocket; void ReceiveMessages() { char buffer[BUFFER_SIZE]; while (true) { int bytesReceived = recv(clientSocket, buffer, BUFFER_SIZE, 0); if (bytesReceived <= 0) { cout << "与服务器断开连接" << endl; exit(0); } cout << string(buffer, bytesReceived); } } int main() { WSADATA wsaData; WSAStartup(MAKEWORD(2, 2), &wsaData); clientSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); sockaddr_in serverAddr; serverAddr.sin_family = AF_INET; serverAddr.sin_port = htons(PORT); inet_pton(AF_INET, SERVER_IP, &serverAddr.sin_addr); if (connect(clientSocket, (sockaddr*)&serverAddr, sizeof(serverAddr)) == SOCKET_ERROR) { cerr << "连接失败" << endl; return 1; } cout << "请输入用户名: "; string username; getline(cin, username); send(clientSocket, username.c_str(), username.size(), 0); thread receiverThread(ReceiveMessages); receiverThread.detach(); while (true) { string input; getline(cin, input); if (input.empty()) continue; if (input == "/exit") { break; } else if (input.find("/pm ") == 0) { send(clientSocket, input.c_str(), input.size(), 0); } else { send(clientSocket, input.c_str(), input.size(), 0); } } closesocket(clientSocket); WSACleanup(); return 0; } ``` 文件传输实现思路: 1. 客户端发送文件传输请求(包含文件名和大小) 2. 服务器转发请求给接收方 3. 接收方确认接收后开始传输 4. 使用二进制模式分块传输文件数据 5. 接收方保存文件到本地 相关问题:
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值