我正在编写一个简单的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();
答案 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的双元素数组 套接字和对等地址;失败时列表将为空。
这是nice, short, basic description of sockets。
您的服务器代码可能隐藏了一些正在发生的事情。如果它是这样编写的话会更具说明性:
$server_socket = IO::Socket::INET(.....);
...
...
my $client_socket = $server_socket->accept();
...
...
有两个不同的插座。 accept()返回一个新的套接字供服务器和客户端进行通信,因此原始服务器套接字可以继续监听客户端连接。
2)在recv和显示数据之间是否缺少任何步骤?
根据您正在做的事情,您可能希望删除标记数据末尾的字符,代码将用于表示另一方应该停止尝试从数据中读取更多数据插座。
接收数据是棘手的部分。客户端和服务器必须使用商定的协议来避免死锁,即当客户端和服务器都在等待另一方发送数据时。在下面的示例中,我使用了“面向行”协议,其中一方在读取换行符时停止从套接字读取。在网络中,按照惯例,换行符被认为是“\ r \ n”,它代表ascii字符carriage return
和line 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()将某些内容发送到套接字的另一端。 “关闭套接字”,即调用 server.pl: client.pl: 服务器输出: 客户端输出:
shutdown()
,标记数据的结尾是您可以采用的另一种协议。它使事情变得非常简单:一方只是在某种读取语句中继续从套接字读取,当另一方关闭套接字时,读取将返回。这是一个例子: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;
}
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
------------------------------