已建立TCP连接但套接字不发送/ recv数据

时间:2018-02-18 19:59:26

标签: perl sockets tcp netcat

我正在编写一个简单的TCP客户端和服务器Perl脚本。 截至目前,我使用wireshark验证3路TCP握手,并建立连接。 但是,当我尝试发送或recv数据时,没有任何反应。

问题:

1)客户端和服务器之间的主要区别仅在于服务器是否添加了LISTEN参数,使其能够侦听传入连接?

2)recv和显示数据之间是否缺少任何步骤?

3)当程序第一次执行while循环时,不应该至少发送硬编码字符串“$ response”?

4)关闭($ sock,1)和sleep(1)在这个实现中有什么不同?让套接字睡眠是否可以,或者我应该使用shutdown($ sock,1)向客户端/服务器发出数据已发送的信号?

在检查wireshark中的连接状态时,我注意到只有握手才会发生。根本没有交换数据。所以我很确定问题出在写入套接字和从套接字读取数据(使用键盘或硬编码字符串)的某个地方。

任何帮助都会非常感激。

客户如下:

#!/usr/bin/perl 
use IO::Socket;
use Getopt::Long;
#$IP_addr = $ARGV[0];
#$tgt_port = $ARG[1];

#netcat client

$port = 9040;
$sock = IO::Socket::INET->new( PeerAddr => 'localhost',
                               PeerPort => $port,
                               LocalPort => 9000,               
                               Proto     => 'tcp')
                               or die "\nunable to bind on localhost : $port...";

while ($sock){
    #Get the clients IP and port number 
        $client_IP = $sock -> peerhost();
    #$client_IP = 'localhost';
        $client_port = $sock -> peerport();
        print "\n Connected to $client_IP $client_port \n";

    #Reading from socket
    $data;  
    $sock ->recv($data, 1024);
    print $data;

    #writing to  socket
    $sock->autoflush(1);
    $response = "response: OK recvd\n" ;
    $sock->send($response);
    shutdown($sock,1);

}

$sock -> close();

服务器如下:

#!/usr/bin/perl 
use IO::Socket;
use Getopt::Long;

#$IP_addr = $ARGV[0];
#$tgt_port = $ARG[1];

#netcat server
$port = 9040;
$sock = IO::Socket::INET->new( Listen    => 1,
                               LocalAddr => 'localhost',
                               LocalPort => $port,
                               Proto     => 'tcp')
                               or die "\nunable to bind on localhost : $port...";

while ($sock){
    print "\nListening on port $port ...\n";
        $sock  = $sock -> accept();
    #Get the clients IP and port number 
        $client_IP = $sock->peerhost();
        $client_port = $sock->peerport();
        print "\n Connected from $client_IP $client_port \n";

    #Reading from socket
    $data ->recv($sock, 1024);
    print $data;

    #writing to  socket
    $sock->autoflush(1);
    $response = "oohlalala" ;
    $sock -> send($response);
    shutdown($sock, 1);

}

$sock -> close();

2 个答案:

答案 0 :(得分:2)

服务器代码(使用telnet localhost 9040测试):

use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;

class CreateWritersTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('writers', function (Blueprint $table) {
            $table->increments('id');
            $table->string('name');
            $table->text('description');

            $table->string('nationality');
            $table->date('year_date')->nullable();
            $table->date('dead_date')->nullable();

            $table->timestamp('created_at')->useCurrent();
            $table->timestamp('updated_at')->default(DB::raw('CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP'));
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::dropIfExists('writers');
    }
}

客户端代码(针对上述服务器进行测试):

use strict;
use warnings;

use IO::Socket::INET;

my $port = 9040;
my $listen = IO::Socket::INET->new(Listen    => 1,
                                   LocalPort => $port,
                                   Proto     => 'tcp',
                                   ReuseAddr => 1,
                                   ReusePort => 1);
while ($listen) {
    print "\nListening on port $port ...\n";
    my $sock  = $listen->accept();
    my $client_IP = $sock->peerhost();
    my $client_port = $sock->peerport();
    print "\n Connected from $client_IP $client_port \n";

    my $data;
    $sock->recv($data, 1024);
    print $data;

    #writing to  socket
    $sock->autoflush(1);
    my $response = "oohlalala\n" ;
    $sock->send($response);
    shutdown($sock, 1);
}

服务器输出:

use strict;
use warnings;

use IO::Socket::INET;

my $port = 9040;
my $sock = IO::Socket::INET->new(PeerAddr  => 'localhost',
                                 PeerPort  => $port,
                                 Proto     => 'tcp');

if ($sock) {
    my $server_IP = $sock->peerhost();
    my $server_port = $sock->peerport();
    print "\n Connected to $server_IP $server_port \n";

    # initiate protocol
    $sock->autoflush(1);
    print $sock "OK\n";

    my $data;
    $sock->recv($data, 1024);
    print $data;

    shutdown($sock, 1);
}

客户输出:

Listening on port 9040 ...

 Connected from 127.0.0.1 36106 
OK

Listening on port 9040 ...

答案 1 :(得分:2)

来自IO::Socket

  

从VERSION 1.18开始,所有IO :: Socket对象都启用了autoflush   默认。早期版本的情况并非如此。

Perl 2.0版于1988年发布。您最有可能使用perl版本5.x。

  

1)客户端和服务器之间的主要区别仅在于此   服务器添加了一个LISTEN参数,使其能够监听   传入连接?

根据docs

  

如果定义了Listen,则会创建一个侦听套接字...

侦听套接字可以调用accept()。如果常规套接字调用accept(),则accept()返回undef。

  

接受()

     

在标量上下文中,返回新套接字,或者在失败时返回undef。   在列表上下文中,返回包含new的双元素数组   套接字和对等地址;失败时列表将为空。

     

https://perldoc.perl.org/IO/Socket.html

这是nice, short, basic description of sockets

您的服务器代码可能隐藏了一些正在发生的事情。如果它是这样编写的话会更具说明性:

$server_socket = IO::Socket::INET(.....);
...
...
my $client_socket = $server_socket->accept();
...
...

有两个不同的插座。 accept()返回一个新的套接字供服务器和客户端进行通信,因此原始服务器套接字可以继续监听客户端连接。

  

2)在recv和显示数据之间是否缺少任何步骤?

根据您正在做的事情,您可能希望删除标记数据末尾的字符,代码将用于表示另一方应该停止尝试从数据中读取更多数据插座。

接收数据是棘手的部分。客户端和服务器必须使用商定的协议来避免死锁,即当客户端和服务器都在等待另一方发送数据时。在下面的示例中,我使用了“面向行”协议,其中一方在读取换行符时停止从套接字读取。在网络中,按照惯例,换行符被认为是“\ r \ n”,它代表ascii字符carriage returnline feed,但是要避免在各种操作系统上进行任何类型的自动“\ n”翻译,套接字库使用实际的ascii代码:13和10,以十六进制表示法:“\ x0D \ x0A”。

server.pl:

use strict;  
use warnings; 
use 5.020;
use autodie;
use Data::Dumper;

use IO::Socket::INET;
use Socket qw( :crlf );  # "\x0D\x0A" constants CRLF and $CRLF

my $host = 'localhost';
my $port = 15_678;

my $server_socket = IO::Socket::INET->new(
        Listen    => 5,
        LocalPort => $port,
        LocalAddr => $host,
        Proto     => 'tcp',
        ReuseAddr => 1,
        ReusePort => 1
);

say "Server listening on port: $port\n";

while (my $client_socket = $server_socket->accept() ) {

    my $client_ip = $client_socket->peerhost();
    my $client_port = $client_socket->peerport();
    say "Connection from $client_ip:$client_port";

    {
        local $/ = CRLF; # $/ is the input record separator, which is "\n" 
                         #by default. Both <$INFILE> and getline() read up to
                         #and including the input record separator.
        while(my $line = $client_socket->getline) { #Blocks until CRLF is read 
                                                 #from the socket or the other 
                                                 #side closes the socket.
            chomp $line; #chomp() removes input record separator from end of line.
            say "Server received: $line";

            my $response = reverse $line;
            $client_socket->send("$response$CRLF");

            say "Server sent: $response";
        }

    }  #Here $/ is restored to whatever it was before this parenthesized block. 
       #That is what declaring a variable as local does.

    say "-" x 30;

    #Execution arrives here after the client closes the socket:
    $client_socket->shutdown(2);  #Send signals to other side of socket.
                                  #Not necessary in this example because other threads  
                                  #aren't also reading from the socket.
    $client_socket->close();  #Close the filehandle associated with the socket.
}

请注意,就像读取文件时一样,getline会在收到eof信号时返回到目前为止读取的所有内容,而对于while循环的下一次迭代,getline将返回undef,导致while循环终止。

client.pl:

use strict;  
use warnings; 
use 5.020;
use autodie;
use Data::Dumper;

use IO::Socket::INET;
use Socket qw( :crlf ); #\x0D\x0A constants CRLF and $CRLF 

my $port = 15_678;

my @lines = (
    "hello world",
    "goodbye mars",
);

my $sock = IO::Socket::INET->new("localhost:$port");

for my $line(@lines){

    my $server_ip = $sock->peerhost();
    my $server_port = $sock->peerport();
    say "Connected to $server_ip:$server_port";

    $sock->send("$line$CRLF");
    say "Client sent: $line";

    my $response;
    {
        local $/ = CRLF;
        $response = $sock->getline();
        chomp $response;
    }  

    say "Client received: $response";
    say '-' x 30;

}

#Tell the server that no more data is coming from this client:
$sock->shutdown(2);  #Send signals to other side of socket.
$sock->close();  #Close the filehandle associated with the socket.

运行客户端程序两次后......

服务器输出:

Server listening on port: 15678

Connection from 127.0.0.1:56085
Server received: hello world
Server sent: dlrow olleh
Server received: goodbye mars
Server sent: sram eybdoog
------------------------------
Connection from 127.0.0.1:56096
Server received: hello world
Server sent: dlrow olleh
Server received: goodbye mars
Server sent: sram eybdoog
------------------------------

客户端输出:

$ perl client.pl 
Connected to 127.0.0.1:15678
Client sent: hello world
Client received: dlrow olleh
------------------------------
Connected to 127.0.0.1:15678
Client sent: goodbye mars
Client received: sram eybdoog
------------------------------
$ perl client.pl 
Connected to 127.0.0.1:15678
Client sent: hello world
Client received: dlrow olleh
------------------------------
Connected to 127.0.0.1:15678
Client sent: goodbye mars
Client received: sram eybdoog
------------------------------
$ 
  

3)不应该至少发送硬编码字符串“$ response”   程序第一次执行while循环?

是的,while循环中的所有语句都会在第一个循环执行完毕后执行 - 但是当循环中较高的语句阻塞时,发送将永远不会执行。 / p>

  

4)关闭($ sock,1)和sleep(1)如何在此实现中有所不同?让套接字睡眠或我应该使用它是否可以   shutdown($ sock,1)向客户端/服务器发出数据信号   发送?

我不确定shutdown()和sleep()是如何以任何方式相关的。 sleep()使代码在指定时间内停止执行,而shutdown()将某些内容发送到套接字的另一端。

“关闭套接字”,即调用shutdown(),标记数据的结尾是您可以采用的另一种协议。它使事情变得非常简单:一方只是在某种读取语句中继续从套接字读取,当另一方关闭套接字时,读取将返回。这是一个例子:

server.pl:

use strict;  
use warnings; 
use 5.020;
use autodie;
use Data::Dumper;

use IO::Socket::INET;

my $host = 'localhost';
my $port = 15_678;

my $server_socket = IO::Socket::INET->new(
        Listen    => 5,
        LocalPort => $port,
        LocalAddr => $host,
        Proto     => 'tcp',
        ReuseAddr => 1,
        ReusePort => 1
);

say "Server listening on port: $port\n";

while (my $client_socket = $server_socket->accept() ) {

    my $client_ip = $client_socket->peerhost();
    my $client_port = $client_socket->peerport();
    say "Connection from $client_ip:$client_port";

    my $data;
    {
        local $/ = undef;  #This input record separtor will never be found...
        $data = <$client_socket>;  #...so this reads everything--including newlines--until it gets an eof signal.
    }

    say "Server received: $data";
    my $response = reverse $data;
    $client_socket->send($response);

    $client_socket->shutdown(2); #Doesn't close filehandle -- merely sends signals.
    $client_socket->close();  ##Close the filehandle associated with the socket.

    say "Server sent: $response";
    say "-" x 30;
}

client.pl:

use strict;  
use warnings; 
use 5.020;
use autodie;
use Data::Dumper;

use IO::Socket::INET;

my $port = 15_678;

my @data = (
    "hello \n world",   #Now newlines are in the data
    "goodbye \n mars",
);

for my $data (@data){
    my $sock = IO::Socket::INET->new("localhost:$port");
    my $server_ip = $sock->peerhost();
    my $server_port = $sock->peerport();
    say "Connected to $server_ip:$server_port";

    $sock->send($data);
    say "Client sent: $data";
    $sock->shutdown(1);

    my  $response;
    { 
        local $/ = undef;  #This input record separtor will never be found...
        $response = <$sock>;  ##...so this reads everything--including newlines--until it gets an eof signal.
    }
    $sock->shutdown(0);

    $sock->close();

    say "Client received: $response";
    say '-' x 30;

}

服务器输出:

Server listening on port: 15678

Connection from 127.0.0.1:53139
Server received: hello 
 world
Server sent: dlrow 
 olleh
------------------------------
Connection from 127.0.0.1:53140
Server received: goodbye 
 mars
Server sent: sram 
 eybdoog
------------------------------

客户端输出:

Connected to 127.0.0.1:15678
Client sent: hello 
 world
Client received: dlrow 
 olleh
------------------------------
Connected to 127.0.0.1:15678
Client sent: goodbye 
 mars
Client received: sram 
 eybdoog
------------------------------