这里我们来探讨一下在网络编程过程中,有关read/write 或者send/recv的使用细节。这里有关常用的阻塞/非阻塞的解释在网上有很多很好的例子,这里就不说了,还有errno ==EAGAIN 异常等等。首先我们拿一个简单的实例代码看一下。

read/write面临的是什么问题:

字节流套接字上调用read或write的返回值可能比请求的数量少,这并不是出错的状态,这种情况发生在内核中的用于套接字缓冲区的空间已经达到了极限,需要再次的调用read/write函数才能将剩余数据读出或写入。那么这里可以看到是内核缓冲区到达极限,那么一般情况下是多大呢?

 CLIENT]$ cat /proc/sys/net/ipv4/tcp_wmem

CLIENT]$ cat /proc/sys/net/ipv4/tcp_rmem
        

第一个数据表示最小,第二个表示默认情况下,第三个表示最大,单位是字节。如果read的缓冲区已经到达极限,那么一次read并不能读出自己想要的数据大小。那么更多的情况我们并不知道对方发送的数据量是多大,我们只有一个最大阀值。那么这时该怎样去控制read/write呢?

阻塞的read和write的问题:

我们来看<unix网络编程> 中的read代码如下

ssize_t                     /* Read "n" bytes from a descriptor. */
readn(int fd, void *vptr, size_t n) //这里的n 是接收数据buffer的空间   真实情况下我们确实不太清楚客户端到底它会发多少数据 一般是个阀值。
{
size_t nleft;
ssize_t nread;
char *ptr; ptr = vptr;
nleft = n;
while (nleft > ) {
if ( (nread = read(fd, ptr, nleft)) < ) {
if (errno == EINTR)
nread = ; /* and call read() again */
else
return(-);
} else if (nread == )
break; /* EOF */ nleft -= nread;
ptr += nread;
}
return(n - nleft); /* return >= 0 */
}

我们先看这个参数size_t n,因为多数情况下,我们并不严格规定客户端到底一次要发多少数据,对于常规的服务器来说都要有一个最大阀值,超过这个阀值就表示是异常数据。想像一下如果没有最大阀值,一个恶意的客户端向一个服务器发送一个超大的文件,那么这个服务器很快就会崩溃! 这里的n的大小其实跟我们的业务相关了。文件服务器就除外了我们不谈这种情况。我们继续看 若此时文件描述符为阻塞模式时,那么当一个连接到达并开始发送一段数据后暂停发送数据(还没有断开),因为客户端并没有断开,同时它发送的数据还没有到达阀值 那么势必在read处一直阻塞,那么如果是一个单线程服务器的话就不能处理其他请求了。write的话这种情况我们一般都知道要发送数据的真实大小一般不发生这种情况。

ssize_t                     /* Write "n" bytes to a descriptor. */
writen(int fd, const void *vptr, size_t n) //这里传入的n一般就是数据的实际大小   while循环会正常返回。
{
size_t nleft;
ssize_t nwritten;
const char *ptr; ptr = vptr;
nleft = n;
while (nleft > ) {
if ( (nwritten = write(fd, ptr, nleft)) <= ) {
if (nwritten < && errno == EINTR)
nwritten = ; /* and call write() again */
else
return(-); /* error */
} nleft -= nwritten;
ptr += nwritten;
}
return(n);
}

非阻塞的read/write:

由上面阻塞模式的情况我们再分析一下非组塞:

还是以上的代码:readn来说,如果是非阻塞,我们还是假定这里客户端发送了一点数据并没有断开。

ssize_t                     /* Read "n" bytes from a descriptor. */
readn(int fd, void *vptr, size_t n) //这里的n 是接收数据buffer的空间 这种情况我们确实不太清楚客户端到底它会发多少数据 一般是个阀值。
{
size_t nleft;
ssize_t nread;
char *ptr; ptr = vptr;
nleft = n;
while (nleft > ) {
if ( (nread = read(fd, ptr, nleft)) < ) {
if (errno == EINTR)
nread = ; /* and call read() again */
       if (errno == EAGAIN)    //发生了这种异常我将它返回了,这里表示文件描述符还不可读,没有准备好,我就直接将其返回,最后IO复用select/poll/epoll就会再读取准备好的数据。
          return n - nleft;

else
return(-);
} else if (nread == ){

        //close(fd);
        break; /* EOF */
    }
nleft -= nread;
ptr += nread;
}
return(n - nleft); /* return >= 0 */
}

1.  如果客户端发送了一点数据然后没有断开处于暂停状态的话。

那么在调用read时就会出现EAGAIN的异常,这里当发生这种异常时表示文件描述符还没有准备好,那么我这里直接将其返回已经读到的size。这样就不会造成一直阻塞在这里其他连接无法处理的现象。

2. 如果客户端发送了一点数据然后立刻断开连接了

比如我们第一次read的时候读到了最后发来的数据,当再次读取时读到了EOF客户端断开了连接那我们这个程序还是有问题阿! 我们这里看到跳出来while并返回了正确读到的数据  这时readn的返回是正确的,但是我们有这次返回还是不知道客户端断开了,虽说我们可以向上述代码加入close 但是我们并不能有readn函数知道客户端主动断开连接。

对于2这种情况就是我们在开发过程中常常遇到的情况,这时我们可以在readn中再加入时当的参数就可以解决,比如我们传入的是一个包含文件描述符号的结构体,结构体中含有标志状态的字段,再read == 0时将字段赋予一个值。再readn之后再有这个结构体的某个标志知道已将连接断开后续再断开连接和删除其事件即可。下面找到了Nginx关于recv的使用代码:

ssize_t
ngx_unix_recv(ngx_connection_t *c, u_char *buf, size_t size)
{
ssize_t n;
ngx_err_t err;
ngx_event_t *rev; rev = c->read; #if (NGX_HAVE_KQUEUE) if (ngx_event_flags & NGX_USE_KQUEUE_EVENT) {
ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, ,
"recv: eof:%d, avail:%d, err:%d",
rev->pending_eof, rev->available, rev->kq_errno); if (rev->available == ) {
if (rev->pending_eof) {
rev->ready = ;
rev->eof = ; if (rev->kq_errno) {
rev->error = ;
ngx_set_socket_errno(rev->kq_errno); return ngx_connection_error(c, rev->kq_errno,
"kevent() reported about an closed connection");
} return ; } else {
rev->ready = ;
return NGX_AGAIN;
}
}
} #endif #if (NGX_HAVE_EPOLLRDHUP) if (ngx_event_flags & NGX_USE_EPOLL_EVENT) {
ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, ,
"recv: eof:%d, avail:%d",
rev->pending_eof, rev->available); if (!rev->available && !rev->pending_eof) {
rev->ready = ;
return NGX_AGAIN;
}
} #endif do {
n = recv(c->fd, buf, size, ); ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, ,
"recv: fd:%d %z of %uz", c->fd, n, size); if (n == ) {
rev->ready = ;
rev->eof = ; #if (NGX_HAVE_KQUEUE) /*
* on FreeBSD recv() may return 0 on closed socket
* even if kqueue reported about available data
*/ if (ngx_event_flags & NGX_USE_KQUEUE_EVENT) {
rev->available = ;
} #endif return ;
} if (n > ) { #if (NGX_HAVE_KQUEUE) if (ngx_event_flags & NGX_USE_KQUEUE_EVENT) {
rev->available -= n; /*
* rev->available may be negative here because some additional
* bytes may be received between kevent() and recv()
*/ if (rev->available <= ) {
if (!rev->pending_eof) {
rev->ready = ;
} rev->available = ;
} return n;
} #endif #if (NGX_HAVE_EPOLLRDHUP) if ((ngx_event_flags & NGX_USE_EPOLL_EVENT)
&& ngx_use_epoll_rdhup)
{
if ((size_t) n < size) {
if (!rev->pending_eof) {
rev->ready = ;
} rev->available = ;
} return n;
} #endif if ((size_t) n < size
&& !(ngx_event_flags & NGX_USE_GREEDY_EVENT))
{
rev->ready = ;
} return n;
} err = ngx_socket_errno; if (err == NGX_EAGAIN || err == NGX_EINTR) {
ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, err,
"recv() not ready");
n = NGX_AGAIN; } else {
n = ngx_connection_error(c, err, "recv() failed");
break;
} } while (err == NGX_EINTR); rev->ready = ; if (n == NGX_ERROR) {
rev->error = ;
} return n;
}

其中我们还要注意在writen的非阻塞中,如果第一次写入返回,当第二次写入时对方断了,再次写入时就会发生EPIPE异常

while (nleft > ) {
if ( (nwritten = write(fd, ptr, nleft)) <= ) {
if (nwritten < && errno == EINTR)
nwritten = ; /* and call write() again */
else if(errno == EPIPE)
{
return ;// 这里我返回了0 因为最后一次发送数据并不保证对面已经收到了数据,这个数据到底有没有被正确接收在这里我们无法获得。如果对方关闭了就直接造成异常
}
else if(errno == EAGAIN)
{
return n-left;
}
else
return(-); /* error */
} nleft -= nwritten;
ptr += nwritten;
}

其实write数据并不代表数据被对方成功接收了,只是往内核缓冲区写,如果写入成功write就返回了,所以就无法知道数据是否被收到,在一些严格要求的数据交互中常常使用应用层的确认机制。至于详细的消息接收和发送的内容推荐下列博客: http://blog.csdn.net/yusiguyuan/article/details/24111289  和 http://blog.csdn.net/yusiguyuan/article/details/24671351

TCP程序中发送和接收数据的更多相关文章

  1. udp网络程序-发送、接收数据

    1. udp网络程序-发送数据 创建一个基于udp的网络程序流程很简单,具体步骤如下: 创建客户端套接字 发送/接收数据 关闭套接字 代码如下: #coding=utf-8from socket im ...

  2. [Socket网络编程]由于套接字没有连接并且(当使用一个 sendto 调用发送数据报套接字时)没有提供地址,发送或接收数据的请求没有被接受。

    原文地址:http://blog.sina.com.cn/s/blog_70bf579801017ylu.html,记录在此方便查看 解决办法: MSDN的说明: Close 方法可关闭远程主机连接, ...

  3. C#程序中从数据库取数据时需注意数据类型之间的对应,int16\int32\int64

    private void btn2_Click(object sender, RoutedEventArgs e)         {             using (SqlConnection ...

  4. 手把手教你Android手机与BLE终端通信--连接,发送和接收数据

    假设你还没有看上一篇 手把手教你Android手机与BLE终端通信--搜索,你就先看看吧,由于这一篇要接着讲搜索到蓝牙后的连接.和连接后的发送和接收数据. 评论里有非常多人问假设一条信息特别长,怎么不 ...

  5. 微信小程序中发送模版消息注意事项

    在微信小程序中发送模版消息 参考微信公众平台Api文档地址:https://mp.weixin.qq.com/debug/wxadoc/dev/api/notice.html#模版消息管理 此参考地址 ...

  6. Netty——高级发送和接收数据handler处理器

    netty发送和接收数据handler处理器 主要是继承 SimpleChannelInboundHandler 和 ChannelInboundHandlerAdapter 一般用netty来发送和 ...

  7. MO拆分计划行程序中写入PRODUCTIONORDERS表数据出现重复导致报错(BUG)20180502

    错误提示:ORA-00001: 违反唯一约束条件 (ABPPMGR.C0248833319_6192)ORA-06512: 在 "STG.FP_MO_SPLIT", line 19 ...

  8. netty发送和接收数据handler处理器

    netty发送和接收数据handler处理器 主要是继承 SimpleChannelInboundHandler 和 ChannelInboundHandlerAdapter 一般用netty来发送和 ...

  9. socket 错误之:OSError: [WinError 10057] 由于套接字没有连接并且(当使用一个 sendto 调用发送数据报套接字时)没有提供地址,发送或接收数据的请求没有被接受。

    出错的代码 #server端 import socket import struct sk=socket.socket() sk.bind(('127.0.0.1',8080)) sk.listen( ...

随机推荐

  1. Centos6.8 安装tomcat8.5.11

    1.下载 安装包 wget http://mirrors.aliyun.com/apache/tomcat/tomcat-8/v8.5.11/bin/apache-tomcat-8.5.11.tar. ...

  2. Spring AOP分析(2) -- JdkDynamicAopProxy实现AOP

    上文介绍了代理类是由默认AOP代理工厂DefaultAopProxyFactory中createAopProxy方法产生的.如果代理对象是接口类型,则生成JdkDynamicAopProxy代理:否则 ...

  3. C# linq左连接与分组

    1.左连接使用DefaultIfEmpty(): 2.分组时候判断newper.FirstOrDefault() == null ? null: newper.ToList()这个经常出错误,如果不判 ...

  4. JAVAscript学习笔记 js句柄监听事件 第四节 (原创) 参考js使用表

    <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...

  5. mac链接linux终端,shell脚本发布代码

    项目的业务需求:从mac端直接连上linux服务终端,并发布相关的代码 一.使用ssh链接上linux服务端 1.cd ~/.ssh 2.vi config,按照下面的内容配置config文件,然后: ...

  6. js实现小球的弹性碰撞。

      前  言 MYBG 小编最近在做自己的个人网站,其中就用到了一个小球碰撞检测的功能,想自己写,无奈本人能力不足啊(毕竟还是一个菜鸟)!!就想着找个插件用一下也好,可是找了好久也没有找到一个比较好用 ...

  7. ASP.NET Core 应用程序Startup类介绍

    Startup类配置服务和应用程序的请求管道. Startup 类 ASP.NET Core应用程序需要一个启动类,按照惯例命名为Startup.在主程序的Web Host生成器(WebHostBui ...

  8. Jquery判断Checkbox是否选中三种方法

    方法一:if ($("#checkbox-id")get(0).checked) {    // do something} 方法二:if($('#checkbox-id').is ...

  9. java环境变量和tomcat环境变量配置

    一.JAVA环境变量的配置1.首先下载JDK JDK可以在Oracle(甲骨文)公司的官方网站http://www.oracle.com下载2.安装完成后查看JDK安装路径一般是C:\Program ...

  10. 数据库中File权限的危害

    The FILE privilege gives you permission to read and write files on the server host using the LOAD DA ...