유튜브에 c# tcp소켓통신 채팅 관련 영상이 있었다 그 영상의 코드를 공부해봤다.
먼저 ServerClinet
public class ServerClient
{
public TcpClient tcp;
public string clientName;
public ServerClient(TcpClient clientSocket)
{
clientName = "Guest";
tcp = clientSocket;
}
}
TcpClient : TCP 네트워크 서비스에 대한 클라이언트 연결을 제공한다.
ServerCreate
public void ServerCreate()
{
_listClients = new List<ServerClient>();
_listDisconnect = new List<ServerClient>();
try
{
int port = ins_PortInput.text == "" ? 9999 : int.Parse(ins_PortInput.text);
_server = new TcpListener(IPAddress.Any, port);
_server.Start(); //바인드 처리.
StartListening();
_bserverStarted = true;
Chat.instance.ShowMessage($"서버가 {port}에서 시작되었습니다.");
}
catch (Exception e)
{
Chat.instance.ShowMessage($"Socket error: {e.Message}");
}
}
port로 포트번호를 만들어준다. (현재 사용하지 않는 포트번호로 지정)
IPAddress.Any : 모든 클라이언트에서 오는 요청을 받겠다는 의미
TcpListener.Start : 들어오는 연결 요청의 수신을 시작. (바인드)
StartListening
private void StartListening()
{
_server.BeginAcceptTcpClient(AcceptTcpClient, _server);
}
BeginAcceptTcpClient(AsyncCallback? callback, object? state) : 들어오는 연결 시도를 받아들이는 비동기 작업을 시작한다.
AsyncCallback은 작업이 완료됐을때 호출한다.
state는 연결을 받아들이는 작업자에 대한 정보가 들어 있는 정의 개체이다. 작업이 완료되면 callback대리자에게 전달한다.
AcceptTcpClient
private void AcceptTcpClient(IAsyncResult ar)
{
TcpListener listener = (TcpListener)ar.AsyncState;
_listClients.Add(new ServerClient(listener.EndAcceptTcpClient(ar)));
StartListening();
// 메시지를 연결된 모두에게 보냄
Broadcast("%NAME", new List<ServerClient>() { _listClients[_listClients.Count - 1] });
}
매개변수
IAsyncResult : 비동기 작업의 상태를 나타낸다.
TcpListener.EndAcceptTcpClient() : 들어오는 연결 시도를 비동기적으로 받아들이고 원격 호스트 통신을 처리할 새로운 TcpClient을 만든다. (리턴 TcpListener)
AcceptTcpClient이 실행 된후에 다시 StartListening()를 실행시켜 새로운 Client를 받을 수 있도록 처리한다.
Update()
priavate void Update()
{
if (!_bserverStarted)
{
return;
}
foreach (ServerClient c in _listClients)
{
// 클라이언트가 여전히 연결되있나?
if (!IsConnected(c.tcp))
{
c.tcp.Close();
_listDisconnect.Add(c);
continue;
}
// 클라이언트로부터 체크 메시지를 받는다
else
{
NetworkStream s = c.tcp.GetStream();
if (s.DataAvailable)
{
string data = new StreamReader(s, true).ReadLine();
if (data != null)
OnIncomingData(c, data);
}
}
}
for (int i = 0; i < _listDisconnect.Count - 1; i++)
{
Broadcast($"{_listDisconnect[i].clientName} 연결이 끊어졌습니다", _listClients);
_listClients.Remove(_listDisconnect[i]);
_listDisconnect.RemoveAt(i);
}
}
Update에서 클라이언트들의 접속여부와 채팅 내용을 체크하도록 한다.
IsConnected
bool IsConnected(TcpClient c)
{
try
{
if (c != null && c.Client != null && c.Client.Connected)
{
if (c.Client.Poll(0, SelectMode.SelectRead))
return !(c.Client.Receive(new byte[1], SocketFlags.Peek) == 0);
return true;
}
else
return false;
}
catch
{
return false;
}
}
if(c.Client.Poll(0, SelectMode.SelectRead))
TcpClient.Poll(int microseconds, SelectMode mode); : 메소드는 간단히 말하자면 하려는 행동이 완료할 수 있는 상태면 true를 리턴한다.
매개변수
microseconds : 응답을 기다리는 시간
SelectMode : 오류상태모드, 읽기 상태 모드, 쓰기 상태 모드를 선택한다.
=> 데이터를 읽을 수 있다면 true를 반환한다.
!(c.Client.Receive(new byte[1], SocketFlags.Peek) == 0);
socket.Receive(Byte [], Int32, Int32, SocketFlags, SocketError)
매개변수
byte[] 수신된 데이터에 대한 스토리지 위치인 Byte형식의 배열
SoketFlags.Peek : 소켓 전송 및 수신 동작을 지정(Peek:들어오는 메시지를 미리 본다)
=> 1바이트를 보내고 실제 수신된 바이트를 확인하여 연결여부를 확인한다.
다시 위쪽에 else부분을 살펴보면,
else
{
NetworkStream s = c.tcp.GetStream();
if (s.DataAvailable)
{
string data = new StreamReader(s, true).ReadLine();
if (data != null)
OnIncomingData(c, data);
}
}
TcpClient.GetStream() : 데이터를 보내고 받는 데 사용되는 NetworkStream을 반환한다.
NetworkStream.DataAvailable() : 데이터를 읽을 수 있는지 여부를 나타내는 값을 가져온다.( 읽을 수 있으면 true, 그렇지 않으면 false)
이제 NetworkStream.DataAvailable() 까지 성공했다면 밑에 함수를 통해
void OnIncomingData(ServerClient c, string data)
{
if (data.Contains("&NAME"))
{
c.clientName = data.Split('|')[1];
Broadcast($"{c.clientName}이 연결되었습니다", _listClients);
return;
}
Broadcast($"{c.clientName} : {data}", _listClients);
}
Broadcast()
void Broadcast(string data, List<ServerClient> cl)
{
foreach (var c in cl)
{
try
{
StreamWriter writer = new StreamWriter(c.tcp.GetStream());
writer.WriteLine(data);
writer.Flush();
}
catch (Exception e)
{
Chat.instance.ShowMessage($"쓰기 에러 : {e.Message}를 클라이언트에게 {c.clientName}");
}
}
}
리스트로 받은 모두 ServerClient에 메세지를 전달하는 과정이다.
StreamWriter.Flush : 현재writer의 모든 버퍼를 지우면 버퍼링된 모든 데이터가 내부 스트림에 쓰여진다.
참고
https://www.youtube.com/watch?v=y3FU6d_BpjI
https://docs.microsoft.com/ko-kr/dotnet/api/system.net.sockets
'유니티' 카테고리의 다른 글
[유니티 커스텀 에디터] 맵툴 #2 (0) | 2021.08.04 |
---|---|
유니티)TCP 채팅 #2 (0) | 2021.06.10 |
[유니티] 유니티 PlayerPrefs 저장경로/ Application.dataPath/ (0) | 2021.05.03 |
[유니티] 어드레서블 알아보기 어드레서블 개념과 로드방법 - 1 (0) | 2021.05.01 |
[유니티] Unity IAP 인앱결제 알아보기 (1) | 2021.04.13 |