自己动手完成一款简易P2P共享文件软件的制作(一)


本文实验测试部分可参考基于QT的一款P2P共享文件系统
源码包下载地址基于QT的一款P2P共享文件系统下载,想要免费获取可以私信我
Github地址


1. 前言

文章讲述一款简单P2P共享文件软件的制作,设计环境是mysql、windows与QT5.5。要实现的功能包括用户的注册与登录,用户共享文件的上传、删除与下载。同时也具有精确搜索的功能,用户可以通过搜索结果确定要下载的文件。本文会通过服务器的设计、客户端的设计以及服务端客户端通信协议三个方面进行详细的介绍。本人知识有限,文中可能会有一些纰漏,欢迎留言指正,如有疑问也欢迎留言。

2. 系统总体框架

在本文中,我们设计一款P2P共享文件系统,其P2P网络仿照类似Napster的中心化网络,如下图所示:
在这里插入图片描述
图中,5个客户机与一个服务器连接构成P2P网络,服务器中存放有所有客户机上传的共享文件的meta信息(包括文件的拥有者、文件名、文件在主机中的绝对路径、IP地址、下载端口号、文件大小、文件拥有者主机状态)。橙色线描述出客户机与服务器和客户机之间的互动,客户机1正在共享一个文件并上传其meta信息;客户机2向服务器发起查询,并提交了要查询文件的meta信息,然后服务器返回了查询结果,客户机2利用查询结果找到了文件的位置,并向客户机3发起下载请求,最后,客户机3接受了下载请求并开始传输文件。

3. 服务器设计

服务端要实现的功能主要是meta数据的管理,因此其界面不需要设计的有多复杂。服务器效果图如下:
在这里插入图片描述
界面主要包含四个部分,左上角空白区域用于显示消息,端口左边的输入框用于输入服务器的端口,左下角是一个服务器启动按钮,右边用于刷新当前在线用户,需要添加的代码如下:
建立tcp_server.h,添加如下代码

#ifndef TCP_SERVER_H
#define TCP_SERVER_H

#include <QMainWindow>
#include <QWidget>
#include <QListWidget>
#include <QLabel>
#include <QLineEdit>
#include <QPushButton>
#include <QGridLayout>
#include <QTreeWidgetItem>
#include <QString>

class MainWindow : public QMainWindow
{
    Q_OBJECT

public:
    MainWindow(QWidget *parent = 0);
    ~MainWindow();

private:
    QWidget *myWidget;
    QListWidget *ContentListWidget;
    QLabel *PortLabel;
    QLineEdit *PortLineEdit;
    QPushButton *CreateBtn;
    QTreeWidget *resourceTree;
    QGridLayout *mainLayout;

    int port;

};

#endif // TCP_SERVER_H

建立tcp_server.cpp文件,添加如下代码:

#include "tcp_server.h"
#include <QMessageBox>

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
{
    setWindowTitle("P2P Server");

    ContentListWidget = new QListWidget;

    myWidget = new QWidget;
    PortLabel = new QLabel("端口:");
    PortLineEdit = new QLineEdit;
    CreateBtn = new QPushButton("打开服务器");

    resourceTree = new QTreeWidget();
    resourceTree->setHeaderLabel("在线用户");
    resourceTree->clear();

    mainLayout = new QGridLayout();

    mainLayout->addWidget(ContentListWidget,0,0,1,2);
    mainLayout->addWidget(PortLabel,1,0);
    mainLayout->addWidget(PortLineEdit,1,1);
    mainLayout->addWidget(CreateBtn,2,0,1,2);
    mainLayout->addWidget(resourceTree,0,2,3,1);
    myWidget->setLayout(mainLayout);
    setCentralWidget(myWidget);

    port = 8010;
    PortLineEdit->setText(QString::number(port));
    
}

MainWindow::~MainWindow()
{
    delete PortLabel;
    delete PortLineEdit;
    delete CreateBtn;
    delete ContentListWidget;
    resourceTree->clear();
    delete resourceTree;
    delete mainLayout;
    delete myWidget;
}

为了能够实现不同主机之间的通信,我们使用了tcp可靠传输协议。在服务器这边,我们需要实现一个服务器实例,用于监听保存相应的tcp连接以及完成客户端的相应请求。tcp服务器代码如下:
建立tcp_socket_client.h,添加如下代码:

#ifndef TCP_CLIENT_SOCKET
#define TCP_CLIENT_SOCKET

#include <QTcpSocket>
#include <QObject>
#include <QString>

class Tcp_Client_Socket : public QTcpSocket
{
    Q_OBJECT
public:
    Tcp_Client_Socket(QObject *parent = 0);
    ~Tcp_Client_Socket();
signals:
    void Disconnected(int);
protected slots:
    void DataReceived();
    void slotDisconnected();
};

#endif // TCP_CLIENT_SOCKET

建立tcp_socket_client.cpp文件,添加如下代码:

#include "tcp_client_socket.h"
#include <QByteArray>

Tcp_Client_Socket::Tcp_Client_Socket(QObject *parent)
    :QTcpSocket(parent)
{
    connect(this,SIGNAL(readyRead()),this,SLOT(DataReceived()));
    connect(this,SIGNAL(disconnected()),this,SLOT(slotDisconnected()));
}

void Tcp_Client_Socket::DataReceived(){
    if(bytesAvailable() > 0) {
        //tcp连接有数据过来,需要做相应处理
    }
}

//发送断开连接信号
void Tcp_Client_Socket::slotDisconnected(){
    emit Disconnected(this->socketDescriptor());
}

Tcp_Client_Socket::~Tcp_Client_Socket(){

}

建立server.h,添加如下代码:

#ifndef SERVER
#define SERVER

#include <QTcpServer>
#include <QObject>
#include <QList>
#include <QString>
#include <QPair>

#include "tcp_client_socket.h"
#include "tcp_meta.h"//元数据类

class Server : public QTcpServer
{
    Q_OBJECT
public:
    Server(QObject *parent = 0, int port = 0);
    ~Server();

    QList<QPair<Tcp_Client_Socket*, QString> > tcp_client_socket_list;//用于保存tcp连接

    enum MsgKind{
        UpdateName = 0,
        UPDATEMETA = 1,
        UpdateMsg = 2,
        RemoveName = 3,
    };

signals:
    void UpdateServer(QString, int, Server::MsgKind);
private slots:
    QTcpSocket* find_socket(QString);
    void slotDisconnected(int);
protected:
    void incomingConnection(int socketDescriptor);

};

#endif // SERVER

建立server.cpp文件,添加如下代码:

#include "server.h"
#include <QPair>

Server::Server(QObject *parent, int port)
    :QTcpServer(parent)
{
    listen(QHostAddress::Any, port);
}

QTcpSocket* Server::find_socket(QString username){
    int i = 0;
    for(i = 0; i < tcp_client_socket_list.count(); ++i){
        if(tcp_client_socket_list.at(i).second == username){
            return tcp_client_socket_list.at(i).first;
        }
    }
    return NULL;
}

void Server::incomingConnection(int socketDescriptor){
    Tcp_Client_Socket *tcp_client_socket = new Tcp_Client_Socket(this);
    connect(tcp_client_socket,SIGNAL(Disconnected(int)),this,SLOT(slotDisconnected(int)));
  
    tcp_client_socket->setSocketDescriptor(socketDescriptor);
    QString name = "Unknown";
    QPair<Tcp_Client_Socket*, QString> pair(tcp_client_socket, name);
    tcp_client_socket_list.append(pair);
}

void Server::slotDisconnected(int descriptor){
    int i = 0;
    for(i = 0; i < tcp_client_socket_list.count(); ++i){
        QTcpSocket *item = tcp_client_socket_list.at(i).first;
        if(item->socketDescriptor() == descriptor){
            if(tcp_client_socket_list.at(i).second != "Unknown")
                emit UpdateServer("", i, RemoveName);
            tcp_client_socket_list.removeAt(i);
            break;
        }
    }
}

Server::~Server(){
    for(int i = 0; i < tcp_client_socket_list.count(); ++i){
        delete tcp_client_socket_list.at(i).first;
    }
}

我们为打开服务器按钮连接一个槽函数,用于实例化服务器,并且连接数据库用于增删改查meta元数据。相应文件需要包含QSqlDatabase与QSqlQuery两个头文件。
在tcp_server.h文件中添加如下代码:

#include "server.h"
...
public slots:
    void slotCreateServer();//打开服务器
    void UpdateServer(QString, int, Server::MsgKind);//更新服务器在线用户的显示列表

private:
	...
    Server *server;//服务器
    ...

在tcp_server.cpp中添加如下代码:

#include <QSqlDatabase>
#include <QSqlQuery>

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
{
    ...
	//将按钮点击事件与实例化服务器函数绑定
    connect(CreateBtn,SIGNAL(clicked()),this,SLOT(slotCreateServer()));

    server = 0;
}

void MainWindow::slotCreateServer(){
	//实例化服务器
    server = new Server(this, port);
    server->db = QSqlDatabase::addDatabase("QMYSQL");
    server->db.setHostName("localhost");
    server->db.setDatabaseName("ShareFile");
    server->db.setUserName("root");
    server->db.setPassword("root");
    //连接数据库
    if (!server->db.open()) {
        QMessageBox::critical(0, QObject::tr("无法打开数据库"),
        "无法创建数据库连接! ", QMessageBox::Cancel);
        return;
    }
	//将来自server的updateserver信号与UpdateServer函数绑定    
    connect(server,SIGNAL(UpdateServer(QString,int,Server::MsgKind)),this,SLOT(UpdateServer(QString,int,Server::MsgKind)));
    CreateBtn->setEnabled(false);
}

void MainWindow::UpdateServer(QString msg, int length, Server::MsgKind flag){
    switch (flag) {
    //普通消息,直接显示到content list组件中
    case Server::UpdateMsg:
    {
        ContentListWidget->addItem(msg.left(length));
        break;
    }
    //更新元数据消息
    case Server::UPDATEMETA:
    {
        ContentListWidget->addItem(msg + ": update meta");
        break;
    }
    //更新在线用户,将上线的用户显示到tree组件上
    case Server::UpdateName:
    {
        QTreeWidgetItem *item_name = new QTreeWidgetItem(resourceTree);
        item_name->setText(0,msg);
        break;
    }
    //用户离线消息,更新tree组件,删除相应的在线用户
    case Server::RemoveName:
    {
        QPoint p(length,0);
        resourceTree->removeItemWidget(resourceTree->itemAt(p),0);
        delete resourceTree->itemAt(p);
        break;
    }
    }
}

MainWindow::~MainWindow()
{
    ...
    //删除服务器,关闭数据库连接
    if(server != 0){
        server->db.close();
        delete server;
    }
}

到这,服务器的基本框架已经搭建完成,接着需要添加一些功能,例如数据库的增删改查。
先在server.h文件中添加功能函数的声明:

#include <QSqlDatabase>
#include <QSqlQuery>
#include "tcp_meta.h"//元数据类文件声明

class Server : public QTcpServer
{
    ...
    QSqlDatabase db;//建立连接的数据库
	...
signals:
    void UpdateServer(QString, int, Server::MsgKind);
private slots:
    ...
    void UpdateClients(QString, int);
    void UpdateUserName(QString);
    void UpdateUserInfo(QString);
    void UpdateMeta(QString);
    void ReturnMeta(QString);
    void DeleteMeta(QString);
    void SearchMeta(QString);
	...
};

在server.cpp文件中添加广播消息功能函数:

void Server::UpdateClients(QString msg, int length){
	//发送信号,使得服务器界面中可以显示更新消息
    emit UpdateServer(msg, length, UpdateMsg);
    //遍历所有保存的tcp连接,发送消息
    for(int i = 0; i < tcp_client_socket_list.count(); ++i){
        QTcpSocket *item = tcp_client_socket_list.at(i).first;
        format_packet fmsg(msg, OTHERKIND);//将消息打包
        if(item->write(fmsg.fmpak.toLatin1(),length + 8) != length){ //发送消息
            continue;
        }
    }
}

format_packet是我们自定义的一个包装类,之后会讲到,接下来添加检测登陆信息的函数:

void Server::UpdateUserName(QString msg){
    int i = 0;
    int pos = i;
    //登陆信息的格式为"username;password;"
    //解析消息msg
    while(msg.at(i++) != ';');
    QString username = msg.mid(pos, i - 1);
    pos = i;
    while(msg.at(i++) != ';');
    QString password = msg.mid(pos, i - pos - 1);
    //实例化query
    QSqlQuery query(db);
    //默认用户登陆时的tcp连接为最新一次建立的tcp连接(为了简单,但可能存在bug)
    QTcpSocket *item = tcp_client_socket_list.last().first;

	//从login表中根据用户名查找相应的用户的信息
    query.exec("select * from login where Username='" + username + "'");
    if(!query.next()){
        msg = "Server: login error, unknown user name.";
        format_packet fmsg(msg, ERRDCKIND);
        //通知对应用户登陆失败,没有此用户
        item->write(fmsg.fmpak.toLatin1(),fmsg.fmpak.length());
    }
    else{
    	//密码错误,通知用户
        if(query.value(2).toString() != password){
            msg = "Server: login error, password is wrong.";
            format_packet fmsg(msg, ERRDCKIND);
            item->write(fmsg.fmpak.toLatin1(),fmsg.fmpak.length());
        }
        //为最新建立的tcp连接设置用户名用于辨认
        else{
            tcp_client_socket_list.last().second = username;
            //发送信号,更新在线用户列表
            emit UpdateServer(username, username.length(), UpdateName);
        }
    }
}

其次,我们添加注册功能函数:

void Server::UpdateUserInfo(QString msg){
    int i = 0;
    int pos = i;
    //解析注册信息字符串,格式与登陆一样
    while(msg.at(i++) != ';');
    QString username = msg.mid(pos, i - 1);
    pos = i;
    while(msg.at(i++) != ';');
    QString password = msg.mid(pos, i - pos - 1);
    QSqlQuery query(db);
    QTcpSocket *item = tcp_client_socket_list.last().first;

    //在login表中查找用户名
    query.exec("select * from login where Username='" + username + "'");
    //存在此用户,通知注册失败
    if(query.next()){
        msg = "Server: sign error, user name exists.";
        format_packet fmsg(msg, SIGNFAILKIND);
        item->write(fmsg.fmpak.toLatin1(),fmsg.fmpak.length());
    }
    //更新login表,插入一条数据
    //插入失败,通知注册失败
    else{
        if(!query.exec("insert into login values(0, '" + username +
                   "', '" + password + "')")){
            msg = "Server: sign error";
            format_packet fmsg(msg, SIGNFAILKIND);
            item->write(fmsg.fmpak.toLatin1(),fmsg.fmpak.length());
        }
        //插入成功,通知注册成功
        else {
            msg = "Server: sign success";
            format_packet fmsg(msg, SIGNSCKIND);
            item->write(fmsg.fmpak.toLatin1(),fmsg.fmpak.length());
        }
    }
}

添加“增加共享文件”功能函数:

void Server::UpdateMeta(QString mt){
    QSqlQuery query(db);
    tcp_meta mt_t(mt);//元数据类
    //插入数据到resource表中
    QString query_str = "insert into Resource values(0, ";

    query_str = query_str + "'" + mt_t.filename + "', ";
    query_str = query_str + QString::number(mt_t.size) + ", ";
    query_str = query_str + "'" + mt_t.filepath + "', ";
    query_str = query_str + "'" + mt_t.owner + "', ";
    query_str = query_str + "'" + mt_t.ip + "', ";
    query_str = query_str + QString::number(mt_t.port) + ")";
    //插入一条元数据记录,也就是在服务器中添加了一个共享文件
    query.exec(query_str);
}

tcp_meta是我们设计用于表示共享文件元数据的类,之后我们会讲到。添加“删除共享文件”功能函数:

void Server::DeleteMeta(QString msg){
    int i = 0;
    int pos = i;
    QSqlQuery query(db);

	//删除文件信息格式为"username;filename;...;filename;"
    while(msg.at(i++) != ';');
    QString username = msg.mid(pos, i - 1);
    for(pos = i; i < msg.length(); ++i){
        if(msg.at(i) == ';'){
            QString filename = msg.mid(pos, i - pos);
            //删除对应记录
            query.exec("delete from Resource where FileName='" + filename
                       + "' AND Owner='" + username + "'");
            pos = i + 1;
        }
    }
}

用户通过发送“username;filename”可以接收到以filename命名的所有共享文件元数据信息。添加“搜索共享文件”功能函数:

void Server::SearchMeta(QString msg){
    int i = 0;
    int pos = i;
    //解析msg字符串
    while(msg.at(i++) != ';');
    QString username = msg.mid(pos, i - 1);
    pos = i;
    while(msg.at(i++) != ';');
    QString filename = msg.mid(pos, i - pos - 1);

    bool flag = false;//查找成功标志
    QSqlQuery query(db);
    QTcpSocket *item = find_socket(username);//找到username用户的socket连接
    //查找失败退出函数
    if(item == NULL){
        return;
    }

	//查找以filename命名的所有元数据
    query.exec("select * from Resource where FileName='" + filename + "'");
    //依次发送给username用户
    while(query.next())
    {
        tcp_meta mt;//元数据类
        mt.filename = query.value(1).toString();
        mt.size = query.value(2).toString().toLong();
        mt.filepath = query.value(3).toString();
        mt.owner = query.value(4).toString();
        mt.ip = query.value(5).toString();
        mt.port = query.value(6).toString().toLong();
        //若不存在mt.owner用户的socket连接,说明对方不在线
        if(find_socket(mt.owner) != NULL){
            mt.online = "on";
        }
        else{
            mt.online = "off";
        }
        QString msg = mt.toString();
        format_packet fmsg(msg, SRHKIND);
        //发送元数据信息
        while(item->write(fmsg.fmpak.toLatin1(),msg.length() + 8) != msg.length() + 8);
        //设置flag
        flag = true;
    }
    //若查找失败,发送空字符串
    if(flag == false){
        QString msg = "";
        format_packet fmsg(msg, SRHKIND);
        while(item->write(fmsg.fmpak.toLatin1(),msg.length() + 8) != msg.length() + 8);
    }
}

最后,添加获取自己所有共享文件信息的函数:

void Server::ReturnMeta(QString username){
    bool flag = false;
    QSqlQuery query(db);
    QTcpSocket *item = find_socket(username);
    if(item == NULL){
        return;
    }

	//查找所有owner为username的共享文件元数据
    query.exec("select * from Resource where Owner='" + username + "'");
    //将其依次发送给username用户
    while(query.next())
    {
        tcp_meta mt;
        mt.filename = query.value(1).toString();
        mt.size = query.value(2).toString().toLong();
        mt.filepath = query.value(3).toString();
        mt.owner = query.value(4).toString();
        mt.ip = query.value(5).toString();
        mt.port = query.value(6).toString().toLong();
        QString msg = mt.toString();
        format_packet fmsg(msg, RESKIND);
        while(item->write(fmsg.fmpak.toLatin1(),msg.length() + 8) != msg.length() + 8);
        flag = true;
    }
    //若查找失败或不存在相应文件发送空字符串
    if(flag == false){
        QString msg = "";
        format_packet fmsg(msg, RESKIND);
        while(item->write(fmsg.fmpak.toLatin1(),msg.length() + 8) != msg.length() + 8);
    }
}

至此,我们服务器这边的功能算是都设计好了。接下来,我们要做的是如何在服务器中调用这些函数。前面已经提到,我们为服务器与客户机之间设计了通信协议,这些协议通俗来说就是一个有规律的字符串。在服务器向客户机发送消息或数据时,我们定义了一个format_packet类用于打包服务器向客户机发送的数据,同样地,在客户机向服务器发送数据时也会有相应的函数打包消息。所以,我们在接收到socket连接发送来的数据时,需要安装协议将其解析,并调用相应的功能函数完成客户机的需求。
我们将服务器对客户机消息的解析过程放在了tcp_client_socket.cpp文件中,因此我们需要添加相应的代码到tcp_client_socket.h文件与tcp_client_socket.cpp文件中:
tcp_client_socket.h:

class Tcp_Client_Socket : public QTcpSocket
{
signals:
	...
    void UpdateClients(QString, int);
    void UpdateUserName(QString);
    void UpdateMeta(QString);
    void ReturnMeta(QString);
    void DeleteMeta(QString);
    void SearchMeta(QString);
    void UpdateUserInfo(QString);
	...
};

tcp_client_socket.cpp:

void Tcp_Client_Socket::DataReceived(){
    if(bytesAvailable() > 0) {
        char buf[1024];
        read(buf, 8);

		//解析头信息
        QString head = buf;
        long msglen = 0;
        QString kind;
        if(head == "error|||"){
            return;
        }
        else{
            msglen = head.mid(2,6).toLong();
            kind = head.mid(0,2);
            read(buf,msglen);//读具体消息内容
            buf[msglen] = 0;
        }

        QString msg = buf;//消息字符串
        //根据头信息中消息的kind种类,分别发送不同的信号
        if(kind == "NM"){ //name messsage
            QString logininfo = msg;
            emit UpdateUserName(logininfo);
        }
        else if(kind == "MM"){ //meta message
            emit UpdateMeta(msg);
        }
        else if(kind == "SM"){ //return share file message
            QString username = msg;
            emit ReturnMeta(username);
        }
        else if(kind == "DM"){ //delete share file message
            emit DeleteMeta(msg);
        }
        else if(kind == "SR"){ //search share file message
            emit SearchMeta(msg);
        }
        else if(kind == "DC"){ //disconnected message
        }
        else if(kind == "GM"){ //sign message
            emit UpdateUserInfo(msg);
        }
        else if(kind == "OT"){ //normal message
            emit UpdateClients(msg, msg.length());
        }
    }
}

通过上述代码,很明显我们使用信号与槽的方式调用这些功能函数,所以,我们还需要在tcp建立连接时将这些信号绑定到具体的槽中:
在server.cpp文件中修改incomingConnection函数:

void Server::incomingConnection(int socketDescriptor){
    Tcp_Client_Socket *tcp_client_socket = new Tcp_Client_Socket(this);
    connect(tcp_client_socket,SIGNAL(UpdateClients(QString,int)),this,SLOT(UpdateClients(QString,int)));
    connect(tcp_client_socket,SIGNAL(Disconnected(int)),this,SLOT(slotDisconnected(int)));
    connect(tcp_client_socket,SIGNAL(UpdateUserName(QString)),this,SLOT(UpdateUserName(QString)));
    connect(tcp_client_socket,SIGNAL(UpdateUserInfo(QString)),this,SLOT(UpdateUserInfo(QString)));
    connect(tcp_client_socket,SIGNAL(UpdateMeta(QString)),this,SLOT(UpdateMeta(QString)));
    connect(tcp_client_socket,SIGNAL(ReturnMeta(QString)),this,SLOT(ReturnMeta(QString)));
    connect(tcp_client_socket,SIGNAL(DeleteMeta(QString)),this,SLOT(DeleteMeta(QString)));
    connect(tcp_client_socket,SIGNAL(SearchMeta(QString)),this,SLOT(SearchMeta(QString)));
    tcp_client_socket->setSocketDescriptor(socketDescriptor);
    QString name = "Unknown";
    QPair<Tcp_Client_Socket*, QString> pair(tcp_client_socket, name);
    tcp_client_socket_list.append(pair);
}

除此之外,我们也给出format_packet类定义与实现:
新建format_packet.h文件,内容如下:

#ifndef FORMAT_PACKET
#define FORMAT_PACKET

#include <QString>

enum fkind{
    RESKIND = 0,//返回自己所有共享文件信息的消息
    SRHKIND = 1,//搜索结果消息
    ERRDCKIND = 2,//错误连接消息,一般表示登陆失败
    SIGNSCKIND = 3,//注册成功消息
    SIGNFAILKIND = 4,//注册失败消息
    OTHERKIND = 5,//普通消息
};

struct format_packet{
    format_packet(QString &str, fkind);
    QString fmpak;
};

#endif // FORMAT_PACKET

新建format_packet.cpp文件,内容如下:

#include "format_packet.h"

format_packet::format_packet(QString &str, fkind kind){
    long len = str.length();
    QString s = QString::number(len);//字符串长度
    if(len > 100000){
        str = "error|||";//表示错误消息的头字符串
        return;
    }
    //要求长度字符传长度为6,不够补零
    for(int i = s.length(); i < 6; ++i){
        s = "0" + s;
    }
    switch (kind) {
    case RESKIND:
        fmpak = "RK" + s + str;
        break;
    case SRHKIND:
        fmpak = "SR" + s + str;
        break;
    case ERRDCKIND:
        fmpak = "EK" + s + str;
        break;
    case SIGNSCKIND:
        fmpak = "SS" + s + str;
        break;
    case SIGNFAILKIND:
        fmpak = "SF" + s + str;
        break;
    case OTHERKIND:
        fmpak = "OT" + s + str;
        break;
    }
}

对于通信这部分,我们在此只是简单提及设计,其具体内容之后我们还会提到。现在服务器的设计已经完成,下一步完成客户机的设计。


自己动手完成一款简易P2P共享文件软件的制作(二)

  • 4
    点赞
  • 45
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
P2P文件共享是一种分布式文件共享模型,它允许不同计算机之间直接共享文件,而不需要通过中央服务器。在Java中,我们可以使用Java网络编程来实现P2P文件共享。 以下是一个简单的示例,演示了如何使用Java网络编程实现P2P文件共享: ```java import java.io.*; import java.net.*; public class P2PFileSharing { public static void main(String[] args) { // 客户端 new Thread(() -> { try { // 创建客户端Socket,连接到指定的服务器IP和端口 Socket clientSocket = new Socket("服务器IP", 8888); // 从服务器接收文件 InputStream inputStream = clientSocket.getInputStream(); FileOutputStream fileOutputStream = new FileOutputStream("接收的文件路径"); byte[] buffer = new byte[1024]; int bytesRead; while ((bytesRead = inputStream.read(buffer)) != -1) { fileOutputStream.write(buffer, 0, bytesRead); } fileOutputStream.close(); clientSocket.close(); } catch (IOException e) { e.printStackTrace(); } }).start(); // 服务器 new Thread(() -> { try { // 创建服务器Socket,监听指定的端口 ServerSocket serverSocket = new ServerSocket(8888); while (true) { // 等待客户端连接 Socket clientSocket = serverSocket.accept(); // 读取要共享的文件 File file = new File("要共享的文件路径"); FileInputStream fileInputStream = new FileInputStream(file); // 将文件发送给客户端 OutputStream outputStream = clientSocket.getOutputStream(); byte[] buffer = new byte[1024]; int bytesRead; while ((bytesRead = fileInputStream.read(buffer)) != -1) { outputStream.write(buffer, 0, bytesRead); } outputStream.close(); clientSocket.close(); } } catch (IOException e) { e.printStackTrace(); } }).start(); } } ``` 请注意,上述示例中的"服务器IP"和"要共享的文件路径"需要根据实际情况进行替换。
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值