100多行PHP代码实现socks5代理服务器,这次是使用swoole纯异步来写,使用状态机来处理数据。目前用它访问开源中国木有压力,但访问网易新闻就压力山大。我发现我用别的语言写得代理,访问网易新闻都压力大。嘎嘎,学艺不精。
对swoole理解不深,不知道怎么处理socket shutdown只关闭读/写这样,还有就是连接超时,读写超时这种怎么处理。在网上看到作者说要用定时器,感觉好麻烦,所以,这次的代理,虽然个人用,一般不会有什么问题,但离产品级的代理,还有段路要走。
如果要利用多核,就使用process模式,设置worker个数为cpu数量即可。
对swoole理解不深,不知道怎么处理socket shutdown只关闭读/写这样,还有就是连接超时,读写超时这种怎么处理。在网上看到作者说要用定时器,感觉好麻烦,所以,这次的代理,虽然个人用,一般不会有什么问题,但离产品级的代理,还有段路要走。
如果要利用多核,就使用process模式,设置worker个数为cpu数量即可。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
|
<?php
class Client
{
public $connected = true;
public $data = '' ;
public $remote = null;
public $status = 0;
}
class Server
{
public $clients = [];
public function start()
{
$server = new swoole_server( '0.0.0.0' , 8388, SWOOLE_BASE, SWOOLE_SOCK_TCP);
$server ->set([
'max_conn' => 1000,
'daemonize' => 1,
'reactor_num' => 1,
'worker_num' => 1,
'dispatch_mode' => 2,
'buffer_output_size' => 128 * 1024 * 1024,
'open_cpu_affinity' => 1,
'open_tcp_nodelay' => 1,
'log_file' => 'socks5_server.log' ,
]);
$server ->on( 'connect' , [ $this , 'onConnect' ]);
$server ->on( 'receive' , [ $this , 'onReceive' ]);
$server ->on( 'close' , [ $this , 'onClose' ]);
$server ->start();
}
public function onConnect( $server , $fd , $fromID )
{
$this ->clients[ $fd ] = new Client();
}
public function onReceive( $server , $fd , $fromID , $data )
{
( $this ->clients[ $fd ])->data .= $data ;
$this ->parse( $server , $fd );
}
public function onClose( $server , $fd , $fromID )
{
$client = $this ->clients[ $fd ];
$client ->connected = false;
}
private function parse( $server , $fd )
{
$client = $this ->clients[ $fd ];
switch ( $client ->status) {
case 0: {
if ( strlen ( $client ->data) >= 2) {
$request = unpack( 'c*' , substr ( $client ->data, 0, 2));
if ( $request [1] !== 0x05) {
echo '协议不正确:' . $request [1], PHP_EOL;
$server ->close( $fd );
break ;
}
$nmethods = $request [2];
if ( strlen ( $client ->data) >= 2 + $nmethods ) {
$client ->data = substr ( $client ->data, 2 + $nmethods );
$server ->send( $fd , "\x05\x00" );
$client ->status = 1;
}
}
}
case 1: {
if ( strlen ( $client ->data) < 5)
break ;
$request = unpack( 'c*' , $client ->data);
$aType = $request [4];
if ( $aType === 0x03) { // domain
$domainLen = $request [5];
if ( strlen ( $client ->data) < 5 + $domainLen + 2) {
break ;
}
$domain = substr ( $client ->data, 5, $domainLen );
$port = unpack( 'n' , substr ( $client ->data, 5 + $domainLen , 2))[1];
$client ->data = substr ( $client ->data, 5 + $domainLen + 2);
} else if ( $aType === 0x01) { // ipv4
$domain = long2ip(unpack( 'N' , substr ( $client ->data, 4, 4))[1]);
$port = unpack( 'n' , substr ( $client ->data, 8, 2))[1];
$client ->data = substr ( $client ->data, 10);
} else {
echo '不支持的atype:' . $aType , PHP_EOL;
$server ->close( $fd );
break ;
}
$remote = new swoole_client(SWOOLE_SOCK_TCP, SWOOLE_SOCK_ASYNC);
$remote ->on( 'connect' , function ( $cli ) use ( $client , $server , $fd , $remote ) {
$server ->send( $fd , "\x05\x00\x00\x01\x00\x00\x00\x00\x00\x00" );
$client ->status = 2;
$client ->remote = $remote ;
});
$remote ->on( "error" , function (swoole_client $cli ) use ( $server , $fd ) {
//$server->send($fd, ""); // todo 连接不上remote
echo 'connect to remote error.' , PHP_EOL;
$server ->close( $fd );
});
$remote ->on( 'receive' , function ( $cli , $data ) use ( $server , $fd , $client ) {
if (! $client ->connected) {
echo 'connection has been closed.' , PHP_EOL;
return ;
}
$server ->send( $fd , $data );
});
$remote ->on( 'close' , function ( $cli ) use ( $server , $fd , $client ) {
$client ->remote = null;
});
if ( $aType === 0x03) {
swoole_async_dns_lookup( $domain , function ( $host , $ip ) use ( $remote , $port , $server , $fd ) {
//todo 当host为空时的处理。貌似不存在的域名都解析成了本机的外网ip,奇怪
if ( empty ( $ip ) || empty ( $host )) {
echo "host:{$host}, ip:{$ip}\n" ;
$server ->close( $fd );
return ;
}
$remote ->connect( $ip , $port );
});
} else {
$remote ->connect( $domain , $port );
}
}
case 2: {
if ( strlen ( $client ->data) === 0) {
break ;
}
if ( $client ->remote === null) {
echo 'remote connection has been closed.' , PHP_EOL;
break ;
}
$sendByteCount = $client ->remote->send( $client ->data);
if ( $sendByteCount === false || $sendByteCount < strlen ( $client ->data)) {
echo 'data length:' , strlen ( $client ->data), ' send byte count:' , $sendByteCount , PHP_EOL;
echo $client ->data, PHP_EOL;
$server ->close( $fd );
}
$client ->data = '' ;
}
}
}
}
( new Server())->start();
|