socket 编程入门教程(三)TCP原理:6、字节流的发送与接收

从TCP三次握手的原理我们可以看到,TCP有“保障”的连接实际上可以看做是两个单向的连接:一个通道只负责发送,另外一个只负责接收。并且,传送的信息是以字节为单位保证顺序的。
在socket机制中,应用层的程序以send()函数将数据首先发送到本机系统的发送缓存中,我们称之为SendQ,意指这是一个FIFO(先进先出)的队列。这个缓存是系统决定的,并不是在我们的程序中指定的。然后socket机制负责将SendQ中的数据以字节为单位,按照顺序发送给对方的接收缓存RecvQ中。RecvQ也是一个属于系统的FIFO缓存队列。从程序员的角度看,send()函数只负责把数据送入SendQ,而SendQ何时将数据发送则是不可控的。所以,send()通常不会阻塞,只有在不能立即将数据发送给SendQ的时候才会阻塞,这往往是因为SendQ缓存已满。另外,SendQ并不负责统计每次send()所发送来的字节流的长度,事实上这个长度在TCP中没有意义,因为所有数据都以字节为单位按照FIFO的形式排列在队列中,而并不在乎来自于哪一次的send()。这也就是所谓的TCP无边缘保证,TCP的send()并不在乎每次传送的数据有多少,而只是致力于将数据以字节为单位按照FIFO的形式排列在SendQ队列中。我们看一下TCPServerSock和TCPClientSock的TCPSend()方法:

int  TCPServerSock::TCPSend( const   char *  send_data,
                           
const   int &  data_length)  const
{
    
if  (data_length  >  preBufferSize) {
        sockClass::error_info(
" Data is too large, resize preBufferSize. " );
    }

    
int  sent_length  =  send(    sockFD,
                            send_data,
                            data_length,
                            
0 );
    
if  (sent_length  <   0 ) {
        sockClass::error_info(
" send() failed. " );
    } 
else   if  (sent_length  !=  data_length) {
        sockClass::error_info(
" sent unexpected number of bytes. " );
    }

    
return  sent_length;
}
int  TCPClientSock::TCPSend( const   char *  send_data,
                           
const   int &  data_length)  const
{
    
if  (data_length  >  preBufferSize) {
        sockClass::error_info(
" Data is too large, resize preBufferSize. " );
    }

    
int  sent_length  =  send(    sockFD,
                            send_data,
                            data_length,
                            
0 );
    
if  (sent_length  <   0 ) {
        sockClass::error_info(
" send() failed. " );
    } 
else   if  (sent_length  !=  data_length) {
        sockClass::error_info(
" sent unexpected number of bytes. " );
    }

    
return  sent_length;
}

可以看到,这两个方法除了分属于不同的类名字不一样,其他都是一样的。send()的返回值是实际发送的字节长度。
在收信息的另外一边,当RecvQ没有数据时,recv()就会阻塞(默认情况下),每当有数据可接收,recv()就会返回实际接收到的数据长度。recv()同样不在乎每次接收的数据有多少,其参数只有一个最大长度限制,这个限制是应用程序分配给每次recv()储存数据的缓存大小。所以TCP的send()和recv()不是一一对应的:send()只负责将数据写入本机的SendQ,而recv()只负责把本机RecvQ中的数据读出来。假设send()传送了m+n字节,但是第一次到达远程目的地的RecvQ中只有m字节,于是这里的recv()就会马上返回m字节;剩下的n字节第二次才姗姗来迟,那么就需要第二次调用recv()来接收。

int  TCPServerSock::TCPReceive()  const
{
    preReceivedLength 
=  recv(    sockFD,
                                preBuffer,
                                preBufferSize,
                                
0 );
    
if  (preReceivedLength  <   0 ) {
        sockClass::error_info(
" recv() failed. " );
    } 
else   if  (preReceivedLength  ==   0 ) {
        std::cout 
<<   " Client has been disconnected.\n " ;
        
return   0 ;
    }
    
return  preReceivedLength;
}
int  TCPClientSock::TCPReceive()  const
{
    preReceivedLength 
=  recv(    sockFD,
                                preBuffer,
                                preBufferSize,
                                
0 );
    
if  (preReceivedLength  <   0 ) {
        sockClass::error_info(
" recv() failed. " );
    } 
else   if  (preReceivedLength  ==   0 ) {
        std::cout 
<<   " Disconnected from server.\n " ;
        
return   0 ;
    }
    
return  preReceivedLength;
}

可以看到这2个方法也几乎是一模一样——除了名字和对异常信息的描述。因为我们这里并不知道需要recv()的确切长度,所以这里的TCPReceive()也跟recv()一样,有数据就返回。需要验证数据长度的,比如echo服务,我们另外写验证长度的代码。
最后需要说明的是,虽然SYN和FIN都会占用一个字节的数据,但是对于应用层的send()和recv()来说是不可见的。FIN会让recv()返回0,表示连接正常断开。

转载于:https://www.cnblogs.com/yin-jingyu/archive/2012/06/06/2537566.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值