CC BY 4.0 (除特别声明或转载文章外)
如果这篇博客帮助到你,可以请我喝一杯咖啡~
实验题目
文件传输实验(选做)
实验目的
学习利用套接字传送文件
实验内容
利用数据表示实验和 Echo 实验实现以下功能:
- 运行服务器端程序,输入接收文件的文件夹,然后等待客户端连接,并接收客户端发来的文件并保存,直到客户端关闭连接。有重名文件时,文件名增加序号保存。
- 客户端先连接服务器,每次输入一个文件名(包含路径)就传输它,直到输入 exit 时退出并关闭套接字。
实验结果
服务器程序:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
#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();
}
客户端程序:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
#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
看是否成功的傻办法来说好用了很多。