Linux socket文件传输
参考资料[1]套接字传输文件的试验
1 简介
在《Linux socket编程案例》中,实现了服务器/客户端之间的字符串传输,本文要将其改造为实现文件传输。
角色:服务器--文件接收者
客户端--文件发送者
2 主要流程
文件发送端(client):fopen(打开本地文件)->fread(读取本地文件内容)->write(通过socket将本地文件内容发送到服务器)
文件接收端(server):read(通过socket读取client发送过来的内容)->fopen(创建一个本地文件)->fwrite(将通过socket接收的内容写入文件)
3 主要难题
文件传输一般需要协议(例如ftp)。由于这里基于TCP实现文件传输,发送端/接收端之间需要协调一致,例如,何时开始传输,何时结束传输。
开始传输:如果发送端与接收端连接上,就随时可以传输内容,一般以发送端为主导;
结束传输:a) 接收端主动结束;b) 发送端主动结束。
文件的开始传输比较简单,这里重点分析结束传输。
3.1 接收端主动结束
接收端主动结束接受文件,有多种情况:a)当接受不到新数据时,结束文件接收;当接收到特定的字符时,停止接收。
对于第a)种情况,如果文件传输过程中出现中断,或者网络状况不好时,数据之间出现时间间隔,将会导致错误的结束文件传输。
3.2 发送端主动结束
3.2.1 发送结束符
文件发送端发送完数据后,发送一个结束符告诉接收端,通知其结束文件接收。
3.2.2 断开连接
文件发送端发送完数据后,直接断开与接收端(服务器)的连接。接受端要负责检测连接是否已经断开,如果是,则停止接受文件。本文将重点分析此方法。下面,先给出发送端代码:
/******* 发送端:客户端sent.c ************/
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>
#include <netdb.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <sys/types.h>
#include <arpa/inet.h>
int main(int argc, char *argv[])
{
int sockfd;
char buffer[1024];
struct sockaddr_in server_addr;
struct hostent *host;
int portnumber;
FILE *fp = fopen( "./sent.c", "rb" );
if ( fp == NULL) {
fprintf(stderr, "Open file error\n");
exit( 1 );
}
if( argc != 3) {
fprintf( stderr, "Usage:%s hostname portnumber\a\n", argv[0] );
exit(1);
}
if( ( host = gethostbyname( argv[1] ) ) == NULL) {
fprintf(stderr,"Gethostname error\n");
exit(1);
}
if( ( portnumber = atoi( argv[2] ) )<0) {
fprintf( stderr, "Usage:%s hostname portnumber\a\n", argv[0] );
exit(1);
}
/* 客户程序开始建立 sockfd描述符 */
if( ( sockfd = socket( AF_INET,SOCK_STREAM, 0 ) ) == -1) {
fprintf( stderr, "Socket Error:%s\a\n", strerror(errno) );
exit(1);
}
/* 客户程序填充服务端的资料 */
bzero( &server_addr, sizeof( server_addr ) );
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons( portnumber );
server_addr.sin_addr = *( ( struct in_addr * )host->h_addr );
/* 客户程序发起连接请求 */
if( connect( sockfd, ( struct sockaddr * )( &server_addr ), sizeof( struct sockaddr ) ) ==-1 ) {
fprintf(stderr, "Connect Error:%s\a\n", strerror(errno));
exit(1);
}
size_t nreads, nwrites;
while( nreads = fread( buffer, sizeof(char), sizeof( buffer ), fp) ) {
if ( ( nwrites = write( sockfd, buffer , nreads) ) != nreads ) {
fprintf(stderr, "write error\n");
fclose( fp );
close( sockfd );
exit( 1 );
}
}
/* 结束通讯 */
fclose( fp );
close( sockfd );
exit(0);
}
说明:上述代码将本地文件sent.c读取并通过socket发送。
4 网络连接状况检测
根据第3章的描述,为了实现由文件发送端断开连接而达到结束文件传输的目的,需要接收端能够检测当前的网络状况(是否已经断开)。那么,怎样检测呢[11]? 参考资料[9]说明了如果不检测是否已经断开就继续发数据,将会导致程挂掉。参考资料[10]总结了判断客户端socket断开连接的方法,本文选择其方法二,接收端完整代码如下:
/******* 文件接收端:服务器(recv.c) ************/
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>
#include <netdb.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <sys/types.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <netinet/tcp.h> // struct tcp_info类定义
#include <stdbool.h> // bool类型[12]
int main(int argc, char *argv[])
{
int sockfd;
struct sockaddr_in server_addr;
struct sockaddr_in client_addr;
int portnumber;
if( argc != 2 ) {
fprintf(stderr,"Usage:%s portnumber\a\n", argv[0]);
exit(1);
}
if( ( portnumber = atoi( argv [1] )) < 0 ){
fprintf(stderr,"Usage:%s portnumber\a\n",argv[0]);
exit(1);
}
/* 服务器端开始建立socket描述符 */
if( ( sockfd = socket( AF_INET, SOCK_STREAM, 0) ) == -1 ) {
fprintf(stderr,"Socket error:%s\n\a",strerror(errno));
exit(1);
}
/* 服务器端填充sockaddr结构 */
bzero( &server_addr, sizeof(struct sockaddr_in) );
server_addr.sin_family = AF_INET;
server_addr.sin_addr.s_addr = htonl( INADDR_ANY );
server_addr.sin_port = htons( portnumber );
/* 捆绑sockfd描述符 */
if( bind( sockfd, (struct sockaddr *)(&server_addr), sizeof(struct sockaddr))==-1) {
fprintf( stderr, "Bind error:%s\n\a", strerror( errno ) );
exit(1);
}
/* 监听sockfd描述符 */
if( listen(sockfd,5) == -1) {
fprintf( stderr, "Listen error:%s\n\a", strerror( errno ) );
exit(1);
}
while( 1 )
{
/* 服务器阻塞,直到客户程序建立连接 */
int sin_size = sizeof(struct sockaddr_in);
int new_fd = accept( sockfd, (struct sockaddr *)(&client_addr), &sin_size );
if( new_fd == -1 ) {
fprintf(stderr,"Accept error:%s\n\a", strerror( errno ) );
exit( 1 );
}
printf("Server get connection from %s\n",
inet_ntoa( client_addr.sin_addr ) );
bool tcp_established = true;
FILE *save_fp = fopen("out.txt", "w");
while( tcp_established ) {
struct tcp_info info;
int len = sizeof(info);
getsockopt( new_fd, IPPROTO_TCP, TCP_INFO, &info, (socklen_t *)&len );
if( info.tcpi_state == TCP_ESTABLISHED ) { // TCP连接还没有中断,可以读数据
char buffer[1024];
ssize_t length = read( new_fd, buffer, sizeof(buffer) );
if( length == -1 ) {
fprintf(stderr, "Read Error:%s\n", strerror( errno ));
exit(1);
}
else if ( length > 0){
fwrite(buffer, sizeof(char), length, save_fp);
}
}
else {
tcp_established = false;
fclose( save_fp );
printf("received finished !\n");
}
}
/* 这个通讯已经结束 */
close( new_fd );
/* 循环下一个 */
}
close( sockfd );
exit(0);
}
说明:上述代码将接受到的文件内容保存于out.txt文件中。
5 代码完善
更加完善的代码,见《Linux socket文件传输2》
参考资料
[1]套接字传输文件的试验
[2]套接字实现文件传输
[3]怎么利用套接字传输大文件
更多推荐
所有评论(0)