p2p查询服务器
多线程得P2P实现
Phase 1: Establishing Client-Server Communications
1.题目要求
第一阶段要求实现一个多线程的C/S结构系统,主要实现了用户的四种功能,分别是登陆,上传文件,查询文件,退出。登陆需要对密码在客户端和服务器端分别加密和解密,如果匹配错误需要在客户端返回信息格式为LGINusername#password;上传文件可以支持多次上传,并且为文件指定端口号,格式为SHREportnumber#filename,在模拟数据库中存储文件相应得信息(文件只能是.txt)上传成功回服务器返回SHOK的信息;查询文件要求根据文件名,查询数据库中所有符合要求的文件并且输出格式设置为SRCHfilename# username #ip#port的信息,一次输出一条;退出命令执行时需要将用户上传得所有文件信息删除,然后关闭SOCKET。
2.开发环境
操作系统:windows XPp
编程语言:C
编译器:VC6.0
3.程序详解:
3.1Socket连接和数据库模拟
这里第一阶段主要是先实现Client和Server可以相互交流的功能,这里主调用的Windows的API函数来建立socket和连接client和server,函数listen监听,bind绑定socket和IP,accept用来创建连接的socket,然后通过send和recv函数来发送和接受数据。
数据库主要用了结构体来模拟,userid(username,pwd) usefile ( username , filename[][] ,port[][] ,ip)。Usefile采取的对应每一个用户开出一张表,这样的优点是在删除的时候比较节省时间,缺点是对硬盘有浪费,需要很多多余的开销。3.2登陆部分
这部分主要是客户端输入用户名密码,然后服务器验证返回的一个过程,首先这里需要对用户的密码采取一个加密得措施,公式为password=password-key,key为随机生成得0到9的int型数据,加密以后需要把key添加在密码的前面,使得服务器可以验证密码,需要注意得是key+48才是此int相对应得char型字符。服务器解码后取相应的部分到username和password,然后和已经初始化在
服务器端的用户列表(username,password)匹配,正确标记位flag置1,发送登陆成功信息到客户端,并且记录客户的ip和username,等待客户端输入下一步指令,错误返回登陆错误,提示用户输入正确信息。
3.3共享文件
登陆成功后才可以执行,这里用户输入格式为SHREport#filename#...#fileN/n 的命令,服务器端判断命令的首部字段,如果符合格式,对命令按字符扫描,记录port,判断#和/n两个分界点,取出filename,然后将等到的(filename,port)插入到相应用户的表中去。服务器端成功记录后返回SHOK到客户端通知客户共享成功。客户可以选择继续共享文件还是进入下一步操作,这里主要是通过对首部字段的判断来实现这个循环
3.4查询文件
共享文件结束后,可以通过输入格式为SRCHfilename/n的命令来查询相的的文件,这里服务器端口首先需要判断首部字段如果是SRCH进入SRCH模块然后取出filename,在数据库中进行匹配,每次匹配成功就生成一条信息,格式为SRESfilename#username#ip#port 然后发送给客户端,客户端判断信息如果只是SRES或者首部字段不是SRES就直接进行到下一阶段,除此之外说明服务器端还在继续发送搜索结果,则客户端继续recv直到收到的信息为SRES,在输入.txt时可以查询到所有服务器上的有效文件
3.5退出部分
这部分比较简单,但是一直贯穿在前面几个模块中,如果用户在任何一个阶段输入了QUIT指令,则都会出发QUIT部分,这里先通过前面记录下来得clientname对于用户自己的datafile表中的信息初始化,然后关闭socket
3.6多线程部分
本部分在服务器端得main函数中,当服务器端得接口监听到一个Client的请求就创建一个线程来处理这个Client
4.程序流程图
图1 登陆流程图
图2 文件共享流程图
图3 文件查询流程图
图4 QUIT过程流程图
5.程序源代码服务器端程序源码:
#include
#include
#include
#include
//用户账号表
struct userid
{
char *username;
char *pwd;
}user[3];
//用户文件表
struct usefile
{
char *username;
char filename[20][30];
char port[20][5];
char *ip;
}datafile[3];
#define eeee "/n"
#define LGINOK "LRES connection is OK !"
#define LGINER "EROR incorrect username and password,please input correct information!" #define LGIN "LGIN"
#define SHRE "SHRE"
#define WWWW '/0'
#define SHOK "SHOK"
#define SHREER "EROR upload failed"
#define SRCH "SRCH"
#define SRES "SRES/0"
#define SHARP "#"
#define FAIL "ERORcann`t find it"
#define QUIT "QUIT/0"
#define EXIT "EXIT"
#define TXT ".txt"
DWORD WINAPI ClientThread(LPVOID lpParam)
{
SOCKET NewConnection=(SOCKET)lpParam;
SOCKADDR_IN ClientAddr;
int ClientAddrLen= sizeof (ClientAddr);
char recvbuf[1500]={'/0'};
char sendbuf[1500]={'/0'};
int i=0,j=0,k=0;
char *shre="SHRE";
char *shes="SHES";
char *quit="QUIT";
//进入一次循环
while(1)
{
int i,count=0;
char recvbuf[1500]={'0'};
char sendbuf[1500]={'0'};
char clientname[6]={'/0'};
char clientpwd[5]={'/0'};
char head[5]={'/0'}; //首部字段
char port[5]={'/0'};
//char head2[4]={'/0'};
int flag=0; //标记本次登陆是否成功
int filenumber=0; //文件号
int usernumber=0; /用户号
//接受客户端信息,输出客户端信息
recv(NewConnection,recvbuf,1500,0);
strcpy(recvbuf+strlen(recvbuf),eeee);
printf("Client:");
printf("%s",recvbuf);
//登陆检查
for(i=0;i<4;i++)
head[i]=recvbuf[i]; //得到CLIENT发的信息的首部字段while((flag==0)&&(strcmp(head,QUIT)))
{
if(!strcmp(head,LGIN)) //校验ID,PWD
{
//解密,把USERNAME 和PWD都存入中间字符串中
int i=4,j=0,m=0,key;
key=recvbuf[10]-48;
for(j=0;j<4;j++)
{
recvbuf[10+j]=recvbuf[11+j]+key;
}
recvbuf[14]='/0';
for(j=0;j<5;j++,i++)
clientname[j]=recvbuf[i];
for(j=0;j<4;j++,i++)
//匹配用户和密码
for(i=0;i<3;i++)
{
if(!strcmp(clientname,user[i].username))
{
if(!strcmp(clientpwd,user[i].pwd))
flag=1;
}
}
printf("%d",flag);
if(flag==1) //成功传输OK
{
printf("Server:");
printf("%s",LGINOK);
strcpy(sendbuf,LGINOK);
send(NewConnection,sendbuf,1500,0);
printf("/n");
break;
}
else //失败传输EROR
{
printf("Server:");
printf("%s",LGINER);
strcpy(sendbuf,LGINER);
send(NewConnection,sendbuf,1500,0);
printf("/n");
}
}
if(strcmp(head,LGIN))
{
printf("Server:");
printf("%s",LGINER);
strcpy(sendbuf,LGINER);
send(NewConnection,sendbuf,1500,0);
}
for(i=0;i<1500;i++)
{
recvbuf[i]='/0';
sendbuf[i]='/0';
}
recv(NewConnection,recvbuf,1500,0);
strcpy(recvbuf+strlen(recvbuf),eeee);
printf("Client:");
printf("%s",recvbuf);
//登陆检查
for(i=0;i<4;i++)
head[i]=recvbuf[i];
}
//第二部分文件传输
if(flag==1)
{
for(i=0;i<3;i++)
{
if(!strcmp(clientname,datafile[i].username))
usernumber=i;
}
//发送共享文件操作
//初始化RECVBUF和SENDBUF
for(i=0;i<1500;i++)
{
recvbuf[i]='/0';
sendbuf[i]='/0';
}
recv(NewConnection,recvbuf,1500,0);
strcpy(recvbuf+strlen(recvbuf),eeee);
printf("Client:");
printf("%s",recvbuf);
//首部字段判断
head[i]=recvbuf[i];
}
//多次SHRE
while(!strcmp(head,SHRE)&&(flag==1))
{
if((flag==1)&&(!strcmp(head,SHRE))&&(recvbuf[8]=='#')&&(filenumber<=19)) //登陆成功并且首部字段为SHRE3333#,将(文件,端口号)录入数据库
{
i=9;
for(i=4;i<8;i++)
port[i-4]=recvbuf[i];
datafile[usernumber].ip=inet_ntoa(ClientAddr.sin_addr);
//记录用户IP进入表
while(recvbuf[i]!='/n')
{
char temfilename[30]={'/0'};
int j=0;
while((recvbuf[i]!='#')&&(recvbuf[i]!='/n'))
{
temfilename[j]=recvbuf[i];
i++;
j++;
}
//添加该文件到该用户的文件表
strcpy(datafile[usernumber].filename[filenumber],temfilename);
strcpy(datafile[usernumber].port[filenumber],port);
filenumber++;
if(recvbuf[i]!='/n')
i++;
}
printf("Server:");
printf("%s",SHOK);
strcpy(sendbuf,SHOK);
send(NewConnection,sendbuf,1500,0);
}
else
{
printf("Server:");
printf("%s",SHREER);
strcpy(sendbuf,SHREER);
send(NewConnection,sendbuf,1500,0);
printf("/n");
}
for(i=0;i<1500;i++)
{
recvbuf[i]='/0';
sendbuf[i]='/0';
}
recv(NewConnection,recvbuf,1500,0);
strcpy(recvbuf+strlen(recvbuf),eeee);
printf("Client:");
printf("%s",recvbuf);
//首部字段判断
for(i=0;i<4;i++)
head[i]=recvbuf[i];
}
//第三部份搜索部分
//初始化发送BUF 否则BUF中又SHOK等信息
for(i=0;i<1500;i++)
{
sendbuf[i]='/0';
}
while(!strcmp(head,SRCH))
{
if((flag==1)&&(!strcmp(head,SRCH))&&(strlen(recvbuf)>6))
{
char temsrchfile[31]={'/0'};
int sign=0,m; //找到的标记位;
i=4;
//取文件名
while(recvbuf[i]!='/n'&&(i<35))
{
temsrchfile[i-4]=recvbuf[i];
i++;
}
if(!strcmp(temsrchfile,TXT))
{
for(i=0;i<3;i++)
{
for(j=0;j<20;j++)
{
for(m=0;m<1500;m++)
{
sendbuf[m]='/0';
}
sign=1;
if(datafile[i].filename[j][0]!='/0')
{
strcpy(sendbuf+strlen(sendbuf),SRES);
strcpy(sendbuf+strlen(sendbuf),datafile[i].filename[j]);
strcpy(sendbuf+strlen(sendbuf),SHARP);
strcpy(sendbuf+strlen(sendbuf),datafile[i].username);
strcpy(sendbuf+strlen(sendbuf),SHARP);
strcpy(sendbuf+strlen(sendbuf),datafile[i].ip);
strcpy(sendbuf+strlen(sendbuf),SHARP);
strcpy(sendbuf+strlen(sendbuf),datafile[i].port[j]);
strcpy(sendbuf+strlen(sendbuf),eeee);
send(NewConnection,sendbuf,strlen(sendbuf),0);
printf("Server:");
printf("%s",sendbuf);
printf("/n");
}
}
}
}
//在数据库中查找符合目标得文件,并且输出到屏幕上发送标记位记录else{
for(i=0;i<3;i++)
{
for(j=0;j<20;j++)
{
for(m=0;m<1500;m++)
{
sendbuf[m]='/0';
}
if(!strcmp(temsrchfile,datafile[i].filename[j]))
{
sign=1; //标记找到知道一条;
//制作输出找到的信息
strcpy(sendbuf+strlen(sendbuf),SRES);
strcpy(sendbuf+strlen(sendbuf),datafile[i].filename[j]);
strcpy(sendbuf+strlen(sendbuf),SHARP);
strcpy(sendbuf+strlen(sendbuf),datafile[i].username);
strcpy(sendbuf+strlen(sendbuf),SHARP);
strcpy(sendbuf+strlen(sendbuf),datafile[i].ip);
strcpy(sendbuf+strlen(sendbuf),SHARP);
strcpy(sendbuf+strlen(sendbuf),datafile[i].port[j]);
strcpy(sendbuf+strlen(sendbuf),eeee);
send(NewConnection,sendbuf,strlen(sendbuf),0);
printf("Server:");
printf("%s",sendbuf);
printf("/n");
}
}
}
}
//清空SENDBUF
for(i=0;i<1500;i++)
{
sendbuf[i]='/0';
}
if(sign==1)
{
strcpy(sendbuf,SRES);
send(NewConnection,sendbuf,1500,0);
printf("Server:");
printf("%s",sendbuf);
printf("/n");
}
else
{
printf("Server:");
printf("%s",FAIL);
strcpy(sendbuf,FAIL);
send(NewConnection,sendbuf,strlen(sendbuf),0);
printf("/n");
}
//继续输入
for(i=0;i<1500;i++)
{
recvbuf[i]='/0';
sendbuf[i]='/0';
}
recv(NewConnection,recvbuf,1500,0);
strcpy(recvbuf+strlen(recvbuf),eeee);
printf("Client:");
printf("%s",recvbuf);
//首部字段判断
for(i=0;i<4;i++)
head[i]=recvbuf[i];
}
else
{
printf("Server:");
printf("%s",FAIL);
strcpy(sendbuf,FAIL);
send(NewConnection,sendbuf,strlen(sendbuf),0);
printf("/n");
for(i=0;i<1500;i++)
{
recvbuf[i]='/0';
sendbuf[i]='/0';
}
recv(NewConnection,recvbuf,1500,0);
strcpy(recvbuf+strlen(recvbuf),eeee);
printf("Client:");
printf("%s",recvbuf);
//首部字段判断
for(i=0;i<4;i++)
head[i]=recvbuf[i];
}
if(!strcmp(head,QUIT))
break;
}
//第四部分QUIT单元
if(!strcmp(head,QUIT))
{
int m=0;
j=0;
//清空数据库
for(i=0;i<3;i++)
{
if(!strcmp(clientname,datafile[i].username))
{
datafile[i].ip="/0";
for(j=0;j<20;j++)
{
for(m=0;m<30;m++)
{
datafile[i].filename[j][m]='/0';
}
for(m=0;m<5;m++)
{
datafile[i].filename[j][m]='/0';
}
}
}
}
//发送结束信息
strcpy(sendbuf,EXIT);
send(NewConnection,sendbuf,strlen(sendbuf),0);
//closesocket(NewConnection);
break;
}
}
}
void main(void){
WSADA TA wsaData;
SOCKET ListeningSocket;
SOCKET NewConnection;
SOCKADDR_IN ServerAddr;
SOCKADDR_IN ClientAddr;
int port=8058;
int i,j,k,ret,iAddrSize;
HANDLE clientThread;
DWORD clientThreadId;
//用户ID表初始化
user[0].username="user0/0";
user[1].username="user1/0";
user[2].username="user2/0";
user[0].pwd="pwd0/0";
user[1].pwd="pwd1/0";
user[2].pwd="pwd2/0";
//数据库初始化
for(i=0;i<3;i++)
{
datafile[i].username=user[i].username;
datafile[i].ip="/0";
for(j=0;j<20;j++)
for(k=0;k<30;k++)
{
datafile[i].filename[j][k]='/0';
datafile[i].port[j][k]='/0';
}
}
//监听socket
WSAStartup(MAKEWORD(2,2),&wsaData);
ListeningSocket = socket(AF_INET,SOCK_STREAM,IPPROTO_TCP); ServerAddr.sin_family = AF_INET;
ServerAddr.sin_port = htons(port);
ServerAddr.sin_addr.s_addr = htonl(INADDR_ANY);
bind(ListeningSocket,(SOCKADDR *)&ServerAddr,sizeof(ServerAddr)); listen(ListeningSocket,5);
//多线程等待创建socket
while(1)
{
iAddrSize =
sizeof(ClientAddr); NewConnection=accept(ListeningSocket,(SO
CKADDR*)&ClientAddr,&iAddrSize);
if(NewConnection==INV ALID_SOCKET)
{
printf("accept() failed:%d/n",WSAGetLastError());
break;
}
printf("Acceptedclient :%s:%d/n",inet_ntoa(Clie ntAddr.sin_addr),ntohs(ClientAddr.sin_port));
clientThread=CreateThread(NULL,0,ClientThread,(LPVOID)NewCo nnection,0,&clientThreadId);
if(clientThread==NULL)
{
printf("createthreaad()failed:%d/n",GetLastError());
break;
}
CloseHandle(clientThread);
}
closesocket(ListeningSocket);
WSACleanup();
return;
}
以下是客户端程序源码:
#include
#include
#include
#include
#define QUIT "EXIT"
void main(void)
#define SRES "SRES/0"
#define EROR "EROR"
#define LGIN "LGIN/0"
{
WSADA TA wsaData;
SOCKET s;
SOCKADDR_IN ServerAddr;
int Port = 8058,i;
char recvbuf[1500]={'/0'};
char sendbuf[1500]={'/0'};
char head[5]={'/0'};
//建立连接
WSAStartup(MAKEWORD(2,2),&wsaData);
s = socket (AF_INET,SOCK_STREAM,IPPROTO_TCP);
ServerAddr.sin_family =AF_INET;
ServerAddr.sin_port = htons(Port);
ServerAddr.sin_addr.s_addr = inet_addr("127.0.0.1");
connect(s,(SOCKADDR *)&ServerAddr,sizeof(ServerAddr));
printf("please enter the username and password as the form :LGINusername#password/n");
while(1)
{
int key=0;
for(i=0;i<1500;i++)
{
recvbuf[i]='/0';
sendbuf[i]='/0';
}
printf("Client:");
scanf("%s",sendbuf);
//加密
for(i=0;i<4;i++)
{
head[i]=sendbuf[i];
}
if(!strcmp(head,LGIN))
{
char tem;
srand((unsigned)time(NULL));
key = rand() % 10;
if(strlen(sendbuf)>10)
{
for(i=13;i>=10;i--)
{
tem=sendbuf[i]-key;
sendbuf[i+1]=tem;
}
sendbuf[10]=key+48;
}
send(s,sendbuf,1500,0);
}
else
{
send(s,sendbuf,1500,0);
}
//判断QUIT
if(!strcmp(sendbuf,QUIT))
break;
//多次自动接受SRES结果
do{
recv(s,recvbuf,1500,0);
for(i=0;i<4;i++)