php
PHP - 소켓 네트워크 프로그래밍
소켓은 서버와 클라이언트가 서로 특정 규약을 사용하여 데이터를 전송하기 위한 방식입니다. 서버와 클라이언트는 소켓 연결을 기다렸다가 소캣이 연결되면 서로 데이터를 전송하게 됩니다. PHP에서 소켓을 생성하기위해 socket_create() 함수를 사용합니다.
socket_create() 함수는 원하는 통신 프로토콜의 종류를 설정합니다. domain 파라미터에는 아래 상수를 사용할 수 있습니다.
파라미터 address 에는 IP 또는 unix 도메인의 경로를 지정할 수 있는데, 여기서 address 는 서버의 IP 주소이고, port는 연결할 포트번호입니다. socket_bind() 할 때 반환 값이 0이면 socket_bind() 함수가 수행에 성공한 것이고, 음수를 반환하면 에러가 발생한 것입니다.
socket_recvfrom() 함수는 연결 여부에 관계없이 소켓에서 데이터를 수신합니다.
socket_sendto() 함수는 연결 여부에 관계없이 소켓에 메시지를 보냅니다.
아래 코드는 UDP를 이용한 서버 스크립트입니다.
아래 코드는 UDP 클라이언트로서 서버에 데이터를 전송하고, 서버에서 보내온 데이터를 다시 받는 역할을 합니다.
클라이언트가 서버와 연결하기 위해서 서버로 socket_connect()를 보냅니다. socket_connect() 함수의 문법은 다음과 같습니다. 여기서 address 와 port는 서버의 IP address와 포트 번호입니다.
클라이언트는 socket_connect()를 보내고 대기 상태에서 기다리게 되는데, 서버에서 socket_accept()를 보내면 실제로 연결이 됩니다. socket_accept()가 성공해야 연결이 끝나게 됩니다. socket_accept() 함수는 다음과 같습니다.
socket_read()와 socket_write()는 TCP 통신에서 데이터 전송에 사용합니다. socket_read() 함수의 문법은 다음과 같습니다. socket_read()에서 length는 데이터 크기가 저장됩니다.
socket_write() 함수의 문법은 다음과 같습니다. socket_write()에서 buffer와 length는 보낼 데이터와 데이터의 크기가 저장됩니다.
아래 코드는 TCP를 이용한 서버입니다. 이 서버는 UDP에서와 마찬가지로 클라이언트로부터 데이터를 받아 단어별로 정렬한 후 클라이언트로 다시 보내는 역할을 합니다. 즉 클라이언트는 Apple Orange Banana Pear를 보내고 서버 측에서는 정렬한 Pear Orange Banana Apple을 클라이언트로 보내게 됩니다.
아래 코드는 TCP를 이용한 클라이언트 프로그램으로 서버에 데이터를 보내고 결과를 다시 서버에서 받는 역할을 합니다.
chat_client.php 는 대화창의 클라이언트 측 프로그램입니다. chat_name.html 파일은 사용자의 이름을 입력하는 스크립트로 이름을 입력받은 후 chat_client.php 코드를 실행합니다.
대화창 클라이언트 프로그램에서 쓰레드를 적용하지 않았기 때문에 사용자의 동작이 있을 때 새롭게 서버에서 변경된 내용이 갱신되도록 프로그램하였습니다.
resource socket_create(int domain , int type , int protocol);
- AF_INET: IPv4 인터넷 기반 프로토콜
- AF_INET6: IPv6 인터넷 기반 프로토콜
- AF_UNIX: 유닉스 기반 프로토콜(예: /tmp/my.sock)
type 파라미터에는 소켓에서 사용할 수 있는 통신 유형을 지정합니다.
- SOCK_STREAM: 순차적이고 안정적인 전이중 연결 기반 바이트 스트림을 제공하며 TCP 프로토콜은 이 소켓 유형을 기반으로 합니다.
- SOCK_DGRAM: 데이터그램을 지원합니다.
UDP 프로토콜은 이 소켓 유형을 기반으로 합니다. - SOCK_SEQPACKET: 고정된 최대 길이의 데이터그램에 대해 순차적이고 안정적인 양방향 연결 기반 데이터 전송 경로를 제공합니다.
- SOCK_RAW: 원시 네트워크 프로토콜 액세스를 제공합니다.
이 특별한 유형의 소켓은 모든 유형의 프로토콜을 수동으로 구성하는 데 사용할 수 있습니다. 이 소켓 유형의 일반적인 용도는 ICMP 요청(예: ping)을 수행하는 것입니다. - SOCK_RDM: 순서를 보장하지 않는 안정적인 데이터그램 계층을 제공합니다. 이것은 운영 체제에서 구현되지 않았을 가능성이 큽니다.
예를 들어 UDP 소켓을 사용할 때는 SOCK_DGRAM을 사용하고, TCP소켓을 사용할 때는 SOCK_STREAM을 사용합니다.
그리고 socket_create() 함수의 반환 값이 -1이면 연결에 실패하였음을 나타냅니다.
파라미터 protocol 에는 소켓에서 사용할 수 있는 통신 유형을 지정합니다.
UDP 비연결형 통신규약
UDP는 비연결형 통신규약으로 비교적 간단한 네트워크 프로그래밍을 할 때 사용합니다.
UDP는 패킷에 데이터를 실어서 보내는데, 데이터를 보내기 전에 서버와 클라이언트를 연결하는 과정이 생략됩니다.
UDP에서는 데이터 전송에 대한 신뢰성을 보장하지 않기 때문에 경우에 따라서 데이터 전송 중에 패킷을 잃어버릴 수 있습니다.
UDP로 데이터를 전송하기 위해서는 다음과 같은 순서로 함수들이 호출됩니다.
- 클라이언트 측 호출 순서:
socket_create() -> socket_sendto() -> socket_recvfrom() - 서버 측 호출 순서:
socket_create() -> socket_bind() -> socket_recvfrom() -> socket_sendto()
UDP 연결을 위한 socket_create()에서 type 파라미터는 SOCK_DGRAM을 사용합니다. 그리고 socket_bind()에서 socket과 IP address, port를 연결합니다.
bool socket_bind(resource socket, string address [, int port = 0]);
UDP에서 데이터를 전송하기 위해서는 socket_recvfrom() 함수와 socket_sendto() 함수를 사용합니다. socket_recvfrom() 함수는 데이터를 받는 함수이고, socket_sendto() 함수는 데이터를 보내는 함수입니다. 마지막으로 연결을 종료할 때 socket_close() 함수를 사용합니다.
socket_recvfrom(resource socket, string &data, int length, int flag, string &address[, int &port = null]);
socket_sendto(resource socket, string data, int length, int flag, string address[, int port = null]);
이 서버는 클라이언트에서 문자열을 받은 다음에 그 문자열을 단어별로 정렬하여 다시 클라이언트로 보내는 역할을 합니다. 즉, 클라이언트에서 $data = "Apple Orange Banana Pear"를 서버로 보내면 서버에서는 이 문자열을 Pear, Orange, Banana, Apple 순서로 문자열을 만든 후 다시 클라이언트로 전송합니다.
<?php
$addr = gethostbyname('127.0.0.1');
$port = 5090;
$buff = '';
$sock = socket_create(AF_INET, SOCK_DGRAM, 0);
if($sock < 0)
{
die(socket_strerror($sock));
}
if(($ref = socket_bind($sock, $addr, $port)) < 0)
{
die(socket_strerror($ref));
}
do
{
$read = socket_recvfrom($sock, $buff, 2048, 0, $addr, $port);
echo "Recelve data: $buff <br/>";
$tmp = preg_split("/\s+/", $buff);
sort($tmp);
for($i = count($tmp) - 1; $i > 0; $i--)
{
$resp .= $tmp[$i] . " ";
}
$send = socket_sendto($sock, $resp, strlen($resp), 0, $addr, $port);
echo "Send data: $resp <br/>";
} while($read < 0);
socket_close($sock);
?>
<?php
$addr = gethostbyname('127.0.0.1');
$port = 5090;
$data = "Apple Orange Banana Pear";
$buff = '';
$sock = socket_create(AF_INET, SOCK_DGRAM, 0);
if($sock < 0)
{
die(socket_strerror($sock));
}
$ref = socket_sendto($sock, $data, strlen($data), 0, $addr, $port);
echo "Send data: $data <br/>";
do
{
$read = socket_recvfrom($sock, $buff, 2048, 0, $addr, $port);
} while($read < 0);
echo "Receive data: $buff <br/>";
socket_close($sock);
?>
TCP 연결형 통신규약
TCP(Transmission Control Protocol)는 연결지향형 통신규약이며 안전한 데이터 전송을 보장해 주는 프로토콜입니다.
TCP의 연결은 서버에서 클라이언트의 연결을 기다리는 서버소켓이 있으며 클라이언트는 미리 정해진 포트를 통해서 서버와 연결합니다.
한번 연결이 된 후에는 소켓을 통해 데이터를 전달합니다. 데이터는 신뢰성이 보장되어 전송되는 과정에 순서가 바뀌어도 이를 재조합하여 정확한 데이터를 보내고 받을 수 있습니다.
TCP로 데이터를 전송하기 위해서는 다음과 같은 순서로 함수가 호출됩니다.
- 클라이언트 측 호출 순서:
socket_create() -> socket_connect() -> socket_write() -> socket_read() - 서버 측 호출 순서:
socket_create() -> socket_bind() -> socket_listen() -> socket_accept() -> socket_read() -> socket_write()
UDP와 다른 점은 서버에서 연결을 기다린다는 점입니다. 즉 socket_listen() 함수를 이용하여 클라이언트 측에서 보낸 socket_connect() 함수가 올 때까지 서버는 기다리고 socket_connect() 함수가 도착하면 socket_accept() 함수를 보낸 후 데이터 전송을 시작합니다. 그리고 마찬가지로 socket_close() 함수를 이용하여 연결을 종료합니다.
TCP에서 socket_create() 설정 시에 소켓 종류 설정 파라미터에 SOCK_STREAM을 사용합니다.
TCP에서 데이터를 전송할 경우 socket_read(), socket_write() 함수를 사용합니다. socket_read() 함수는 데이터를 받는 함수이고, socket_write() 는 데이터를 보냅니다.
각각의 함수는 다음과 같습니다. socket_listen 은 TCP에서 사용하는 함수로 서버에서 클라이언트의 연결을 기다리는 역할을 합니다.
socket_listen(resource socket[, int backlog = 0]);
socket_connect(resource socket, string address[, int port = null]);
socket_accept(resource socket);
socket_read(resource socket, int length[, int mode = PHP_BINARY_READ]);
socket_write(resource socket, string data[, int length = null]);
<?php
$addr = gethostbyname('127.0.0.1');
$port = 5091;
if(($sock = socket_create(AF_INET, SOCK_STREAM, 0)) < 0)
{
echo "socket_create() failed: reason: " . socket_strerror($sock) . "<br/>";
}
if(($ref = socket_bind($sock, $address, $port == false)) < 0)
{
echo "socket_bind() failed: reason: " . socket_strerror($ref) . "<br/>";
}
if(($ref = socket_listen($sock, 0)) == false)
{
echo "socket_listen() failed: reason: " . socket_strerror($ref) . "<br/>";
}
if(($msg = socket_accept($sock)) < 0)
{
echo "socket_accept() failed: reason: " . socket_strerror($msg) . "<br/>";
}
$buff = "";
$buff = socket_read($msg, 2048);
if($buff == null)
{
echo "socket_read() failed: reason: " . socket_strerror($buff) . "<br/>";
}
echo "Receive data: $buff <br/>";
$tmp = preg_split("/\s+/", $buff);
sort($tmp);
$talk = "";
for($i = count($tmp) - 1; $i > 0; $i--)
{
$talk .= $tmp[$i] . " ";
}
socket_write($msg, $talk, strlen($talk));
echo "Send data: $talk <br/>";
socket_close($msg);
socket_close($sock);
?>
<?php
$addr = gethostbyname('127.0.0.1');
$port = 5091;
if(($sock = socket_create(AF_INET, SOCK_STREAM, 0)) < 0)
{
echo "socket_create() failed: reason: " . socket_strerror($sock) . "<br/>";
}
if(($result = socket_connect($sock, $address, $port)) == false)
{
echo "socket_connect() failed: reason: " . socket_strerror($result) . "<br/>";
}
$in = "Apple Orange Banana Pear";
$out = "";
socket_write($sock, $in, strlen($in));
echo "Send data: $in <br/>";
$out = socket_read($sock, 2048);
echo "Receive data: $out <br/>";
socket_close($sock);
?>
네트워크 응용프로그램
서버와 클라이언트 간의 네트워크 프로그램을 이용하여 간단한 대화창 프로그램을 만들 수 있습니다. 다수의 클라이언트가 메시지를 주고받기 위해서 중간 전달자로 서버를 둡니다.
chat_server.php 코드는 비연결형 통신규약인 UDP를 이용하여 구현한 대화창입니다.
<?php
$addr = gethostbyname('127.0.0.1');
$port = 5092;
$buff = '';
$sock = socket_create(AF_INET, SOCK_DGRAM, 0);
if($sock < 0)
{
die(socket_strerror($sock));
}
if(($ref = socket_bind($sock, $addr, $port)) < 0)
{
die(socket_strerror($ref));
}
do
{
$read = socket_recvfrom($sock, $buff, 2048, 0, $addr, $port);
$resp .= $buff . "<br/>";
$send = socket_sendto($sock, $resp, strlen($resp), 0, $addr, $port);
} while($read);
socket_close($sock);
?>
chat_client.php 파일의 코드는 다음과 같습니다.
<!-- chat_name.html -->
<form method="post" action="chat_client.php">
user: <input type="text" name="username">
<input type="hidden" name="msg" value="Entrance">
<input type="submit" value="submit">
</form>
<form method="post" action="chat_client.php">
<? echo $username; ?>
<input type="hidden" name="username" value="<? echo $username; ?>">
<input type="text" name="msg">
<input type="submit" value="Go!">
</form>
<?php
$addr = gethostbyname('127.0.0.1');
$port = 5092;
$buff = '';
$sock = socket_create(AF_INET, SOCK_DGRAM, 0);
if($sock < 0)
{
die(socket_strerror($sock));
}
$data = $username . " : " . $msg;
$ref = socket_sendto($sock, $data, strlen($data), 0, $addr, $port);
do
{
$read = socket_recvfrom($sock, $buff, 2048, 0, $addr, $port);
} while($read < 0);
echo "$buff\r\n";
socket_close($sock);
?>
0 댓글