TCP协议的定义:传输控制协议(TCP,Transmission Control Protocol)是一种面向连接的、可靠的、基于字节流的传输层通信协议。
一、TCP传输的流程
1.WSAStartup()
在应用程序当中调用Winsock API 函数,首先通过WSAStartup 函数完成对Winsock服务的初始化,因此需要调用WSAStartup 函数,使用Socket的程序在使用Socket之前必须调用WSAStartup函数。
WORD wVersionRequested; WSADATA wsaData; int err; wVersionRequested = MAKEWORD(2, 2); err = WSAStartup(wVersionRequested, &wsaData); if (err != 0) { printf("WSAStartup failed with error: %d\n", err); return 1; } if (LOBYTE(wsaData.wVersion) != 2 || HIBYTE(wsaData.wVersion) != 2) { printf("Could not find a usable version of Winsock.dll\n"); WSACleanup(); return 1; } else printf("The Winsock 2.2 dll was found okay\n");
2.socket()
套接字(socket)是一个抽象层,应用程序可以通过它发送或接收数据,可对其进行像对文件一样的打开、读写和关闭等操作。套接字允许应用程序将I/O插入到网络中,并与网络中的其他应用程序进行通信。网络套接字是IP地址与端口的组合。
int socket(int af, int type, int protocol);
1)af 为是 IP 地址类型,常用的有 AF_INET(IPv4 地址)和 AF_INET6(IPv6 地址);
2)type 为数据传输方式,常用的有 SOCK_STREAM(流格式套接字/面向连接的套接字) 和 SOCK_DGRAM ( 数据报套接字/无连接的套接字 );
3)protocol 表示传输协议,常用的有 IPPROTO_TCP 和 IPPTOTO_UDP,分别表示 TCP 传输协议和 UDP 传输协议,设为0时系统自动匹配对应传输协议;例如:
SOCKET sockclient = socket(AF_INET,SOCK_STREAM,0);
3.connect()
connect () 系统调用将套接字(socket)连接到 addr 指定的地址。
int connect(int socket, const struct sockaddr *addr, socklen_t addrlen);
1) socket 为连接所用的套接字;
2)addr 该地址为所要连接的socket的地址;
3)addrlen为指定 addr 的大小;
例如:
sockaddr_in service; service.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");//本地 service.sin_family = AF_INET;//IPV4 service.sin_port = htons(1234);//端口号 connect(sockclient,(sockaddr*)&service,sizeof(service));
4.send()
int send( SOCKET s, const char FAR *buf, int len, int flags );
1)s 为指定发送端套接字;
2)buf 为一个存放应用程序要发送数据的缓冲区;
3)len 为实际要发送的数据的字节数;
4)flags 为执行方式,一般置0;
例如:
char szbuf[1024]; send(sockclient,szbuf,sizeof(szbuf),0);
5.recv()
recv函数是指从TCP连接的另一端接收数据。
int recv( SOCKET s, char FAR *buf, int len, int flags );
1)s 为指定发送端套接字;
2)buf 为一个存放应用程序要接收数据的缓冲区;
3)len 为实际要发送的数据的字节数;
4)flags 为执行方式,一般置0;
例如:
char rebuf[4]= {0}; recv(sockclient,rebuf,sizeof(rebuf),0);
6.关闭...
closesocket(sockclient);
该函数是Windows系统中的一个重要函数,它用来关闭已经打开的socket,参数是一个已经创建好的socket描述符,该函数会将socket描述符标记为无效,并将相应的描述符从系统中移除。在程序完成之后,都应该使用closesocket函数关闭socket,以保证系统资源能够及时被释放。如果不关闭socket,将出现资源泄漏的情况,影响程序的正常运行。
WSACleanup();一旦程序结束需要停止Socket库的使用,需要调用WSACleanup函数,这一步和最开始的WSAStartup是对应的。
二、main代码
#include <QCoreApplication>
#include <winsock.h>
#include <iostream>
#include <sys/stat.h>
using namespace std;
#define Onepage 4096
//若使用VS,在此需引入ws2_32网络库
//qt则在.pro文件中加入
struct FILEHEADER{
char m_FileName[MAX_PATH];
long m_Filesize;
};
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
/*
* 1.WSAStartup()
* 2.socket()
* 3.connect()
* 4.send()
* 5.recv()
* 6.关闭...
*
*/
/*****************************1************************************/
WORD wVersionRequested;
WSADATA wsaData;
int err;
/* Use the MAKEWORD(lowbyte, highbyte) macro declared in Windef.h */
wVersionRequested = MAKEWORD(2, 2);
err = WSAStartup(wVersionRequested, &wsaData);
if (err != 0) {
/* Tell the user that we could not find a usable */
/* Winsock DLL. */
printf("WSAStartup failed with error: %d\n", err);
return 1;
}
/* Confirm that the WinSock DLL supports 2.2.*/
/* Note that if the DLL supports versions greater */
/* than 2.2 in addition to 2.2, it will still return */
/* 2.2 in wVersion since that is the version we */
/* requested. */
if (LOBYTE(wsaData.wVersion) != 2 || HIBYTE(wsaData.wVersion) != 2) {
/* Tell the user that we could not find a usable */
/* WinSock DLL. */
printf("Could not find a usable version of Winsock.dll\n");
WSACleanup();
return 1;
}
else
printf("The Winsock 2.2 dll was found okay\n");
/*****************************2************************************/
cout<<" this is client "<<endl;
SOCKET sockclient = socket(AF_INET,SOCK_STREAM,0);
if(INVALID_SOCKET == sockclient){
printf("sock erro\n");
}
/*****************************3************************************/
sockaddr_in service;
service.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
service.sin_family = AF_INET;
service.sin_port = htons(1234);
if(SOCKET_ERROR == connect(sockclient,(sockaddr*)&service,sizeof(service))){
printf("connect erro\n");
}
/*****************************传输文件******************************/
char szPath[MAX_PATH];
//1.获取文件的名、大小
cout<<"please input Path information: "<<endl;
cin>>szPath;
char *ptemp = szPath;
while(*++ptemp!='\0');
while(*--ptemp!='\\');
++ptemp;//从拖入地址中找到,目标文件的名字
struct stat s;
stat(szPath,&s);//获取目标文件大小
cout<<"File name: "<<*ptemp<<endl;
cout<<"File size: "<<s.st_size<<endl;
/*****************************4************************************/
//发送信息给服务器
//先向服务器发送要传输的文件名字、大小
FILEHEADER fh;
strcpy(fh.m_FileName,ptemp);
fh.m_Filesize = s.st_size;
send(sockclient,(char*)&fh,sizeof(fh),0);
/*****************************5************************************/
//接收服务器是否继续传输文件的回复
char szbuf[4]= {0}; //接收缓存区
char rebuf[Onepage];//文件读取缓存区
recv(sockclient,szbuf,sizeof(szbuf),0);
if(0 == strcmp(szbuf,"yes"))//如果回复的是接收("yes")
{
FILE* pf = fopen(szPath,"rb");//打开文件
while (1) {
//读文件内容并发送
size_t nread = fread(rebuf,sizeof(char),Onepage,pf);
if(nread > 0){
send(sockclient,rebuf,nread,0);
}
else{
break;
}
}
//关闭文件
fclose(pf);
}
/*****************************关闭*********************************/
closesocket(sockclient);
WSACleanup();
return a.exec();
}
引入所需ws2_32网络库(.pro文件)
方法一:QT为例,在.pro文件中引入所需库:
/****************************************************************************/
QT = core
CONFIG += c++17 cmdline
# You can make your code fail to compile if it uses deprecated APIs.
# In order to do so, uncomment the following line.
#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0
SOURCES += \
main.cpp
# Default rules for deployment.
qnx: target.path = /tmp/$${TARGET}/bin
else: unix:!android: target.path = /opt/$${TARGET}/bin
!isEmpty(target.path): INSTALLS += target
/****************************************************************************/
LIBS+=-lws2_32//在最后引入lws2_32库
方法二:以VS为例,在main函数前加入
#pragma comment(lib, "ws2_32.lib")