Beej网络socket编程指南——实例(5)

客户-服务器背景知识 
  这里是个客户--服务器的世界。在网络上的所有东西都是在处理客户进 程和服务器进程的交谈。举个telnet 的例子。当你用 telnet (客户)通过23 号端口登陆到主机,主机上运行的一个程序(一般叫 telnetd,服务器)激活。 它处理这个连接,显示登陆界面,等等。 


2:客户机和服务器的关系 
 2 说明了客户和服务器之间的信息交换。 
注意,客户--服务器之间可以使用SOCK_STREAMSOCK_DGRAM 或者其它(只要它们采用相同的)。一些很好的客户--服务器的例子有 telnet/telnetd ftp/ftpd  bootp/bootpd。每次你使用 ftp 的时候,在远 端都有一个 ftpd 为你服务。 
一般,在服务端只有一个服务器,它采用 fork() 来处理多个客户的连 接。基本的程序是:服务器等待一个连接,接受 (accept()) 连接,然后 fork() 一个子进程处理它。这是下一章我们的例子中会讲到的。



简单的服务器 
  这个服务器所做的全部工作是在流式连接上发送字符串 "Hello, World!/n"。你要测试这个程序的话,可以在一台机器上运行该程序,然后 在另外一机器上登陆: 
   $ telnet remotehostname 3490 
remotehostname 
是该程序运行的机器的名字。 
服务器代码:   

如果你很挑剔的话,一定不满意我所有的代码都在一个很大的main() 函数中。如果你不喜欢,可以划分得更细点。 
你也可以用我们下一章中的程序得到服务器端发送的字符串。



简单的客户程序 
  这个程序比服务器还简单。这个程序的所有工作是通过 3490 端口连 接到命令行中指定的主机,然后得到服务器发送的字符串。 
客户代码

   

 

注意,如果你在运行服务器之前运行客户程序,connect() 将返回 "Connection refused" 信息,这非常有用。


数据报 Sockets 
  我不想讲更多了,所以我给出代码 talker.c  listener.c 
listener 
在机器上等待在端口 4590 来的数据包。talker 发送数据包到 一定的机器,它包含用户在命令行输入的内容。 
这里就是 listener.c 
   

注意在我们的调用 socket(),我们最后使用了 SOCK_DGRAM。同时, 没有必要去使用 listen() 或者 accept()。我们在使用无连接的数据报套接 字! 
下面是 talker.c 

这就是所有的了。在一台机器上运行 listener,然后在另外一台机器上 运行 talker。观察它们的通讯! 
除了一些我在上面提到的数据套接字连接的小细节外,对于数据套接 字,我还得说一些,当一个讲话者呼叫connect()函数时并指定接受者的地 址时,从这点可以看出,讲话者只能向connect()函数指定的地址发送和接 受信息。因此,你不需要使用sendto()recvfrom(),你完全可以用send() recv()代替。


阻塞 
  阻塞,你也许早就听说了。"阻塞" "sleep" 的科技行话。你可能注意 到前面运行的 listener 程序,它在那里不停地运行,等待数据包的到来。 实际在运行的是它调用 recvfrom(),然后没有数据,因此 recvfrom() 阻塞 (block)",直到数据的到来。 
很多函数都利用阻塞。accept() 阻塞,所有的 recv*() 函数阻塞。 们之所以能这样做是因为它们被允许这样做。当你第一次调用 socket()  立套接字描述符的时候,内核就将它设置为阻塞。如果你不想套接字阻塞, 你就要调用函数 fcntl() 
#include <unistd.h> 
  #include <fontl.h> 
   . 
   . 
   sockfd = socket(AF_INET, SOCK_STREAM, 0); 
   fcntl(sockfd, F_SETFL, O_NONBLOCK); 
   . 
   . 
  通过设置套接字为非阻塞,你能够有效地"询问"套接字以获得信息。如 果你尝试着从一个非阻塞的套接字读信息并且没有任何数据,它不允许阻 --它将返回 -1 并将 errno 设置为 EWOULDBLOCK 
但是一般说来,这种询问不是个好主意。如果你让你的程序在忙等状 态查询套接字的数据,你将浪费大量的 CPU 时间。更好的解决之道是用 下一章讲的 select() 去查询是否有数据要读进来。


select()--多路同步 I/O 
  虽然这个函数有点奇怪,但是它很有用。假设这样的情况:你是个服务器,你一边在不停地从连接上读数据,一边在侦听连接上的信息。 没问题,你可能会说,不就是一个 accept() 和两个 recv() 这么容易吗,朋友如果你在调用 accept() 的时候阻塞呢你怎么能够同时接  recv() 数据“用非阻塞的套接字啊!” 不行!你不想耗尽所有的 CPU那么,该如何是好
select() 
让你可以同时监视多个套接字。如果你想知道的话,那么它就 会告诉你哪个套接字准备读,哪个又准备写,哪个套接字又发生了例外 (exception) 
闲话少说,下面是 select() 
#include <sys/time.h> 
#include <sys/types.h> 
#include <unistd.h> 
int select(int numfds, fd_set *readfds, fd_set *writefds,fd_set 
*exceptfds, struct timeval *timeout); 

这个函数监视一系列文件描述符,特别是 readfdswritefds  exceptfds。如果你想知道你是否能够从标准输入和套接字描述符 sockfd 读入数据,你只要将文件描述符 0  sockfd 加入到集合 readfds 中。参  numfds 应该等于最高的文件描述符的值加1。在这个例子中,你应该 设置该值为 sockfd+1。因为它一定大于标准输入的文件描述符 (0) 当函数 select() 返回的时候,readfds 的值修改为反映你选择的哪个 文件描述符可以读。你可以用下面讲到的宏 FD_ISSET() 来测试。 在我们继续下去之前,让我来讲讲如何对这些集合进行操作。每个集 合类型都是 fd_set。下面有一些宏来对这个类型进行操作: 
FD_ZERO(fd_set *set) 
 清除一个文件描述符集合 
FD_SET(int fd, fd_set *set) - 添加fd到集合
 
FD_CLR(int fd, fd_set *set)  从集合中移去
fd 
FD_ISSET(int fd, fd_set *set)  测试fd是否在集合中
 
最后,是有点古怪的数据结构 struct timeval。有时你可不想永远等待 别人发送数据过来。也许什么事情都没有发生的时候你也想每隔96秒在终 端上打印字符串 "Still Going..."。这个数据结构允许你设定一个时间,如果 时间到了,而 select() 还没有找到一个准备好的文件描述符,它将返回让 你继续处理。 
数据结构 struct timeval 是这样的: 
struct timeval { 
   int tv_sec; /* seconds */ 
   int tv_usec; /* microseconds */ 
   }; 
只要将 tv_sec 设置为你要等待的秒数,将 tv_usec 设置为你要等待 的微秒数就可以了。是的,是微秒而不是毫秒。1,000微秒等于1毫秒,1,000 毫秒等于1秒。也就是说,1秒等于1,000,000微秒。为什么用符号 "usec" 字母 "u" 很象希腊字母 Mu,而 Mu 表示 "的意思。当然,函数 返回的时候 timeout 可能是剩余的时间,之所以是可能,是因为它依赖于 你的 Unix 操作系统。 
哈!我们现在有一个微秒级的定时器!别计算了,标准的 Unix 系统 的时间片是100毫秒,所以无论你如何设置你的数据结构 struct timeval 你都要等待那么长的时间。 
还有一些有趣的事情:如果你设置数据结构 struct timeval 中的数据为 0select() 将立即超时,这样就可以有效地轮询集合中的所有的文件描述 符。如果你将参数 timeout 赋值为 NULL,那么将永远不会发生超时,即 一直等到第一个文件描述符就绪。最后,如果你不是很关心等待多长时间, 那么就把它赋为 NULL 吧。 
下面的代码演示了在标准输入上等待 2.5 秒: 

如果你是在一个 line buffered 终端上,那么你敲的键应该是回车 (RETURN),否则无论如何它都会超时。 
现在,你可能回认为这就是在数据报套接字上等待数据的方式--你是对 的:它可能是。有些 Unix 系统可以按这种方式,而另外一些则不能。你 在尝试以前可能要先看看本系统的 man page 了。 
最后一件关于 select() 的事情:如果你有一个正在侦听 (listen()) 的套 接字,你可以通过将该套接字的文件描述符加入到 readfds 集合中来看是 否有新的连接。 
这就是我关于函数select() 要讲的所有的东西。 
  参考书目
  Internetworking with TCP/IP, volumes I-III by Douglas E. Comer and 
David L. Stevens. Published by Prentice Hall. Second edition ISBNs: 
0-13-468505-9, 0-13-472242-6, 0-13-474222-2. There is a third edition of 
this set which covers IPv6 and IP over ATM. 
  Using C on the UNIX System by David A. Curry. Published by 
O'Reilly & Associates, Inc. ISBN 0-937175-23-4. 
  TCP/IP Network Administration by Craig Hunt. Published by O'Reilly 
& Associates, Inc. ISBN 0-937175-82-X. 
  TCP/IP Illustrated, volumes 1-3 by W. Richard Stevens and Gary R. 
Wright. Published by Addison Wesley. ISBNs: 0-201-63346-9, 
0-201-63354-X, 0-201-63495-3. 
Unix Network Programming by W. Richard Stevens. Published by 
Prentice Hall. ISBN 0-13-949876-1. 
  On the web: 
  BSD Sockets: A Quick And Dirty Primer 
  (http://www.cs.umn.edu/~bentlema/unix/--has other great Unix 
system programming info, too!) 
Client-Server Computing 
  (http://pandonia.canberra.edu.au/ClientServer/socket.html) 
Intro to TCP/IP (gopher) 

(gopher://gopher-chem.ucdavis.edu/11/Index/Internet_aw/Intro_the_Inter 
net/intro.to.ip/) 
Internet Protocol Frequently Asked Questions (France) 
  (http://web.cnam.fr/Network/TCP-IP/) 
The Unix Socket FAQ 
  (http://www.ibrado.com/sock-faq/) 
RFCs--the real dirt: 
  RFC-768 -- The User Datagram Protocol (UDP) 
   (ftp://nic.ddn.mil/rfc/rfc768.txt) 
RFC-791 -- The Internet Protocol (IP) 
  (ftp://nic.ddn.mil/rfc/rfc791.txt) 
RFC-793 -- The Transmission Control Protocol (TCP) 
   (ftp://nic.ddn.mil/rfc/rfc793.txt) 
RFC-854 -- The Telnet Protocol 
   (ftp://nic.ddn.mil/rfc/rfc854.txt) 
RFC-951 -- The Bootstrap Protocol (BOOTP) 
 (ftp://nic.ddn.mil/rfc/rfc951.txt) 
RFC-1350 -- The Trivial File Transfer Protocol (TFTP) 
   (ftp://nic.ddn.mil/rfc/rfc1350.txt)

 

 

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值