システム概要
このシステムは、中央サーバーをハブとするクライアント/サーバー型ファイル共有プラットフォームです。複数のクライアントがサーバーを経由してファイルのアップロード、ダウンロード、管理を行うことができます。社内ネットワークでのドキュメント共有やチームプロジェクトでのファイル配布などの用途を想定しています。
システム設計
graph LR
SubA[クライアントA] --> B[中央サーバー]
SubC[クライアントB] --> B
SubD[クライアントC] --> B
B --> E[(ファイルシステム)]
B --> F[(メタデータDB)]
システムは以下の主要コンポーネントで構成されています:
- 中央サーバー: クライアント接続管理、コマンド処理、ファイル転送制御
- ファイル管理システム: アップロードされたファイルの物理的保存
- メタデータ管理: ユーザー情報、ファイル情報、アクセス権限の管理
- クライアントアプリケーション: ユーザーインターフェースとファイル操作機能の提供
サーバー実装 (サーバープログラム)
#include <winsock2.h>
#include <iostream>
#include <unordered_map>
#include <thread>
#include <mutex>
#define DEFAULT_PORT 5555
#define TRANSFER_BLOCK 8192
#define MAX_CONCURRENT_USERS 50
using std::string;
using std::unordered_map;
std::mutex sessionLock;
struct SessionData {
SOCKET connection;
string loginName;
bool isAuthenticated;
};
struct FileRecord {
string name;
string uploader;
size_t fileSize;
std::time_t timestamp;
};
unordered_map<SOCKET, SessionData> activeSessions;
unordered_map<string, FileRecord> fileRegistry;
const string STORAGE_DIR = "ServerFiles/";
void manageClientConnection(SOCKET clientSock);
void interpretClientRequest(SOCKET sock, const string& request);
void transmitFileToClient(SOCKET sock, const string& fileName);
void saveFileFromClient(SOCKET sock, const string& fileName, size_t totalSize);
void notifyAllUsers(SOCKET excludedSocket = INVALID_SOCKET);
void recordEvent(const string& logMsg);
int main() {
WSADATA wsa;
if (WSAStartup(MAKEWORD(2, 2), &wsa)) {
std::cerr << "Winsock初期化エラー" << std::endl;
return 1;
}
_mkdir(STORAGE_DIR.c_str());
SOCKET mainSocket = socket(AF_INET, SOCK_STREAM, 0);
if (mainSocket == INVALID_SOCKET) {
std::cerr << "ソケット生成失敗" << std::endl;
WSACleanup();
return 1;
}
sockaddr_in addrConfig;
addrConfig.sin_family = AF_INET;
addrConfig.sin_addr.s_addr = INADDR_ANY;
addrConfig.sin_port = htons(DEFAULT_PORT);
if (bind(mainSocket, (sockaddr*)&addrConfig, sizeof(addrConfig)) == SOCKET_ERROR) {
std::cerr << "バインド失敗" << std::endl;
closesocket(mainSocket);
WSACleanup();
return 1;
}
if (listen(mainSocket, SOMAXCONN) == SOCKET_ERROR) {
std::cerr << "リスニング失敗" << std::endl;
closesocket(mainSocket);
WSACleanup();
return 1;
}
std::cout << "サーバーがポート " << DEFAULT_PORT << " で起動しました" << std::endl;
recordEvent("サーバー起動");
while (true) {
sockaddr_in clientAddr;
int addrLen = sizeof(clientAddr);
SOCKET clientSocket = accept(mainSocket, (sockaddr*)&clientAddr, &addrLen);
if (clientSocket == INVALID_SOCKET) {
std::cerr << "接続受付エラー" << std::endl;
continue;
}
char clientIP[INET_ADDRSTRLEN];
inet_ntop(AF_INET, &clientAddr.sin_addr, clientIP, INET_ADDRSTRLEN);
std::cout << "新規接続: " << clientIP << ":" << ntohs(clientAddr.sin_port) << std::endl;
std::thread clientHandler(manageClientConnection, clientSocket);
clientHandler.detach();
}
closesocket(mainSocket);
WSACleanup();
return 0;
}
void manageClientConnection(SOCKET clientSock) {
char inputBuffer[TRANSFER_BLOCK];
int bytesRead;
{
std::lock_guard<std::mutex> guard(sessionLock);
activeSessions[clientSock] = {clientSock, "", false};
}
string greeting = "ファイル転送サーバーへようこそ\n";
send(clientSock, greeting.c_str(), greeting.length(), 0);
while (true) {
bytesRead = recv(clientSock, inputBuffer, TRANSFER_BLOCK, 0);
if (bytesRead <= 0) break;
inputBuffer[bytesRead] = '\0';
string clientRequest(inputBuffer);
interpretClientRequest(clientSock, clientRequest);
}
{
std::lock_guard<std::mutex> guard(sessionLock);
string userName = activeSessions[clientSock].loginName;
activeSessions.erase(clientSock);
std::cout << "接続切断: " << userName << std::endl;
recordEvent(userName + " が切断しました");
}
closesocket(clientSock);
}
void interpretClientRequest(SOCKET sock, const string& request) {
std::lock_guard<std::mutex> guard(sessionLock);
string command = request.substr(0, request.find(' '));
string parameters = request.substr(request.find(' ') + 1);
if (command == "AUTH") {
size_t sep = parameters.find(' ');
string userId = parameters.substr(0, sep);
string passcode = parameters.substr(sep + 1);
activeSessions[sock].loginName = userId;
activeSessions[sock].isAuthenticated = true;
string reply = "認証成功 " + userId + "\n";
send(sock, reply.c_str(), reply.length(), 0);
std::cout << "ユーザー認証: " << userId << std::endl;
recordEvent(userId + " がログインしました");
notifyAllUsers(sock);
}
else if (command == "DIR") {
if (!activeSessions[sock].isAuthenticated) {
send(sock, "認証が必要です\n", 18, 0);
return;
}
string listing = "サーバーファイル一覧:\n";
for (const auto& entry : fileRegistry) {
listing += entry.first + " (" + std::to_string(entry.second.fileSize) +
" bytes) - " + entry.second.uploader + "\n";
}
send(sock, listing.c_str(), listing.length(), 0);
}
else if (command == "PUT") {
if (!activeSessions[sock].isAuthenticated) {
send(sock, "認証が必要です\n", 18, 0);
return;
}
size_t spacePos = parameters.find(' ');
string fileName = parameters.substr(0, spacePos);
size_t fileBytes = std::stoul(parameters.substr(spacePos + 1));
FileRecord newFile;
newFile.name = fileName;
newFile.uploader = activeSessions[sock].loginName;
newFile.fileSize = fileBytes;
newFile.timestamp = std::time(nullptr);
fileRegistry[fileName] = newFile;
string ack = "受信準備完了 " + fileName + "\n";
send(sock, ack.c_str(), ack.length(), 0);
saveFileFromClient(sock, fileName, fileBytes);
notifyAllUsers();
recordEvent(activeSessions[sock].loginName + " が " + fileName + " をアップロード");
}
else if (command == "GET") {
if (!activeSessions[sock].isAuthenticated) {
send(sock, "認証が必要です\n", 18, 0);
return;
}
string targetFile = parameters;
if (fileRegistry.find(targetFile) != fileRegistry.end()) {
string fileHeader = "SENDING " + targetFile + " " +
std::to_string(fileRegistry[targetFile].fileSize) + "\n";
send(sock, fileHeader.c_str(), fileHeader.length(), 0);
transmitFileToClient(sock, targetFile);
recordEvent(activeSessions[sock].loginName + " が " + targetFile + " をダウンロード");
} else {
string errMsg = "ファイルが見つかりません\n";
send(sock, errMsg.c_str(), errMsg.length(), 0);
}
}
else if (command == "REMOVE") {
if (!activeSessions[sock].isAuthenticated) {
send(sock, "認証が必要です\n", 18, 0);
return;
}
string targetFile = parameters;
if (fileRegistry.find(targetFile) != fileRegistry.end() &&
fileRegistry[targetFile].uploader == activeSessions[sock].loginName) {
fileRegistry.erase(targetFile);
string filePath = STORAGE_DIR + targetFile;
std::remove(filePath.c_str());
string confirm = "削除完了\n";
send(sock, confirm.c_str(), confirm.length(), 0);
notifyAllUsers();
recordEvent(activeSessions[sock].loginName + " が " + targetFile + " を削除");
} else {
string deny = "権限がありません\n";
send(sock, deny.c_str(), deny.length(), 0);
}
}
else {
string unknown = "未定義のコマンドです\n";
send(sock, unknown.c_str(), unknown.length(), 0);
}
}
void transmitFileToClient(SOCKET sock, const string& fileName) {
string fullPath = STORAGE_DIR + fileName;
std::ifstream sourceFile(fullPath, std::ios::binary | std::ios::ate);
if (!sourceFile) {
std::cerr << "ファイルオープン失敗: " << fullPath << std::endl;
return;
}
std::streamsize remaining = sourceFile.tellg();
sourceFile.seekg(0, std::ios::beg);
char dataBlock[TRANSFER_BLOCK];
while (remaining > 0) {
std::streamsize blockSize = (remaining < TRANSFER_BLOCK) ? remaining : TRANSFER_BLOCK;
sourceFile.read(dataBlock, blockSize);
send(sock, dataBlock, static_cast<int>(blockSize), 0);
remaining -= blockSize;
}
sourceFile.close();
}
void saveFileFromClient(SOCKET sock, const string& fileName, size_t totalSize) {
string destinationPath = STORAGE_DIR + fileName;
std::ofstream destFile(destinationPath, std::ios::binary);
if (!destFile) {
std::cerr << "ファイル作成失敗: " << destinationPath << std::endl;
return;
}
size_t bytesRemaining = totalSize;
char dataBlock[TRANSFER_BLOCK];
while (bytesRemaining > 0) {
int blockSize = (bytesRemaining < TRANSFER_BLOCK) ? static_cast<int>(bytesRemaining) : TRANSFER_BLOCK;
int received = recv(sock, dataBlock, blockSize, 0);
if (received <= 0) break;
destFile.write(dataBlock, received);
bytesRemaining -= received;
}
destFile.close();
}
void notifyAllUsers(SOCKET excludedSocket) {
string updateMsg = "LIST_UPDATE\n";
for (const auto& entry : fileRegistry) {
updateMsg += entry.first + "|" + entry.second.uploader + "|" +
std::to_string(entry.second.fileSize) + "|" +
std::to_string(entry.second.timestamp) + "\n";
}
for (const auto& session : activeSessions) {
if (session.first != excludedSocket && session.second.isAuthenticated) {
send(session.first, updateMsg.c_str(), updateMsg.length(), 0);
}
}
}
void recordEvent(const string& logMsg) {
std::ofstream logStream("operation.log", std::ios::app);
if (logStream) {
std::time_t now = std::time(nullptr);
char* timeStr = std::ctime(&now);
logStream << "[" << timeStr << "] " << logMsg << std::endl;
logStream.close();
}
}
クライアント実装 (クライアントプログラム)
#include <winsock2.h>
#include <windows.h>
#include <commctrl.h>
#include <string>
#include <thread>
#define SERVER_PORT 5555
#define DATA_CHUNK 8192
class FileTransferClient : public CDialog {
public:
FileTransferClient(CWnd* parent = nullptr);
enum { IDD = IDD_CLIENT_INTERFACE };
protected:
virtual BOOL OnInitDialog();
virtual void DoDataExchange(CDataExchange* pDX);
DECLARE_MESSAGE_MAP()
private:
CEdit m_txtServerAddr;
CEdit m_txtUserID;
CEdit m_txtPasscode;
CButton m_btnConnect;
CButton m_btnDisconnect;
CListBox m_lstFiles;
CEdit m_txtLocalFile;
CButton m_btnSelectFile;
CButton m_btnSendFile;
CButton m_btnFetchFile;
CButton m_btnRemoveFile;
CProgressCtrl m_prgTransfer;
CStatic m_lblStatus;
SOCKET m_connectionSocket;
bool m_isConnected;
string m_currentUser;
std::thread m_receiverThread;
void establishConnection();
void terminateConnection();
void sendServerCommand(const string& cmd);
void requestFileListing();
void initiateFileUpload(const string& filePath);
void initiateFileDownload(const string& fileName);
void initiateFileRemoval(const string& fileName);
void refreshInterface(bool connected);
void updateFileDisplay(const string& fileData);
void updateTransferPercentage(int percent);
void showStatusMessage(const string& msg);
static DWORD WINAPI backgroundReceiver(LPVOID context);
void launchReceiver();
void haltReceiver();
};
BEGIN_MESSAGE_MAP(FileTransferClient, CDialog)
ON_BN_CLICKED(IDC_CONNECT, &FileTransferClient::OnConnectClicked)
ON_BN_CLICKED(IDC_DISCONNECT, &FileTransferClient::OnDisconnectClicked)
ON_BN_CLICKED(IDC_SELECT_FILE, &FileTransferClient::OnFileSelectClicked)
ON_BN_CLICKED(IDC_UPLOAD_FILE, &FileTransferClient::OnUploadClicked)
ON_BN_CLICKED(IDC_DOWNLOAD_FILE, &FileTransferClient::OnDownloadClicked)
ON_BN_CLICKED(IDC_DELETE_FILE, &FileTransferClient::OnDeleteClicked)
ON_LBN_DBLCLK(IDC_FILE_LIST, &FileTransferClient::OnFileDoubleClick)
END_MESSAGE_MAP()
FileTransferClient::FileTransferClient(CWnd* parent)
: CDialog(IDD_CLIENT_INTERFACE, parent), m_connectionSocket(INVALID_SOCKET), m_isConnected(false) {
}
BOOL FileTransferClient::OnInitDialog() {
CDialog::OnInitDialog();
m_txtServerAddr.SetWindowText(_T("localhost"));
m_txtUserID.SetWindowText(_T("testuser"));
m_txtPasscode.SetWindowText(_T("testpass"));
m_txtLocalFile.SetWindowText(_T("C:\\example.txt"));
m_btnDisconnect.EnableWindow(FALSE);
m_btnSendFile.EnableWindow(FALSE);
m_btnFetchFile.EnableWindow(FALSE);
m_btnRemoveFile.EnableWindow(FALSE);
m_prgTransfer.SetRange(0, 100);
m_prgTransfer.SetPos(0);
WSADATA wsa;
if (WSAStartup(MAKEWORD(2, 2), &wsa)) {
MessageBox(_T("ネットワーク初期化エラー"), _T("エラー"), MB_ICONERROR);
return FALSE;
}
return TRUE;
}
void FileTransferClient::establishConnection() {
CString serverAddr, userID, passcode;
m_txtServerAddr.GetWindowText(serverAddr);
m_txtUserID.GetWindowText(userID);
m_txtPasscode.GetWindowText(passcode);
m_connectionSocket = socket(AF_INET, SOCK_STREAM, 0);
if (m_connectionSocket == INVALID_SOCKET) {
MessageBox(_T("ソケット作成失敗"), _T("エラー"), MB_ICONERROR);
return;
}
sockaddr_in serverInfo;
serverInfo.sin_family = AF_INET;
serverInfo.sin_port = htons(SERVER_PORT);
InetPton(AF_INET, CT2A(serverAddr), &serverInfo.sin_addr);
if (connect(m_connectionSocket, (sockaddr*)&serverInfo, sizeof(serverInfo)) == SOCKET_ERROR) {
MessageBox(_T("サーバー接続失敗"), _T("エラー"), MB_ICONERROR);
closesocket(m_connectionSocket);
m_connectionSocket = INVALID_SOCKET;
return;
}
string authCmd = "AUTH " + string(CT2A(userID)) + " " + string(CT2A(passcode)) + "\n";
send(m_connectionSocket, authCmd.c_str(), authCmd.length(), 0);
launchReceiver();
m_isConnected = true;
m_currentUser = CT2A(userID);
refreshInterface(true);
showStatusMessage("サーバーに接続しました");
}
void FileTransferClient::sendServerCommand(const string& cmd) {
if (m_isConnected) {
send(m_connectionSocket, cmd.c_str(), cmd.length(), 0);
}
}
void FileTransferClient::initiateFileUpload(const string& filePath) {
std::ifstream localFile(filePath, std::ios::binary | std::ios::ate);
if (!localFile) {
MessageBox(_T("ファイルを開けません"), _T("エラー"), MB_ICONERROR);
return;
}
std::streamsize fileLength = localFile.tellg();
localFile.seekg(0, std::ios::beg);
string fileName = filePath.substr(filePath.find_last_of("\\/") + 1);
string uploadCmd = "PUT " + fileName + " " + std::to_string(fileLength) + "\n";
sendServerCommand(uploadCmd);
char buffer[DATA_CHUNK];
while (fileLength > 0) {
std::streamsize chunk = (fileLength < DATA_CHUNK) ? fileLength : DATA_CHUNK;
localFile.read(buffer, chunk);
send(m_connectionSocket, buffer, static_cast<int>(chunk), 0);
fileLength -= chunk;
int progressPercent = static_cast<int>(100 - (100.0 * fileLength / localFile.tellg()));
updateTransferPercentage(progressPercent);
}
localFile.close();
showStatusMessage("アップロード完了: " + fileName);
}
DWORD WINAPI FileTransferClient::backgroundReceiver(LPVOID context) {
FileTransferClient* client = (FileTransferClient*)context;
char receiveBuffer[DATA_CHUNK];
while (client->m_isConnected) {
int received = recv(client->m_connectionSocket, receiveBuffer, DATA_CHUNK, 0);
if (received <= 0) {
client->terminateConnection();
break;
}
receiveBuffer[received] = '\0';
string response(receiveBuffer);
if (response.find("SENDING ") == 0) {
size_t firstSpace = response.find(' ');
size_t secondSpace = response.find(' ', firstSpace + 1);
string incomingFile = response.substr(firstSpace + 1, secondSpace - firstSpace - 1);
long fileBytes = std::stol(response.substr(secondSpace + 1));
CFileDialog saveDialog(FALSE, NULL, CA2T(incomingFile.c_str()),
OFN_HIDEREADONLY | OFN_OVERWRITEPROMPT,
_T("すべてのファイル (*.*)|*.*||"), client);
if (saveDialog.DoModal() == IDOK) {
CString saveLocation = saveDialog.GetPathName();
std::ofstream outputFile(CT2A(saveLocation), std::ios::binary);
if (outputFile) {
long remaining = fileBytes;
while (remaining > 0) {
int chunkSize = (remaining < DATA_CHUNK) ? remaining : DATA_CHUNK;
int bytesRead = recv(client->m_connectionSocket, receiveBuffer, chunkSize, 0);
if (bytesRead <= 0) break;
outputFile.write(receiveBuffer, bytesRead);
remaining -= bytesRead;
int progress = static_cast<int>(100 - (100.0 * remaining / fileBytes));
client->updateTransferPercentage(progress);
}
outputFile.close();
client->showStatusMessage("ダウンロード完了: " + CT2A(saveLocation));
}
}
} else {
client->updateFileDisplay(response);
}
}
return 0;
}
void FileTransferClient::launchReceiver() {
m_receiverThread = std::thread(backgroundReceiver, this);
}
void FileTransferClient::OnConnectClicked() {
establishConnection();
requestFileListing();
}
void FileTransferClient::OnUploadClicked() {
CString localPath;
m_txtLocalFile.GetWindowText(localPath);
initiateFileUpload(CT2A(localPath));
}
void FileTransferClient::OnDownloadClicked() {
int selectedIndex = m_lstFiles.GetCurSel();
if (selectedIndex != LB_ERR) {
CString fileName;
m_lstFiles.GetText(selectedIndex, fileName);
initiateFileDownload(CT2A(fileName));
}
}