TCP/IP编程实现远程文件传输(UNIX)

 在TCP/IP网络结构中,为了确保网络安全,网络人员往往需要在路由器上添加防火墙,禁止非法用户用ftp等安全危害较大的TCP/IP协议访问主机。而有时系统维护人员需要用ftp将一些文档从中央机房主机传到前端网点主机上,比如应用程式的替换升级。假如每次传输文档时都要打开防火墙,未免显得有些繁琐,要是在自己的应用程式中增加一个专门的文档传输模块,那将是十分愉快的事情。

  UNIX网络程式设计一般都采用套接字(socket)系统调用。针对现在十分流行的客户/服务器模式,其程式编写步骤如下:

  1.Socket系统调用

  为了进行网络I/O,服务器和客户机两端的UNIX进程要做的第一件事是调用socket()系统调用,建立软插座,指明合适的通讯协议。格式为:

  #include >sys/types.h>

  #include >sys/socket.h>

  int socket(int family,int type,int protocol)

  其中:(1)family指明套节字族,其值包括:

  AF_UNIX

  (UNIX内部协议族)

  AF_INET

  (Iternet协议)

  AF_NS (XeroxNs协议,TCP/IP编程取该值)

  AF_IMPLINK

  (IMP链接层)

  (2)type 指明套接字类型,取值有:

  SOCK_STREAM

  (流套接字)

  SOCK_DGRAM

  (数据报套接字)

  SOCK_RAW

  (原始套接字)

  SOCK_SEQPACKET

  (定序分组套接字)

  一般情况下,前两个参数的组合就能够决定所使用的协议,这时第三个参数被置为0,假如第一个参数为AF_INET,第二个参数选SOCK_STREAM,则使用的协议为TCP;第二个参数选SOCK_DGRAM,则使用的协议为UDP;当第二个参数选SOCK_RAW时,使用的协议为IP。值得指出的是并不是任何的族和类型的组合都是合法的,具体请查阅相关资料。该系统调用若成功则返回一个类似文档描述符,成为套节字描述字,能够像文档描述符那样用read和write对其进行I/O操作。当一个进程使用完该软插座时,需用close(<描述符>)关闭(具体见后面内容)。

  2.服务器端Bind系统调用

  软插座创建时并没有和任何地址相关联,必须用bind()系统调用为其建立地址联系。其格式为:

  #include <sys/types.h>

  #include <sys/socket.h>

  int bind(int socketfd,struct sockaddr_in *localaddr,sizeof(localaddr));

  其中:(1)第一个参数socketfd是前步socket()系统调用返回的套节字描述符。

  (2)第二个参数被捆向本地地址的一种结构,该结构在sys/netinet/in.h中定义:

  struct sockaddr_in{

  short sin_family;/*socket()系统调用的协议族如AF_INET*/

  u_short sin_port;/*网络字节次序形式的端口号码*/

  struct in_addr sin_addr;/*网络字节次序形式的网络地址*/

  char sin_zero[8];

  }

  一台机器上的每个网络程式使用一个各自单独的端口号码,例如:telnet程式使用端口号23,而ftp文档传输程式使用端口号21。我们在设计应用程式时,端口号码能够由getservbyname()函数从/etc/services库文档中获取,也能够由htons (int portnum)函数将任意正整数转换为网络字节次序形式来得到,有些版本的UNIX操作系统则规定1024以下的端口号码只可被终极用户使用,普通用户程式使用的端口号码只限于1025到32767之间。网络地址能够由gethostbyname(char*hostname)函数得到(该函数和getservbyname()相同都以网络字节次序形式返回任何在他们结构中的数据),参数hostname为/etc/hosts文档中某一网络地址所对应的机器名。该函数返回一个类型为hostent的结构指针,hostent结构在netdb.h中定义:

  struct hostent{

  char *h_name;

  char **h_aliases;

  int h_addrtype;

  int h_length;

  /*地址长度*/

  char **h_addr_list;

  #define h_addr h_addr_list[0];/*地址*/

  }

  (3)第三个参数为第二个结构参数的长度,假如调用成功,bind返回0,否则将返回-1并配置errno。

  3.服务器端系统调用listen,使服务器愿意接受连接

  格式:int listen(int socketfd,int backlong)

  他通常在socket和bind调用后在accept调用前执行。第二个参数指明在等待服务器执行accept调用时系统能够排队多少个连接需要。此参数常指定为5,也是现在允许的最大值。

  4.服务器调用accept,以等待客户机调用connect进行连接。格式如下:

  int newsocket=(int socketfd,struct sockaddr_in *peer,int*addrlen);

  该调用取得队列上的第一个连接请求并建立一个具备和sockfd相同特性的套节字。假如没有等待的连接请求,此调用阻塞调用者直到一连接请求到达。连接成功后,该调用将用对端的地址结构和地址长度填充参数peer和addlen,假如对客户端的地址信息不感兴趣,这两个参数用0代替。

  5.客户端调用connect()和服务器建立连接。格式为:

  connect(int socketfd,struct sockaddr_in *servsddr,int addrlen)

  客户端取得套接字描述符后,用该调用建立和服务器的连接,参数socketfd为socket()系统调用返回的套节字描述符,第二和第三个参数是指向目的地址的结构及以字节计量的目的地址的长度(这里目的地址应为服务器地址)。调用成功返回0,否则将返回-1并配置errno。

  6.通过软插座发送数据

  一旦建立连接,就能够用系统调用read和write像普通文档那样向网络上发送和接受数据。Read接受三个参数:一个是套节字描述符;一个为数据将被填入的缓冲区,更有一个整数指明要读的字节数,他返回实际读入的字节数,出错时返回-1,碰到文档尾则返回0。Write也接受三个参数:一个是套节字描述符;一个为指向需要发送数据的缓冲区,更有一个整数指明要写入文档的字节个数,他返回实际写入的字节数,出错时返回-1。当然,也能够调用send和recv来对套节字进行读写,其调用和基本的read和write系统调用相似,只是多了一个发送方式参数。

  7.退出程式时,应按正常方式关闭套节字。格式如下:

  int close(socketfd)

  前面介绍了UNIX客户/服务器模式网络编程的基本思路和步骤。值得指出的是socket编程所涉及的系统调用不属于基本系统调用范围,其函数原形在libsocket.a文档中,因此,在用cc命令对原程式进行编译时需要带-lsocket选项。

  现在,我们能够针对文章开头提出的问题着手进行编程了。在图示的网络结构中,为使中央机房的服务器能和网点上的客户机进行通信,需在服务器端添加通过路由器1 1 1 2到客户机的路由,两台客户机也必须添加通过路由器2 2 2 1到服务器的路由。在服务器的/etc/hosts文档中应该包含下面内容:

  1.1.1.1

  server

  2.2.2.2

  cli1

  2.2.2.3

  cli2

  客户机的/etc/hosts文档中应该有本机地址信息和服务器的地址信息,如cli1客户机的/etc/hosts文档:

  2.2.2.2

  cli1

  1.1.1.1

  server

  网络环境搭建好后,我们能够在服务器端编写fwq.c程式,负责接受客户机的连接请求,并将从源文档中读取的数据发送到客户机。客户机程式khj.c向服务器发送连接请求,接收从服务器端发来的数据,并将接收到的数据写入目标文档。源程式如下:

  /*服务器源程式fwq.c*/

  #include <stdio.h>

  #include <sys/types.h>

  #include <sys/fcntl.h>

  #include <sys/socket.h>

  #include <sys/netinet/in.h>

  #include <netdb.h>

  #include <errno.h>

  main()

  {

  char c,buf[1024],file[30];

  int fromlen,source;

  register int k,s,ns;

  struct sockaddr_in sin;

  struct hostent *hp;

  system(″clear″);

  printf(″

  ″);

  printf(″

   输入要传输的文档名:″);

  scanf(″%s″,file);

  if ((source=open(file,O_RDONLY))<0){

  perror(″源文档打开出错″);

  exit(1);

  }

  printf(″

   在传送文档,稍候…″);

  hp=gethostbyname(″server″);

  if (hp==NULL){

  perror(″返回主机地址信息错!!!″);

  exit(2);

  }

  s=socket(AF_INET,SOCK_STREAM,0);

  if(s<0){

  perror(″获取SOCKET号失败!!!″);

  exit(3);

  }

  sin.sin_family=AF_INET;

  sin.sin_port=htons(1500);/*使用端口1500*/

  bcopy(hp->h_addr,&sin.sin_addr,hp->h_length);

  if(bind(s,&sin,sizeof(sin))<0){

  perror(″不能将服务器地址捆绑到SOCKET号上!!!″);

  colse(s);

  exit(4);

  }

  if(listen(s,5)<0{

  perror(″sever:listen″);

  exit(5);

  }

  while(1){

  if((ns=accept(s,&sin,&fromlen))<0){

  perror(″sever:accept″);

  exit(6);

  }

  lseek(source,OL,0);/*每次接受客户机连接,应将用于读的源文档指针移到文档头*/

  write(ns,file,sizeof(file)); /*发送文档名*/

  while((k=read(source,buf,sizeof(buf)))>0)

  write(ns,buf,k);

  printf(″

   传输完毕!!!

  ″);

  close(ns);

  }

  close(source);

  exit(0);

  /*客户机源程式khj.c*/

  #include >stdio.h>

  #include >sys/types.h>

  #include >sys/fcntl.h>

  #include >sys/socket.h>

  #include >sys/netinet/in.h>

  #include >netdb.h>

  #include >errno.h>

  #include >string.h>

  main()

  {

  char buf[1024],file[30];

  char *strs=″

   正在接收文档″;

  int target;

  register int k,s;

  struct sockaddr_in sin;

  struct hostent *hp;

  system(″clear″);

  printf(″

  ″);

  hp=gethostbyname(″server″);

  if(hp==NULL){

  perror(″返回服务器地址信息错!!!″);

  exit(1);

  }

  s=socket(AF_INET,SOCK_STREAM,0);

  if(s<0){

  perror(″获取SOCKET号失败!!!″);

  exit(2);

  }

  sin.sin_family=AF_INET;

  sin.sin_port=htons(1500);/*端口号需和服务器程式使用的一致*/

  bcopy(hp->h_addr,&sin.sin_addr,hp->h_length);

  printf(″

   正在和服务器连接…″);

  if(connect(s,&sin,sizeof(sin),0)<0){

  perror(″不能和服务器连接!!!″);

  exit(3);

  }

  while((k=read(s,file,sizeof(file)))<?0/*接收文档名*/

  if((target=open(file,o_WRONLY|O_CREAT|O_TRUNC,0644))<0){

  perror(″不能打开目标文档!!″);

  exit(4);

  }

  strcat(strs,file);

  strcat(strs,″,稍候…″);

  write(1,strs,strlen(strs));

  while((k=read(s,buf,sizeof(buf)))>0)

  write(tatget,buf,k);

  printf(″

   接收文档成功!!!

  ″);

  close(s);

  close(target);

  }

  上述程式在Sco Unix System v3.2及Sco TCP/IP Rumtime环境下调试通过。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值