Skip to content

Commit 768129a

Browse files
committed
php로 tcp 소켓 서버 만들기
1 parent 54e8b0b commit 768129a

File tree

109 files changed

+10051
-0
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

109 files changed

+10051
-0
lines changed

php_tcp_server/README.md

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
# PHP TCP 서버 개발 기술 R&D
2+
3+
4+

php_tcp_server/binaryData.md

+73
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
# 바이너리 데이터 다루기
2+
[이 문서](https://www.slideshare.net/yoyayoya1/php-10133775 )를 정리
3+
4+
PHP에서는 String 타입으로 바이너리 데이터를 다룰 수 있다.
5+
PHP는 String 타입에 대해서 문자로서 특별한 것을 하지 않는다.
6+
PHP에서 문자열 타입은 바이트 배열과 정수 값(버퍼 길이)로 구현되어 있다. 바이트 열을 문자열로 변환하는 방법에 대해서는 어떤 정보도 가지고 있지 않고, 완전하게 프로그래머에게 맡긴다.
7+
바이너리 데이터 조작은 `str` 계열의 함수를 사용한다.
8+
9+
10+
## 바이너리와 정수 값 상호 변환
11+
바이너리 -> 정수 값 (빅엔디언)
12+
```
13+
// 2바이트
14+
$b = unpack('n', $a)
15+
16+
// 4바이트
17+
$b = unpack('N', $a)
18+
```
19+
20+
바이너리 -> 정수 값 (리틀엔디언)
21+
```
22+
// 2바이트
23+
$b = unpack('v', $a)
24+
25+
// 4바이트
26+
$b = unpack('V', $a)
27+
```
28+
29+
pack은 `unsigned`로 처리한다.
30+
31+
32+
## 사용 예
33+
![pack](./images/001.png)
34+
35+
![pack](./images/002.png)
36+
37+
38+
### GIF 파일 데이터 변환 예
39+
파일의 선두에서
40+
```
41+
47 49 46 38 39 61 78 00 5A 00
42+
```
43+
와 같은 데이터다 저장되어 있다면
44+
```
45+
$fp = fopen( 'sample.gif', 'rb' );
46+
$data = fread( $fp, 10 );
47+
48+
$format = 'a3signature/a3version/vwidth/vheight';
49+
$info = unpack( $format, $data );
50+
```
51+
52+
이렇게 하면 unpack()이 반환하는 배열에는 아래와 같은 데이터가 저장되어 있다
53+
```
54+
Array
55+
(
56+
[signature] => GIF // 47 49 46
57+
[version] => 89a // 38 39 61
58+
[width] => 120 // 78 00 (0x0078 = 120)
59+
[height] => 90 // 5A 00 (0x005A = 90)
60+
)
61+
```
62+
63+
64+
65+
## 포맷 문자열
66+
![pack](./images/003.png)
67+
68+
69+
## 참고 글
70+
- [How to work with binary data in PHP](https://blog-en.openalfa.com/how-to-work-with-binary-data-in-php )
71+
- [PHP pack API](https://www.php.net/manual/ja/function.pack.php )
72+
- [PHP unpack API](https://www.php.net/manual/ja/function.unpack.php )
73+
- [mdurrant/php-binary-reader](https://github.com/mdurrant/php-binary-reader )
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
using System;
2+
using System.Net.Sockets;
3+
using System.Threading.Tasks;
4+
5+
namespace NPSBDummyLib
6+
{
7+
public class AsyncSocket
8+
{
9+
Socket Conn = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
10+
11+
bool IsConnectToDisconnected = false;
12+
13+
string LastExceptionMessage;
14+
15+
16+
public bool IsConnected() { return Conn != null && Conn.Connected; }
17+
18+
19+
public async Task<(bool,int,string)> ConnectAsync(string ip, int port)
20+
{
21+
try
22+
{
23+
Conn.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true);
24+
await Conn.ConnectAsync(ip, port);
25+
26+
Conn.NoDelay = true;
27+
28+
DummyManager.DummyConnected();
29+
30+
IsConnectToDisconnected = false;
31+
return (true, 0, "");
32+
}
33+
catch (Exception ex)
34+
{
35+
var sockEx = (SocketException)ex;
36+
LastExceptionMessage = sockEx.Message;
37+
return (false, sockEx.ErrorCode, sockEx.Message);
38+
}
39+
}
40+
41+
public (int, string) Receive(int bufferSize, byte[] buffer)
42+
{
43+
// 대기조건. 시간, 원하는 패킷, 횟수
44+
try
45+
{
46+
if (Conn.Poll(100, SelectMode.SelectRead) == false)
47+
{
48+
return (0, "");
49+
}
50+
51+
var size = Conn.Receive(buffer);
52+
return (size, "");
53+
}
54+
catch (Exception ex)
55+
{
56+
// 더미가 끊은 것이므로 에러가 아니다
57+
if (IsConnectToDisconnected)
58+
{
59+
return (0, "");
60+
}
61+
62+
LastExceptionMessage = ex.Message;
63+
Close();
64+
return (-1, ex.Message);
65+
}
66+
}
67+
68+
public string Send(int bufferSize, byte[] buffer)
69+
{
70+
try
71+
{
72+
Conn.Send(buffer);
73+
return "";
74+
}
75+
catch (Exception ex)
76+
{
77+
LastExceptionMessage = ex.Message;
78+
Close();
79+
return ex.Message;
80+
}
81+
}
82+
83+
public Int64 Close()
84+
{
85+
Int64 currentCount = 0;
86+
87+
try
88+
{
89+
IsConnectToDisconnected = true;
90+
91+
Conn.LingerState = new LingerOption(false, 0);
92+
//Conn.Close();
93+
Conn.Disconnect(true);
94+
Conn.Dispose();
95+
96+
Conn = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
97+
}
98+
catch(Exception ex)
99+
{
100+
LastExceptionMessage = ex.Message;
101+
}
102+
finally
103+
{
104+
currentCount = DummyManager.DummyDisConnected();
105+
}
106+
107+
return currentCount;
108+
}
109+
}
110+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Threading;
4+
using System.Threading.Tasks;
5+
6+
namespace NPSBDummyLib
7+
{
8+
public partial class Dummy
9+
{
10+
public Int32 Index { get; private set; }
11+
12+
public Int32 Number { get; private set; }
13+
14+
public bool IsRun { get; private set; }
15+
16+
public UInt64 CurActionRepeatCount = 0;
17+
18+
string LastExceptionMessage;
19+
20+
DummyScenarioResult ScenarioResult = new DummyScenarioResult();
21+
22+
List<string> ActionHistory = new List<string>(); // 더미의 행동 이력
23+
24+
25+
public string GetUserID() => $"User_{Number}";
26+
27+
public bool IsSuccessScenarioResult() => ScenarioResult.IsSuccess;
28+
29+
public string GetScenarioResult() => ScenarioResult.Result;
30+
31+
public void Init(Int32 index, Int32 number)
32+
{
33+
Index = index;
34+
Number = number;
35+
ClientSocket = new AsyncSocket();
36+
RecvPacketInfo.Init(DummyManager.Config.PacketSizeMax);
37+
}
38+
39+
40+
public void AddActionDesc(string desc) => ActionHistory.Add($"[{DateTime.Now}] {desc}");
41+
42+
public void SetScenarioResult(bool isSuccess, string result)
43+
{
44+
ScenarioResult.IsSuccess = isSuccess;
45+
ScenarioResult.Result = result;
46+
}
47+
48+
public void StartScenario()
49+
{
50+
ActionHistory.Clear();
51+
CurActionRepeatCount = 0;
52+
ScenariActionMaxWaitTimeSec = 0;
53+
IsRun = true;
54+
}
55+
56+
57+
58+
Int64 ScenariActionMaxWaitTimeSec = 0;
59+
60+
public void SetScenariActionMaxWaitTime(Int64 timeSec) => ScenariActionMaxWaitTimeSec = timeSec;
61+
62+
public bool IsOverScenariActionMaxWaitTimeThenStop(Int64 curTimeSec)
63+
{
64+
// 단순하게 변수의 값을 읽어서 체크 하는 것이라서 스레드 세이프하지 않아도 괜찮다
65+
if (ScenariActionMaxWaitTimeSec == 0 || curTimeSec < ScenariActionMaxWaitTimeSec)
66+
{
67+
return false;
68+
}
69+
70+
AddActionDesc($"[실패] 시나리오 액션의 최대 대기 시간을 넘었음");
71+
IsRun = false;
72+
return true;
73+
}
74+
}
75+
76+
77+
public class DummyScenarioResult
78+
{
79+
public bool IsSuccess;
80+
public string Result;
81+
}
82+
83+
84+
}

0 commit comments

Comments
 (0)