兄弟萌用虚拟机装上新系统或者租用云服务器之后,总想要能在PC和服务器之间传些东西。
一般说来,Filezilla等FTP工具就很好用了(甚至支持拖拽式上传下载)。但是,能写写代码当然更有意思。本文介绍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);
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 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()
std::string Socket_ClientGet(const std::string& address, const unsigned short& port) {
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);
closesocket(sock);
return std::string(szBuffer);
}
第三个是获取返回信息的Socket_Socket_ReadInfo()
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#include #include #include #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#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); 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四 区别和注意 区别#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.dll 或 ws2_32.dll,必须提前加载。DLL 有两种加载方式,请查看:动态链接库DLL的加载
Linux 使用“文件描述符”的概念,而 Windows 使用“文件句柄”的概念;Linux 不区分 socket 文件和普通文件,而 Windows 区分;Linux 下 socket() 函数的返回值为 int 类型,而 Windows 下为 SOCKET 类型,也就是句柄。
Linux 下使用 read() / write() 函数读写,而 Windows 下使用 recv() / send() 函数发送和接收。
关闭 socket 时,Linux 使用 close() 函数,而 Windows 使用 closesocket() 函数。
注意测试示例代码先运行服务器端,再运行客户端,文件路径用绝对路径,要保证对应的文件存在并且可读。



