100多行PHP代码实现socks5代理服务器[2]
对swoole理解不深,不知道怎么处理socketshutdown只关闭读/写这样,还有就是连接超时,读写超时这种怎么处理。在网上看到作者说要用定时器,感觉好麻烦,所以,这次的代理,虽然个人用,一般不会有什么问题,但离产品级的代理,还有段路要走。
如果要利用多核,就使用process模式,设置worker个数为cpu数量即可。
<?php
classClient
{
public$connected=true;
public$data='';
public$remote=null;
public$status=0;
}
classServer
{
public$clients=[];
publicfunctionstart()
{
$server=newswoole_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();
}
publicfunctiononConnect($server,$fd,$fromID)
{
$this->clients[$fd]=newClient();
}
publicfunctiononReceive($server,$fd,$fromID,$data)
{
($this->clients[$fd])->data.=$data;
$this->parse($server,$fd);
}
publicfunctiononClose($server,$fd,$fromID)
{
$client=$this->clients[$fd];
$client->connected=false;
}
privatefunctionparse($server,$fd)
{
$client=$this->clients[$fd];
switch($client->status){
case0:{
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;
}
}
}
case1:{
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);
}elseif($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=newswoole_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'connecttoremoteerror.',PHP_EOL;
$server->close($fd);
});
$remote->on('receive',function($cli,$data)use($server,$fd,$client){
if(!$client->connected){
echo'connectionhasbeenclosed.',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);
}
}
case2:{
if(strlen($client->data)===0){
break;
}
if($client->remote===null){
echo'remoteconnectionhasbeenclosed.',PHP_EOL;
break;
}
$sendByteCount=$client->remote->send($client->data);
if($sendByteCount===false||$sendByteCount<strlen($client->data)){
echo'datalength:',strlen($client->data),'sendbytecount:',$sendByteCount,PHP_EOL;
echo$client->data,PHP_EOL;
$server->close($fd);
}
$client->data='';
}
}
}
}
(newServer())->start();