大作业:网络Socket编程
程序员:熊宇龙200630501407 信息安全5班
本程序实现了多客户端访问和一定大小内的文件传输(已测试bmp和txt文件),并且客户端在连接时就将得到服务器端的文件列表。
为方便解说起见,我将研发说明文档和操作说明集中在这里一起了
声明:部分源码来自参考书《WINDOWS网络编程》中的TCP连接部分,本程序也是基于其源码上进行了大幅度修改完成的。
大部分研发说明都在程序内写出来了。我特地用红字表示出来。其中主要是表示的拓展的地方、需要注意的地方,以及核心的地方。
其他说明:由于时间不够的缘故,本程序旨在实现其传输文件的功能、列表发送以及多客户端多线程传输的功能。并没有对其人性化进行太多设计,程序员可以按照我在程序中的提示通过直接修改程序达到其他目的(如扩大传输量),主要可修改的包括:
1.文件列表,保存在1.txt中可以任意修改,不过请不要对客户端造成欺骗。
2.Buff容量,通过修改DEFAULT_BUFFER 达到目的。
拓展构想:计划内却因为时间关系未完成的的部分包括
1.实现任意大小文件传输:可以通过if语句判定大小,产生多个buff文件顺序传输并顺序
写入。不难但是时间不够了。
2.实现断点续传:判定部分遇到了困难,未完成。
具体操作流程图请见最后
程序如下:
客户端:
// Client.c
// 声明:部分代码来自《WINDOWS网络编程》一书中关于建立TCP连接的部分
// 功能:连接到服务器端并下载文件
// 注意:build时请在工程设置中添加ws2_32.lib
//
// Command Line Options:
// client [-p:x] [-s:IP] [-n:x] [-o]
// -p:x Remote port to send to
// -s:IP Server's IP address or hostname
// -n:x Number of times to send message
// -o Send messages only; don't receive
//
#include
#include
#include
#include
#define DEFAULT_COUNT 20
#define DEFAULT_PORT 5150
#define DEFAULT_BUFFER 4096//希望调整最大可传输文件大小请调整此处不要忘了将server的也同样更改#define DEFAULT_MESSAGE "This is a test of the emergency \
broadcasting system"
char szServer[128],
szMessage[1024];
int iPort = DEFAULT_PORT;
DWORD dwCount = DEFAULT_COUNT;
BOOL bSendOnly = FALSE;
void usage()
{
printf("usage: client [-p:x] [-s:IP] [-n:x] [-o]\n\n");
printf(" -p:x Remote port to send to\n");
printf(" -s:IP Server's IP address or hostname\n");
printf(" -n:x Number of times to send message\n");
printf(" -o Send messages only; don't receive\n");
ExitProcess(1);
}
void V alidateArgs(int argc, char **argv)
{
int i;
for(i = 1; i < argc; i++)
{
if ((argv[i][0] == '-') || (argv[i][0] == '/'))
{
switch (tolower(argv[i][1]))
{
case 'p':
if (strlen(argv[i]) > 3)
iPort = atoi(&argv[i][3]);
break;
case 's':
if (strlen(argv[i]) > 3)
strcpy(szServer, &argv[i][3]);
break;
case 'n':
if (strlen(argv[i]) > 3)
dwCount = atol(&argv[i][3]);
break;
case 'o':
bSendOnly = TRUE;
break;
default:
usage();
break;
}
}
}
}
int main(int argc, char **argv)
{
WSADATA wsd;
SOCKET sClient;
char szBuffer[DEFAULT_BUFFER];
int ret;
struct sockaddr_in server;
struct hostent *host = NULL;
cout<<"input szServer"<
ValidateArgs(argc, argv);
if (WSAStartup(MAKEWORD(2,2), &wsd) != 0)
{
printf("Failed to load Winsock library!\n");
return 1;
}
strcpy(szMessage, DEFAULT_MESSAGE);
sClient = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (sClient == INV ALID_SOCKET)
{
printf("socket() failed: %d\n", WSAGetLastError());
return 1;
}
server.sin_family = AF_INET;
server.sin_port = htons(iPort);
server.sin_addr.s_addr = inet_addr(szServer);//连接到目标地址
if (server.sin_addr.s_addr == INADDR_NONE)
{
host = gethostbyname(szServer);
if (host == NULL)
{
printf("Unable to resolve server: %s\n", szServer);
return 1;
}
CopyMemory(&server.sin_addr, host->h_addr_list[0],
host->h_length);
}
if (connect(sClient, (struct sockaddr *)&server,
sizeof(server)) == SOCKET_ERROR)
{
printf("connect() failed: %d\n", WSAGetLastError());
return 1;
}
ret = recv(sClient, szBuffer, DEFAULT_BUFFER, 0);//等待接收文件列表
if (ret == SOCKET_ERROR)
{
printf("recv() failed: %d\n", WSAGetLastError());
}
szBuffer[ret] = '\0';
printf("FileList:\n");
printf("%s\n", szBuffer);
FILE *fp;
while(1)
{
cout<<"input szMessage"<
ret = send(sClient, szMessage, strlen(szMessage), 0);
if (ret == 0)
{
break;
}
else if (ret == SOCKET_ERROR)
{
printf("send() failed: %d\n", WSAGetLastError());
break;
}
printf("Send %d bytes\n", ret);
if (!bSendOnly)
{
ret = recv(sClient, szBuffer, DEFAULT_BUFFER, 0);//接收文件的szBuff并开始准备转换回文件
if (ret == 0)
break;
else if (ret == SOCKET_ERROR)
{
printf("recv() failed: %d\n", WSAGetLastError());
break;
}
szBuffer[ret] = '\0';
printf("RECV [%d bytes]: '%s'\n", ret, szBuffer);
char s[10]="RECV"; //对接受的文件进行重命名在前面加上RECV 后几句都是此作用
strncat(s,szMessage,strlen(szMessage));
fp=fopen(s,"a+");
fwrite(szBuffer,1,ret,fp);
fclose(fp);
}
}
closesocket(sClient);
WSACleanup();
return 0;
}
服务器端:
// Server.c
// 声明:部分代码来自《WINDOWS网络编程》一书中关于建立TCP连接的部分
// 功能:建立一个全自动服务器,当收到连接申请则连接并发送文件列表,并等待客户端发送申请下载请求
// 注意:build时请在工程设置中加入ws2_32.lib
#include
#include
#include
#define DEFAULT_PORT 5150
#define DEFAULT_BUFFER 4096
int iPort = DEFAULT_PORT;
BOOL bInterface = FALSE,
bRecvOnly = FALSE;
char szAddress[128];
//
// Function: usage
//
// Description:
// Print usage information and exit
//
void usage()
{
printf("usage: server [-p:x] [-i:IP] [-o]\n\n");
printf(" -p:x Port number to listen on\n");
printf(" -i:str Interface to listen on\n");
printf(" -o Don't echo the data back\n\n");
ExitProcess(1);
}
void V alidateArgs(int argc, char **argv)
{
int i;
for(i = 1; i < argc; i++)
{
if ((argv[i][0] == '-') || (argv[i][0] == '/'))
{
switch (tolower(argv[i][1]))
{
case 'p':
iPort = atoi(&argv[i][3]);
break;
case 'i':
bInterface = TRUE;
if (strlen(argv[i]) > 3)
strcpy(szAddress, &argv[i][3]);
break;
case 'o':
bRecvOnly = TRUE;
break;
default:
usage();
break;
}
}
}
}
DWORD WINAPI ClientThread(LPVOID lpParam)//多线程控制通过给不同的客户不同的线
{
SOCKET sock=(SOCKET)lpParam;
char szBuff[DEFAULT_BUFFER];
int ret,
nLeft,
idx;
FILE *fp;
while(1)
{
ret = recv(sock, szBuff, DEFAULT_BUFFER, 0);//收听客户端想下载什么文件
if (ret == 0)
break;
else if (ret == SOCKET_ERROR)
{
printf("recv() failed: %d\n", WSAGetLastError());
break;
}
szBuff[ret] = '\0';
printf("RECV: '%s'\n", szBuff);
fp=fopen(szBuff,"rb");//打开目标文件将目标文件内容存入szBuff
memset(szBuff,0,DEFAULT_BUFFER);//初始化szBuff 不然传送的数据后面会自动添加很多东西
ret=fread(szBuff,1,DEFAULT_BUFFER-1,fp);//将目标文件中的东西读入szBuff
send(sock,szBuff,ret,0);//发送给客户端
fclose(fp);
}
return 0;
}
int main(int argc, char **argv)
{
WSADATA wsd;
SOCKET sListen,
sClient;
int iAddrSize;
HANDLE hThread;
DWORD dwThreadId;
struct sockaddr_in local,
client;
ValidateArgs(argc, argv);
if (WSAStartup(MAKEWORD(2,2), &wsd) != 0)
{
printf("Failed to load Winsock!\n");
return 1;
}
sListen = socket(AF_INET, SOCK_STREAM, IPPROTO_IP); if (sListen == SOCKET_ERROR)
{
printf("socket() failed: %d\n", WSAGetLastError());
return 1;
}
if (bInterface)
{
local.sin_addr.s_addr = inet_addr(szAddress);
if (local.sin_addr.s_addr == INADDR_NONE)
usage();
}
else
local.sin_addr.s_addr = htonl(INADDR_ANY);
local.sin_family = AF_INET;
local.sin_port = htons(iPort);
if (bind(sListen, (struct sockaddr *)&local,
sizeof(local)) == SOCKET_ERROR)
{
printf("bind() failed: %d\n", WSAGetLastError());
return 1;
}
listen(sListen, 8);
while (1)
{
iAddrSize = sizeof(client);
sClient = accept(sListen, (struct sockaddr *)&client,
&iAddrSize);
if (sClient == INV ALID_SOCKET)
{
printf("accept() failed: %d\n", WSAGetLastError());
break;
}
printf("Accepted client: %s:%d\n",
inet_ntoa(client.sin_addr), ntohs(client.sin_port));
char fileList[DEFAULT_BUFFER]="";//创建文件表地址
FILE*fp;
fp=fopen("1.txt","r");//读取文件表,文件表默认为保存在1.txt中
int flen=fread(fileList,1,DEFAULT_BUFFER,fp);
send(sClient,fileList,flen,0);
flen=0;
hThread = CreateThread(NULL, 0, ClientThread, //多线程控制
(LPVOID)sClient, 0, &dwThreadId);
if (hThread == NULL)
{
printf("CreateThread() failed: %d\n", GetLastError());
break;
}
CloseHandle(hThread);
}
closesocket(sListen);
WSACleanup();
return 0;
}
操作流程:
1.编译过程请在工程—设置—连接—工程、选项中添加ws2_3
2.lib 不然link过程会出现错误。
2.编译成功打开如图两个窗口(可打开多个客户端窗口)。
这时server已经开始服务,请在客户端输入目标IP:127.0.0.1
此时如图
可以看到我们客户端得到了列表,有2.txt 3.txt 1.jpg可下载
此时可以输入其中一个,我们测试其中传输最复杂的1.bmp。
此时可以看到文件夹多出了个名为RECV1.bmp的文件,打开后发现和1.bmp图一样,说明
完成了传输并重命名。。
如果是txt文档,那么客户端将返回txt文档内容给服务器。
测试完毕。
主要目的是完成传输,错误控制部分除了ip地址的合法性部分,其他都较为完善,所以请输入正确的IP。
关于多客户端部分,用户可以自行测试,由于多线程设计,互相之间传输基本是不会有影响
的。