서버 개발 시리즈 2
3주 동안의 미친 야근을 거친 후에 서버 개발 리듬을 마침내 놓을 수 있게 되었고, 짬이 나면 외적인 각도로 이번 프로젝트를 잘 볼 수 있게 되었다.
swoole 누드 쓰기 tcp 서버 사용하기
'스위스 군도'(익숙한 틀) 가 없는 상황에서 나체 swoole는 다음과 같이 변했다.
require_once __DIR__ . '/../vendor/autoload.php'; // composer autoload
require_once __DIR__ . '/config.php'; //
//---------------------server
$serv = new swoole_server("0.0.0.0", 9999);
$serv->set([
'worker_num' => 4,
'task_worker_num' => 8,
// 'daemonize' => true,
'pid' => __DIR__ . '/server.pid',
'log_file' => __DIR__ . '/../log/swoole.log',
//
'open_length_check' => 1, //
'package_length_type' => 'N', //
'package_length_offset' => 4, // N
'package_body_offset' => 8, // N
'package_max_length' => 2000000, //
]);
// swoole table ,
//$swooleTable = new swoole_table('100000'); // 10w
//$swooleTable->column('auth', swoole_table::TYPE_INT, '1');
//$swooleTable->create();
//$serv->table = $swooleTable;
$serv->zoneId = $config['zone_id']; //
$serv->userinfo = []; //
// onWorkerStart redis/mysql
$serv->on('workerStart', 'onWorkerStart');
// mysql
$serv->on('task', 'onTask');
$serv->on('finish', 'onFinish');
$serv->on('connect', 'onConnect');
$serv->on('receive', 'onReceive'); //
$serv->on('close', 'onClose');
$serv->start();
function
가 사용되었다function onReceive(swoole_server $serv, $fd, $from_id, $data)
{
// decode() , autoload psr-4
$data = decode($data);
//
if ($data['msg_type'] == 0) {
function_msg0($serv, $fd, $data);
} else if ($data['msg_type'] == 1) {
function_msg1($serv, $fd, $data);
}
}
worker
, 하나는 cache
function onWorkerStart(swoole_server $serv, $id){
// worker
if ($id < $serv->setting['worker_num']) {
// cache
$cache = new \Redis();
$cache->connect($config['cache']['host'], $config['cache']['port'], $config['cache']['timeout']);
$cache->auth($config['cache']['auth']);
$serv->cache = $cache;
// pub
$pub = new \Redis();
$pub->connect($config['pub_sub']['host'], $config['pub_sub']['port'], $config['pub_sub']['timeout']);
$pub->auth($config['pub_sub']['auth']);
$serv->pub = $pub;
}
}
//
$serv->cache->set('key1', 'value1');
$serv->pub->publish('topic1', 'data1');
function onTask($serv, $task_id, $from_id, $data) {
static $link = null;
if ($link == null) {
$link = mysqli_connect($config['mysql']['host'], $config['mysql']['user'], $config['mysql']['password'], $config['mysql']['database']);
if (!$link) {
$link = null;
return;
}
}
// , sql ,
list($queryType, $sql) = explode('|', $data);
$result = $link->query($sql);
if ($result) {
if ($queryType == 'select') {
$result = $result->fetch_all(MYSQLI_ASSOC);
} else if ($queryType == 'insert') {
$result = mysqli_insert_id($link);
}
return $result;
}
}
function onFinish($serv, $data)
{
//
}
//
$res = $serv->taskWait('select|select name from user where id=xxx'); //
구독 시 밟힌 구덩이 사용하기
swoole에서 구독을 실현하려면 이 블로그를 참고하십시오. Redis에서 구독 메시지를 WebSocket 클라이언트로 전달하는 방법
onWorkerStart 콜백 함수에서 시작해야 함
pub/sub
function onWorkerStart(swoole_server $serv, $id){
// if ($id == 0) { // sub
$sub = new swoole_redis(); // swoole_redis
$sub->on('message', function (swoole_redis $redis, $result) use ($serv, $config) {
if ($result[0] == 'message') {
list($userId, $status) = explode(':', $result[2]);
// fd, client
$userFd = $serv->userinfo[$userId]['fd'] ?? 0;
if ($userId && $userFd && in_array($status, [0, 1, 2])) {
foreach ($serv->connections as $fd) {
if ($userFd == $fd) { //
//
// client
$serv->send($fd, encode('foo'));
break;
}
}
}
}
});
$sub->connect($config['pub_sub']['host'], $config['pub_sub']['port'], function (swoole_redis $redis, $result) use ($config) {
$redis->auth($config['pub_sub']['auth'], function (swoole_redis $redis, $result) use ($config) {
$redis->subscribe('game_result_'. $config['zone_id']);
});
});
// }
}
}
코드를 자세히 보면'하나의sub만 시작합니다'라는 주석이 있습니다. 이것은 업무에 의해 결정됩니다. 구독 메시지를 받을 때 특정한 사용자에게만 전달할 수 있습니다.그러나 onWorkerStart 콜백 함수에서 시작할 수 없습니다.왜냐하면 우리는 먼저 swoole의 프로세스 모델을 이해해야 한다.
redis sub
프로세스가 먼저 시작됩니다master
프로세스 시작master
프로세스 및 manager
라인reactor
라인, tcp 연결과 tcp 데이터의 송수신을 관리하는 데 사용reactor
프로세스는 manager
프로세스와 worker
프로세스를 관리하는 데 사용되며, 위의 task_worker
구성에 따라 worker_num/task_worker_num
프로세스처리worker
라인에서 전송된 데이터, 업무 논리를 처리한 후 reactor
라인에 전송, reactor
라인에서 사용자reactor
프로세스는 소모된 작업을 worker
프로세스에 전송하고, task_worker
프로세스가 처리된 후 촉발task_worker
이벤트 리셋onFinish
에 근거하여 우리가 현재 $work_id < $serv->setting['worker_num']
프로세스인지 worker
프로세스인지 판단할 수 있다첫 번째 버전을 쓸 때 저는 업무 수요에 따라
task_worker
프로세스에서만 redis sub를 열 수 있도록 제한했습니다. 그러나 문제는 바로 왔습니다. 현재 사용자의 fd가 반드시 $work_id = 0
프로세스에 있는 것은 아닙니다. 그러면 다음 코드가 효력을 잃게 됩니다.foreach ($serv->connections as $fd) { // $serv worker
if ($userFd == $fd) {
// do something
}
}
그러나
$work_id = 0
의 제한을 가하지 않으면 우리가worker 프로세스를 얼마나 열었는지,sub가 얼마나 많은지, 메시지의 중복 구독, 중복 업무 논리 처리를 초래할 수 있다.이럴 때 swoole가 제공하는 Process 프로세스 관리 모듈을 알아야 합니다. 저희는 단독으로 하나의 프로세스를 만들어서sub를 유지하면 됩니다.
swoole를 사용하여 서버에서 발견한 문제를 나체로 쓰기.
분명히 위의 업무 논리가 복잡하지 않고 사용하는 서비스도 많지 않지만 전체적으로 개발된 불편함은 매우 뚜렷하다.
$work_id = 0
프로필config.php
의 사용자 정의 프로토콜을 사용하여 프로토콜과 업무를 분리하는 것이 좋은 디자인이다비교해 보면 php의 웹 프레임워크가 이렇게 많은데 MVC가 크게 유행하고 있는데 php의 서버 프레임워크도 있어서 위의 공통된 문제를 해결할 수 있습니까?
여기서 swoole distribution을 추천합니다. 재구성할 때 이 프레임워크를 선택했습니다. 장점을 간단하게 말씀드리겠습니다.
$value = yield $this->redis_pool->getCoroutine()->get('key1');
namespace app\Process;
use Server\Components\Process\Process;
class MyProcess extends Process
{
public function start($process)
{
parent::start($process);
// redis sub
}
// controller rpc
public function getData()
{
return '123';
}
}
마지막에 쓰다
확실히 이전에 서버를 쓴 적이 없어서'탁상공론'의 단계에 머물렀는데, 진정으로 썼을 때'이 일은 정말 힘들다'는 것을 발견했다.그러나 그 몇 년 동안 당신이 읽은 책, 브러시 기술 블로그, 참가한 기술 대회는 어쨌든 유용합니다. 입문 장소에 가로막힌 것은 언어가 아니라 이 분야의'기초'입니다. 이런 것들을 통해 얻을 수 있습니다. 부족한 것은 스스로 그것을 체계화해야 합니다.물론 그 다음은 코딩
+ protobuf
이 일선 프로그래머들에게 계속 유용할 것이다.
이 내용에 흥미가 있습니까?
현재 기사가 여러분의 문제를 해결하지 못하는 경우 AI 엔진은 머신러닝 분석(스마트 모델이 방금 만들어져 부정확한 경우가 있을 수 있음)을 통해 가장 유사한 기사를 추천합니다:
다양한 언어의 JSONJSON은 Javascript 표기법을 사용하여 데이터 구조를 레이아웃하는 데이터 형식입니다. 그러나 Javascript가 코드에서 이러한 구조를 나타낼 수 있는 유일한 언어는 아닙니다. 저는 일반적으로 '객체'{}...
텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
CC BY-SA 2.5, CC BY-SA 3.0 및 CC BY-SA 4.0에 따라 라이센스가 부여됩니다.