在《Qt实现客户端与服务器消息发送与文件传输》一文里Jungle用Qt和Qt封装的类实现了客户端与服务器之间的消息发送和文件传输。本文Jungle尝试用Windows编程实现客户端与服务器之间的文件传输。不过本文仅仅简单介绍如何实现socket套接字传输。不足之处请各位CSDNer指出。
借用百度百科:“WinSock2是连接系统和用户使用的软件之间用于交流的一个接口。Winsock2 SPI(Service Provider Interface)服务提供者接口建立在Windows开放系统架构WOSA(Windows Open System Architecture)之上,是Winsock系统组件提供的面向系统底层的编程接口。Winsock系统组件向上面向用户应用程序提供一个标准的API接口;向下在Winsock组件和Winsock服务提供者(比如TCP/IP协议栈)之间提供一个标准的SPI接口。”
简而言之,这是Windows提供的一个编程接口,用户可通过该接口访问系统底层。WinSock2.h头文件里包含了很多接口的定义,比如本小节要用到的socket:
#if INCL_WINSOCK_API_PROTOTYPES
WINSOCK_API_LINKAGE
SOCKET
WSAAPI
socket(
IN int af,
IN int type,
IN int protocol
);
#endif /* INCL_WINSOCK_API_PROTOTYPES */
typedef struct WSAData {
WORD wVersion;//Windows Sockets DLL期望调用者使用的Windows Sockets规范的版本
WORD wHighVersion;//这个DLL能够支持的Windows Sockets规范的最高版本
#ifdef _WIN64
unsigned short iMaxSockets;
unsigned short iMaxUdpDg;
char FAR * lpVendorInfo;
char szDescription[WSADESCRIPTION_LEN+1];//以null结尾的ASCII字符串,Windows Sockets DLL将对Windows Sockets实现的描述拷贝到这个字符串中,包括制造商标识
char szSystemStatus[WSASYS_STATUS_LEN+1];
#else
char szDescription[WSADESCRIPTION_LEN+1];
char szSystemStatus[WSASYS_STATUS_LEN+1];//以null结尾的ASCII字符串,Windows Sockets DLL把有关的状态或配置信息拷贝到该字符串中
unsigned short iMaxSockets;//单个进程能够打开的socket的最大数目
unsigned short iMaxUdpDg;//Windows Sockets应用程序能够发送或接收的最大的用户数据包协议(UDP)的数据包大小,以字节为单位
char FAR * lpVendorInfo;//指向销售商的数据结构的指针
#endif
} WSADATA, FAR * LPWSADATA;
#include
#include
#include
#include
#define PORT 8001 ///端口号
#define SERVER_IPADRESS "127.0.0.1" // 服务器IP地址,这里设为本机地址
#define BUFFER_SIZE 1024
#define FILE_NAME_MAX_SIZE 512
#pragma comment(lib, "WS2_32")
#include
#include
#include
#include
#define PORT 8001
#define SERVER_IPADRESS "127.0.0.1" // 192.168.1.6
#define BUFFER_SIZE 1024
#define FILE_NAME_MAX_SIZE 512
#pragma comment(lib, "WS2_32")
int main()
{
// 初始化socket dll
WSADATA wsaData;
// MAKEWORD
// 原型:#define MAKEWORD(a, b) ((WORD)(((BYTE)(((DWORD_PTR)(a)) & 0xff)) | ((WORD)((BYTE)(((DWORD_PTR)(b)) & 0xff))) << 8))
// 作用:将两个byte型的a和b合并为一个word型,高8为是b,低8位是a;
// 返回值:一个无符号的16位整形
WORD socketVersion = MAKEWORD(2, 0);
if(WSAStartup(socketVersion, &wsaData) != 0)
{
printf("Init socket dll error!");
exit(1);
}
// 创建socket
// AF_INET: IPv4 网络协议的套接字类型;
// SOCK_STREAM:提供面向连接的稳定数据传输,即TCP协议;
SOCKET c_Socket = socket(AF_INET, SOCK_STREAM, 0);
if (SOCKET_ERROR == c_Socket)
{
printf("Create Socket Error!");
system("pause");
exit(1);
}
// 指定服务端的地址
sockaddr_in server_addr;
server_addr.sin_family = AF_INET;
server_addr.sin_addr.S_un.S_addr = inet_addr(SERVER_IPADRESS);
server_addr.sin_port = htons(PORT);
// 建立连接
if (SOCKET_ERROR == connect(c_Socket, (LPSOCKADDR)&server_addr, sizeof(server_addr)))
{
printf("Can Not Connect To Server IP!\n");
system("pause");
exit(1);
}
// 输入文件名
char file_name[FILE_NAME_MAX_SIZE+1];
memset(file_name, 0, FILE_NAME_MAX_SIZE+1);
printf("Please Input File Name On Server: ");
scanf("%s", &file_name);
char buffer[BUFFER_SIZE];
memset(buffer, 0, BUFFER_SIZE);
strncpy(buffer, file_name, strlen(file_name)>BUFFER_SIZE ? BUFFER_SIZE:strlen(file_name));
// 向服务器发送文件名
if(send(c_Socket, buffer, BUFFER_SIZE, 0) < 0)
{
printf("Send File Name Failed\n");
system("pause");
exit(1);
}
// 打开文件,准备写入
FILE * fp = fopen(file_name, "wb"); //windows下是"wb",表示打开一个只写的二进制文件
if(NULL == fp)
{
printf("File: %s Can Not Open To Write\n", file_name);
system("pause");
exit(1);
}
else
{
// memset函数:
// 原型:memset(void *s,int ch,size_t n);
// 说明:将s所指向的某一块内存中的后n个字节的内容全部设置为ch指定的ASCII值
memset(buffer, 0, BUFFER_SIZE);
int length = 0;
while ((length = recv(c_Socket, buffer, BUFFER_SIZE, 0)) > 0)
{
if (fwrite(buffer, sizeof(char), length, fp) < length)
{
printf("File: %s Write Failed\n", file_name);
break;
}
memset(buffer, 0, BUFFER_SIZE);
}
printf("Receive File: %s From Server Successful!\n", file_name);
}
fclose(fp);
closesocket(c_Socket);
//释放winsock库
WSACleanup();
system("pause");
return 0;
}
#include
#include
#include
#include
#define PORT 8001
#define SERVER_IPADRESSADRESS "127.0.0.1"
#define BUFFER_SIZE 1024
#define FILE_NAME_MAX_SIZE 512
#pragma comment(lib, "WS2_32")
int main()
{
// 声明并初始化一个服务端(本地)的地址结构
sockaddr_in server_addr;
server_addr.sin_family = AF_INET;
server_addr.sin_addr.S_un.S_addr = INADDR_ANY;
server_addr.sin_port = htons(PORT);
// 初始化socket dll
WSADATA wsaData;
WORD socketVersion = MAKEWORD(2, 0);
if(WSAStartup(socketVersion, &wsaData) != 0)
{
printf("Init socket dll error!");
exit(1);
}
// 创建socket
SOCKET m_Socket = socket(AF_INET, SOCK_STREAM, 0);
if (SOCKET_ERROR == m_Socket)
{
printf("Create Socket Error!");
exit(1);
}
// 绑定socket和服务端(本地)地址
if (SOCKET_ERROR == bind(m_Socket, (LPSOCKADDR)&server_addr, sizeof(server_addr)))
{
printf("Server Bind Failed: %d", WSAGetLastError());
exit(1);
}
// 监听
if (SOCKET_ERROR == listen(m_Socket, 10))
{
printf("Server Listen Failed: %d", WSAGetLastError());
exit(1);
}
while(1)
{
printf("Listening To Client...\n");
sockaddr_in client_addr;
int client_addr_len = sizeof(client_addr);
SOCKET m_New_Socket = accept(m_Socket, (sockaddr *)&client_addr, &client_addr_len);
if (SOCKET_ERROR == m_New_Socket)
{
printf("Server Accept Failed: %d", WSAGetLastError());
break;
}
char buffer[BUFFER_SIZE];
memset(buffer, 0, BUFFER_SIZE);
if (recv(m_New_Socket, buffer, BUFFER_SIZE, 0) < 0)
{
printf("Server Receive Data Failed!");
break;
}
char file_name[FILE_NAME_MAX_SIZE+1];
memset(file_name, 0, FILE_NAME_MAX_SIZE+1);
strncpy(file_name, buffer, strlen(buffer)>FILE_NAME_MAX_SIZE ? FILE_NAME_MAX_SIZE:strlen(buffer));
printf("%s\n", file_name);
FILE * fp = fopen(file_name, "rb"); // windows下是"rb",表示打开一个只读的二进制文件
if (NULL == fp)
{
printf("File: %s Not Found\n", file_name);
}
else
{
memset(buffer, 0, BUFFER_SIZE);
int length = 0;
while ((length = fread(buffer, sizeof(char), BUFFER_SIZE, fp)) > 0)
{
if (send(m_New_Socket, buffer, length, 0) < 0)
{
printf("Send File: %s Failed\n", file_name);
break;
}
memset(buffer, 0, BUFFER_SIZE);
}
fclose(fp);
printf("File: %s Transfer Successful!\n", file_name);
}
closesocket(m_New_Socket);
}
closesocket(m_Socket);
// 释放winsock库
WSACleanup();
return 0;
}
先运行服务端程序,在运行客户端程序。在客户端控制台输入服务端工程文件夹下的文件名字,回车键,该文件传输到了客户端工程文件夹下。
按下回车键后,可以看到,客户端工程文件夹下接收到了服务端传送的文件: