最近计算机网络课需要我们做一个图片传输程序,在客户端上传输图片至服务器,要求服务器能过滤文件并展示客户端上传的图片,我带着亲爱的”C++”,开着”百度”,开始畅游”网络”,并在这里写下总结。
TCP与UDP都是OSI(Open System Interconnection 开放式系统互联)参考模型下第四层——传输层协议,在这里总结一下它们的特点。
TCP
TCP(Transmission Control Protocol 传输控制协议),是面向连接的。交互双方都建立一个流式套接字,服务器需要等待客户端连接,一旦连接上就立即返回一个新的套接字描述符,通过描述符调用数据传输函数与客户端进行数据的收发。
- 优点:可靠、稳定。
- 缺点:速度慢、效率低、占用系统资源多、易被攻击。
- 适合场景:网络通讯质量要求高的地方。
UDP
UDP(User Datagram Protocol 用户数据报协议),是面向无连接的。双方建立的都是数据报套接字,服务器与客户端在进行传描数据之前不需要进行连接的申请和建立,可随时向对方发送消息。
- 优点:速度快、比TCP稍安全
- 缺点:不可靠,不稳定
- 使用场合:网络通讯质量要求不高、速度快的场所。
TCP/IP三次握手
这里有一段生动地描述,我摘抄如下:
主机A向主机B发出连接请求数据包:”我想给你发数据,可以吗?”,这是第一次对话;主机B向主机A发送同意连接和要求同步(同步就是两台主机一个在发送,一个在接收,协调工作)的数据包:”可以,你什么时候发?”,这是第二次对话;主机A再发出一个数据包确认主机B的要求同步:”我现在就发,你接着吧!”,这是第三次对话。三次”对话”的目的是使数据包的发送和接收同步,经过三次”对话”之后,主机A才向主机B正式发送数据。
摘自百度百科TCP/UDP协议。经过这样三次对话,TCP才能建立稳定、可靠的连接,保证发送数据的正确性。
C++实现TCP/IP三次握手,并在传输文件后关闭连接
运行环境: VS 2017
服务器端
#include "pch.h"
#include <iostream>
#include <WinSock2.h>
#include <string>
#include <fstream>
#define _WINSOCK_DEPRECATED_NO_WARNINGS
#pragma comment(lib, "ws2_32.lib") // 导入动态链接库
SOCKET sockSrv;
void RecvFile();
int main(int argc, CHAR* argv[])
{
WSADATA wsaData;
int port = 8800;
// 加载套接字
if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)
{
std::cout<<"加载套接字失败: "<< WSAGetLastError()<<"......\n";
return 1;
}
sockSrv = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); // TCP/IP协议
// 初始化IP和端口信息
SOCKADDR_IN addrSrv;
addrSrv.sin_family = AF_INET;
addrSrv.sin_port = htons(port); // 1024以上的端口号
addrSrv.sin_addr.S_un.S_addr = INADDR_ANY;
// bind()
if (bind(sockSrv, (LPSOCKADDR)&addrSrv, sizeof(SOCKADDR_IN)) == SOCKET_ERROR)
{
std::cout<<"套接字绑定失败:"<< WSAGetLastError()<<"......\n";
return 1;
}
// listen()
if (listen(sockSrv, 1) == SOCKET_ERROR)
{
std::cout<<"套接字监听失败:"<<WSAGetLastError()<<"......\n";
return 1;
}
// 客户端信息
SOCKADDR_IN addrClient;
int len = sizeof(SOCKADDR);
// 开始监听
std::cout<<"服务端启动成功......开始监听...\n";
while (1)
{
// 等待客户请求到来
sockSrv = accept(sockSrv, (SOCKADDR *)&addrClient, &len);
if (sockSrv == SOCKET_ERROR) {
std::cout<<"建立连接失败:"<<WSAGetLastError()<<"......\n";
break;
}
std::cout<<"与客户端建立连接......IP:"<<inet_ntoa(addrClient.sin_addr)<<std::endl;
std::cout << "开始接受数据..." << std::endl;
RecvFile();
std::cout << "接受完毕" << std::endl;
// 关闭套接字
closesocket(sockSrv);
}
WSACleanup();
system("pause");
return 0;
}
void RecvFile() {
const int bufferSize = 1024;
char buffer[bufferSize] = { 0 };
int readLen = 0;
std::string desFileName = "E:\\demo.jpg"; // 打开我的E盘的一个图片文件
std::ofstream desFile;
desFile.open(desFileName.c_str(), std::ios::binary); // 以二进制文件方式,不然图片会乱码
do {
readLen = recv(sockSrv, buffer, bufferSize, 0);
if (readLen == 0) // 循环接受文件,直到发送的文件为 EOF
{
break;
}
else
{
desFile.write(buffer, readLen);
}
} while (true);
desFile.close(); // 莫忘关闭文件
}
客户端
#include "pch.h"
#include <iostream>
#include <WinSock2.h>
#include <string>
#include <fstream>
#pragma comment(lib, "ws2_32.lib")
SOCKET sockClient;
void SendFile() {
std::streamsize haveSend = 0;
const int bufferSize = 1024;
char buffer[bufferSize] = {0};
std::streamsize readLen=0;
std::string srcFileName = "E:\\upload\\universe.jpg"; // 打开E盘的一个图片文件
std::ifstream srcFile;
srcFile.open(srcFileName.c_str(), std::ios::binary); // 以二进制文件打开
while (!srcFile.eof()) { // 当读到结尾时会多循环一次,以作为发送结束标志
srcFile.read(buffer, bufferSize);
readLen = srcFile.gcount();
send(sockClient, buffer, readLen, 0);
haveSend += readLen;
}
srcFile.close();
}
int main(int argc, CHAR* argv[])
{
WSADATA wsaData;
int port = 8800;
// 加载套接字
if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)
{
std::cout << "加载套接字失败:" << WSAGetLastError() << "......"<<std::endl;
system("pause");
return 1;
}
// 初始化IP和端口信息
SOCKADDR_IN addrSrv;
addrSrv.sin_family = AF_INET;
addrSrv.sin_port = htons(port);
addrSrv.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
// socket()
sockClient = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (SOCKET_ERROR == sockClient)
{
std::cout << "创建套接字失败:" << WSAGetLastError() << "......" << std::endl;
system("pause");
return 2;
}
// 向服务器发出连接请求
if (connect(sockClient, (struct sockaddr*)&addrSrv, sizeof(addrSrv)) == INVALID_SOCKET)
{
std::cout << "连接服务器失败:" << WSAGetLastError() << "......" << std::endl;
system("pause");
return 3;
}
else
{
std::cout << "连接成功!\n\n" << std::endl;
std::cout << "开始发送" << std::endl;
SendFile();
std::cout << "\n发送成功!" << std::endl;
}
// 关闭套接字
closesocket(sockClient);
WSACleanup();
system("pause");
return 0;
}
考虑在发送图片文件之前,给服务器发送一些该文件的信息,如名称,大小等,这样服务器可以精确的保存图片文件,诸如此类。
参考资料:
windows环境下使用C++&Socket实现文件传输