⭐ 欢迎来到虫虫下载站! | 📦 资源下载 📁 资源专辑 ℹ️ 关于我们
⭐ 虫虫下载站

📄 251.htm

📁 unix高级编程原吗
💻 HTM
字号:
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=gb2312">
<title>CTerm非常精华下载</title>
</head>
<body bgcolor="#FFFFFF">
<table border="0" width="100%" cellspacing="0" cellpadding="0" height="577">
<tr><td width="32%" rowspan="3" height="123"><img src="DDl_back.jpg" width="300" height="129" alt="DDl_back.jpg"></td><td width="30%" background="DDl_back2.jpg" height="35"><p align="center"><a href="http://apue.dhs.org"><font face="黑体"><big><big>apue</big></big></font></a></td></tr>
<tr>
<td width="68%" background="DDl_back2.jpg" height="44"><big><big><font face="黑体"><p align="center">               ● UNIX网络编程                       (BM: clown)                </font></big></big></td></tr>
<tr>
<td width="68%" height="44" bgcolor="#000000"><font face="黑体"><big><big><p   align="center"></big></big><a href="http://cterm.163.net"><img src="banner.gif" width="400" height="60" alt="banner.gif"border="0"></a></font></td>
</tr>
<tr><td width="100%" colspan="2" height="100" align="center" valign="top"><br><p align="center">[<a href="index.htm">回到开始</a>][<a href="246.htm">上一层</a>][<a href="252.htm">下一篇</a>]
<hr><p align="left"><small>发信人: guru (好读书,不求甚解), 信区: UNP <br>

标  题: 一个简单聊天室的两种实现 (fcntl 和 select) <br>

发信站: UNIX编程 (2001年08月27日11:46:52 星期一), 站内信件 <br>

  <br>

:非常对不起原作者,可能在转载过程中掐头去尾,我拿到的时候 <br>

:就没有版权信息,向作者表示致歉。 <br>

:文章中提到的源代码在给定地址无法得到。 <br>

功能是当在这个聊天室中的任何一个 <br>

  <br>

用户输入一段字符之后, 室内的所有其他用户都可以得到这一句 <br>

话. 本聊天室功能非常简单, 感兴趣的朋友可以将其功能扩展, 发 <br>

展成一个功能比较完整的聊天室, 如加上用户认证, 用户昵称, 秘密 <br>

信息, semote 等功能. 这个聊天室有两种实现方法: fcntl 和 select, <br>

下面就这个聊天室的实现做一个介绍, 并对两种方法进行比较. <br>

  <br>

聊天室是一个 client/server 结构的程序, 首先启动 server, <br>

然后用户使用 client 进行连接. client/server 结构的优点是速度 <br>

快, 缺点是当 server 进行更新时, client 也必需更新. <br>

  <br>

首先是 初始化 server, 使server 进入监听状态: (为了简洁起见, <br>

以下引用的程序与实际程序略有出入, 下同) <br>

  <br>

sockfd = socket( AF_INET,SOCK_STREAM, 0); <br>



// 首先建立一个 socket, 族为 AF_INET, 类型为 SOCK_STREAM. <br>

// AF_INET = ARPA Internet protocols 即使用 TCP/IP 协议族 <br>

// SOCK_STREAM 类型提供了顺序的, 可靠的, 基于字节流的全双工连接. <br>

// 由于该协议族中只有一个协议, 因此第三个参数为 0 <br>

  <br>

bind( sockfd, ( struct sockaddr *)&serv_addr, sizeof( serv_addr)); <br>

// 再将这个 socket 与某个地址进行绑定. <br>

// serv_addr 包括 sin_family = AF_INET 协议族同 socket <br>

// sin_addr.s_addr = htonl( INADDR_ANY) server 所接受的所有其他 <br>

// 地址请求建立的连接. <br>

// sin_port = htons( SERV_TCP_PORT) server 所监听的端口 <br>

// 在本程序中, server 的 IP和监听的端口都存放在 config 文件中. <br>

  <br>

  <br>

listen( sockfd, MAX_CLIENT); <br>

// 地址绑定之后, server 进入监听状态. <br>

// MAX_CLIENT 是可以同时建立连接的 client 总数. <br>

  <br>

server 进入 listen 状态后, 等待 client 建立连接. 此时如果有 <br>

  <br>

client 进行连接时, 也要先进行网络部分的初始化工作: <br>

  <br>

  <br>

sockfd = socket( AF_INET,SOCK_STREAM,0)); <br>

// 同样的, client 也先建立一个 socket, 其参数与 server 相同. <br>

  <br>

connect( sockfd, ( struct sockaddr *)&serv_addr, sizeof( serv_addr)); <br>

// client 使用 connect 建立一个连接. <br>

// serv_addr 中的变量分别设置为: <br>

// sin_family = AF_INET 协议族同 socket <br>

// sin_addr.s_addr = inet_addr( SERV_HOST_ADDR) 地址为 server <br>

// 所在的计算机的地址. <br>

// sin_port = htons( SERV_TCP_PORT) 端口为 server 监听的端口. <br>

  <br>

当 client 建立新连接时, server 使用 accept 来接受该连接: <br>

  <br>

accept( sockfd, (struct sockaddr*)&cli_addr, &cli_len); <br>

// 在函数返回时, cli_addr 中保留的是该连接对方的信息 <br>

// 包括对方的 IP 地址和对方使用的端口. <br>

// accept 返回一个新的文件描述符. <br>

  <br>

在 server 进入 listen 状态之后, 我们下面分别讨论两种实现方法: <br>

  <br>

1. fcntl 方法 <br>

  <br>

  <br>

  <br>

对一个文件描述符指定的文件或设备, 有两种工作方式: 阻塞与非阻塞, <br>

  <br>

阻塞的意思是指, 当试图对该文件描述符进行读写时, 如果当时没有东西可读, <br>

  <br>

或者暂时不可写, 程序就进入等待状态, 直到有东西可读或者可写为止. 而对于 <br>

  <br>

非阻塞状态, 如果没有东西可读, 或者不可写, 读写函数马上返回, 而不会等待. <br>

  <br>

缺省情况下, 文件描述符处于阻塞状态. 在实现聊天室时, server 轮流查询与各 <br>

  <br>

client 建立 socket, 一旦可读就将该 socket 中的字符读出来并向所有其他 <br>

  <br>

client 发送. 并且, server 还要随时查看是否有新的 client 试图建立连接, <br>

  <br>

这样, 如果 server 在任何一个地方阻塞了, 其他 client 发送的内容就会受到 <br>

  <br>

影响, 新 client 试图建立连接也会受到影响. 因此, 我们使用 fcntl 将该 <br>

  <br>

文件描述符变为非阻塞的: <br>

  <br>

  <br>

  <br>

fcntl( sockfd, F_SETFL, O_NONBLOCK); <br>

  <br>

// sockfd 是要改变状态的文件描述符. <br>

  <br>

// F_SETFL 表明要改变文件描述符的状态 <br>

  <br>

// O_NONBLOCK 表示将文件描述符变为非阻塞的. <br>

  <br>

  <br>

使用自然语言描述聊天室 server : <br>

  <br>

  <br>

while ( 1) { <br>

  <br>

if 有新连接 then 建立并记录该新连接; <br>

  <br>

for ( 所有的有效连接) { <br>

  <br>

if 该连接中有字符可读 then { <br>

  <br>

for ( 所有其他的有效连接) { <br>

  <br>

  <br>

将该字符串发送给该连接; <br>

  <br>

} <br>

  <br>

} <br>

  <br>

} <br>

  <br>

} <br>

  <br>

  <br>

由于判断是否有新连接, 是否可读都是非阻塞的, 因此每次判断, 不管 <br>

  <br>

有还是没有, 都会马上返回. 这样, 任何一个 client 向 server 发送字符 <br>

  <br>

或者试图建立新连接, 都不会对其他 client 的活动造成影响. <br>

  <br>

  <br>

对 client 而言, 建立连接之后, 只需要处理两个文件描述符, 一个是 <br>

  <br>

建立了连接的 socket 描述符, 另一个是标准输入. 和 server 一样, 如果 <br>

  <br>

  <br>

使用阻塞方式的话, 很容易因为其中一个暂时没有输入而影响另外一个的读 <br>

  <br>

入. 因此将它们都变成非阻塞的, 然后client 进行如下动作: <br>

  <br>

  <br>

while ( 不想退出) { <br>

  <br>

if ( 与 server 的连接有字符可读) { <br>

  <br>

从该连接读入, 并输出到标准输出上去. <br>

  <br>

} <br>

  <br>

if ( 标准输入可读) { <br>

  <br>

从标准输入读入, 并输出到与 server 的连接中去. <br>

  <br>

} <br>

  <br>

} <br>

  <br>

  <br>

  <br>

  <br>

  <br>

上面的读写分别调用这样两个函数: <br>

  <br>

  <br>

read( userfd[i], line, MAX_LINE); <br>

  <br>

// userfd[i] 是指第 i 个 client 连接的文件描述符. <br>

  <br>

// line 是指读出的字符存放的位置. <br>

  <br>

// MAX_LINE 是一次最多读出的字符数. <br>

  <br>

// 返回值是实际读出的字符数. <br>

  <br>

  <br>

write( userfd[j], line, strlen( line)); <br>

  <br>

// userfd[j] 是第 j 个 client 的文件描述符. <br>

  <br>

// line 是要发送的字符串. <br>

  <br>

  <br>

// strlen( line) 是要发送的字符串长度. <br>

  <br>

  <br>

分析上面的程序可以知道, 不管是 server 还是 client, 它们都 <br>

  <br>

不停的轮流查询各个文件描述符, 一旦可读就读入并进行处理. 这样的 <br>

  <br>

程序, 不停的在执行, 只要有CPU 资源, 就不会放过. 因此对系统资源 <br>

  <br>

的消耗非常大. server 或者 client 单独执行时, CPU 资源的 98% 左 <br>

  <br>

右都被其占用. 因此, 我们可以使用另外一种阻塞的方法来解决这个问题, <br>

  <br>

这就是 select. <br>

  <br>

  <br>

2. select 方法 <br>

  <br>

  <br>

select 方法中, 所有文件描述符都是阻塞的. 使用 select 判断一 <br>

  <br>

组文件描述符中是否有一个可读(写), 如果没有就阻塞, 直到有一个的 <br>



  <br>

时候就被唤醒. 我们先看比较简单的 client 的实现: <br>

  <br>

  <br>

由于 client 只需要处理两个文件描述符, 因此, 需要判断是否有可 <br>

  <br>

读写的文件描述符只需要加入两项: <br>

  <br>

  <br>

FD_ZERO( sockset); <br>

  <br>

// 将 sockset 清空 <br>

  <br>

FD_SET( sockfd, sockset); <br>

  <br>

// 把 sockfd 加入到 sockset 集合中 <br>

  <br>

FD_SET( 0, sockset); <br>

  <br>

// 把 0 (标准输入) 加入到 sockset 集合中 <br>

  <br>

  <br>

  <br>

然后 client 的处理如下: <br>

  <br>

  <br>

while ( 不想退出) { <br>

  <br>

select( sockfd+1, &sockset, NULL, NULL, NULL); <br>

  <br>

// 此时该函数将阻塞直到标准输入或者 sockfd 中有一个可读为止 <br>

  <br>

// 第一个参数是 0 和 sockfd 中的最大值加一 <br>

  <br>

// 第二个参数是 读集, 也就是 sockset <br>

  <br>

// 第三, 四个参数是写集和异常集, 在本程序中都为空 <br>

  <br>

// 第五个参数是超时时间, 即在指定时间内仍没有可读, 则出错 <br>

  <br>

// 并返回. 当这个参数为NULL 时, 超时时间被设置为无限长. <br>

  <br>

// 当 select 因为可读返回时, sockset 中包含的只是可读的 <br>

  <br>

// 那些文件描述符. <br>



  <br>

  <br>

if ( FD_ISSET( sockfd, &sockset)) { <br>

  <br>

// FD_ISSET 这个宏判断 sockfd 是否属于可读的文件描述符 <br>

  <br>

  <br>

从 sockfd 中读入, 输出到标准输出上去. <br>

  <br>

} <br>

  <br>

if ( FD_ISSET( 0, &sockset)) { <br>

  <br>

// FD_ISSET 这个宏判断 sockfd 是否属于可读的文件描述符 <br>

  <br>

从标准输入读入, 输出到 sockfd 中去. <br>

  <br>

} <br>

  <br>

重新设置 sockset. (即将 sockset 清空, 并将 sockfd 和 0 加入) <br>

  <br>

} <br>

} <br>

  <br>

  <br>

下面看 server 的情况: <br>

  <br>

  <br>

设置 sockset 如下: <br>

  <br>

  <br>

FD_ZERO( sockset); <br>

  <br>

FD_SET( sockfd, sockset); <br>

  <br>

for ( 所有有效连接) <br>

  <br>

FD_SET( userfd[i], sockset); <br>

  <br>

} <br>

  <br>

maxfd = 最大的文件描述符号 + 1; <br>

  <br>

  <br>

server 处理如下: <br>



  <br>

  <br>

while ( 1) { <br>

  <br>

select( maxfd, &sockset, NULL, NULL, NULL); <br>

  <br>

if ( FD_ISSET( sockfd, &sockset)) { <br>

  <br>

// 有新连接 <br>

  <br>

建立新连接, 并将该连接描述符加入到 sockset 中去了. <br>

  <br>

} <br>

  <br>

for ( 所有有效连接) { <br>

  <br>

if ( FD_ISSET ( userfd[i], &sockset)) { <br>

  <br>

// 该连接中有字符可读 <br>

  <br>

从该连接中读入字符, 并发送到其他有效连接中去. <br>

  <br>

  <br>

} <br>

  <br>

} <br>

  <br>

重新设置 sockset; <br>

  <br>

} <br>

  <br>

  <br>

由于采用 select 机制, 因此当没有字符可读时, 程序处于阻塞状态, <br>

  <br>

最小程度的占用CPU 资源, 在同一台机器上执行一个 server 和若干个 <br>

  <br>

client 时, 系统负载只有 0.1 左右, 而采用原来的 fcntl 方法, 只运行 <br>

  <br>

一个 server, 系统负载就可以达到 1.5 左右. 因此我们推荐使用 select. <br>

  <br>

  <br>

By Simon Lei, Jul.01,1999. All Rights Reserved. <br>

  <br>

欢迎传播, 请保留作者信息. <br>

  <br>



-- <br>

Target Locked:Guru In Darkness. <br>

我只是一只静静卧着的狮子。。。 <br>

※ 修改:·guru 於 08月27日11:59:27 修改本文·[FROM: 202.114.36.225] <br>

※ 来源:·UNIX编程 www.tiaozhan.com/unixbbs/·[FROM: 202.114.36.225] <br>

</small><hr>
<p align="center">[<a href="index.htm">回到开始</a>][<a href="246.htm">上一层</a>][<a href="252.htm">下一篇</a>]
<p align="center"><a href="http://cterm.163.net">欢迎访问Cterm主页</a></p>
</table>
</body>
</html>

⌨️ 快捷键说明

复制代码 Ctrl + C
搜索代码 Ctrl + F
全屏模式 F11
切换主题 Ctrl + Shift + D
显示快捷键 ?
增大字号 Ctrl + =
减小字号 Ctrl + -