文件传输实验 晓风の个人博客

实验题目

文件传输实验(选做)

实验目的

学习利用套接字传送文件

实验内容

利用数据表示实验和 Echo 实验实现以下功能:

  1. 运行服务器端程序,输入接收文件的文件夹,然后等待客户端连接,并接收客户端发来的文件并保存,直到客户端关闭连接。有重名文件时,文件名增加序号保存。
  2. 客户端先连接服务器,每次输入一个文件名(包含路径)就传输它,直到输入 exit 时退出并关闭套接字。

实验结果

图片描述

服务器程序:

#include <stdio.h>
#include <stdlib.h>
#include <direct.h>
#include <process.h>
#include <ws2tcpip.h> //gcc Server.c -lWs2_32 -o Server
#define BUFLEN (20 << 20)
#define WSVERS MAKEWORD(2, 0)
#pragma comment(lib, "ws2_32.lib") //使用winsock 2.2 library
SOCKET ssock[BUFLEN];			   //master & *p sockets
struct sockaddr_in ssin[BUFLEN];
int sbadd[BUFLEN], ssock_size = 0;
char fileAddr[99];
unsigned __stdcall server(int *p)
{
	char *msg = malloc(BUFLEN);
	for (msg[0] = 0; strcmp(msg, "exit");)
	{
		int cc = recv(ssock[*p], msg, BUFLEN, 0);
		if (cc > 0)
		{
			msg[cc] = 0;
			char file[99];
			sprintf(file, "%s\\%s", fileAddr, msg);
			for (int i = 0; !access(file, F_OK);)
				sprintf(file, "%s\\%d.%s", fileAddr, ++i, msg);
			cc = recv(ssock[*p], msg, BUFLEN, 0);
			if (cc >= 0)
			{
				FILE *fout = fopen(file, "wb");
				fwrite(msg, sizeof(*msg), cc, fout);
				fclose(fout);
				printf("Download %s (%d bytes) finished.\n", file, cc);
			}
		}
		else
			sprintf(msg, "exit");
	}
	return free(msg), printf("Connect close.\n"), closesocket(ssock[*p]), 0;
}
int main()
{
	printf("Input file address:");
	gets(fileAddr);
	_mkdir(fileAddr);
	WSADATA wsadata;
	WSAStartup(WSVERS, &wsadata);							  //加载winsock library,WSVERS为请求版本,wsadata返回系统实际支持的最高版本
	struct sockaddr_in msin;								  //an Internet endpoint addresss
	memset(&msin, 0, sizeof(msin));							  //从&sin开始的长度为sizeof(sin)的内存清0 , sin为一个地址结构
	msin.sin_family = AF_INET;								  //因特网地址簇(INET-Internet)
	msin.sin_addr.s_addr = INADDR_ANY;						  //监听所有(接口的)IP地址(32位),0.0.0.0
	msin.sin_port = htons(atoi("50500"));					  //监听的端口号(16位) 。atoi--把ascii转化为int,htons—主机序到网络序
	SOCKET msock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP); //创建套接字。参数:因特网协议簇(family),字节流,TCP协议号。 返回:要监听套接字的描述符或INVALID_SOCKET
	bind(msock, (struct sockaddr *)&msin, sizeof(msin));	  //通过msin把要监听的IP地址和端口号绑定到套接字上
	listen(msock, 5);										  //建立长度为5的连接请求队列,并开始监听是否有连接请求到来,来了则放入队列
	printf("Server Start to listen.\n");
	while (!_kbhit()) //检测是否有按键 (什么时候执行?)
	{
		int alen = sizeof(struct sockaddr);												//from-address length
		ssock[ssock_size] = accept(msock, (struct sockaddr *)&ssin[ssock_size], &alen); //accept:如果有新的连接请求,返回连接套接字,否则,被阻塞,ssin包含客户端IP地址和端口号
		sbadd[ssock_size] = ssock_size;
		_beginthreadex(NULL, 0, &server, &sbadd[ssock_size++], 0, NULL);
	}
	closesocket(msock);
	WSACleanup();
	printf("Press any key to continue...");
	getch();
}

客户端程序:

#include <stdio.h>
#include <string.h>
#include <direct.h>
#include <ws2tcpip.h> //gcc Client.c -lWs2_32 -o Client
#pragma comment(lib, "ws2_32.lib")
#define BUFLEN (20 << 20) //20MB
char file[BUFLEN], msg[BUFLEN];
int main()
{
	WSADATA wsadata;
	WSAStartup(MAKEWORD(2, 0), &wsadata);
	struct addrinfo *host;
	getaddrinfo("127.0.0.1", "50500", NULL, &host);
	SOCKET sock = socket(host->ai_family, host->ai_socktype, host->ai_protocol);
	if (connect(sock, host->ai_addr, host->ai_addrlen))
		printf("Connect unsuccessfully.\n");
	while (1)
	{
		printf("Input name:");
		do
			gets(file);
		while (strcmp(file, "exit") || access(file, R_OK));
		if (!strcmp(file, "exit"))
			break;
		int c = send(sock, file, strlen(file), 0);
		if (!c)
		{
			printf("Send connect closed.\n");
			break;
		}
		else if (c == SOCKET_ERROR)
			printf("Send Error:\n%d\n", GetLastError());
		else
		{
			FILE *fin = fopen(file, "rb+");
			int cc = fread(msg, sizeof(*msg), sizeof(msg), fin);
			fclose(fin);
			if (cc >= 0)
			{
				send(sock, msg, cc, 0);
				printf("Upload %s (%d bytes) finished.\n", file, cc);
			}
		}
	}
	closesocket(sock);
	freeaddrinfo(host);
	WSACleanup();
	printf("Press any key to continue...");
	getch();
}

实验体会

在本次实验中并没有使用之前数据表示实验中使用的 Base64 编码,原因是 Base64 编码之后文件大小凭空增加了许多,反而使传输的性能下降了。仔细想了想,还是使用了简单的两次发送,第一次发送文件名,第二次按二进制模式明文传输这个文件。在编程的过程中一开始没有办法向一个不存在的文件夹里写入文件,然后学会了用<direct.h>库里的_mkdir创建目录;同时翻了这个库还发现很多有用的函数,比如这次用到的access函数,可以判断一个文件是否存在,以及是否有读写访问权限等,比起之前每次都是直接fopen看是否成功的傻办法来说好用了很多。