개발기간 24.11.08 ~ 24.11.09
참고자료
https://www.csharpstudy.com/net/article/1
C# 네트워크 프로그래밍 - C# 프로그래밍 배우기 (Learn C# Programming)
C# 네트워크 프로그래밍 네트워크 프로그래밍은 그 범위를 상당히 넓은데, 이 섹션에서는 .NET에 기반한 TCP/IP와 UDP 프로그래밍을 중점적으로 다룬다. 네트워크 모델 네트워크의 세부 기능들을 매
www.csharpstudy.com
현재까지 개발된 기능순서
1. 프로그램 동작시 클라이언트가 사용할 이름을 받는다.
1-1 클라이언트 이름이 admin 이라면 서버를 개설하는 관리자로 판단
1-2 admin 이 아니라면 서버에서 사용될 닉네임이 된다.
2. 서버는 클라이언트들의 입장, 메세지 전달의 기능을 기다리고, 동작이 있다면 전부 log를 남긴다.
3. 서버는 클라이언트가 메세지를 전달할때, 현재 모든 클라이언트들에게 해당 메세지를 전부 뿌린다.
4. 클라이언트는 입장할때 서버에 연결이 됐는지 확인 후 서버에게 입장로그를 띄우도록 한다.
5. 클라이언트가 메세지 입력 후 전달시 서버가 동작을 감지해 위에 2~3 동작을 수행한다.
서버 관리자는 방화벽 관리 사전작업이 필요하다. = 네트워크 포트포워딩
현재 작성한 코드는 포트를 9999로 설정하였고, 클라이언트는 이 포트로 데이터를 전달하게 된다.
그럼 서버가 이 데이터를 받아들일때 9999포트로 데이터를 받게 끔 미리 작업이 필요하다.
프로그램 동작 확인결과 클라이언트 측에서는 별도의 작업이 필요한것 같지 않다. // 다른 PC환경 테스트 확인
본인은 현재 공유기 환경으로써 ipTime에서 별도 설정을 해봤다.
9999 포트 외에는 사용하지 않을것 이므로 9999만 사용하게끔 설정해뒀다.
프로그램의 문제
클라이언트로 입장하게 될 경우 서버의 정보를 파악할 수가없음(서버와 연결된 소켓만으로 통신중)
구현 코드
프로그램 동작 로직
static async Task Main(string[] args)
{
Client newClient = new Client();
Server server = null;
if(newClient.Name == "admin")
{
// 이미 동작중인 서버가 있다면 중복 방지
if(server != null)
{
Console.WriteLine("이미 서버가 존재합니다");
Environment.Exit(0);
}
// 이번이 처음이라면 서버 생성
server = new Server();
while (true)
{
// 클라이언트 정보 갱신
await server.WaitClientAsync();
}
}
else // 접속한게 클라이언트라면
{
// 클라 입장에 대한 확인정보 갱신을 받고
bool isConnected = await newClient.ConnectServer();
// 클라 접속 성공여부를 판단
if(isConnected)
{
// 접속 됐다면
while (true)
{
// 클라는 수시로 메세지를 전달시키게 한다.
await newClient.SendMessage();
}
}
else
{
Console.WriteLine("서버에 연결할 수 없습니다. 프로그램 종료");
}
Console.ReadLine();
}
}
서버
public class Server
{
public const string TITLE = "ㅇㅇㅇ의 채팅서버";
private TcpListener myServer;
private List<TcpClient> clients;
public Server()
{
clients = new List<TcpClient>();
myServer = new TcpListener(IPAddress.Any, 9999);
myServer.Start();
}
public async Task WaitClientAsync()
{
// 새 클라이언트가 접속하면 (접속 될때까지 대기)
TcpClient client = await myServer.AcceptTcpClientAsync();
// 클라 목록에 추가
clients.Add(client);
// 공간을 생성해두고
byte[] data = new byte[1024];
// 클라이언트 버퍼를 불러옴
int bytesRead = await client.GetStream().ReadAsync(data, 0, data.Length);
// 클라이언트 버퍼를 풀어서
string strData = Encoding.Default.GetString(data, 0, bytesRead);
// 콘솔에 출력
Console.WriteLine(strData);
// 개별 쓰레드를 동작시켜서 대상 클라이언트에게 붙임
_ = Task.Run(() => ControlClient(client));
}
private async Task ControlClient(TcpClient client)
{
// 공간을 미리할당
byte[] buffer = new byte[1024];
NetworkStream stream = client.GetStream();
while (true)
{
// 클라가 데이터 전달을 하면
int bytesRead = await stream.ReadAsync(buffer, 0, buffer.Length);
// 풀어서
string message = Encoding.Default.GetString(buffer, 0, bytesRead);
// 로그에 남김
Console.WriteLine($"Log: {message}");
// 그 내용을 클라들에게 전달
await SendMessage(message, client);
}
}
private async Task SendMessage(string message, TcpClient sender)
{
// 메세지 내용을 다시 암호화
byte[] messageBytes = Encoding.Default.GetBytes(message);
foreach (var client in clients)
{
// 발신자를 제외하고
if (client != sender)
{
NetworkStream stream = client.GetStream();
// 수신자들에게 전부 내용을 전달함
await stream.WriteAsync(messageBytes, 0, messageBytes.Length);
}
}
}
}
클라이언트
public class Client
{
private TcpClient tcpClient;
public string Name { get; private set; }
public Client()
{
tcpClient = new TcpClient();
Console.Write("사용할 이름을 입력해주세요 : ");
Name = Console.ReadLine();
}
public async Task<bool> ConnectServer()
{
// 접속을 시도해봄
try
{
// 기준 서버에 접속 - 여기서 해당 소켓을 판별(서버가 내쪽에 다리를 놔주었는가)
// IP는 서버 관리자측 IP가 필요함
await tcpClient.ConnectAsync("개설된 서버의 IP", 9999);
// 접속했다라는 메세지 내용을 버퍼시킴
byte[] buf = Encoding.Default.GetBytes($"{Name} 접속");
// 소켓 = 연결된 다리, 스트림 = 데이터를 담은 자동차
NetworkStream stream = tcpClient.GetStream();
// 위 메세지를 서버쪽에 전달시킴
await stream.WriteAsync(buf, 0, buf.Length);
// 접속한 클라에게 접속된다면 아래 내용을 출력시킴
Console.WriteLine($"{Server.TITLE}에 접속 성공");
// 클라는 개별 스레드를 활성시킨다. ReceiveMessagesAsync의 기능을 돌리는
_ = Task.Run(() => ReceiveMessagesAsync());
return true;
}
// 소켓(다리)이 없다면
catch (SocketException ex)
{
// 걍 종료
Console.WriteLine("서버가 동작중이지 않습니다.");
return false;
}
}
// 클라쪽의 메세지 전달기능
public async Task SendMessage()
{
// 입력 메세지
string str = Console.ReadLine();
// 본인 이름과 묶기
string totalStr = $"{this.Name} : {str}";
// 전체 암호화
byte[] buf = Encoding.Default.GetBytes(totalStr);
// 해당 스트림에다가
NetworkStream stream = tcpClient.GetStream();
// 위 내용 쏴버리기
await stream.WriteAsync(buf, 0, buf.Length);
}
private async Task ReceiveMessagesAsync()
{
// 공간을 미리 만들어두고
byte[] data = new byte[1024];
NetworkStream stream = tcpClient.GetStream();
while (true)
{
// 해당 스트림에 버퍼 중인 데이터가 있다면
int byteRead = await stream.ReadAsync(data, 0, data.Length);
// 풀어서
string strData = Encoding.Default.GetString(data, 0, byteRead);
// 클라 화면에 띄운다.
Console.WriteLine(strData);
}
}
}
'C#' 카테고리의 다른 글
내 코드가 느린 이유 - 최적화에 대한 고찰 (1) | 2025.02.10 |
---|---|
C# - stack 주소와 heap 주소 찾기 (0) | 2024.07.25 |
C# 가변성,공변성, 반공변성, 불공변성 (0) | 2024.07.21 |
C# out , ref 키워드의 차이 (0) | 2024.07.17 |
C# in 키워드 (0) | 2024.07.14 |