openKylin论坛

 找回密码

socket传输疑问。 [复制链接]

首先献上我的代码:
server.c:
---------------------------------------------------------------------------------------
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <string.h>
#include <sys/sendfile.h>
#include <sys/stat.h>
#include <fcntl.h>

#define MYPORT 5588
#define FILE_BLOCK 1048576
#define MAX_FILENAME_LEN 5000
void run();
void push_file(int fd,char* cmd);
void pull_file(int fd,char* cmd);
int main(int argc,char **argv){

  switch(fork()){
  case -1:
    puts("cannot fork\n");
    exit(-1);
  case 0:
    setsid();
    switch(fork()){
    case -1:
      puts("cannot fork\n");
      exit(-1);
    case 0:
      //       close(0);
      //      close(1);
      //     close(2);
      chdir("/");
      run();
      break;
    default:
      exit(0);
    }

  default:
    exit(0);
  }

  run();
  return 0;
}

void run(){
  int socket_fd,connect_fd,remote_addr_len,msg_len;
  struct sockaddr_in local_addr,remote_addr;
  char *recv_cmd =(char*) malloc(MAX_FILENAME_LEN);
  if((socket_fd = socket(AF_INET,SOCK_STREAM,0))==-1){
    perror("socket\n");
    exit(-1);
  }

  local_addr.sin_family = AF_INET;
  local_addr.sin_port = htons(MYPORT);
  local_addr.sin_addr.s_addr = INADDR_ANY;
  bzero(&(local_addr.sin_zero),8);

  if((bind(socket_fd,&local_addr,sizeof(local_addr)))==-1){
    perror("bind\n");
    exit(-1);
  }

  if((listen(socket_fd,10))==-1){
    perror("listen\n");
    exit(-1);
  }
  while(1){
    char *cmd = recv_cmd;
    remote_addr_len = sizeof(remote_addr);
    if((connect_fd=accept(socket_fd,&remote_addr,&remote_addr_len))==-1){
      perror("accept\n");
      exit(-1);
    }
    ssize_t real_len;
    if((real_len=recv(connect_fd,cmd,MAX_FILENAME_LEN,0))==-1){
      perror("recv\n");
      exit(-1);
    }
    cmd[real_len] = '\0';
    puts(cmd);
    if(!strncmp(cmd,"push",4)){
      cmd +=4;
      push_file(connect_fd,cmd);
    }
    else if(!strncmp(cmd,"pull",4)){
      cmd+=4;
      pull_file(connect_fd,cmd);
    }

  }

}

void push_file(int fd,char* filename){

  FILE *lf;
  ssize_t real_len;
  void *tmpbuf = malloc(FILE_BLOCK);
if(!tmpbuf){
    puts("no enouch memory!\n");
    return;
  }
  real_len = recv(fd,tmpbuf,FILE_BLOCK,0);
  if(real_len<0){
    free(tmpbuf);
    close(fd);
    return;
  }
  lf = fopen(filename,"wb");
  if(!lf){
    fprintf(stderr,"can not write file %s\n",filename);
    close(fd);
    free(tmpbuf);
    return;
  }
  fwrite(tmpbuf,1,real_len,lf);
  while(1){
    real_len = recv(fd,tmpbuf,FILE_BLOCK,0);
    fwrite(tmpbuf,1,real_len,lf);
    if(real_len<=0){
      fclose(lf);
      close(fd);
      free(tmpbuf);
      return;
    }

  }

}

void pull_file(int fd,char* filename){
  int lf = open(filename,O_RDONLY);
  if(fd==-1){
    fprintf(stderr,"can not open file %s\n",filename);
    close(fd);
    return;
  }
  struct stat file_stat;
  fstat(lf,&file_stat);
  off_t file_offset=0;
  if(sendfile(fd,lf,&file_offset,file_stat.st_size)!=file_stat.st_size){
    fprintf(stderr,"error occur when sending file %s\n",filename);
  }
  close(fd);
  close(lf);
}
--------------------------------------------------------------------------------------------

client.c:
--------------------------------------------------------------------------------------------
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/wait.h>
#include <unistd.h>
#include <sys/sendfile.h>
#include <sys/stat.h>
#include <fcntl.h>

#define FILE_BLOCK 1048576
#define MAX_NAME_LEN 5000
void push_file(int fd,char*);
void pull_file(int fd,char*);

int main(int argc,char **argv){

  int sock_fd;
  struct sockaddr_in remote_addr;
  char *local_file;
  if(argc!=6){
    fprintf(stderr,"use: %s  ipaddr port push[|pull] localfile[|remotefile] remotefile[|localfile]\n",argv[0]);
    exit(-1);
  }
  if(strlen(argv[4])>MAX_NAME_LEN||strlen(argv[5])>MAX_NAME_LEN){
    fprintf(stderr,"Error: filename too long!\n");
    exit(-1);
  }
  const char *remote_ip = argv[1];
  const char *remote_port = argv[2];
  remote_addr.sin_family = AF_INET;
  remote_addr.sin_port = htons(atoi(remote_port));
  remote_addr.sin_addr.s_addr = inet_addr(remote_ip);
  bzero(&(remote_addr.sin_zero),8);
  sock_fd = socket(AF_INET,SOCK_STREAM,0);
  if(sock_fd==-1){
    perror("socket\n");
    exit(-1);
  }
  if((connect(sock_fd,&remote_addr,sizeof(remote_addr)))==-1){
    perror("connect\n");
    exit(-1);
  }
  if(!strcmp(argv[3],"push")){
    char *cmd = (char*) malloc(strlen(argv[3])+strlen(argv[5])+1);
    strcpy(cmd,argv[3]);
    strcat(cmd,argv[5]);
    if((send(sock_fd,cmd,MAX_NAME_LEN,0))==-1){
      perror("send\n");
    }
    free(cmd);
    push_file(sock_fd,argv[4]);
  }
  else if(!strcmp(argv[3],"pull")){
    char *cmd = (char*) malloc(strlen(argv[3])+strlen(argv[4])+1);
    strcpy(cmd,argv[3]);
    strcat(cmd,argv[4]);
    if((send(sock_fd,cmd,strlen(cmd),0))==-1){
      perror("send\n");
    }
    free(cmd);
    pull_file(sock_fd,argv[5]);
  }
  else{
    fprintf(stderr,"no such command: %s\n",argv[3]);
    close(sock_fd);
  }
}

void push_file(int fd,char *filename){
   int lf = open(filename,O_RDONLY);
   if(fd==-1){
    fprintf(stderr,"can not open file %s\n",filename);
    close(fd);
    return;
  }
  struct stat file_stat;
  fstat(lf,&file_stat);
  off_t file_offset=0;
  if(sendfile(fd,lf,&file_offset,file_stat.st_size)!=file_stat.st_size){
    fprintf(stderr,"error occur when transfering file %s \n",filename);
  }
  close(fd);
  close(lf);
}

void pull_file(int fd,char *filename){
  void *tmpbuf = malloc(FILE_BLOCK);
  if(!tmpbuf){
    puts("no enouch memory!\n");
    return;
  }
  ssize_t real_len = recv(fd,tmpbuf,FILE_BLOCK,0);
  if(real_len<0){
    fprintf(stderr,"file %s not found in remote pc.\n",filename);
    close(fd);
    free(tmpbuf);
    return;
  }       
  FILE *lf = fopen(filename,"wb");
  if(!lf){
    fprintf(stderr,"can not write file %s\n",filename);
    free(tmpbuf);
    close(fd);
    return;
  }
  fwrite(tmpbuf,1,real_len,lf);
  while(1){
    real_len = recv(fd,tmpbuf,FILE_BLOCK,0);
    fwrite(tmpbuf,1,real_len,lf);
    if(real_len<=0){
      fclose(lf);
      close(fd);
      free(tmpbuf);
      return;
    }
  }
}
---------------------------------------------------------------------------------

软件意图很简单,就是传输文件,不过我不明白,send和recv这两个函数是不是成对出现的?有没有可能连续调用两次send后,只调用一次recv就把那两次send的数据全部接收完?或者一次send的数据需要两次recv才能接收完?

起初,我client端第一次send的时候,是send(sock_fd,cmd,strlen(cmd),0),结果server端第一次recv得到的数据中竟然包含有client端第二次发送的数据,表现为server端生成的文件名包含有文件魔数,比如发送png时名字最后有PNG,发送elf时名字含有ELF.
楼主
发表于 2013-5-6 20:46:35
回复

使用道具 举报

socket传输疑问。 [复制链接]

Tcp协议本身是可靠的,并不等于应用程序用tcp发送数据就一定是可靠的.不管是否阻塞,send发送的大小,并不代表对端recv到多少的数据.

在阻塞模式下, send函数的过程是将应用程序请求发送的数据拷贝到发送缓存中发送并得到确认后再返回.但由于发送缓存的存在,表现为:如果发送缓存大小比请求发送的大小要大,那么send函数立即返回,同时向网络中发送数据;否则,send向网络发送缓存中不能容纳的那部分数据,并等待对端确认后再返回(接收端只要将数据收到接收缓存中,就会确认,并不一定要等待应用程序调用recv);

在非阻塞模式下,send函数的过程仅仅是将数据拷贝到协议栈的缓存区而已,如果缓存区可用空间不够,则尽能力的拷贝,返回成功拷贝的大小;如缓存区可用空间为0,则返回-1,同时设置errno为EAGAIN.


linux下可用sysctl -a | grep net.ipv4.tcp_wmem查看系统默认的发送缓存大小:
net.ipv4.tcp_wmem = 4096 16384 81920
这有三个值,第一个值是socket的发送缓存区分配的最少字节数,第二个值是默认值(该值会被net.core.wmem_default覆盖),缓存区在系统负载不重的情况下可以增长到这个值,第三个值是发送缓存区空间的最大字节数(该值会被net.core.wmem_max覆盖).
根据实际测试,如果手工更改了net.ipv4.tcp_wmem的值,则会按更改的值来运行,否则在默认情况下,协议栈通常是按net.core.wmem_default和net.core.wmem_max的值来分配内存的.

应用程序应该根据应用的特性在程序中更改发送缓存大小:

socklen_t sendbuflen = 0;
socklen_t len = sizeof(sendbuflen);
getsockopt(clientSocket, SOL_SOCKET, SO_SNDBUF, (void*)&sendbuflen, &len);
printf("default,sendbuf:%d/n", sendbuflen);

sendbuflen = 10240;
setsockopt(clientSocket, SOL_SOCKET, SO_SNDBUF, (void*)&sendbuflen, len);
getsockopt(clientSocket, SOL_SOCKET, SO_SNDBUF, (void*)&sendbuflen, &len);
printf("now,sendbuf:%d/n", sendbuflen);


需要注意的是,虽然将发送缓存设置成了10k,但实际上,协议栈会将其扩大1倍,设为20k.
-------------------实例分析---------------

在实际应用中,如果发送端是非阻塞发送,由于网络的阻塞或者接收端处理过慢,通常出现的情况是,发送应用程序看起来发送了10k的数据,但是只发送了2k到对端缓存中,还有8k在本机缓存中(未发送或者未得到接收端的确认).那么此时,接收应用程序能够收到的数据为2k.假如接收应用程序调用recv函数获取了1k的数据在处理,在这个瞬间,发生了以下情况之一,双方表现为:

A. 发送应用程序认为send完了10k数据,关闭了socket:
发送主机作为tcp的主动关闭者,连接将处于FIN_WAIT1的半关闭状态(等待对方的ack),并且,发送缓存中的8k数据并不清除,依然会发送给对端.如果接收应用程序依然在recv,那么它会收到余下的8k数据(这个前题是,接收端会在发送端FIN_WAIT1状态超时前收到余下的8k数据.), 然后得到一个对端socket被关闭的消息(recv返回0).这时,应该进行关闭.

B. 发送应用程序再次调用send发送8k的数据:
假如发送缓存的空间为20k,那么发送缓存可用空间为20-8=12k,大于请求发送的8k,所以send函数将数据做拷贝后,并立即返回8192;

假如发送缓存的空间为12k,那么此时发送缓存可用空间还有12-8=4k,send()会返回4096,应用程序发现返回的值小于请求发送的大小值后,可以认为缓存区已满,这时必须阻塞(或通过select等待下一次socket可写的信号),如果应用程序不理会,立即再次调用send,那么会得到-1的值, 在linux下表现为errno=EAGAIN.

C. 接收应用程序在处理完1k数据后,关闭了socket:
接收主机作为主动关闭者,连接将处于FIN_WAIT1的半关闭状态(等待对方的ack).然后,发送应用程序会收到socket可读的信号(通常是 select调用返回socket可读),但在读取时会发现recv函数返回0,这时应该调用close函数来关闭socket(发送给对方ack);

如果发送应用程序没有处理这个可读的信号,而是在send,那么这要分两种情况来考虑,假如是在发送端收到RST标志之后调用send,send将返回-1,同时errno设为ECONNRESET表示对端网络已断开,但是,也有说法是进程会收到SIGPIPE信号,该信号的默认响应动作是退出进程,如果忽略该信号,那么send是返回-1,errno为EPIPE(未证实);如果是在发送端收到RST标志之前,则send像往常一样工作;

以上说的是非阻塞的send情况,假如send是阻塞调用,并且正好处于阻塞时(例如一次性发送一个巨大的buf,超出了发送缓存),对端socket关闭,那么send将返回成功发送的字节数,如果再次调用send,那么会同上一样.

D. 交换机或路由器的网络断开:
接收应用程序在处理完已收到的1k数据后,会继续从缓存区读取余下的1k数据,然后就表现为无数据可读的现象,这种情况需要应用程序来处理超时.一般做法是设定一个select等待的最大时间,如果超出这个时间依然没有数据可读,则认为socket已不可用.

发送应用程序会不断的将余下的数据发送到网络上,但始终得不到确认,所以缓存区的可用空间持续为0,这种情况也需要应用程序来处理.

如果不由应用程序来处理这种情况超时的情况,也可以通过tcp协议本身来处理,具体可以查看sysctl项中的:
net.ipv4.tcp_keepalive_intvl
net.ipv4.tcp_keepalive_probes
net.ipv4.tcp_keepalive_time


///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
http://www.ixpub.net/thread-1446913-1-1.html

发送成功只是表示发到了内核socket缓冲区
此时如果close,正常情况会进入TIME_WAIT状态,在此状态,对端可以继续接收数据
但是如果发送方的接收缓冲区还有未读数据,就会走异常close的途径,置RST,立刻结束连接,没有TIME_WAIT状态。这时对端就收不全数据,报错: Connection reset by peer。

///////////////////////////////////////////////////////////////////////////////////////////
值得参考的 TCP send和recv函数解析
http://blog.csdn.net/wjtxt/article/details/6603456
一、 滑动窗口的概念
        TCP数据包的TCP头部有一个window字段,它主要是用来告诉对方自己能接收多大的数据(注意只有TCP包中的数据部分占用这个空间),这个字段在通信双方建立连接时协商确定,并且在通信过程中不断更新,故取名为滑动窗口。有了这个字段,数据发送方就知道自己该不该发送数据,以及该发多少数据了。TCP协议的流量控制正是通过滑动窗口实现,从而保证通信双方的接收缓冲区不会溢出,数据不会丢失。
由于窗口大小在TCP头部只有16位来表示,所以它的最大值是65536,但是对于一些情况来说需要使用更大的滑动窗口,这时候就要使用扩展的滑动窗口,如光纤高速通信网络,或者是卫星长连接网络,需要窗口尽可能的大。这时会使用扩展的32位的滑动窗口大小。
二、 滑动窗口移动规则
        1、窗口合拢:在收到对端数据后,自己确认了数据的正确性,这些数据会被存储到接收缓冲区,等待应用程序获取。但这时候因为已经确认了数据的正确性,需要向对方发送确认响应ACK,又因为这些数据还没有被应用进程取走,这时候便需要进行窗口合拢,缓冲区的窗口左边缘向右滑动。注意响应的ACK序号是对方发送数据包的序号,一个对方发送的序号,可能因为窗口张开会被响应(ACK)多次。
        2、窗口张开:窗口收缩后,应用进程一旦从缓冲区(滑动窗口区或接收缓冲区)中取出数据,TCP的滑动窗口需要进行扩张,这时候窗口的右边缘向右扩张,实际上窗口这是一个环形缓冲区,窗口的右边缘扩张会使用原来被应用进程取走内容的缓冲区。在窗口进行扩张后,需要使用ACK通知对端,这时候ACK的序号依然是上次确认收到包的序号。
        3、窗口收缩,窗口的右边缘向左滑动,称为窗口收缩,HostRequirement RFC强烈建议不要这样做,但TCP必须能够在某一端产生这种情况时进行处理。
三、send行为
        默认情况下,send的功能是拷贝指定长度的数据到发送缓冲区,只有当数据被全部拷贝完成后函数才会正确返回,否则进入阻塞状态或等待超时。如果你想修改这种默认行为,将数据直接发送到目标机器,可以将发送缓冲区大小设为0,这样当send返回时,就表示数据已经正确的、完整的到达了目标机器。注意,这里只表示数据到达目标机器网络缓冲区,并不表示数据已经被对方应用层接收了。
        协议层在数据发送过程中,根据对方的滑动窗口,再结合MSS值共同确定TCP报文中数据段的长度,以确保对方接收缓冲区不会溢出。当本方发送缓冲区尚有数据没有发送,而对方滑动窗口已经为0时,协议层将启动探测机制,即每隔一段时间向对方发送一个字节的数据,时间间隔会从刚开始的30s调整为1分钟,最后稳定在2分钟。这个探测机制不仅可以检测到对方滑动窗口是否变化,同时也可以发现对方是否有异常退出的情况。
        push标志指示接收端应尽快将数据提交给应用层。如果send函数提交的待发送数据量较小,例如小于1460B(参照MSS值确定),那么协议层会将该报文中的TCP头部的push字段置为1;如果待发送的数据量较大,需要拆成多个数据段发送时,协议层只会将最后一个分段报文的TCP头部的push字段置1。
四、recv行为
        默认情况下,recv的功能是从接收缓冲区读取(其实就是拷贝)指定长度的数据。如果将接收缓冲区大小设为0,recv将直接从协议缓冲区(滑动窗口区)读取数据,避免了数据从协议缓冲区到接收缓冲区的拷贝。recv返回的条件有两种:
      1. recv函数传入的应用层接收缓冲区已经读满
      2. 协议层接收到push字段为1的TCP报文,此时recv返回值为实际接收的数据长度
        协议层收到TCP数据包后(保存在滑动窗口区),本方的滑动窗口合拢(窗口值减小);当协议层将数据拷贝到接收缓冲区(滑动窗口区—>接收缓冲区),或者应用层调用recv接收数据(接收缓冲区—>应用层缓冲区,滑动窗口区—>应用层缓冲区)后,本方的滑动窗口张开(窗口值增大)。收到数据更新window后,协议层向对方发送ACK确认。
        协议层的数据接收动作完全由发送动作驱动,是一个被动行为。在应用层没有任何干涉行为的情况下(比如recv操作等),协议层能够接收并保存的最大数据大小是窗口大小与接收缓冲区大小之和。Windows系统的窗口大小默认是64K,接收缓冲区默认为8K,所以默认情况下协议层最多能够被动接收并保存72K的数据。
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
http://blog.csdn.net/wjtxt/article/details/6598925
TCP连接关闭的问题:

从TCP协议角度来看,一个已建立的TCP连接有两种关闭方式,一种是正常关闭,即四次挥手关闭连接;还有一种则是异常关闭,我们通常称之为连接重置(RESET)。
        首先说一下正常关闭时四次挥手的状态变迁,关闭连接的主动方状态变迁是FIN_WAIT_1->FIN_WAIT_2->TIME_WAIT,而关闭连接的被对方的状态变迁是CLOSE_WAIT->LAST_ACK->TIME_WAIT。在四次挥手过程中ACK包都是协议栈自动完成的,而FIN包则必须由应用层通过closesocket或shutdown主动发送,通常连接正常关闭后,recv会得到返回值0,send会得到错误码10058。
        除此之外,在我们的日常应用中,连接异常关闭的情况也很多。比如应用程序被强行关闭、本地网络突然中断(禁用网卡、网线拔出)、程序处理不当等都会导致连接重置,连接重置时将会产生RST包,同时网络络缓冲区中未接收(发送)的数据都将丢失。连接重置后,本方send或recv会得到错误码10053(closesocket时是10038),对方recv会得到错误码10054,send则得到错误码10053(closesocket时是10054)。
        操作系统为我们提供了两个函数来关闭一个TCP连接,分别是closesocket和shutdown。通常情况下,closesocket会向对方发送一个FIN包,但是也有例外。比如有一个工作线程正在调用recv接收数据,此时外部调用closesocket,会导致连接重置,同时向对方发送一个RST包,这个RST包是由本方主动产生的。
        shutdown可以用来关闭指定方向的连接,该函数接收两个参数,一个是套接字,另一个是关闭的方向,可用值为SD_SEND,SD_RECEIVE和SD_BOTH。方向取值为SD_SEND时,无论socket处于什么状态(recv阻塞,或空闲状态),都会向对方发送一个FIN包,注意这点与closesocket的区别。此时本方进入FIN_WAIT_2状态,对方进入CLOSE_WAIT状态,本方依然可以调用recv接收数据;方向取值为SD_RECEIVE时,双发连接状态没有改变,依然处于ESTABLISHED状态,本方依然可以send数据,但是,如果对方再调用send方法,连接会被立即重置,同时向对方发送一个RST包,这个RST包是被动产生的,这点注意与closesocket的区别。
沙发
发表于 2013-5-6 22:02:58
回复

使用道具 举报

socket传输疑问。 [复制链接]

我们都知道,TCP协议是面向流的。面向流是指无保护消息边界的,如果发送端连续发送数据,接收端有可能在一次接收动作中会接收两个或者更多的数据包。

      那什么是保护消息边界呢?就是指传输协议把数据当做一条独立的消息在网上传输,接收端只能接收独立的消息。也就是说存在保护消息边界,接收端一次只能接收发送端发出的一个数据包。

     举个例子来说,连续发送三个数据包,大小分别是1k,2k,4k,这三个数据包都已经到达了接收端的缓冲区中,如果使用UDP协议,不管我们使用多大的接收缓冲区去接收数据,则必须有三次接收动作,才能把所有数据包接受完。而使用TCP协议,只要把接收数据的缓冲区大小设置在7kb以上,就能够一次把所有的数据包接收下来,即只需要有一次接收动作。

     这样问题就来了,由于TCP协议是流传输的,它把数据当作一串数据流,所以他不知道消息的边界,即独立的消息之间是如何被分隔开的。这便会造成消息的混淆,也就是说不能够保证一个Send方法发出的数据被一个Recive方法读取。在讲解缓冲区的时候,我们已经讲过Recive方法是从系统缓冲区上读取数据的,所以只要数据缓冲区的容量足够大,该方法不单单接收第一个包的数据,可能是所有的数据。

      例如,有两台网络上的计算机,客户机发送的消息是:第一次发送abcde,第二次发送12345,服务器方接收到的可能是abcde12345,即一次性收完;也可能是第一次接收到abc,第二次接收到de123,第三次接收到45.

      针对这个问题,一般有3种解决方案:
      (1)发送固定长度的消息
      (2)把消息的尺寸与消息一块发送
      (3)使用特殊标记来区分消息间隔
      
下面我们主要分析下前两种方法:

1、发送固定长度的消息
这种方法的好处是他非常容易,而且只要指定好消息的长度,没有遗漏未未发的数据,我们重写了一个SendMessage方法。代码如下:

  private static int SendMessage(Socket s, byte[] msg)

        {
            int offset = 0;
            int size = msg.Length;
            int dataleft = size;
            while (dataleft > 0)
            {

                int sent = s.Send(msg, offset, SocketFlags.None);
                offset += sent;
                dataleft -= sent;

            }

            return offset;
        }

简要分析一下这个函数:形参s是进行通信的套接字,msg即待发送的字节数组。该方法使用while循环检查是否还有数据未发送,尤其当发送一个很庞大的数据包,在不能一次性发完的情况下作用比较明显。特别的,用sent来记录实际发送的数据量,和recv是异曲同工的作用,最后返回发送的实际数据总数。

   有sentMessage函数后,还要根据指定的消息长度来设计一个新的Recive方法。代码如下:

  private byte[] ReciveMessage(Socket s, int size)
        {
            int offset = 0;
            int recv;
            int dataleft = size;
            byte[] msg = new byte[size];

            while (dataleft > 0)

            {

                //接收消息
                recv = s.Receive(msg, offset, dataleft, 0);
                if (recv == 0)

                {

                    break;

                }
                offset += recv;
                dataleft -= recv;

            }

            return msg;

        }

以上这种做法比较适合于消息长度不是很长的情况。
2、消息长度与消息一同发送

我们可以这样做:通过使用消息的整形数值来表示消息的实际大小,所以要把整形数转换为字节类型。下面是发送变长消息的SendMessage方法。具体代码如下:

  private static int SendMessage(Socket s, byte[] msg)
        {

            int offset = 0;
            int sent;
            int size = msg.Length;
            int dataleft = size;
            byte[] msgsize = new byte[2];

            //将消息尺寸从整形转换成可以发送的字节型
            msgsize = BitConverter.GetBytes(size);

            //发送消息的长度信息
            sent = s.Send(size);

            while (dataleft > 0)

            {

                sent = s.Send(msg, offset, dataleft, SocketFlags.None);

                //设置偏移量

                offset += sent;
                dataleft -= sent;

            }

            return offset;

        }


下面是接收变长消息的ReciveVarMessage方法。代码如下:

private byte[] ReciveVarMessage(Socket s)
        {

            int offset = 0;
            int recv;
            byte[] msgsize = new byte[2];


            //将字节数组的消息长度信息转换为整形
            int size = BitConverter.ToInt16(msgsize);
            int dataleft = size;
            byte[] msg = new byte[size];


            //接收2个字节大小的长度信息
            recv = s.Receive(msgsize, 0, 2, 0);
            while (dataleft > 0)
            {

                //接收数据
                recv = s.Receive(msg, offset, dataleft, 0);
                if (recv == 0)
                {
                    break;
                }
                offset += recv;
                dataleft -= recv;

            }

            return msg;

        }
板凳
发表于 2013-5-6 22:12:48
回复

使用道具 举报

socket传输疑问。 [复制链接]

liuxing 发表于 2013-5-6 22:12 我们都知道,TCP协议是面向流的。面向流是指无保护消息边界的,如果发送端连续发送数据,接收端有可能在一次 ...

send(int sockfd,const void *buf,size_t len,int flags)这个函数发送的数据长度是len决定的?那如果len大于buf的长度,send用什么数据来填充?随机无意义数据?
地板
 楼主| 发表于 2013-5-7 09:09:18 来自手机
回复

使用道具 举报

socket传输疑问。 [复制链接]

东方巽雷 发表于 2013-5-7 09:09
send(int sockfd,const void *buf,size_t len,int flags)这个函数发送的数据长度是len决定的?那如果len大 ...

send函数

int send( SOCKET s,      const char FAR *buf,      int len,      int flags );  

不论是客户还是服务器应用程序都用send函数来向TCP连接的另一端发送数据。

客户程序一般用send函数向服务器发送请求,而服务器则通常用send函数来向客户程序发送应答。

该函数的第一个参数指定发送端套接字描述符;

第二个参数指明一个存放应用程序要发送数据的缓冲区;

第三个参数指明实际要发送的数据的字节数;

第四个参数一般置0。

这里只描述同步Socket的send函数的执行流程。当调用该函数时,send先比较待发送数据的长度len和套接字s的发送缓冲的 长度,如果len大于s的发送缓冲区的长度,该函数返回SOCKET_ERROR;如果len小于或者等于s的发送缓冲区的长度,那么send先检查协议是否正在发送s的发送缓冲中的数据,如果是就等待协议把数据发送完,如果协议还没有开始发送s的发送缓冲中的数据或者s的发送缓冲中没有数据,那么send就比较s的发送缓冲区的剩余空间和len,如果len大于剩余空间大小send就一直等待协议把s的发送缓冲中的数据发送完,如果len小于剩余空间大小send就仅仅把buf中的数据copy到剩余空间里(注意并不是send把s的发送缓冲中的数据传到连接的另一端的,而是协议传的,send仅仅是把buf中的数据copy到s的发送缓冲区的剩余空间里)。如果send函数copy数据成功,就返回实际copy的字节数,如果send在copy数据时出现错误,那么send就返回SOCKET_ERROR;如果send在等待协议传送数据时网络断开的话,那么send函数也返回SOCKET_ERROR。

要注意send函数把buf中的数据成功copy到s的发送缓冲的剩余空间里后它就返回了,但是此时这些数据并不一定马上被传到连接的另一端。如果协议在后续的传送过程中出现网络错误的话,那么下一个Socket函数就会返回SOCKET_ERROR。(每一个除send外的Socket函数在执行的最开始总要先等待套接字的发送缓冲中的数据被协议传送完毕才能继续,如果在等待时出现网络错误,那么该Socket函数就返回SOCKET_ERROR)

注意:在Unix系统下,如果send在等待协议传送数据时网络断开的话,调用send的进程会接收到一个SIGPIPE信号,进程对该信号的默认处理是进程终止。
5#
发表于 2013-5-7 09:20:02
回复

使用道具 举报

socket传输疑问。 [复制链接]

你的send(sock_fd,cmd,strlen(cmd),0)中,第一次发送长度为strlen(cmd),而服务端recv的时候是按MAX_FILENAME_LEN长度来recv的,如果strlen(cmd)<MAX_FILENAME_LEN,那么recv就会有第二次send的内容,一般是你客户端发多少,服务器端就收多少
6#
发表于 2013-5-7 09:29:40
回复

使用道具 举报

socket传输疑问。 [复制链接]

penghuan 发表于 2013-5-7 09:29
你的send(sock_fd,cmd,strlen(cmd),0)中,第一次发送长度为strlen(cmd),而服务端recv的时候是按MAX_FILENAM ...

这位版主您好!感谢您的指导。send(sock_fd,cmd,strlen(cmd),0)如果改为send(sock_fd,cmd,MAX_FILENAME_LEN,0)就能正常接收了。此时非常明显,cmd内容、实际长度都没有变化。那么,是不是send用什么数据来填充MAX_FILENAME_LEN-strlen(cmd)这部分差值呢?随机数据?如果不填充,服务器又怎么接收到MAX_FILENAME_LEN长度的数据(而不包含send第二次发的内容)?
7#
 楼主| 发表于 2013-5-7 11:14:19
回复

使用道具 举报

socket传输疑问。 [复制链接]

具体填充什么我没研究过,你这里要传文件的话,应该先把文件名和文件长度传给服务器,服务器基于文件名和文件长度进行收文件操作

客户端你可以定义一个结构体,包含filename和filelength,这样结构体的长度就是固定的

send的时候,第一次先发该结构体(包含文件名和文件长度),这样你发送长度也就是固定的,服务器端recv的时候就基于该长度(结构体长度)接收客户端发来的结构体,接收成功你就得到了filename和filelength,因为指定了接收长度,所以它收到的一定是客户端第一次发送的内容

客户端第二次send,紧接在第一次send后面,发送文件内容,服务器端由于第一次收到了文件长度,第二次recv的时候就基于该长度接收文件内容,就能不多不少的接收到文件了
8#
发表于 2013-5-7 11:44:32
回复

使用道具 举报

socket传输疑问。 [复制链接]

penghuan 发表于 2013-5-7 11:44
具体填充什么我没研究过,你这里要传文件的话,应该先把文件名和文件长度传给服务器,服务器基于文件名和文 ...

文件长度对于tcp是必须的吗?tcp不保证传输的准确性么?如果客户端发送的数据服务器没收到,客户端还是会重新发送吧?所以服务器只管接收就可以了吧?或者是我对tcp理解有误?
9#
 楼主| 发表于 2013-5-7 13:00:01
回复

使用道具 举报

socket传输疑问。 [复制链接]

东方巽雷 发表于 2013-5-7 13:00
文件长度对于tcp是必须的吗?tcp不保证传输的准确性么?如果客户端发送的数据服务器没收到,客户端还是会 ...

你不告诉服务器文件长度是多少,它怎么知道要收多少
10#
发表于 2013-5-7 13:10:00
回复

使用道具 举报

openKylin

GMT+8, 2024-5-17 09:43 , Processed in 0.030631 second(s), 17 queries , Gzip On.

Copyright ©2022 openKylin. All Rights Reserved .

ICP No. 15002470-12 Tianjin

快速回复 返回顶部 返回列表