TCP批量文件传输服务端与客户端(二)----客户端接收端

#ifndef _TCP_NET_SHARE_CLIENT_H__
#define _TCP_NET_SHARE_CLIENT_H__

#include <iostream>
#include <vector>
#include <map>
#include "sysfun.h"

#define MAX_BUFFER 65535
#define PACKSIZE 65000

#ifdef WIN32
#define OUT_CLASS
#ifdef OUT_CLASS  
#define OUT_EX_CLASS _declspec(dllexport)  
#else  
#define OUT_EX_CLASS _declspec(dllimport)  
#endif  
#endif

#ifdef WIN32
class OUT_EX_CLASS TCPNetShareClient 
{
#else
class TCPNetShareClient 
{
#endif

public:
	TCPNetShareClient();
	virtual ~TCPNetShareClient();
	
	//�ͻ��˶˳�ʼ��Socket
	lcmf_net_error InitWithPort(int port);

	//�ͻ������ӷ�����ip��˿�
	lcmf_net_error ConnectServer(const char* strIP, int nPort);

	//设置缓冲区大小
	int SetTmpBuffer(int nSize);

	int GetTmpBuffer();

	int RecvData(int *nSize, char** pData);
public:
	std::vector<std::map<int, char*>> m_arrData;
#ifdef WIN32
	SOCKET m_sock;
#else
	int m_sock;
#endif

	CThreadLock m_RecvLock;
	int m_nbufsize;
};

#ifdef WIN32
	OUT_EX_CLASS TCPNetShareClient* createNetUtil();
#else
	TCPNetShareClient* createNetUtil();
#endif

#endif
#include "TCPNetShareClient.h"

#ifdef WIN32
#pragma comment(lib, "ws2_32.lib")
#pragma comment(lib, "pthreadVC2.lib")
#endif

#define BEGIN_NUM 19900711  
#define DATA_NUM  20160113  
#define END_NUM   11700991  
#define BLOCK_DATA_SIZE (10 * 1024)  
#define FILE_HEAD  4  
#define BLOCK_HEAD 4  

using namespace std;


TCPNetShareClient* createNetUtil()
{
	return new TCPNetShareClient();
}

TCPNetShareClient::TCPNetShareClient():m_nbufsize(2000000)
{
	
}

TCPNetShareClient::~TCPNetShareClient()
{

}

//设置缓冲区大小
int TCPNetShareClient::SetTmpBuffer(int nSize)
{
	m_nbufsize = nSize;
	if(setsockopt(m_sock, SOL_SOCKET, SO_RCVBUF, &m_nbufsize, sizeof(int)) == -1)
	{
		return -1;
	}
	return m_nbufsize;
}

int TCPNetShareClient::GetTmpBuffer()
{
	return m_nbufsize;
}

//�ͻ��˶˳�ʼ��Socket
lcmf_net_error TCPNetShareClient::InitWithPort(int port)
{
	m_sock = socket(AF_INET, SOCK_STREAM, 0);
#ifdef WIN32
	if(m_sock == INVALID_SOCKET)
#else
	if (m_sock <= 0)
#endif
	{
		cout << "create socket error" << endl;
		return lcmf_net_error_createsocket;
	}
	sockaddr_in cliaddr;
	cliaddr.sin_family = AF_INET;
	cliaddr.sin_port = htons(port);
#ifdef WIN32
	cliaddr.sin_addr.S_un.S_addr = htonl(INADDR_ANY);
#else
	cliaddr.sin_addr.s_addr = htonl(INADDR_ANY);
#endif
	if (bind(m_sock, (struct sockaddr*)&cliaddr, sizeof(sockaddr)) < 0)
	{
		cout << "bind error port:" << port << endl;
		return lcmf_net_error_bindport;
	}
	cout << "bind success port:" << port << endl;

	sockaddr_in testaddr;
#ifdef WIN32
	int ntestsocklen = sizeof(sockaddr);
#else
	socklen_t ntestsocklen = sizeof(sockaddr);
#endif
	getsockname(m_sock, (struct sockaddr*)&testaddr, &ntestsocklen); 
	string stmp = inet_ntoa(testaddr.sin_addr);
	int nPort = testaddr.sin_port;
	cout << "port:" << nPort << "ip:" <<  stmp.c_str() << "--connect can use? 1" << endl;

	cout << "set no block" << endl;
	//int flags = fcntl(m_sock, F_GETFL, 0);        //获取文件的flags值。
      	//fcntl(m_sock, F_SETFL, flags | O_NONBLOCK);   //设置成非阻塞模式;	
	return lcmf_net_error_success;
}


#if 0
void RecvNetData(void* pArgs)  
{   
    TCPNetShareClient *pUtil = (TCPNetShareClient*)pArgs;
    if (pUtil != NULL)
    {
	    char *eachBuf = new char[BLOCK_DATA_SIZE + 2 * FILE_HEAD];  
	    memset(eachBuf, 0, BLOCK_DATA_SIZE + 2 * FILE_HEAD);
		unsigned int RecvNum = 0, flag_status = 0, flag_recv = 1;
		unsigned int dwFileSize = 0;  
	#if 0
	    FILE *fp;  
	    fp = fopen(FILENAME, "wb");  
	#endif

	    //1、读取第一组数据,获取文件大小,建立连接
niubiflag:
		cout << "recv header: " << endl;
	    recv(pUtil->m_sock, eachBuf, 2 * FILE_HEAD, 0);//----------------recv 
		cout << "recv header: " << eachBuf << "end" << endl; 
	    char charFileSize[4] = { 0 };  
	    memcpy(charFileSize, eachBuf, FILE_HEAD); //拷贝前4个字节  
	    for (int i = 0; i < 4; i++)  
	    {  
			flag_status += ((unsigned char)charFileSize[i]) << (8 * (4 - i - 1)); //获取文件起始符  
	    }  
	    memcpy(charFileSize, eachBuf + FILE_HEAD, FILE_HEAD); //拷贝第5-8个字节  
	    for (int i = 0; i < 4; i++)  
	    {  
			dwFileSize += ((unsigned char)charFileSize[i]) << (8 * (4 - i - 1)); //获取文件大小  
	    }  
	    int start = clock();
		cout << "start recv data, datasize: " << dwFileSize << endl;   
	    {  
			//开辟接收内存  
			int DataPos = 0;  
			char *FileBuffer = new char[dwFileSize];  
			memset(FileBuffer, 0, dwFileSize);
			cout << "malloc memery" << endl;
			while (1)  
			{  
				cout << "recv" << endl;
				int ret = recv(pUtil->m_sock, eachBuf, BLOCK_DATA_SIZE, 0);
				cout << "recv end" << endl; 
				if (ret <= 0 || ret == 8 || ret < BLOCK_DATA_SIZE)  
					break;
				cout << "Get Data Size: " << ret << endl;  
				memcpy(FileBuffer + DataPos, eachBuf, ret);  
				DataPos = DataPos + ret;
			}  
			//fwrite(FileBuffer, dwFileSize, 1, fp);  
			//fclose(fp);
			cout << "get data size----: " << DataPos << endl;
			std::map<int, char*> mapData;
			char* tmpData = (char*)malloc(dwFileSize);
			memcpy(tmpData, FileBuffer, dwFileSize);
			mapData.insert(std::pair<int, char*>(dwFileSize, FileBuffer));
			pUtil->m_RecvLock.Lock();
			pUtil->m_arrData.push_back(mapData);
			pUtil->m_RecvLock.UnLock();  
			goto niubiflag;
			delete eachBuf;
			delete FileBuffer;
	    }  
	    int end = clock();  
	    std::cout << "time:" << end - start << "ms" << RecvNum << std::endl;
     }  
}  

void* IdleNetRecvDataThread(void* args)
{
	TCPNetShareClient *pUtil = (TCPNetShareClient*)args;
	if (pUtil != NULL)
	{
#ifdef WIN32
		if(pUtil->m_sock != INVALID_SOCKET)
#else
		if (pUtil->m_sock >= 0)
#endif
		{
			while (true)
			{
				//RecvNetData(pUtil);
				unsigned char* lenbuf = (unsigned char*)malloc(14);
				int len = recv(pUtil->m_sock, (unsigned char*)lenbuf, 14, 0);
				if (len > 0)
				{
					if(lenbuf[0] == '0' && lenbuf[1] == '1' && lenbuf[2] == '1' && lenbuf[3] == 'A' && lenbuf[4] == 'D')
					{
						char tmpSize[10] = "0";
						memcpy(tmpSize, lenbuf + 5, 9);
						cout <<  "niubi class :" << tmpSize << endl;
						const int nSize = atoi(tmpSize);
						cout << "Get nSize ------------------: "<< nSize << endl;
						unsigned char* ptmpbuf = (unsigned char*)malloc(TCP_MAX_BUFFER);
						int len1 = recv(pUtil->m_sock, (unsigned char*)ptmpbuf, nSize, 0);
						if(len1 > 0)
						{
							std::map<int, char*> mapData;
							cout << "complete recv size: " << len1 << endl;
							cout << "get nsize: " << nSize << endl;
							{
								unsigned char* tmpData = (unsigned char*)malloc(nSize);
								memcpy(tmpData, ptmpbuf, nSize);
								mapData.insert(std::pair<int, char*>(nSize, (char*)tmpData));
								pUtil->m_RecvLock.Lock();
								pUtil->m_arrData.push_back(mapData);
								pUtil->m_RecvLock.UnLock();
							}
						}
					}
				}
			}
		}
	} 
	return NULL;
}
#endif

#define MXB 165000

void* IdleRecvDataThread(void* args)
{
	TCPNetShareClient *pUtil = (TCPNetShareClient*)args;
	if (pUtil != NULL)
	{
#ifdef WIN32
		if(pUtil->m_sock != INVALID_SOCKET)
#else
		if (pUtil->m_sock >= 0)
#endif
		{	
			time_t  timeStart = time(NULL);
			RECVDATA data;
			//data.pBuf = (char*)malloc(1024 * 10000000);
			BYTE* buffer = new BYTE[TCP_MAX_BUFFER];
			int m_recvCount = 0;
			int nSize = 0;
			int nIndex = 0;
			unsigned char* ptmpbuf = (unsigned char*)malloc(MXB);
			while (true)
			{
				memset(ptmpbuf, '\0', MXB);
				//cout << "wait data--" << endl;
				//int len = recv(pUtil->m_sock, (char*)ptmpbuf, TCP_MAX_BUFFER, 0);
				//int len = recv(pUtil->m_sock, (char*)ptmpbuf, MXB, MSG_DONTWAIT);
				int len = recv(pUtil->m_sock, (unsigned char*)ptmpbuf, MXB, 0);
				if (len > 0)
				{
					#if 0
					std::map<int, char*> mapData;
					char* tmpData = (char*)malloc(len);
					cout << "recv size:" << len << endl;
					memcpy(tmpData, buffer, len);
					mapData.insert(std::pair<int, char*>(len, tmpData));
					pUtil->m_RecvLock.Lock();
					pUtil->m_arrData.push_back(mapData);
					pUtil->m_RecvLock.UnLock();
					#endif
					
					cout << "recv Data**" << endl;

					//cout << ptmpbuf[0] << ptmpbuf[1] << ptmpbuf[2] << ptmpbuf[3] << endl;

					if(ptmpbuf[0] == '0' && ptmpbuf[1] == '1' && ptmpbuf[2] == '1' && ptmpbuf[3] == 'A' && ptmpbuf[4] == 'D')
					{
						cout << "get header---------------------------------------------------------------new --" << endl;
						if(m_recvCount != 0)
						{
							std::map<int, char*> mapData;
							cout << "complete recv size: " << m_recvCount << endl;
							cout << "get nsize: " << nSize << endl;
							if(nSize == m_recvCount)
							{
								unsigned char* tmpData = (unsigned char*)malloc(nSize);
								memset(tmpData, '\0', nSize);
								memcpy(tmpData, buffer, nSize);
								char strName[20]; 
								sprintf(strName, "./jpg/img-%d.jpg", nIndex++);
								FILE* fp = fopen(strName, "wb");
								if(fp != NULL)
								{
									if(fwrite(tmpData, 1, nSize, fp) < nSize)
									{
										cout << "write size error:" << nSize << endl;
									}
									fclose(fp);
								}
								mapData.insert(std::pair<int, char*>(nSize, (char*)tmpData));
								pUtil->m_RecvLock.Lock();
								pUtil->m_arrData.push_back(mapData);
								pUtil->m_RecvLock.UnLock();
								memset(buffer, '\0', TCP_MAX_BUFFER);
								m_recvCount = 0;
								nSize = 0;
							}
							else
							{
								cout << "this is a uhly pix" << endl;
								memset(buffer, '\0', TCP_MAX_BUFFER);
								m_recvCount = 0;
								nSize = 0;
							}
						}
						char tmpSize[10] = "0";
						memcpy(tmpSize, ptmpbuf + 5, 9);
						cout <<  "niubi class :" << tmpSize << endl;
						nSize = atoi(tmpSize);
						cout << "Get nSize ------------------: "<< nSize << endl;
						memset(buffer, '\0', TCP_MAX_BUFFER);
						memcpy(buffer + m_recvCount, ptmpbuf + 14, len - 14);
						cout << "usedata size=: " << len -14 << endl;
						m_recvCount += len;
						m_recvCount -= 14;
					}
					else
					{
						cout << "not header========" << len << endl;
						if(TCP_MAX_BUFFER < (m_recvCount + len))
						{
							m_recvCount = 0;
							nSize = 0;
							memset(buffer, '\0', TCP_MAX_BUFFER);
						}
						usleep(3000);
						memcpy(buffer + m_recvCount, ptmpbuf, len);
						m_recvCount += len;
						if(nSize < m_recvCount)
						{
							nSize = 0;
							m_recvCount = 0;
							memset(buffer, '\0', TCP_MAX_BUFFER);
						}
						usleep(3000);
					}
					
					//结束发送数据标志
					timeStart = time(NULL);
				}
			//	memset(ptmpbuf, '\0', MXB);
				
				if(len <= 0)
				{
					//cout << "get no data" << endl;
					time_t timeEnd = time(NULL);
					if(timeEnd - timeStart > 2)
					{
						if(nSize > 0 && nSize == m_recvCount)
						{	
							std::map<int, char*> mapData;
							cout << "complete recv size: " << m_recvCount << endl;
							cout << "get nsize: " << nSize << endl;
							char* tmpData = (char*)malloc(nSize);
							memcpy(tmpData, buffer, nSize);
							mapData.insert(std::pair<int, char*>(nSize, tmpData));
							pUtil->m_RecvLock.Lock();
							pUtil->m_arrData.push_back(mapData);
							pUtil->m_RecvLock.UnLock();
							memset(buffer, '\0', TCP_MAX_BUFFER);
							m_recvCount = 0;
							nSize = 0;
						}
					}
				}

				#if 0
				BYTE* buffer = new BYTE[MAX_BUFFER];
				int fsize = sizeof(sockaddr);
				int len = recv(pUtil->m_sock, (char*)buffer, MAX_BUFFER, 0);
				if (len == -1)
					m_recvCount = 1;

				NetData *pack;
				pack = (NetData*)buffer;
				int iCount = pack->nIndex;
				int iBufferSize = pack->nBufSize;
				bool bFinish = pack->bFinish;
				printf("%d--%d--%d--%d--%s\n", m_recvCount, iCount, iBufferSize, bFinish, "buffer");
				if (m_recvCount == iCount)
				{
					memcpy(data.pBuf + PACKSIZE*(m_recvCount - 1), pack->buffer, iBufferSize);
					m_recvCount += 1;
					if (bFinish)
					{
						m_recvCount = 1;
						data.nDataSize = pack->nTotalSize;
						std::map<int, char*> mapData;
						char* tmpData = (char*)malloc(data.nDataSize);
						memcpy(tmpData, data.pBuf, data.nDataSize);
						mapData.insert(std::pair<int, char*>(data.nDataSize, tmpData));
						pUtil->m_RecvLock.Lock();
						pUtil->m_arrData.push_back(mapData);
						pUtil->m_RecvLock.UnLock();
						printf("finish\n");
					}
				}
				else
				{
					m_recvCount = 1;
				}
#ifdef WIN32
				Sleep(1);
#else
				usleep(1);
#endif
				delete buffer;
				#endif
			}
			delete buffer;
			free(ptmpbuf);
		}
	}
}

//�ͻ������ӷ�����ip��˿�
lcmf_net_error TCPNetShareClient::ConnectServer(const char* strIP, int nPort)
{
#ifdef WIN32
	if(m_sock == INVALID_SOCKET)
#else
	if (m_sock <= 0)
#endif
	{
		cout << "invalid socket" << endl;
		return lcmf_net_error_createsocket;
	}

	cout << "valid socket" << endl;

	sockaddr_in servaddr;
	servaddr.sin_family = AF_INET;
	servaddr.sin_port = htons(nPort);
#ifdef WIN32
	servaddr.sin_addr.S_un.S_addr = inet_addr(strIP);
#else
	servaddr.sin_addr.s_addr = inet_addr(strIP);
#endif	
	string stmp = inet_ntoa(servaddr.sin_addr);
	int tmpPort = servaddr.sin_port;
	cout << "port:" << tmpPort << "ip:" <<  stmp.c_str() << "--connect can use? 1" << endl;
	int err = connect(m_sock, (struct sockaddr *)&servaddr, sizeof(sockaddr));
	if (err == 0)
	{
		cout << "connect server ip:" << strIP << "--port:" << nPort << "success" << endl;
		//return lcmf_net_error_success;
	}
	else
	{
		cout << "connect server ip:" << strIP << "--port:" << nPort << "failed" << endl;
		return lcmf_net_error_connect;
	}

#if 1
	//create thread to recvdata
	pthread_t pid;
	if (pthread_create(&pid, NULL, IdleRecvDataThread, this) != 0)
	{

		cout << "thread recv create failed" << endl;
		return lcmf_net_error_createthread;
	}
	else
	{
		pthread_detach(pid);
		cout << "thread recv create success" << endl;
		return lcmf_net_error_success;
	}
#endif

#if 0
	//create thread to recvdata
	pthread_t pid;
	if (pthread_create(&pid, NULL, IdleNetRecvDataThread, this) != 0)
	{
		cout << "thread recv create failed" << endl;
		return lcmf_net_error_createthread;
	}
	else
	{
		pthread_detach(pid);
		cout << "thread recv create success" << endl;
		return lcmf_net_error_success;
	}
#endif
}

int TCPNetShareClient::RecvData(int *nSize, char** pData)
{
#if 1
//	int i = 0;
	if (m_arrData.size() > 0)
	{
		std::vector<std::map<int, char*>>::iterator iterData = m_arrData.begin();
		{
			m_RecvLock.Lock();
			std::map<int, char*>::iterator iterMap = iterData->begin();
			#if 0
			*nSize = iterMap->first;
			*pData = (char*)malloc(*nSize); 
			cout << "--------size1:" << *nSize << endl; 
			memcpy(*pData, iterMap->second, *nSize);
			free(iterMap->second);
			cout << "size2:" << *nSize << endl; 
			m_arrData.erase(iterData);
			cout << "size3:" << *nSize << endl;
			#endif
			*nSize = iterMap->first;
			*pData = iterMap->second;
			//char strName[20]; 
			//sprintf(strName, "./jpg/img-%d.jpg", i++);
			//FILE* fp = fopen(strName, "wb");
			//if(fp != NULL)
		//	{
		//		fwrite(*pData, 1, *nSize, fp);
		//		fclose(fp);
		//	}
			m_arrData.erase(iterData);
			m_RecvLock.UnLock(); 
			return 1;
		}
	}
#endif
	return -1;
}

公共函数

#ifndef _SYS_FUN_H__
#define _SYS_FUN_H__

//#define WIN32

#ifdef WIN32
#include <windows.h>
#else
#include <stdlib.h>
#include <string.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <stdint.h>
#include <pthread.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <syslog.h>
#include <dirent.h>
#include <sys/time.h>
#include <sys/socket.h>
#include <stdio.h>
#include <stdlib.h>
#endif

#include <pthread.h>

//#define WIN32

#ifdef WIN32
#define nsleep  Sleep
#else
//#define BYTE unsigned char*  这真是世界第一大bug啊,接受文件总是出错,多加了×号,害得我找了好几天的问题
#define BYTE unsigned char
#define nsleep  usleep
#endif

#define MAX_BUFFER 65535
#define TCP_MAX_BUFFER 3400000
#define PACKSIZE 65000

typedef struct _RECVDATA {
	int nDataSize;
	int nDataIndex;
	char* pBuf;
}RECVDATA;

typedef struct _NetData {
	int nIndex;
	int nBufSize;
	bool bFinish;
	char buffer[PACKSIZE];
	int nTotalSize;
}NetData;

enum lcmf_net_error {
	lcmf_net_error_none,
	lcmf_net_error_createsocket,
	lcmf_net_error_bindport,
	lcmf_net_error_connect,
	lcmf_net_error_createthread,
	lcmf_net_error_success
};

class CThreadLock
{
public:
	CThreadLock() { pthread_mutex_init(&m_lock, NULL); }
	~CThreadLock() { pthread_mutex_destroy(&m_lock); }

public:
	void Lock() { pthread_mutex_lock(&m_lock); }
	void UnLock() { pthread_mutex_unlock(&m_lock); }

private:
	pthread_mutex_t m_lock;
};

#endif

Makefile

CC = g++ -std=c++11


cflags = -I. #./libevent/include
INC = -L. #libevent/lib


SRCS = TCPNetShareClient.cpp


C_FLAGS = -lpthread #-levent_core -levent_extra -levent_openssl -levent_pthreads
# -lopencv_imgcodecs

LIBRARY = libmultinetutil.so

all:
	$(CC) $(SRCS) $(C_FLAGS) -fPIC --shared -O2  -g -o $(LIBRARY) $(INC) $(cflags)
	cp $(LIBRARY) ./test
	#cp MultiNetUtil.h ./test
	cp sysfun.h     ./test
	cp TCPNetShareClient.h ./test
clean:
	rm libmultinetutil.so

测试程序

#include "TCPNetShareClient.h"

using namespace std;

int main(void)
{
	TCPNetShareClient * pClient = createNetUtil();
	if(pClient == NULL)
	{
		cout << "create TCPNetUtilClient failed" << endl;
		return -1;
	}

	pClient->InitWithPort(9999);
	pClient->SetTmpBuffer(2000000);
	pClient->ConnectServer("127.0.0.1", 6666);

	int i = 0;
	char strName[100] = "abcde";
	for(;;)
	{
		int nSize = 0;
		char* pBuf;
		if(pClient->RecvData(&nSize, &pBuf) > 0)
		{
			sprintf(strName, "./jpg/%d.jpg", i);
			i++;
			cout << "-=-==-=-=-" << strName << endl;
			FILE* fp = fopen(strName, "wb");
			fwrite(pBuf, 1, nSize, fp);
			fclose(fp);
			free(pBuf);
			//cout << "-=-==-=-=--=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-==-==-=-=-=-=-=-=" << strName << endl;
		}

		nsleep(1);
	}
	
	return 0;
}

测试程序Makefile

CC = g++ -std=c++11


cflags = -I.#./libevent/include
INC = -L. # ../libevent/lib -L ./


SRCS = test.cpp


C_FLAGS = -lpthread -lmultinetutil #-levent_core -levent_extra -levent_openssl -levent_pthreads
# -lopencv_imgcodecs

LIBRARY = test

all:
	$(CC) $(SRCS) $(C_FLAGS) -O2  -g -o $(LIBRARY) $(INC) $(cflags)
	#cp $(LIBRARY) /home/VideoAnalysis/Bin/
clean:
	rm test

客户端和服务端源文件地址:

源码中有点问题,有一个宏定义写错了

#define BYTE unsigned char*

改成

#define BYTE unsigned char

否则收到文件出错

https://download.csdn.net/download/chnim/10499725


刚开始研究网络传输,看到一些关于粘包的问题,虽然这个例子没有出现这种问题,但是有必要做一下修改

以后有空了再写一个例子

思路是:

将文件名,文件长度,包头标识符放到包头,包头定长便于每次读取,然后在读取数据包时,先去读取文件头获取文件名,打开文件根据获取的文件长度,循环获取数据写入文件,知道将当前文件内容获取完毕,然后在开始获取下一个文件的包头信息如此往复

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

telllong

你的鼓励是我创作最大的动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值