基本通讯建立流程
#详见 《Linux高级程序设计》.第五章 或 《UNIX网络编程》.第四章
简单思路
- 性能相比下更好的一方作为服务端
- 相互透明。对于双方而言,只存在数据的传入/传出
- 通用性。可随时调用该程序传输
- 服务端只负责等待连接与接收数据
- 工作于同一局域网下(功能简单,只考虑数据传输)
- 使用TCP , IPv4
一些细节
- accept与recv都可能阻塞程序。
- 当recv堵塞时客户端断开连接会使其返回0
- TCP发送缓冲区满时,send将堵塞
服务端源码
/**
argv[1]:IP地址
argv[2]:端口号
示范:
# ./filesoc 192.168.1.1 8081
*/
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>
#include <stdlib.h>
int main( int argc , char *argv[] ) {
//----------build link----------//
//----------------------------------------------------------------------------------------------------------//
int mysoc , tosoc , len , sin_size;
struct sockaddr_in myaddr , toaddr ;
char buf[BUFSIZ] ;
//装填sockaddr结构内容
memset( &myaddr , 0 , sizeof (myaddr) ) ;
myaddr.sin_family = PF_INET ;
myaddr.sin_addr.s_addr = inet_addr(argv[1]) ;
myaddr.sin_port = htons(atoi(argv[2])) ;
printf( "Server IP %s:%s\n" , argv[1] , argv[2] ) ;
//socket()
printf("Setting socket.....") ;
if ( ( mysoc = socket( PF_INET , SOCK_STREAM , 0 ) ) < 0 )
return printf("errof\n") ;
printf("done\n") ;
//bind()
printf("Setting bind.....") ;
if( bind( mysoc , (struct sockaddr*)&myaddr , sizeof (struct sockaddr) ) < 0 )
return printf("error\n") ;
printf("done\n") ;
//listen()与 accept()
listen( mysoc , 2 ) ;
printf("default number of links: 2\n") ;
printf("withing accept....") ;
sin_size = sizeof (struct sockaddr) ;
if ( ( tosoc = accept( mysoc , (struct sockaddr*)&toaddr , &sin_size ) ) < 0 )
printf("error\n") ;
printf("done\nlink to %s\n" , inet_ntoa(toaddr.sin_addr) ) ;
//发送一条欢迎消息
send( tosoc , "link done\n" , 10 , 0 ) ;
//----------------------------------------------------------------------------------------------------------//
//客户端的第一条消息是文件名,服务端依照此消息建立同名文件
FILE *fp ;
len = recv( tosoc , buf , BUFSIZ , 0 ) ;
buf[len] = '\0' ;
printf(buf,"\n") ;
if ( ( fp = fopen( buf , "ab" ) ) == NULL )
return printf("open file error\n") ;
//由客户端发送数据,由客户端关闭连接
printf("creating file.....") ;
while ( ( len = recv( tosoc , buf , BUFSIZ , 0 ) ) != 0 )
fwrite( buf , 1 , len , fp ) ;
printf("done") ;
printf("closed.....") ;
shutdown( tosoc , 2 ) ;
fclose(fp);
printf("done\n") ;
return 0 ;
}
客户端实现
/**
argv[1]:服务器IP地址
argv[2]:服务器程序端口号
argv[3]: 文件名
示范:
# ./tofilesoc 192.168.1.1 8081 new.jpg
*/
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>
#include <stdlib.h>
int main( int argc , char *argv[] ) {
//----------build link----------//
//----------------------------------------------------------------------------------------------------------//
FILE *fp ;
int mysoc , len ;
struct sockaddr_in toaddr ;
char buf[BUFSIZ] ;
//装填sockaddr结构
memset( &toaddr , 0 , sizeof (toaddr) ) ;
toaddr.sin_family = AF_INET ;
toaddr.sin_addr.s_addr = inet_addr(argv[1]) ;
toaddr.sin_port = htons(atoi(argv[2])) ;
printf( "Server IP %s:%s\n" , argv[1] , argv[2] ) ;
//socket()
printf("Setting socket.....") ;
if ( ( mysoc = socket( AF_INET , SOCK_STREAM , 0 ) ) < 0 )
return printf("errof\n") ;
printf("done\n") ;
//connect()
printf("Setting connect.....") ;
if ( connect( mysoc , (struct sockaddr*)&toaddr ,
sizeof (struct sockaddr) ) < 0 )
return printf("error\n") ;
printf("done\n") ;
//----------------------------------------------------------------------------------------------------------//
//接收服务器发来的欢迎消息
len = recv( mysoc , buf , BUFSIZ , 0 ) ;
buf[len] = '\0' ;
printf(buf) ;
//打开文件,发送文件名
if ( ( fp = fopen( argv[3] , "rb" ) ) == NULL )
return printf("open file error") ;
send( mysoc , argv[3] , stelen(argv[3]) , 0 ) ;
//发送,当理想发送字节长度与实际长度不符,报错退出
printf("sending.....") ;
while (!feof(fp)) {
len = fread( buf , 1 , BUFSIZ , fp ) ;
if ( len != send( mysoc , buf , BUFSIZ , 0 ) ) {
printf("error\n") ;
break ;
}
}
printf("done\n") ;
printf("closed.....") ;
shutdown( mysoc , 2 ) ;
printf("done\n") ;
return 0 ;
}
总结
优点:
- 极简
- 通用,可所随时调用
缺点:
- 只能工作于极小型网络
理由:考虑多路由网络,到达指定路由可以由多条路线,此时两次发送的数据到达顺序不一致(因为只要服务端接收到数据,则立马填入文件,但可以简单的给每发送的数据一个序号解决) - 功能不完善,可能引发一系列错误
实例
主机:kali linux gcc version 9.3.0 ( Debian 9.3.0-15 )
树莓派:官方系统 lite版 gcc version 9.3.0 ( Debian 9.3.0-15 )
网络:2.4G无线局域网
文件:图片
服务器等待连接,现在我们去启动客户端
客户端(树莓派)
中间似乎由上面奇怪的代码,别担心,那是我的树莓派电压不足的表现。先看看服务端的反映
工作以及完成 , 看看效果
图片可以正常观看
来看看图片大小
2 596 864 字节
源码中设置为每次传输512个字节(BUFSIZ定义为512),效果还是不错的