关于如何用C++ Socket替代FTP工具这回小事

C++ Socket返回文件内容

兄弟萌用虚拟机装上新系统或者租用云服务器之后,总想要能在PC和服务器之间传些东西。


一般说来,FilezillaFTP工具就很好用了(甚至支持拖拽式上传下载)。但是,能写写代码当然更有意思。本文介绍Windows环境下的实现方式(Linux环境代码上的区别很小,文中会指出),提供全部的代码。

一、概览菜单

socket这玩意儿,就是服务端监听某个端口,根据客户端的请求返回对应的内容。这样说来,服务端需要解析客户端的请求,那就涉及到请求格式和格式解析,很麻烦嘛。不过如果我们固定返回某句话,或者某个文件,这样就简单了。而且有了完整的应答接口,后续再加上解析也是可以的。

经过简化,这个看似工程量很大的计划,就转化成实现下面三个函数啦。


1 SOCKET Socket_SetupSever(const std::string& address, const unsigned short& port);
2 std::string Socket_ClientGet(const std::string& address, const unsigned short& port);
3 std::string Socket_Socket_ReadInfo(const std::string& infopath, char mod);

三个函数按顺序依次是1 在服务器建立服务、2在客户端向服务端发起请求、3获取准备向客户端返回的文件内容。


那服务端就可以用下面的代码建立服务


服务端

    /* 建立端口监听服务 */
	SOCKET servSock = Socket_SetupSever("127.0.0.1",1234);
	/* 接收客户端请求 */
	SOCKADDR clntAddr;
	int nSize = sizeof(SOCKADDR);
	/* 准备通过clntSock向客户回传数据 */
	while (true) {
		SOCKET clntSock = accept(servSock, (SOCKADDR*)& clntAddr, &nSize);
		if (clntSock != 0) {
			/* 向客户端发送数据 */
			/* 这里设计成两个接口——简单字串和文件,类型由第二个参数确定 */
			std::string info = Socket_Socket_ReadInfo("info.txt",'f');
			send(clntSock, info.c_str(), info.size(), NULL);
			//关闭此位client的socket
			closesocket(clntSock);
		}
	}
	closesocket(servSock);

首先用Socket_SetupSever获取配置好的socket,这里在本地做实验,IP配置为127.0.0.1,端口号为1234,你也可以选其他可用的端口。
然后进入While循环中,用accpe()t持续监听是否有client访问本地的1234端口。
如果有客户成功连接(if (clntSock != 0)),那就用Socket_Socket_ReadInfo()获取准备返回的内容,用send()发回客户端。


客户端

客户端只需要用Socket_ClientGet()发起请求就行啦,这里把返回的内容输出到标准输出。

std::cout << Socket_ClientGet("127.0.0.1", 1234) << endl;

二、细品代码

话不多说,上菜。


第一个是建立端口监听服务的Socket_SetupSever,以主机IP和开放端口为参数,返回配置好的Socket。


/*
	Socket_SetupSever:建立端口监听服务
	param:
		address:服务器主机ip
		port:服务器准备监听的端口
		retval:根据参数配置好的socket
*/
SOCKET Socket_SetupSever(const std::string& address, const unsigned short& port)
{
	//创建套接字
	SOCKET servSock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
	//绑定套接字
	sockaddr_in sockAddr;
	memset(&sockAddr, 0, sizeof(sockAddr));  //每个字节都用0填充
	sockAddr.sin_family = PF_INET;  //使用IPv4地址
	sockAddr.sin_addr.s_addr = inet_addr(address.c_str());  //具体的IP地址
	sockAddr.sin_port = htons(port);  //端口
	bind(servSock, (SOCKADDR*)& sockAddr, sizeof(SOCKADDR));
	//进入监听状态
	listen(servSock, 20);

	return servSock;
}

第二个是在客户端发起请求的Socket_ClientGet()


/*
	Socket_ClientGet:发起客户请求
	param:
		address:目标服务器ip
		port:服务器提供回传服务的端口
		retval:服务器返回的信息
*/
std::string Socket_ClientGet(const std::string& address, const unsigned short& port) {
	/* 获取sccket */
	SOCKET sock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
	/* 配置请求信息 */
	sockaddr_in sockAddr;
	memset(&sockAddr, 0, sizeof(sockAddr));  //每个字节都用0填充
	sockAddr.sin_family = PF_INET;
	sockAddr.sin_addr.s_addr = inet_addr(address.c_str());
	sockAddr.sin_port = htons(port);
	/* 发起请求 */
	connect(sock, (SOCKADDR*)& sockAddr, sizeof(SOCKADDR));
	/* 接收服务器回传数据 */
	char szBuffer[MAXINT16] = { 0 };
	recv(sock, szBuffer, MAXINT16, NULL);
	/* 关闭socket */
	closesocket(sock);
	return std::string(szBuffer);
}

第三个是获取返回信息的Socket_Socket_ReadInfo()

/* 
	Socket_Socket_ReadInfo:获取向clinet回传的数据
	param:
		infopath:
		mod: 说明info的来源,默认为's',指明为'f'时,是文件路径
		retval:mod=='s'时返回infopath,mod=='f'时返回文件内容
*/
std::string Socket_Socket_ReadInfo(const std::string& infopath,char mod='s')
{
	if (mod == 's') {
		return infopath;
	}else {
		std::ifstream f(infopath);
		std::string text, te;
		while (getline(f, te)) {
			text.append(te);
		}
		return text;
	}
}

注意第一部分展示的服务端代码中有一句

std::string info = Socket_Socket_ReadInfo("info.txt",'f');

就是返回文本文件info.txt的内容。而如果写成

std::string info = Socket_Socket_ReadInfo("info.txt");

返回的就是值为"info.txt"的string类型对象。


三、可用代码

把上面介绍的三个函数放到头文件"socketpocket.h"中。


socketpocket.h

#pragma once
#define _WINSOCK_DEPRECATED_NO_WARNINGS
#include <string>
#include <iostream>
#include <fstream>
#include <winsock2.h>

#pragma comment (lib, "ws2_32.lib")  //加载 ws2_32.dll

SOCKET Socket_SetupSever(const std::string& address, const unsigned short& port);
std::string Socket_Socket_ReadInfo(const std::string& infopath, char mod);
std::string Socket_ClientGet(const std::string& address, const unsigned short& port);

服务端

#include <stdio.h>
#include "socketpocket.h"
using namespace std;
int main() {
	//初始化 DLL
	WSADATA wsaData;
	WSAStartup(MAKEWORD(2, 2), &wsaData);
	/* 建立端口监听服务 */
	SOCKET servSock = Socket_SetupSever("127.0.0.1",1234);
	/* 接收客户端请求 */
	SOCKADDR clntAddr;
	int nSize = sizeof(SOCKADDR);
	/* 准备通过clntSock向客户回传数据 */
	while (true) {
		SOCKET clntSock = accept(servSock, (SOCKADDR*)& clntAddr, &nSize);
		if (clntSock != 0) {
			/* 向客户端发送数据 */
			/* 这里设计成两个接口——简单字串和文件,类型由第二个参数确定 */
			std::string info = Socket_Socket_ReadInfo("info.txt",'f');
			send(clntSock, info.c_str(), info.size(), NULL);
			//关闭此位client的socket
			closesocket(clntSock);
		}
	}
	closesocket(servSock);
	//终止 DLL 的使用
	WSACleanup();
	return 0;
}

客户端

#include <iostream>
#include "socketpocket.h"
int main() {
	/* 动态链接库相关 */
	WSADATA wsaData;
	WSAStartup(MAKEWORD(2, 2), &wsaData);	      
	/* 发起连接,将返回信息输出到标准输出 */           
	std::cout << Socket_HttpGet("127.0.0.1", 1234) << endl;
	/* 停用动态链接库 */
	WSACleanup();
	system("pause");
	return 0;
}

四 区别和注意

区别

Windows 下的 socket 程序和 Linux 思路相同,但细节有所差别:
Windows 下的 socket 程序依赖 Winsock.dllws2_32.dll,必须提前加载。DLL 有两种加载方式,请查看:动态链接库DLL的加载

Linux 使用“文件描述符”的概念,而 Windows 使用“文件句柄”的概念;Linux 不区分 socket 文件和普通文件,而 Windows 区分;Linuxsocket() 函数的返回值为 int 类型,而 Windows 下为 SOCKET 类型,也就是句柄。

Linux 下使用 read() / write() 函数读写,而 Windows 下使用 recv() / send() 函数发送和接收。

关闭 socket 时,Linux 使用 close() 函数,而 Windows 使用 closesocket() 函数。

注意

测试示例代码先运行服务器端,再运行客户端,文件路径用绝对路径,要保证对应的文件存在并且可读。

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值