前言:最近搞了一下andriod和socket网络通信,结合之前搞得opencv,立即有了一个把三者结合起来的想法。好了,搞吧。所以搞出来一个远程监控,功能就是把电脑的摄像头数据传到手机上。


环境:VS2015,opencv,android studio


VS端:

其实就是打开一个摄像头,再把图像数据通过socket发送出去,问题不大。但要注意图片尺寸默认480*640。

main函数伪代码:

初始化tcp和udp服务端,

开启tcp和udp监听线程,

打开摄像头,

打开录像功能,

一直读图,并转化为灰色,

如果收到服务端的信号,则传输图像,

由于android端bitmap与这里灰度图的显示是相反的,所以传图的时候也对每一个像素取反,

最后结束。


初始化部分其实网上有许多,直接用就好了。

监听线程如果收到“ss",则表示开始传输,收到”ee"则表示结束。

这里要注意的是,使用udp传输是不稳定的,所以传输的时候每一帧都加上开头和结尾的判断标志,而且发送过快的话andriod端会出现丢包的情况,所以每次传输后面都加了延时。并且由于MTU限制,每一帧的实际数据长度不能超过1472。

对于TCP来说则没有限制,直接传输图片数据就好了。这个就挺快的,实测在同一局域网下能达到20帧。

下面是详细代码:

#include<opencv2\opencv.hpp>
#include <math.h>
#include <process.h>
#include "winsock.h"
//socket库的lib
#pragma comment(lib,"ws2_32.lib")

#define PORT 8888

using namespace cv;
using namespace std;

SOCKET socksvr,tcpsvr, tcpsockclient;
CHAR szRecv[1472] = { 0 };
CHAR szSend[1472] = { 0 };
struct sockaddr_in clientaddr = { 0 };
struct sockaddr_in tcpclientaddr = { 0 };
int nLen = sizeof(clientaddr);
int tcpnLen = sizeof(tcpclientaddr);
volatile HANDLE udpreceive,tcpreceive;
UINT threadid,tcpthreadid;
int start = 0;


void delay(int s)
{
	while (s > 0)s--;
}
void TCPServerInit()
{
	tcpsvr = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
	if (INVALID_SOCKET == tcpsvr)
	{
		return;
	}
	/*************建立服务器端套接字地址***********************/
	/********************绑定IP和端口号******************/
	struct sockaddr_in svraddr = { 0 };
	svraddr.sin_family = AF_INET;//代表internet协议族
	svraddr.sin_port = htons(PORT);
	//htonl()函数是将u_long型变量从主机字节顺序变为TCP/IP网络字节顺序。
	svraddr.sin_addr.S_un.S_addr = htonl(INADDR_ANY);//inet_addr("127.0.0.1");//htonl(INADDR_ANY);//此宏为0,当前机器上任意IP地址,也可以指定当前机的ip和端口。//127.0
														  //绑定,将服务器端套接字与服务器端套接字地址绑定
	bind(tcpsvr, (struct sockaddr *)&svraddr, sizeof(svraddr));//指定名字,类型,长度。绑定套接字。
																//侦听
	listen(tcpsvr, SOMAXCONN);//第一个参数是套接字,第二个参数是等待连接队列的最大长度。
}
void UDPServerInit()
{
	//创建socket
	socksvr = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
	if (INVALID_SOCKET == socksvr)
	{
		return;
	}
	//服务器套接字地址
	//绑定ip与端口,先定义端口等一些信息。
	struct sockaddr_in svraddr = { 0 };
	svraddr.sin_family = AF_INET;
	svraddr.sin_port = htons(PORT);
	svraddr.sin_addr.S_un.S_addr = htonl(INADDR_ANY);
	bind(socksvr, (struct sockaddr*)&svraddr, sizeof(svraddr));

}



void TCPSendImage(Mat image)
{
	//int i;
	//int datanum;
	//send(tcpsockclient, "ss", 2, 0);//发送函数。
	//sendto(socksvr, "ss", 2, 0, (struct sockaddr*)&clientaddr, nLen);
	if(start==0)closesocket(tcpsockclient);
	else send(tcpsockclient, (const char*)image.data, image.cols*image.rows, 0);
	//for (i = 0; i < image.rows; i += 2)
	//{
	//	send(tcpsockclient, (const char*)image.ptr<uchar>(i), image.cols * 2, 0);//发送函数。
	//	//sendto(socksvr, (const char*)image.ptr<uchar>(i), image.cols * 2, 0, (struct sockaddr*)&clientaddr, nLen);
	//	//datanum=recvfrom(socksvr, szRecv, sizeof(szRecv), 0, (struct sockaddr*)&clientaddr, &nLen);

	//	//delay(100000);
	//}
	//send(tcpsockclient, "ee", 2, 0);//发送函数。
	//sendto(socksvr, "ee", 2, 0, (struct sockaddr*)&clientaddr, nLen);
	//datanum = recvfrom(socksvr, szRecv, sizeof(szRecv), 0, (struct sockaddr*)&clientaddr, &nLen);
}


void SendImage(Mat image)
{
	int i;
	int datanum;
	sendto(socksvr, "ss", 2, 0, (struct sockaddr*)&clientaddr, nLen);
	for (i = 0; i < image.rows; i+=2)
	{
		sendto(socksvr, (const char*)image.ptr<uchar>(i), image.cols*2, 0, (struct sockaddr*)&clientaddr, nLen);
		//datanum=recvfrom(socksvr, szRecv, sizeof(szRecv), 0, (struct sockaddr*)&clientaddr, &nLen);

		delay(200000);
	}
	sendto(socksvr, "ee", 2, 0, (struct sockaddr*)&clientaddr, nLen);
	//datanum = recvfrom(socksvr, szRecv, sizeof(szRecv), 0, (struct sockaddr*)&clientaddr, &nLen);
}

UINT WINAPI TCPListenThread(void* pParam)
{
	int datanum;
	printf("TCP listening!\n");
	while (1)
	{
		//以下是建立客户端套接字并建立连接函数。有一个确认的过程。
		//注:后面填的是客户端地址长度的地址。
		tcpsockclient = accept(tcpsvr, (struct sockaddr*)&tcpclientaddr, &tcpnLen);//建立连接函数
		printf("TCP客户端已连接\n");
		while (1) {
			if (start == 0) {
				datanum = recv(tcpsockclient, szRecv, sizeof(szRecv), 0); //接收函数,一直处于侦听模式,等待服务器端发送数据的到来。//构造ip地址
				printf("Recieve:%s From:%s:%d\n", szRecv, inet_ntoa(tcpclientaddr.sin_addr), ntohs(tcpclientaddr.sin_port));
				if (datanum == 2 && szRecv[0] == 's'&&szRecv[1] == 's')
				{
					start = 2;
					printf("Start!\n");
				}
			}
			else if (start == 2) {
				datanum = recv(tcpsockclient, szRecv, sizeof(szRecv), 0); //接收函数,一直处于侦听模式,等待服务器端发送数据的到来。//构造ip地址
				if (datanum == 2 && szRecv[0] == 'e'&&szRecv[1] == 'e')
				{
					start = 0;
					printf("End!\n");
					break;
				}
			}
		}
		

	}
	CloseHandle(tcpreceive);
	return 0;
}
UINT WINAPI UDPListenThread(void* pParam)
{

	int datanum;
	printf("UDP listening!\n");
	while (1)
	{

		if (start == 0) {
			datanum = recvfrom(socksvr, szRecv, sizeof(szRecv), 0, (struct sockaddr*)&clientaddr, &nLen);//构造ip地址
			printf("Recieve:%s From:%s:%d\n", szRecv, inet_ntoa(clientaddr.sin_addr), ntohs(clientaddr.sin_port));
			if (datanum == 2 && szRecv[0] == 's'&&szRecv[1] == 's')
			{
				start = 1;
				printf("Start!\n");
			}
		}
		else if (start == 1) {
			datanum = recvfrom(socksvr, szRecv, sizeof(szRecv), 0, (struct sockaddr*)&clientaddr, &nLen);//构造ip地址
			if (datanum == 2 && szRecv[0] == 'e'&&szRecv[1] == 'e')
			{

				start = 0;
				printf("End!\n");
			}
		}
		//if(!(szRecv[0] == 's'&&szRecv[1] == 's')&&!(szRecv[0] == 'e'&&szRecv[1] == 'e'))
		//{
		//	sprintf(szSend, "Received %d bytes data!\n", datanum);
		//	sendto(socksvr, szSend, sizeof(szSend), 0, (struct sockaddr*)&clientaddr, nLen);//发送时构造ip地址和端口。
		//}

	}
	CloseHandle(udpreceive);
	return 0;
}


int main(int argc, char** argv) {
	WSADATA wsa = { 0 }; //WinSockApi 取WSA+DATA组成套接字结构体
	WSAStartup(MAKEWORD(2, 2), &wsa);
	UDPServerInit();
	TCPServerInit();
	udpreceive = (HANDLE)_beginthreadex(NULL, 0, UDPListenThread, 0, 0, &threadid);
	tcpreceive = (HANDLE)_beginthreadex(NULL, 0, TCPListenThread, 0, 0, &tcpthreadid);
	//DrawCir();
	Mat img,gray; 
	VideoCapture cap(0);
	if (!cap.isOpened()) return -1;
	VideoWriter me("dir.avi", CV_FOURCC('M', 'J', 'P', 'G'), 20, { 640, 480 });
	int startre = 0;
	while (1)
	{
		cap >> img;
		namedWindow("circles", 1);
		cvtColor(img, gray, CV_BGR2GRAY);
		imshow("circles", gray);
		if (startre)me << img;
		if (start==1) { 
			SendImage(~gray); 
		}
		else if (start == 2) {
			TCPSendImage(~gray);
		}
		waitKey(1);
	}
	//
	//清理套接字资源
	closesocket(tcpsvr);
	closesocket(socksvr);
	WSACleanup();
	return 0;
}


代码在配置好opencv环境的VS里面应该能直接运行。


android端:

这里主要就是搞了一个图像显示界面以及socket配置界面。

socket配置部分也是网上找的,直接用就好。

图像使用bitmap显示在imageview上面。

流程也是发送ss开始,发送ee结束。

对于tcp来说,接收到480*640个数据就直接认为是一帧了。

对于udp来说,需要检验数据的头和尾。接收到ss表示图片开始,接收到ee表示图片结束。

接收到一个字符串后,通过copyPixelsFromBuffer将数据从内存放到bitmap里,再setImageBitmap就可以看了。

效果如图所示。

android 开发手机远程 android实现远程监控技术_套接字


图中左边是vs显示,右边是andriod模拟器界面。

操作步骤:

打开VS程序,

打开APP,输入PC的ip地址和端口,选择连接类型,点击确定。

点击连接,输入ss,点击发送,就可以看到图片了。

这个程序比较大,就直接上工程文件吧。

点击打开链接


要注意的是没有连接就发送的话会闪退,退出前不发ee的话VS这边就得重启才能重新连接。这些可以避免,但是毕竟不是做产品,所以咱没有搞那么精细。

同时彩色图在andriod这边是用的RGB_565格式,也就是说数据量会比灰度大一倍,这个做起来也不难,就是没想搞了。


搞这个的时候其实想搞一个带公网的,就是能远程访问,不在同一个局域网也能连。按道理是可以的,只要有PC端有公网IP,即使连了路由的话,做一个端口映射就搞定了。到时候手机这边输入路由器WAN口的IP就可以了。

但是我这个WAN口IP是个教育网IP,这就尴尬了,反正访问不了。


很不爽啊。要不再搞个中间服务器吧,正好有个aliyun主机,可以嘛。

这样把PC端和手机端都设成客户端,阿里云上面开两个线程,跑两个tcp server,监听两个端口,然后把我收到的信息发给你,你收到的信息发给我,这就搞定了嘛。


手机端代码不用动,然后PC端的server改成client就行。

PC端代码:

#include<opencv2\opencv.hpp>
#include <math.h>
#include <process.h>
#include "winsock.h"
//socket库的lib
#pragma comment(lib,"ws2_32.lib")

#define PORT 9999

using namespace cv;
using namespace std;

SOCKET socksvr, tcpsockclient;
CHAR szRecv[1472] = { 0 };
CHAR szSend[1472] = { 0 };
struct sockaddr_in clientaddr = { 0 };
struct sockaddr_in tcpclientaddr = { 0 };
int nLen = sizeof(clientaddr);
int tcpnLen = sizeof(tcpclientaddr);
volatile HANDLE udpreceive, tcpreceive;
UINT threadid, tcpthreadid;
int start = 0;


void delay(int s)
{
	while (s > 0)s--;
}
void TCPServerInit()
{
	tcpsockclient = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
	if (INVALID_SOCKET == tcpsockclient)
	{
		return;
	}
	/*************建立服务器端套接字地址***********************/
	/********************绑定IP和端口号******************/
	struct sockaddr_in svraddr = { 0 };
	svraddr.sin_family = AF_INET;//代表internet协议族
	svraddr.sin_port = htons(PORT);
	//htonl()函数是将u_long型变量从主机字节顺序变为TCP/IP网络字节顺序。
	svraddr.sin_addr.S_un.S_addr = inet_addr("120.79.34.31");//inet_addr("127.0.0.1");//htonl(INADDR_ANY);//此宏为0,当前机器上任意IP地址,也可以指定当前机的ip和端口。//127.0
													 //绑定,将服务器端套接字与服务器端套接字地址绑定
	int iRetVal = connect(tcpsockclient, (struct sockaddr*)&svraddr, sizeof(svraddr));
	if (SOCKET_ERROR == iRetVal)
	{
		printf("服务器连接失败!");
		closesocket(tcpsockclient);
		return;
	}
	printf("服务器连接成功!\n");
}
void UDPServerInit()
{
	//创建socket
	socksvr = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
	if (INVALID_SOCKET == socksvr)
	{
		return;
	}
	//服务器套接字地址
	//绑定ip与端口,先定义端口等一些信息。
	struct sockaddr_in svraddr = { 0 };
	svraddr.sin_family = AF_INET;
	svraddr.sin_port = htons(PORT);
	svraddr.sin_addr.S_un.S_addr = htonl(INADDR_ANY);
	bind(socksvr, (struct sockaddr*)&svraddr, sizeof(svraddr));

}



void TCPSendImage(Mat image)
{
	//int i;
	//int datanum;
	//send(tcpsockclient, "ss", 2, 0);//发送函数。
	//sendto(socksvr, "ss", 2, 0, (struct sockaddr*)&clientaddr, nLen);
	if (start !=0)
		send(tcpsockclient, (const char*)image.data, image.cols*image.rows, 0);
	//for (i = 0; i < image.rows; i += 2)
	//{
	//	send(tcpsockclient, (const char*)image.ptr<uchar>(i), image.cols * 2, 0);//发送函数。
	//	//sendto(socksvr, (const char*)image.ptr<uchar>(i), image.cols * 2, 0, (struct sockaddr*)&clientaddr, nLen);
	//	//datanum=recvfrom(socksvr, szRecv, sizeof(szRecv), 0, (struct sockaddr*)&clientaddr, &nLen);

	//	//delay(100000);
	//}
	//send(tcpsockclient, "ee", 2, 0);//发送函数。
	//sendto(socksvr, "ee", 2, 0, (struct sockaddr*)&clientaddr, nLen);
	//datanum = recvfrom(socksvr, szRecv, sizeof(szRecv), 0, (struct sockaddr*)&clientaddr, &nLen);
}


void SendImage(Mat image)
{
	int i;
	int datanum;
	sendto(socksvr, "ss", 2, 0, (struct sockaddr*)&clientaddr, nLen);
	for (i = 0; i < image.rows; i += 2)
	{
		sendto(socksvr, (const char*)image.ptr<uchar>(i), image.cols * 2, 0, (struct sockaddr*)&clientaddr, nLen);
		//datanum=recvfrom(socksvr, szRecv, sizeof(szRecv), 0, (struct sockaddr*)&clientaddr, &nLen);

		delay(200000);
	}
	sendto(socksvr, "ee", 2, 0, (struct sockaddr*)&clientaddr, nLen);
	//datanum = recvfrom(socksvr, szRecv, sizeof(szRecv), 0, (struct sockaddr*)&clientaddr, &nLen);
}

UINT WINAPI TCPListenThread(void* pParam)
{
	int datanum;
		while (1) {
			if (start == 0) {
				datanum = recv(tcpsockclient, szRecv, sizeof(szRecv), 0); //接收函数,一直处于侦听模式,等待服务器端发送数据的到来。//构造ip地址
				printf("Recieve:%s From:%s:%d\n", szRecv, inet_ntoa(tcpclientaddr.sin_addr), ntohs(tcpclientaddr.sin_port));
				if (datanum == 2 && szRecv[0] == 's'&&szRecv[1] == 's')
				{
					start = 2;
					printf("Start!\n");
				}
			}
			else if (start == 2) {
				datanum = recv(tcpsockclient, szRecv, sizeof(szRecv), 0); //接收函数,一直处于侦听模式,等待服务器端发送数据的到来。//构造ip地址
				if (datanum == 2 && szRecv[0] == 'e'&&szRecv[1] == 'e')
				{
					start = 0;
					printf("End!\n");
				}
			}


	}
	CloseHandle(tcpreceive);
	return 0;
}
UINT WINAPI UDPListenThread(void* pParam)
{

	int datanum;
	printf("UDP listening!\n");
	while (1)
	{

		if (start == 0) {
			datanum = recvfrom(socksvr, szRecv, sizeof(szRecv), 0, (struct sockaddr*)&clientaddr, &nLen);//构造ip地址
			printf("Recieve:%s From:%s:%d\n", szRecv, inet_ntoa(clientaddr.sin_addr), ntohs(clientaddr.sin_port));
			if (datanum == 2 && szRecv[0] == 's'&&szRecv[1] == 's')
			{
				start = 1;
				printf("Start!\n");
			}
		}
		else if (start == 1) {
			datanum = recvfrom(socksvr, szRecv, sizeof(szRecv), 0, (struct sockaddr*)&clientaddr, &nLen);//构造ip地址
			if (datanum == 2 && szRecv[0] == 'e'&&szRecv[1] == 'e')
			{

				start = 0;
				printf("End!\n");
			}
		}
		//if(!(szRecv[0] == 's'&&szRecv[1] == 's')&&!(szRecv[0] == 'e'&&szRecv[1] == 'e'))
		//{
		//	sprintf(szSend, "Received %d bytes data!\n", datanum);
		//	sendto(socksvr, szSend, sizeof(szSend), 0, (struct sockaddr*)&clientaddr, nLen);//发送时构造ip地址和端口。
		//}

	}
	CloseHandle(udpreceive);
	return 0;
}


int main(int argc, char** argv) {
	WSADATA wsa = { 0 }; //WinSockApi 取WSA+DATA组成套接字结构体
	WSAStartup(MAKEWORD(2, 2), &wsa);
	UDPServerInit();
	TCPServerInit();
	udpreceive = (HANDLE)_beginthreadex(NULL, 0, UDPListenThread, 0, 0, &threadid);
	tcpreceive = (HANDLE)_beginthreadex(NULL, 0, TCPListenThread, 0, 0, &tcpthreadid);
	//DrawCir();
	Mat img, gray;
	VideoCapture cap(0);
	if (!cap.isOpened()) return -1;
	VideoWriter me("dir.avi", CV_FOURCC('M', 'J', 'P', 'G'), 20, { 640, 480 });
	int startre = 0;
	while (1)
	{
		cap >> img;
		namedWindow("circles", 1);
		cvtColor(img, gray, CV_BGR2GRAY);
		imshow("circles", gray);
		if (startre)me << img;
		if (start == 1) {
			SendImage(~gray);
		}
		else if (start == 2) {
			TCPSendImage(~gray);
		}
		waitKey(1);
	}
	//
	//清理套接字资源
	closesocket(socksvr);
	WSACleanup();
	return 0;
}



再加一个阿里云ubuntu的代码:


root@ch:~/camserver# cat pc.c
#include <stdio.h>  
#include <stdlib.h>  
#include <string.h>  
#include <unistd.h>  
#include <sys/socket.h>  
#include <netinet/in.h>  
#include <arpa/inet.h>  
#include <ctype.h>  
#include <pthread.h>
#define MAXLINE 640*480
#define SERV_PORT 9999  

int listenfda, connfda;
int connectstatusa = 0;

extern void SendPh(char *input, int n);
extern void SePh(void);

void SendPc(char *input, int n)
{
	if (connectstatusa)
	{
		write(connfda, input, n);
	}
}




void SePc(void)
{
	struct sockaddr_in servaddr, cliaddr;
	socklen_t cliaddr_len;

	char buf[MAXLINE], tempbuf[MAXLINE];
	char str[INET_ADDRSTRLEN];
	int i, n, error = 0;

	listenfda = socket(AF_INET, SOCK_STREAM, 0);
	bzero(&servaddr, sizeof(servaddr));
	servaddr.sin_family = AF_INET;
	servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
	servaddr.sin_port = htons(SERV_PORT);
	bind(listenfda, (struct sockaddr *)&servaddr, sizeof(servaddr));
	listen(listenfda, 20);
	cliaddr_len = sizeof(cliaddr);
	while (1) {
		connfda = accept(listenfda, (struct sockaddr *)&cliaddr, &cliaddr_len);
		connectstatusa = 1;
		printf("PC connect %s:%d\n", inet_ntop(AF_INET, &cliaddr.sin_addr, str, sizeof(str)), ntohs(cliaddr.sin_port));
		while (1) {
			n = read(connfda, buf, MAXLINE);
			if (n > 0)
			{
				SendPh(buf, n);
			}
			else error++;
			if (error > 10)
			{
				error = 0;
				break;
			}
		}
		connectstatusa = 0;
		close(connfda);
	}
	return;

}


int main()
{
	pthread_t thrdhealth, thrdupload;
	pthread_create(&thrdhealth, NULL, (void *)SePh, NULL);
	pthread_create(&thrdupload, NULL, (void *)SePc, NULL);
	while (1)
	{
		printf("Main running\n");
		usleep(5000000);
	}
	pthread_join(thrdhealth, NULL);
	pthread_join(thrdupload, NULL);
	return 0;

}

root@ch:~/camserver# cat ph.c
#include <stdio.h>  
#include <stdlib.h>  
#include <string.h>  
#include <unistd.h>  
#include <sys/socket.h>  
#include <netinet/in.h>  
#include <arpa/inet.h>  
#include <ctype.h>  
#include <pthread.h>
#define MAXLINE 640*480
#define SERV_PORT 8888  

int listenfd, connfd;
int connectstatus = 0;

extern void SendPc(char *input, int n);


void SendPh(char *input, int n)
{
	if (connectstatus)
	{
		write(connfd, input, n);
	}
}




void SePh(void)
{
	struct sockaddr_in servaddr, cliaddr;
	socklen_t cliaddr_len;

	char buf[MAXLINE], tempbuf[MAXLINE];
	char str[INET_ADDRSTRLEN];
	int i, n, error = 0;

	listenfd = socket(AF_INET, SOCK_STREAM, 0);
	bzero(&servaddr, sizeof(servaddr));
	servaddr.sin_family = AF_INET;
	servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
	servaddr.sin_port = htons(SERV_PORT);
	bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr));
	listen(listenfd, 20);
	cliaddr_len = sizeof(cliaddr);
	while (1) {
		connfd = accept(listenfd, (struct sockaddr *)&cliaddr, &cliaddr_len);
		connectstatus = 1;
		printf("Phone connect %s:%d\n", inet_ntop(AF_INET, &cliaddr.sin_addr, str, sizeof(str)), ntohs(cliaddr.sin_port));
		while (1) {
			n = read(connfd, buf, MAXLINE);
			if (n > 0)
			{
				SendPc(buf, n);
			}
			else error++;
			if (error > 10)
			
{
				printf("Phone lost\n");
				SendPc("ee", 2);
				error = 0;
				break;
			}
		}
		connectstatus = 0;
		close(connfd);
	}
	return;

}


注意上面是两个文件,分别是PCserver和PhoneServer 。把两个文件一起编一下就行,要带上-lpthread编译,像下面这样:

gcc pc.c ph.c -o server -lpthread



运行流程:

先开阿里云server,然后把连个client打开,都打开后再在手机上连接阿里云的ip就可以了。

要注意的是这里只能用tcp了,udp差不多,但是没搞啊。还有阿里云的安全组要打开所有的IP和端口访问。

完成了。但是这速度慢的,差不多有2帧吧。手机上看到速度只有100多k/s的速度。和在同一局域网下的1M多/s的速度没法比啊。可能是阿里云的学生套餐太慢了吧。

就这样吧。