socket 的那些事

--------------------------------------------------------------------------------
auhtor: hjjdebug
date:   Fri Jul 18 08:55:02 CST 2014
--------------------------------------------------------------------------------
--------------------------------------------------------------------------------
socket 每次发送多大的数据合适呢?
--------------------------------------------------------------------------------
UDP的sendto对应着recvfrom,一发一收.
如果sendto的数据大于MTU,则会在IP层分片发送,到达目标后由IP层重组,再从recvfrom一次性返回.
如果使用IP层分片重组则存在乱序,丢包,重包的问题. 所以udp 的sendto 数据包要小于MTU.
调用一次sendto,只要数据长度小于MTU都会以一个独立的UDP包发送.
recvfrom的接收大小必须大于或等于sendto时的数据大小.
tcp 则没有包的概念, 可以保证数据的正确性。

--------------------------------------------------------------------------------
Linux Socket TCP Recv()的返回值:
--------------------------------------------------------------------------------
>0: 表示从接收缓冲区实际Copy了这么多个字节的数据出来,
也就是表示系统已实际收到发送方发过来的这么多个数据或者更多的数据.
=0: 表示服务器已主动断开连接,也就是已收到了服务器发过来的Fin或者Rst.
=-1:
if(errno == EINTR || errno == EWOULDBLOCK || errno == EAGAIN),
    表示接收缓冲区已无数据可读,但连接是正常的,可以稍后再尝试读.
else 表示连接发生异常,连接已断开,无效描述符之类的.

--------------------------------------------------------------------------------
Linux Socket TCP Send()的返回值:
--------------------------------------------------------------------------------
>0: 表示已经发送了的字节数(未被确认的),
例如要发送10个字节,在异步模式下返回值为5个字节,则表示已经Copy了5个字节到发送缓冲区,系统随后会发送这些数据,
但这5个字节是未被确认的,也就是还没收到接收方的Ack,
如果服务器无回应或网络断开,那么系统将会不断重发该数据包,直到收到这5个字节的Ack.
而剩下的5个未发的字节则由应用程序调用Send再发.
=0: 表示服务器已主动断开连接,也就是已收到了服务器发过来的Fin或者Rst. 还会产生sigpipe 信号,不加处理默认退出程序
=-1:
if(errno == EINTR || errno == EWOULDBLOCK || errno == EAGAIN),
表示系统在忙或者发送缓冲区已满了,但连接是正常的,可以稍后再尝试发.
else 表示连接发生异常,连接已断开,无效描述符之类的.

tcp 的 send 与 recv 不是一问一答对应的。是按流传送的。

--------------------------------------------------------------------------------
在阻塞式Recv(),Send()上设定收发超时。
--------------------------------------------------------------------------------
调用一个setsockopt() 函数。
struct timeval
{
        time_t tv_sec;
        time_t tv_usec;
};
填充这个结构后,我们就可以以如下的方式调用这个函数:
setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, &tv_out, sizeof(tv_out));(具体参数可以man一下,或查看MSDN)

--------------------------------------------------------------------------------
设定端口可以重用
--------------------------------------------------------------------------------
//  设置了该选项后,在父子进程模型中,当子进程为客户服务的时候如果父进程退出,可以重新启动程序完成服务的无缝升级,
//  否则在所有父子进程完全退出前再启动程序会在该端口上绑定失败,也即不能完成无缝升级.
//  SO_REUSEADDR, 地址可以重用,不必time_wait 2个MSL 时间  (maximum segment lifetime)
    int yes = 1;
    if(setsockopt(listen_sock, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(int))==-1)
    {
        printf("error: listen_sock set SO_REUSEADDR failed.\n");
    }


--------------------------------------------------------------------------------
connect() 函数
--------------------------------------------------------------------------------
从客户端运行connect() 函数
拔掉网线:  
拔掉客户端与路由器之间网线: 立即返回, (reason: Network is unreachable)
拔掉路由器与服务器之间网线: 将会等待75秒超时返回,  (reason: No route to host )

连接网线:
不同段的IP地址: 立即返回, (reason: No route to host )
服务器未启动: 立即返回, (reason Connection refused )

原因: 没有网,没有路,拒收。

可见在服务器与路由器之间无路时,为了找到有效路由,要反复试探,有75秒延时。
为了降低75秒的响应事件,可以设置 connect 为异步连接(ioctl()等),并设定定时器超时时间。
用select 监视连接状态。代码, 这里忽略,自己从网上搜索把。


Operation now in progress.  
在非阻塞 connect 中,socket 会以这种方式通知应用程序。,这不算错误
在使用 select 函数时,也遇到了这个问题, 这时socket 已经检测到错误了。可能并没有等time_out.
同样。阻塞式connect 也不一定等75s, 很多情况很快退出或只等几秒就判明情况了。


connect 有三次握手,close 有4次挥手.

----------------------------------------
client 向已经关断的socket 写, 为什么会自动退出程序?
----------------------------------------
用gdb 调试。 gdb报错:
Program received signal SIGPIPE, Broken pipe.
解决办法:
1. 使用signal(SIGPIPE, SIG_IGN);忽略SIGPIPE信号, 该信号的默认行为是终止进程,
2. 在发现发送错误时自己处理错误。 signal(SIGPIPE, myHandler);
   接管SIGPIPE信号 在myHandler 中自己处理或安全退出。


----------------------------------------
error: 'close' was not declared in this scope :                         ---- 在 unistd.h 中
error: 'AF_INET' was not declared in this scope                          ---- 在 sys/socket.h 中
error: aggregate 'sockaddr_in serv_addr' has incomplete type and cannot be defined
error: 'htons' was not declared in this scope                            ---- 3 个均在 netinet/in.h 中
error: 'INADDR_ANY' was not declared in this scope              
error: 'errno' was not declared in this scope                             ---- 在 errno.h 中

 

--------------------------------------------------------------------------------
关于 socket 的 SIGPIPE
--------------------------------------------------------------------------------

在一个成千上万的连接中, 出现的SIGPIPE 进程退出问题。

SIGPIPE: When writing onto a connection-oriented socket that has been shut down (by the local or the remote end) SIGPIPE is sent to the writing process
SIGPIPE:  13  Broken pipe, write to pipe with no readers

简单说,向一个没有读操作的管道或socket 写数据,会产生SIGPIPE 信号.
具体的分析可以结合TCP的"四次挥手"关闭.
TCP是全双工的信道, 可以看作两条单工信道, TCP连接两端的两个端点各负责一条.
当对端调用close时, 虽然本意是关闭整个两条信道, 但本端只是收到FIN包. 按照TCP协议的语义, 表示对端只是关闭了其所负责的那一条单工信道, 仍然可以继续接收数据.
也就是说, 因为TCP协议的限制, 一个端点只能控制本端socket状态(调用close函数), 无法实时获知或控制对端的socket状态(是否调用了close), 这也是双工必须付出的代价。

第一次对其调用write方法时, 如果发送缓冲没问题, 会返回正确写入(发送). 但发送的报文会导致对端发送RST报文, 因为对端的socket已经调用了close, 关闭了半个链接,
第二次调用write方法(假设在收到RST之后), 会生成SIGPIPE信号, 导致进程退出.

在一个多线程,多事件的模型下,应用有可能向一个已经关闭的管道书写数据, 为了避免程序退出。
解决办法:
1. signal(SIGPIPE, SIG_IGN);
忽略SIGPIPE 信号, 此时write 会产生-1返回, 自己处理这种错误
2. signal(SIGPIPE, PipeHandler);  自己写PipeHandler 程序

 

------------------------------------------------------------
errno 有什么用
------------------------------------------------------------
linux 下一个简单的udp 发送程序,却是发不出去数据。
我不得不借助errno 来查找问题。
 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值