1 | 用PERL编写的socket聊天室服务器程序 | |
next if($socket==$listen); $socket->send($line) or do { print $socket->fileno . ": disconnectedn"; $select->remove($socket); $socket->close; }; } 下面是这个聊天程序的所有代码: #!/usr/local/bin/perl -wT require 5.002; use strict; use IO::Socket; use IO::Select; #创建一个socket监听端口 my $listen = IO::Socket::INET->new(Proto => 'tcp', Reuse => 1) or die $!; #$select只包含我们正在监听的socket my $select = IO::Select->new($listen); my @ready; # 等待 while(@ready = $select->can_read) { my $socket; # 处理每个准备好的端口 for $socket (@ready) { # 如果被监听的端口准备好,接收一个新的连接 if($socket == $listen) { my $new = $listen->accept; $select->add($new); print $new->fileno . ": connectedn"; } else { # 读入一行文字 # 如果recv()失败,关闭连接 my $line=""; $socket->recv($line,80); if($line eq "") { print $socket->fileno . ": disconnectedn"; $select->remove($socket); $socket->close; }; my $socket; # 向所有人广播,如果send()失败则关闭连接. for $socket ($select->handles) { next if($socket==$listen); $socket->send($line) or do { print $socket->fileno . ": disconnectedn"; $select->remove($socket); $socket->close; }; } } } } 1; 我是谁? 我们的聊天程序还有一个问题,就是我们不知道是谁在说话.真正的聊天室服务器能让你知道谁是谁,在发言后面把他们的名字显示出来. 如果我们只能在一个时刻做一件事情,请求一个handle的较为直接的程序代码就象这个样子: my $new = $listen->accept; $select->add($new); print $new->fileno . ": connectedn"; $new->write("choose a handle> "); $handle[$new->fileno] = $new->recv; 问题是,我们不能要服务器停下来等待用户输入,我们需要把用户在那里的信息保存下来,当一个用户在输入的时候,可以处理其他用户,当这个用户输入完了以后在回来.完成这些功能的代码可以分为两部分: sub login { my($new) = @_; $select->add($new); print $new->fileno . ": connectedn"; $new->write("choose a handle> "); save_where_we_are(); } sub get_handle { my($socket) = @_; $handle[$socket->fileno] = $socket->recv; } #!/usr/local/bin/perl -wT require 5.002; use strict; use IO::Socket; use IO::Select; my $port = scalar(@ARGV)>0 ? $ARGV[0] : 2323; $| = 1; my $listen = IO::Socket::INET->new(Proto => 'tcp', LocalPort => $port, Reuse => 1) or die $!; $ENV{'PATH'} = "/usr/bin"; my $date = `date`; warn "started on $port on $date"; my $select = IO::Select->new($listen); my @chatters; # 在win32中,注释掉下面这句 $SIG{'PIPE'} = 'IGnorE'; my @ready; while(@ready = $select->can_read) { print "going: ".join(',',map {$_->fileno} @ready) . "n"; my $socket; for $socket (@ready) { if($socket == $listen) { my $new_socket = $listen->accept; Chatter->new($new_socket,$select,@chatters); } else { my $chatter = $chatters[$socket->fileno]; if(defined $chatter) { &{$chatter->nextsub}(); } else { print "unkNown chattern"; } } } } package Chatter; use strict; sub new { my($class,$socket,$chatters) = @_; my $self = { 'socket' => $socket, 'select' => $select, 'chatters' => $chatters }; bless $self,$class; $chatters->[$socket->fileno] = $self; $self->select->add($socket); $self->log("connected"); $self->ask_for_handle; return $self; } sub socket { $_[0]->{'socket'} } sub select { $_[0]->{'select'} } sub chatters { $_[0]->{'chatters'} } sub handle { $_[0]->{'handle'} } sub nextsub { $_[0]->{'nextsub'} } sub ask_for_handle { my($self) = @_; my $welcome =<< END; 欢迎你来到我的聊天室. 使用指南: 请注意这个聊天室程序不完全兼容telnet协议,所以有些telnet客户端程序可能不工作,抱歉! 如果你输入的字符都分行显示,请退出然后试一试其它的telnet客户端程序,最好发一个电子邮件 ([email protected])告诉我你用的是什么程序. 我们已经试过下面的客户端程序,它们都能很好的工作: - "telnet" on Solaris - "telnet" on IRIX - CRT on Windows 95 我们已经收到报告,微软的Telnet不能工作. 另外,有些人登录以后可能去干别的事情了,所以他们不会马上看到你的信息.所以输入以后,保持telnet 窗口开着,等待一会儿. 关闭你的telnet窗口就可以退出.或者假如你是在Unix命令行运行telnet的话,按Control-]然后在提示中按"close"键. __Brian__ END $welcome =~ s:n:rn:g; $self->write($welcome); $self->write("choose a handle> "); $self->{'nextsub'} = sub { $self->get_handle }; } sub get_handle { my($self) = @_; my $handle = $self->read or return; $handle =~ tr/ -~//cd; $self->{'handle'} = $handle; $self->broadcast("[$handle is here]"); $self->log("handle: $handle"); $self->{'nextsub'} = sub { $self->chat }; } sub chat { my($self) = @_; my $line = $self->read; return if($line eq ""); $line =~ tr/ -~//cd; my $handle = $self->handle; $self->broadcast("$handle> $line"); } sub broadcast { my($self,$msg) = @_; my $socket; for $socket ($self->select->handles) { my $chatter = $self->chatters->[$socket->file |
no]; $chatter->write("$msgrn") if(defined $chatter); } } sub read { my($self) = @_; my $buf=""; $self->socket->recv($buf,80); $self->leave if($buf eq ""); return $buf; } sub write { my($self,$buf) = @_; $self->socket->send($buf) or $self->leave; } sub leave { my($self) = @_; print "leave calledn"; $self->chatters->[$self->socket->fileno] = undef; $self->select->remove($self->socket); my $handle = $self->handle; $self->broadcast("[$handle left]") if(defined $handle); $self->log("disconnected"); $self->socket->close; } sub log { my($self,$msg) = @_; my $fileno = $self->socket->fileno; print "$fileno: $msgn"; } __END__ # and here's a chat server in 4 lines :-) #!/usr/local/bin/perl -- minchat: run and telnet to port 5555 - bslesins sub p{print@_}$SIG{CHLD}=sub{wait};socket S,2,6;bind S,pack(Snx12,5555); listen S,5;while(accept C,S){if(!fork){open(STDOUT,">&C");p"name:";$n=substr,-2;$f=fork||exec"tail -f chatlog";open W,">>chatlog";select(W);$|=1;p "[$n here]rn";while(){p"$n> $_";}p"[$n gone]rn";kill 15,$f;exit}} 如何保存用户位置信息呢? 一个方法是保存一个子程序的指针,而这个子例程包含了下一步该做什么: $nextsub[$socket->fileno] = &get_handle; 这样我们就可以在@nextsub中适当的入口找到我们出发的位置. 综合以上所述,我们把程序整理如下. 剩下的工作: 我们的聊天室程序还不是一个完整的作品,如果你象把它放在你的服务器上工作,还有许多事情要做.他们是: 输入缓冲区: 关于recv()函数,它并不总是每次接收一行数据.一个真正的聊天服务器需要把recv()的结果添加到缓冲区中,并找到折行字符,把它分成几行. 输出缓冲区: 如果有人挂起它的telnet进程太长时间,调用send()会中断它.但可以用select()来发现一个socket是否已经准备好. 更好地支持telnet协议 加入常用的命令:帮助,列出在聊天室中的用户名单,退出等等 用户账号密码保护 多个聊天房间 权限控制 私人聊天房间 等等...
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 [email protected] 举报,一经查实,本站将立刻删除。