본문 바로가기
[ Program ]/c#스터디

62.네트워크프로그램

by 관이119 2022. 5. 20.

피씨끼리 데이터 주고 받는 방법을 알아보자.

일단 용어를 몇가지 알아야 한다.

* 엔드 포인트 -  아이피 주소와 포트 번호의 조합을 의미합니다.

* 소켓 - 네트워크 상에서 돌아가는 두 개의 프로그램 간 양방향 통신의 하나의 엔트 포인트

아이피주소는 흔히 생각하는 192.168.1.1 처럼 숫자로 만들어진 PC 의 주소이고, 포트는 각 피씨에 연결할수 있게 만들어진 숫자로 된 가상의 연결 구멍이라고 생각하면 된다.

그림1

 

참고로 그림1처럼 제어판 에서 방화벽을 들어가서 고급설정을 누르면 피씨가 사용중인 포트가 나온다.

각 프로토콜에 따른 기본 포트는 HTTP=80, FTP=21, TELNET=23, SSH=22, SMTP=25, POP3=110 이다.

대부분 저 포트들은 사용중인 포트라고 생각하면 되고 테스트등으로 사용하는 포트는 개인적으로 8000번대 포트를 자주 사용한다.

 

아래 글도 읽어보자.https://m.blog.naver.com/PostView.naver?isHttpsRedirect=true&blogId=onnuri67&logNo=30075970880 

 

컴퓨터에서 말하는 포트(port)란?

포트(port)는 항구다! 교수님께서 늘 하시던 말씀이 생각나네요. 그러나 정작 실제적인 감각을 가지지가 쉽...

blog.naver.com

https://3dmpengines.tistory.com/1904

 

소켓이란 무엇인가? (엔드 포인트)

[소캣(Socket)] 소켓(socket)은 통신의 극점(EndPoint)를 뜻한다. 두 프로세스가 네트워크 상에서 통신을 하려면 양 프로세스마다 하나씩 총 두 개의 소켓이 필요하다. 클라이언트 프로세스가 연결을 요

3dmpengines.tistory.com

 

 

 

통신프로그램은 간단하게 생각하면 두대의 PC가 각자 어디로 데이터를 보내고 어디로 데이터를 받겠다 쌍방이 미리 약속을 하고 약속된 형태로 데이터를 보내면 받는쪽에서도 해당 형태로 데이터를 받아서 그 데이터를 사용하는 형태로 이뤄진다.

일단 간단한 예제를 보자.

(예제에서는 tcpClient 클래스를 사용했는데 Socket 클래스를 사용할수도 있고 tcpClient 는 내부적으로 Socket 클래스를 사용한다.)

 

그림2

프로젝트는 그림2와 같이 2개를 만들어 줬다.

 

그림3

<그림3 코드>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
 
namespace tcp_receive
{
    public partial class receive : Form
    {
        public receive()
        {
            InitializeComponent();
        }
 
        private void button1_Click(object sender, EventArgs e)
        {
            Task.Run(() => tcptest());
        }
 
        public void tcptest()
        {
            labeltextchange(label1, "waiting");
 
            // (1) 로컬 포트 8888 을 Listen
            TcpListener listener = new TcpListener(IPAddress.Any, 8888);
            listener.Start();
 
            byte[] buff = new byte[1024];
 
            while (true)
            {
                // (2) TcpClient Connection 요청을 받아들여
                //     서버에서 새 TcpClient 객체를 생성하여 리턴
                TcpClient tc = listener.AcceptTcpClient();
 
                // (3) TcpClient 객체에서 NetworkStream을 얻어옴 
                NetworkStream stream = tc.GetStream();
 
                // (4) 클라이언트가 연결을 끊을 때까지 데이타 수신
                int nbytes;
                while ((nbytes = stream.Read(buff, 0, buff.Length)) > 0)
                {
                    // (5) 데이타 그대로 송신
                    stream.Write(buff, 0, nbytes);
                    string output = Encoding.ASCII.GetString(buff, 0, nbytes);
                    labeltextchange(label1, output);
                }
 
                // (6) 스트림과 TcpClient 객체 
                stream.Close();
                tc.Close();
 
                // (7) 계속 반복
 
            }
        }
 
        public void labeltextchange(Label lb , string str)
        {
            if (lb.InvokeRequired)
                lb.Invoke((Action)(() => { label1.Text += str; }));
            else
                lb.Text = str;
        }
    }
}
 
cs

 

 

그림4

<그림4코드>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Net.Sockets;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
 
namespace tcp_send
{
    public partial class send : Form
    {
        public send()
        {
            InitializeComponent();
        }
 
        private void button1_Click(object sender, EventArgs e)
        {
            Task.Run(() => tcptest());
        }
 
        public void tcptest()
        {
 
            TcpClient tc = new TcpClient("127.0.0.1"8888);
 
            string msg = "Hello World";
            byte[] buff = Encoding.ASCII.GetBytes(msg);
 
            // (2) NetworkStream을 얻어옴 
            NetworkStream stream = tc.GetStream();
 
            // (3) 스트림에 바이트 데이타 전송
            stream.Write(buff, 0, buff.Length);
 
            // (4) 스트림으로부터 바이트 데이타 읽기
            byte[] outbuf = new byte[1024];
            int nbytes = stream.Read(outbuf, 0, outbuf.Length);
            string output = Encoding.ASCII.GetString(outbuf, 0, nbytes);
 
            labeltextchange(label1, output);
 
            // (5) 스트림과 TcpClient 객체 닫기
            stream.Close();
            tc.Close();
        }
 
        public void labeltextchange(Label lb , string str)
        {
            if (lb.InvokeRequired)
                lb.Invoke((Action)(() => { lb.Text += str; }));
            else
                lb.Text = str;
        }
    }
}
 
cs

 

그림3은 데이터를 받는쪽 코드이고 그림4는 데이터를 보내는쪽 코드이다.

받은데이터를 그대로 받환하는 형태라 받는쪽에서 데이터를 받고 바로 보낸쪽으로 돌려주는 예제다.

 

그림2(받는쪽) 예제부터 하나씩 보자.

 

TcpListener listener = new TcpListener(IPAddress.Any, 8888);
listener.Start();

데이터가 어떤아이피에서 에서 와도 8888 포트로 들어오면 받는행동을 하겠다고 선언한 부분이다.

start 함수를 호출하면서 대기하게 된다.

 

byte[] buff = new byte[1024];

버퍼변수는 데이터가 들어오면 담아둘 변수다.

통신상에 데이터는 텍스트가 아닌 바이트로 서로 주고 받게 되는데 데이터가 큰경우 데이터를 한방에 넘기는건 불가능하다.

그래서 데이터를 두개의 피씨에서 서로 약속한 사이즈로 주고 받게 되는데 여기서는 1024 바이트로 정하고 서로 1024 바이트로 주고 받을수 있게 해당 변수를 설정한 부분이다.

 

복잡한거 다빼고 생각하자.

간단하게 요약하면 아래와 같다.

서버에서(받는쪽) 데이터를 받을 IP와 포트를 지정해서 해당 엔드포인트로 데이터가 들어오나 감시하고

클라이언트에서(보내는쪽) 데이터를 엔드포인트로 보내기만 하면 되는것이다.

 

너무 간단한내용이다.

위에서 사용한 샘플은 서버에서 동기식으로 데이터를 받게 되있다.

그림5

그림5에 나와있는 서버쪽(받는쪽) 소스해당 부분인데 저함수만 비동기로 바꾸면 비동기식으로 받게 된다.

 

비동기로 만들어보자.

클라이언트쪽(보내는쪽)도 stream.Write 로 동기식으로 데이터를 보내는데 보내는쪽도 비동기로 만들어보자.

위 소스랑 다르게 좀더 정리된 소스이므로 다른내용이라고 생각하고 다시 천천히 살펴보자.

 

 

 

 

<<<<<받는쪽>>>>>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
 
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
 
namespace receive
{
    public partial class receive : Form
    {
        public receive()
        {
            InitializeComponent();
        }
 
        private void button1_Click(object sender, EventArgs e)
        {
            Task.Run(() => receiveData());
        }
 
        public async Task receiveData()
        {
            labeltextchange(richTextBox1, "waiting");
 
            //ENDPOINT 지정 - 송신측이 어떤아이피든 내PC의(=현재피씨=서버) 8888 포트로 데이터가 들어온다면 받는다고 지정
            TcpListener listener = new TcpListener(IPAddress.Any, 8888);
            listener.Start();
 
            //데이터를 어느정도 크기로 받을지 지정
            byte[] buff = new byte[128];
 
            while (true)
            {
                try
                {
                    //데이터를 비동기로 받는다고 지정
                    TcpClient tc = await listener.AcceptTcpClientAsync();
 
                    //비동기로 받은데이터
                    NetworkStream stream = tc.GetStream();
 
                    //받아온데이터의 길이가 0보다 크면(계속 데이터가 오고있으면) 계속 받는다.
                    int nbytes;
                    while ((nbytes = stream.Read(buff, 0, buff.Length)) > 0)
                    {
                        //받아온데이터를 문자열로 변경하고 텍스트박스에 데이터를 쓴다.
                        string output = Encoding.ASCII.GetString(buff, 0, nbytes);
                        labeltextchange(richTextBox1, output);
                    }
 
                    //연결종료
                    stream.Close();
                    tc.Close();
                }
                catch(Exception ex)
                {
                    Console.WriteLine(ex.ToString());
                }
            }
        }
 
        public void labeltextchange(RichTextBox lb, string str)
        {
            if (lb.InvokeRequired)
                lb.BeginInvoke((Action)(() => { richTextBox1.Text += str; }));
            else
                lb.Text = str;
        }
    }
}
cs

 

 

<<<<<보내는쪽>>>>>

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
 
namespace send
{
    public partial class send : Form
    {
        public send()
        {
            InitializeComponent();
        }
 
        private void btn_send_Click(object sender, EventArgs e)
        {
            Task.Run(() => sendData());
        }
 
        public void sendData()
        {
 
#if true //데이터 1회 전송
 
            //ENDPOINT 지정 - 대상컴퓨터의 8888 포트로 데이터 전송 - 여기서는 localhost 이므로 127.0.0.1 PC로 데이터를 보낸다고 지정한것임.
//localhost 처럼 DNS 명으로 지정하면 DNS 서버에 따라 속도가 느려질수 있다. IP로 직접지정하자.
            TcpClient tc = new TcpClient("127.0.0.1"8888);
 
            //전송할 데이터
            string msg = "abc";
            byte[] buff = Encoding.ASCII.GetBytes(msg);
 
            //데이터를 전송할 통로(stream)
            NetworkStream stream = tc.GetStream();
 
            //전송통로(stream)로 데이터를 전송
            stream.WriteAsync(buff, 0, buff.Length);
 
            stream.Close();
            tc.Close();
 
#else //데이터 연속으로 전송
 
            //ENDPOINT 지정 - 대상컴퓨터의 8888 포트로 데이터 전송 - 여기서는 localhost 이므로 127.0.0.1 PC로 데이터를 보낸다고 지정한것임.
            TcpClient tc = new TcpClient("localhost"8888);
 
            while (true)
            {
                //전송할 데이터
                string msg = "abc";
                byte[] buff = Encoding.ASCII.GetBytes(msg);
 
                //데이터를 전송할 통로(stream)
                NetworkStream stream = tc.GetStream();
 
                //전송통로(stream)로 데이터를 전송
                stream.WriteAsync(buff, 0, buff.Length);
 
                Thread.Sleep(1);
            }
 
#endif
 
        }
    }
}
 
cs

 

 

주석으로 다 써놔서 따로 설명할만한 내용은 없다.

데이터 보내는 쪽에서 연속으로 데이터 보낼때 어떤형태로 하면되는지만 좀더 유심히 보면 될거같다.

그리고 stream 에서 비동기로 데이터를 보내는 방법이 WriteAsync 와 BeginWrite 가 있는데 WriteAsync 를 사용하는쪽으로 하자.

아래글도 한번 읽어보자.

 

https://social.msdn.microsoft.com/Forums/en-US/f323db47-5c83-41bd-abf3-53587ef2d44b/c-streamwriteasync-vs-beginwrite?forum=csharpgeneral 

 

c# | Stream.WriteAsync() vs BeginWrite()

In the original days of .NET we used BeginWrite to do async requests. However all that has been obsoleted in lieu of using Task and async/await. That is what WriteAsync is for. In modern code there is no reason to use BeginWrite.  Refer to MSDN for an exa

social.msdn.microsoft.com

 

 

 

***숙제 : 여러명이 채팅하는 프로그램을 만들어보자.

'[ Program ] > c#스터디' 카테고리의 다른 글

61. 이벤트  (0) 2022.05.18
60. 유저컨트롤  (0) 2022.05.17
59. lock / Monitor / Mutex  (0) 2022.05.11
58. Threadpool / BackgroundWorker / Parallel  (0) 2022.05.11
57. async / await  (0) 2022.03.29

댓글