且构网

分享程序员开发的那些事...
且构网 - 分享程序员编程开发的那些事

linux下SOCKET深入

更新时间:2022-08-12 15:34:06

一、感谢网友提供的例子

1、def.h

#define MAX_LENGTH 1024

/*包头的结构体定义*/
typedef struct tPachHead
{
char iFileSize; /*为0表示发送的是文件名,1表示发送的是数据,2表示发送完毕*/
int lPayloadLength; /*当发送的是数据的时候,表示数据的大小*/
}PackHead;

/*协议包的结构体定义*/
typedef struct tAppPacket
{
PackHead tAppPackHead; /*包头的结构体*/
char byData[MAX_LENGTH]; /*数据段*/
}AppPacket;

#define MYPORT 8221 /*自定义端口*/
#define BACKLOG 10
2、c_s.c

//server & client
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "def.h"
void server_f(int);
void client_f(int);

/*获取文件名的函数*/
char *getfn(char *pathname)
{
int i,j;
for(i=0;pathname[i]!='\0';i++);
for(j=i;pathname[j]!='/';j--);
return(&pathname[j+1]);
}

int main()
{
int sockfd;
char c;
while(1) /*主界面循环*/
{
if((sockfd=socket(PF_INET,SOCK_STREAM,0))==-1) /*socket的建立*/
{
perror("socket");
exit(1);
}
printf("(%d)File system:\n",sockfd);
printf("1.Send\n");
printf("2.Recieve\n");
printf("3.Exit\n");
printf(">");
c=fgetc(stdin);getchar();
if(c=='1') /*选择了client端,负责发送,详见client_f函数*/
{
client_f(sockfd);
close(sockfd);
continue;
}
else if(c=='2') /*选择了server端,负责接收,详见server_f函数*/
{
server_f(sockfd);
close(sockfd);
continue;
}
else if(c=='3') /*退出*/
{
close(sockfd);
break;
}
else /*错误的操作*/
{
printf("Wrong instruction!\n");
close(sockfd);
continue;
}
}
printf("Bye-bye!\n");
exit(0);
}

/*server端作用函数,负责接收数据*/
void server_f(int sockfd)
{
int fd,sin_size,client_fd,recvbytes;
struct sockaddr_in my_addr,opp_addr;
AppPacket info,*pack;
pack=&info;
my_addr.sin_family=PF_INET;
my_addr.sin_port=htons(MYPORT);
my_addr.sin_addr.s_addr=INADDR_ANY;
bzero(&(my_addr.sin_zero),8);
if(bind(sockfd,(struct sockaddr *)&my_addr,sizeof(struct sockaddr))==-1)
{
perror("bind");
close(sockfd);
return;
}
if(listen(sockfd,BACKLOG)==-1)
{
perror("listen");
close(sockfd);
return;
}
printf("Waiting for connection...\n"); /*打印出等待连接信息,并在下面的accept处等待*/
if((client_fd=accept(sockfd,(struct sockaddr *)&opp_addr,&sin_size))==-1)
{
perror("accept");
close(client_fd);
close(sockfd);
return;
}
/*有连接过后先接收一个文件名的信息*/
if(recv(client_fd,pack,sizeof(AppPacket),0)==-1)
{
perror("recv");
close(client_fd);
close(sockfd);
return;
}
if(((pack->tAppPackHead).iFileSize)==0x00) /*判断为文件名确认信息*/
{
char u;
char pathname[100];
int temp;
char *tempbuf;
/*询问是否接收来自[IP]的大小为[大小]的[文件名]文件*/
printf("===From %s===\n",inet_ntoa(opp_addr.sin_addr));
printf("Do you wanna recieve %s ?(y)\n",pack->byData);
printf("SIZE:%d bytes\n",(pack->tAppPackHead).lPayloadLength);/*新加入文件大小信息*/
u=fgetc(stdin);getchar();
if(u=='y'||u=='Y') /*同意接收*/
{
while(1) /*存放地址的循环,带出错重输*/
{
printf("Type your path to place the file:\n");
scanf("%s",pathname);getchar();
strcat(pathname,pack->byData);
if((fd=open(pathname,O_WRONLY|O_CREAT|O_EXCL,0777)) {
perror("open");
continue;
}
else
break;
}
if(send(client_fd,&u,1,0) {
perror("Response");
close(fd);
close(client_fd);
close(sockfd);
return;
}
while(1) /*接收循环,以前出错的地方!记住,recv此函数不一定将send来的东西全部读完*/
{
/*先接收发过来的包*/
recvbytes=0;
tempbuf=malloc(sizeof(AppPacket)); /*使用临时缓冲区用来recv循环接收socket来的数据*/
while(recvbytes!= sizeof(AppPacket))/*当接收的数据没有达到预期值时,循环接收*/
{
if((temp=recv(client_fd,tempbuf+recvbytes,sizeof(AppPacket)-recvbytes,0))==-1)
{
perror("recv");
close(client_fd);
close(fd);
close(sockfd);
return;
}
recvbytes+=temp;
//printf("%d\n",recvbytes);

}

memcpy(pack,tempbuf,sizeof(AppPacket));/*把在临时缓冲区的数据拷贝到pack里面形成包*/
free(tempbuf);/*开心的释放:)*/
/*然后在下面进行判断是数据还是已经发完了*/
if(((pack->tAppPackHead).iFileSize)==0x01)/*来的是数据*/
{
write(fd,pack->byData,(pack->tAppPackHead).lPayloadLength);/*写入文件*/
// printf("!\n");
continue;
}
else if(((pack->tAppPackHead).iFileSize)==0x02)/*来的是结束信息*/
{
printf("Recieve ok!\n");
close(client_fd);
close(fd);
close(sockfd);
break;
}
/*出错处理!多亏了最开始还是考虑到了出错的情况,才得以发现问题!
因此养成出错处理是好习惯,尽管有些错逻辑上是不会出现的~*/
else
{
printf("Error.\n");
close(client_fd);
close(fd);
close(sockfd);
return;
}
}
return;
}
else /*不同意接收文件*/
{
if(send(client_fd,&u,1,0) {
perror("Response");
close(fd);
close(client_fd);
close(sockfd);
return;
}
printf("Connection denied...\n");
close(fd);
close(client_fd);
close(sockfd);
return;
}
}
else /*判断文件名验证信息失败,不是我们的写的协议,出错*/
{
printf("File protocol not match!\n");
close(client_fd);
return;
}
}
/*client端处理函数,负责发送文件*/
void client_f(int sockfd)
{
int sendbytes,size,fd;
AppPacket info,*pack;
struct hostent *host;
struct sockaddr_in opp_addr;
char pathname[100];
char dest[48],buf[5];
pack=&info;
/*指明发送文件循环,带出错重输*/
while(1)
{
printf("Please tell me which file do you wanna send:\n");
scanf("%s",pathname);getchar();
if((fd=open(pathname,O_RDONLY,0777)) {
perror("open");
continue;
}
else
{
struct stat stat;
strcpy(pack->byData,getfn(pathname));
(pack->tAppPackHead).iFileSize=0x00;
fstat(fd,&stat);
(pack->tAppPackHead).lPayloadLength=stat.st_size;/*把文件大小信息也装进去*/
break;
}
}
/*指明发送IP循环,带出错重输*/
while(1)
{
printf("And tell me where do you wanna send:\n");
scanf("%s",dest);getchar();
if((host=gethostbyname(dest))==NULL)
{
perror("gethost");
continue;
}
else
break;
}
opp_addr.sin_family=PF_INET;
opp_addr.sin_port=htons(MYPORT);
opp_addr.sin_addr=*((struct in_addr *)host->h_addr);
bzero(&(opp_addr.sin_zero),8);
/*开始连接server端,前提是server端已经准备好,在等待连接状态,否则返回*/
if(connect(sockfd,(struct sockaddr *)&opp_addr,sizeof(struct sockaddr))==-1)
{
perror("connect");
close(fd);
return;
}
/*先把文件名发过去*/
if(send(sockfd,pack,sizeof(AppPacket),0)==-1)
{
perror("send filename");
return;
}
printf("Waiting for response...\n");/*打印等待回应信息,并在下面recy处等待*/
/*接收请求反馈信息*/
char u;
if(recv(sockfd,&u,1,0)==-1)
{
perror("check");
return;
}
if(u=='y'||u=='Y')/*'y'或者'Y'表示同意*/
{
printf("Sending request accept...\n");
sleep(1);//此处就是因为会出现发送接收不同步的情况,所以我先睡1秒,稳一下*/

/*发送数据的循环*/
while((size=read(fd,pack->byData,MAX_LENGTH))>0)
{
(pack->tAppPackHead).iFileSize=0x01;/*因为是数据,所以填1*/
(pack->tAppPackHead).lPayloadLength=size;/*读了多少填多少*/
if(send(sockfd,pack,sizeof(AppPacket),0) {
perror("send");
close(fd);
return;
}
}
/*发送完毕,发送结束信息给server端*/
memset(pack->byData,0,MAX_LENGTH);
(pack->tAppPackHead).iFileSize=2;/*结束的标志*/
(pack->tAppPackHead).lPayloadLength=-1;/*不需要数据大小,防止出错填-1*/
if(send(sockfd,pack,sizeof(AppPacket),0) {
perror("send");
close(fd);
return;
}
printf("Send ok!\n");
close(fd);
return;
}
else /*发送请求被驳回...T_T,太冷血了*/
{
printf("Sending request denied...\n");
close(fd);
return;
}
}

二、大文件上传下载,思考多线程编程,类似迅雷

server是父进程,由父进程监听请求,新建一个子进程来应答客户端请求,并且子进程在文件传输前,根据网速开设多线程,使用多个线程共同完成一个进程的任务。