我所不知道的TCP Socket编程(五)-交换数据、套接字读写操作

来源:互联网 发布:java框架教程 编辑:程序博客网 时间:2024/06/07 10:50

五:交换数据

     

已经建立了服务器和客户端的链接,现在需要让它们进行数据交换;

     

     你可以将TCP连接想象成一串连接了本地套接字和远程套接字的管子,我们可以沿着这个管子发送和接受数据;

     实际中,数据被编码为TCP/IP分组,经过多台路由器和主机,抵达终点;

     

     5.1 流:

     

     TCP具有流性质:

     TCP是一个基于流的协议,这也是创建套接字时,传入的:STREAM选项的意思;

     

     前面提到了分组,从协议层面上而言,TCP在网络上发送的就是分组;

     从代码的角度来说,TCP连接提供了一个不间断的,有序的通信流;

     

     比如,你可以在客户端发送3分数据,一次一份,在服务端一次操作(当做一份数据)就可以读取全部数据;

     

     即,流并没有消息边界(流中内容的次序还是会保留的);

     

     5.2 套接字读操作

     

     介绍各种读取数据的方式以及各自的使用场景;

     

     5.2.1 简单的读操作

     

     从套接字读取数据的最简单方法是使用read:

     

     # ./code/snippets/read.rb     require 'socket'     Socket.tcp_server_loop(4481) do | connection |        # 从连接中读取数据的最简单方法        puts connection.read        # 完成读取之后关闭连接,让客户端知道不用在等待数据返回        connection.close     end

     

     服务端开始侦听4481端口,此时可以使用netcat命令,连接本地localhost 4481端口

     

     echo gekko | nc localhost 4481
     

     (没有运行Ruby程序,在终端做了简单测试,查看输出结果)(图1)

     



     Ruby中所有的IO对象(套接字、管道、文件……)都有一套通用的接口,支持read,write,flush等方法;

     底层的read(2)、write(2)等系统调用都可以作用于文件、套接字、管道等之上;这种抽象源自操作系统本身,记住,一切皆文件;

     

     5.2.2 没那么简单

     

     如果运行的是如下命令:

     

     tail -f /var/log/system.log | nc -t 127.0.0.1 4481
     

     然后不管了,服务器将永远不会停止读取数据;(图2)



     

     原因是EOF(end-of-file),之后会介绍;

     

     问题在于tail -f根本就不会停止发送数据;如果tail没有数据可以发送,他会一直等到有为止;这使得netcat的管道一直处于打开状态;

     

     服务器的read调用就一直被阻塞着,直到客户端发送完数据为止;服务器等待的过程中,会将收到的数据缓冲起来,不返回给应用程序;

     

     5.2.3 读取长度

     

     作为上述问题的一个不太成熟的解决方式:指定最小的读取长度;

     这样就不用等到客户端结束发送才停止读取,而是告诉服务器读取(read)特定的数据量;     


    # ./code/snippets/read.rb     require 'socket'     one_kb = 1024 # 字节数     Socket.tcp_server_loop(4481) do | connection |        # 以1kb为单位进行读取        while data = connection.read(one_kb) do            puts data        end        connection.close     end

     

     然后再运行:


     tail -f /var/log/system.log | nc -t 127.0.0.1 4481

     会使服务器以1kb为单位打印数据;

     

     这个例子中给read传递了一个参数:他告诉read在读取了一定数量的数据之后就停止读取并返回;

     由于希望得到所有可用的数据,我们在调用read方法时使用了循环,直到它不再返回数据为止;

     (在《iOS平台Socket编程实践》系列文章中,演示了相关的数据传输)

     

     注:

     请注意区分一个概念,无论是服务器还是客户端(一对多的关系)都只是Socket连接的端点,由于是一对多的关系,对应的Socket链接也将是多个,虽然iOS中,CocoaAsyncSocket把他们封装成了一个类,但请注意区分;

     

     5.2.4 阻塞的本质

     

     read调用会一直阻塞,知道获取了完整的长度(full length)的数据为止;

     上面的例子有个问题,就是如果读取了若干次之后的数据不足1kb,那么read会一直阻塞;

     如此会导致死锁,可以采用的补救措施:

     1)客户端发送了500B后就再发送一个EOF;

     2)服务器采用部分读取(partial read)的方式;

          

     5.2.5 EOF事件

     

     当调用read并受到EOF时,就意味着不再有数据,可以停止读取了;(对于理解IO很重要)

     EOF代表一个状态事件,shutdown和close都会导致一个EOF事件被发送给另一端进行读操作的进程,这样他就知道不会在有数据到达了;

     

     下面是正确的数据读取代码:

     

     # ./code/snippets/read.rb     require 'socket'     one_kb = 1024 # 字节数     Socket.tcp_server_loop(4481) do | connection |     # 以1kb为单位进行读取     while data = connection.read(one_kb) do     puts data     end     connection.close     end

     

     客户端连接:

     

     # ./code/snippets/read.rb     require 'socket'          client = TCPSocket.new('localhost' , 4481)     client.write('gekko')     client.close

     

     5.2.6 部分读取

     

     即readpartial;

     readpartial并不会阻塞,而是立刻返回可用数据;调用时,需要传入整数参数,指定最大长度(readpartial最多读取到指定长度);

     指定读取1kb,但只发送500B的话,readpartial不会阻塞,而是会立刻返回已经读取到的数据;

     

     服务端:

     

     # ./code/snippets/readpartial_with_length.rb     require 'socket'     one_hundred_kb = 1024 * 100     Socket.tcp_server_loop(4481) do | connection |        begin            # 每次读取100KB或更少            while data = connection.readpartial(one_hundred_kb) do                puts data            end        rescue EOFError        end                connection.close     end

     

     这样,只要有数据,readpartial就会将其返回,即便是小于最大长度;

     

     EOF而言,read仅仅是返回,readpartial则会产生一个EOFError异常,提醒我们小心;

     

     总结就是:

     read很懒惰,只会傻等,以求返回尽可能多的数据;

     readpartial更勤快,只要有数据可用就立刻返回;

     

     在学习缓冲区之后,我们会对一次性读取多少数据,小读取量多次和大读取量单次那个更好等问题做出回答;

     

     5.3 套接字写操作

     

     write方法:

     

     # ./code/snippets/readpartial_with_length.rb     require 'socket'          Socket.tcp_server_loop(4481) do | connection |        # 向连接中写入数据的最简单方式        connection.write('Welcome!')        connection.close     end


     5.4 涉及到的系统调用

     

     ·Socket#read->read(2)                   //ri Socket#read -> man 2 read

     ·Socket#readpartial->read(2)            //ri Socket#readpartial -> man 2 read

     ·Socket#write->write(2)                   //ri Socket#write -> man 2 write


     总结:

        下一节将会介绍几个较高级的概念,不过到目前为止,我所不知道的Socket已经没那么多了,你的呢。


     

阅读全文
0 0
原创粉丝点击