2015. 1. 27. 07:39
C# with TCP/IP
닷넷 환경에서 구조체를 이용한 소켓 통신을 지원하기 위한 방법은 크게 두 가지로 나뉩니다.
★ 마샬링(Marshaling)을 이용한 구조체 사용.
★ 바이너리 포매터(Binary Formatter)의 사용.
이번에는 바이너리 포매터 기법을 이용한 구조체를 통신하기 위한 방법을 소개합니다.
프로그램 설명
클라이언트가 학생의 4가지 데이타(이름, 과목, 점수, 메모)를 서버로 전송한다고 가정하고 필요한 클래스는 아래와 같습니다.
[Serializable] struct DataPacket { public string Name; public string Subject; public int Grade; public string Memo; }
실행 후
메시지 전송 후
프로그램 작성 순서
1. 소켓/쓰레드과 관련한 네임스페이스를 서버/클라이언트 모두에 포함 시킵니다.
using System.Net; using System.Net.Sockets; using System.Threading; using System.Diagnostics; using System.IO;
2. 서버 프로그램
TcpListener server = null; public MainForm() { InitializeComponent(); FormClosing += new FormClosingEventHandler(WindowsFormClosing); InitStart(); } private void WindowsFormClosing(object sender, FormClosingEventArgs s) { if (server != null) server = null; Application.Exit(); } private void InitStart() { Thread socketworker = new Thread(new ThreadStart(socketThread)); socketworker.IsBackground = true; socketworker.Start(); } private void socketThread() { try { server = new TcpListener(IPAddress.Parse("192.168.0.12"), 13000); server.Start(); while (true) { TcpClient client = server.AcceptTcpClient(); updateStatusInfo("Connected"); Thread clientworker = new Thread(new ParameterizedThreadStart(clientThread)); clientworker.IsBackground = true; clientworker.Start(client); } } catch (SocketException se) { Debug.WriteLine("SocketException : {0}", se.Message); } catch (Exception ex) { Debug.WriteLine("Exception : {0}", ex.Message); } } private void clientThread(object sender) { // 1. 데이타 받기 TcpClient client = sender as TcpClient; NetworkStream stream = client.GetStream(); byte[] buffer = new byte[8092]; DataPacket packet = new DataPacket(); while (stream.Read(buffer, 0, buffer.Length) != 0) { // deserializing; packet = GetBindAck(buffer); } stream.Close(); client.Close(); // 2. 데이타 표시하기 string Name = packet.Name; string Subject = packet.Subject; Int32 Grade = packet.Grade; string Memo = packet.Memo; Debug.WriteLine("{0} : {1} : {2} : {3}", Name, Subject, Grade, Memo); Invoke((MethodInvoker)delegate { int count = listView1.Items.Count; count++; ListViewItem i = new ListViewItem(); i.Text = count.ToString(); i.SubItems.Add(Name); i.SubItems.Add(Subject); i.SubItems.Add(Grade.ToString()); i.SubItems.Add(Memo); i.SubItems.Add(DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")); listView1.Items.Add(i); listView1.Items[this.listView1.Items.Count - 1].EnsureVisible(); }); // 3. 상태값 표시하기 updateStatusInfo("Data Accepted"); } private void updateStatusInfo(string content) { Action del = delegate() { lblStatus.Text = content; }; Invoke(del); } private void btnDataClear_Click(object sender, EventArgs e) { if (MessageBox.Show("데이타를 모두 삭제 하시겠습니까?", "질문", MessageBoxButtons.OKCancel, MessageBoxIcon.Question) == System.Windows.Forms.DialogResult.OK) { listView1.Items.Clear(); } } private void btnClose_Click(object sender, EventArgs e) { Application.Exit(); } private DataPacket GetBindAck(byte[] btfuffer) { DataPacket packet = new DataPacket(); MemoryStream ms = new MemoryStream(btfuffer, false); BinaryReader br = new BinaryReader(ms); packet.Name = ExtendedTrim(Encoding.UTF8.GetString(br.ReadBytes(20))); packet.Subject = ExtendedTrim(Encoding.UTF8.GetString(br.ReadBytes(20))); packet.Grade = IPAddress.NetworkToHostOrder(br.ReadInt32()); packet.Memo = ExtendedTrim(Encoding.UTF8.GetString(br.ReadBytes(100))); br.Close(); ms.Close(); return packet; } // 문자열 뒤쪽에 위치한 null 을 제거한 후에 공백문자를 제거한다. private string ExtendedTrim(string source) { string dest = source; int index = dest.IndexOf('\0'); if (index > -1) { dest = source.Substring(0, index + 1); } return dest.TrimEnd('\0').Trim(); }
3. 클라이언트 프로그램
public MainForm() { InitializeComponent(); btnSend.MouseEnter += new EventHandler(btnSend_MouseEnter); btnSend.MouseLeave += new EventHandler(btnSend_MouseLeave); } void btnSend_MouseEnter(object sender, EventArgs e) { btnSend.UseVisualStyleBackColor = false; btnSend.BackColor = Color.FromArgb(255, 255, 165, 00); // 배경색을 오렌지 색으로 변경함... btnSend.ForeColor = Color.White; // 글자색을 흰색으로 변경함 } void btnSend_MouseLeave(object sender, EventArgs e) { btnSend.UseVisualStyleBackColor = true; btnSend.BackColor = SystemColors.Control; // 배경색을 시스템 기본색으로 변경함... btnSend.ForeColor = SystemColors.ControlText; // 글자색을 시스템 기본색으로 변경함... } private void btnSend_Click(object sender, EventArgs e) { Thread socketworker = new Thread(new ThreadStart(socketThread)); socketworker.IsBackground = true; socketworker.Start(); } private void socketThread() { // 1. 데이타패킷 조합 Debug.WriteLine("{0} : {1} : {2} : {3}", txtName.Text, txtSubject.Text, txtGrade.Text, txtMemo.Text); DataPacket packet = new DataPacket(); packet.Name = txtName.Text; packet.Subject = txtSubject.Text; Int32 outNum; if (Int32.TryParse(txtGrade.Text, out outNum)) { packet.Grade = Convert.ToInt32(txtGrade.Text); } else { packet.Grade = 0; } packet.Memo = txtMemo.Text; if (string.IsNullOrEmpty(packet.Name)) { MessageBox.Show("[이 름]을 입력하시기 바랍니다", "경고", MessageBoxButtons.OK, MessageBoxIcon.Error); Invoke((MethodInvoker)delegate { txtName.Focus(); }); return; } if (string.IsNullOrEmpty(packet.Subject)) { MessageBox.Show("[과 목]을 입력하시기 바랍니다", "경고", MessageBoxButtons.OK, MessageBoxIcon.Error); Invoke((MethodInvoker)delegate { txtSubject.Focus(); }); return; } if (packet.Grade == 0) { MessageBox.Show("[점 수]을 입력하시기 바랍니다", "경고", MessageBoxButtons.OK, MessageBoxIcon.Error); Invoke((MethodInvoker)delegate { txtGrade.Focus(); }); return; } if (string.IsNullOrEmpty(packet.Memo)) { packet.Memo = " "; } // 2. TcpClient 생성 및 설정 TcpClient client = new TcpClient(txtServerIP.Text, Convert.ToInt32(txtServerPort.Text)); NetworkStream stream = client.GetStream(); updateStatusInfo("Connected"); // 3. 전송하기 byte[] buffer = GetBytes_Bind(packet); stream.Write(buffer, 0, buffer.Length); updateStatusInfo(string.Format("{0} data sent", buffer.Length)); // 4. 스트림과 소켓 닫기 stream.Close(); client.Close(); // 5. listview 에 추가하기 Invoke((MethodInvoker)delegate { int count = listView1.Items.Count; count++; ListViewItem i = new ListViewItem(); i.Text = count.ToString(); i.SubItems.Add(txtName.Text); i.SubItems.Add(txtSubject.Text); i.SubItems.Add(txtGrade.Text); i.SubItems.Add(txtMemo.Text); i.SubItems.Add(DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")); listView1.Items.Add(i); listView1.Items[this.listView1.Items.Count - 1].EnsureVisible(); }); } private void updateStatusInfo(string content) { Action del = delegate() { lblStatus.Text = content; }; Invoke(del); } // 패킷 사이즈 private const int BODY_BIND_SIZE = 20 + 20 + 4 + 100; // 인증 패킷 구조체를 바이트 배열로 변환하는 함수 private byte[] GetBytes_Bind(DataPacket packet) { byte[] btBuffer = new byte[BODY_BIND_SIZE]; MemoryStream ms = new MemoryStream(btBuffer, true); BinaryWriter bw = new BinaryWriter(ms); // Name - string try { byte[] btName = new byte[20]; Encoding.UTF8.GetBytes(packet.Name, 0, packet.Name.Length, btName, 0); bw.Write(btName); } catch (Exception ex) { Console.WriteLine("Error : {0}", ex.Message.ToString()); } // Subject - string try { byte[] btName = new byte[20]; Encoding.UTF8.GetBytes(packet.Subject, 0, packet.Subject.Length, btName, 0); bw.Write(btName); } catch (Exception ex) { Console.WriteLine("Error : {0}", ex.Message.ToString()); } // Grade - long bw.Write(IPAddress.HostToNetworkOrder(packet.Grade)); // Memo - string try { byte[] btName = new byte[100]; Encoding.UTF8.GetBytes(packet.Memo, 0, packet.Memo.Length, btName, 0); bw.Write(btName); } catch (Exception ex) { Console.WriteLine("Error : {0}", ex.Message.ToString()); } bw.Close(); ms.Close(); return btBuffer; }
'C# with TCP/IP' 카테고리의 다른 글
[Program C#]Binary Serialization 을 이용한 소켓통신 - 윈도우버전 (0) | 2015.01.31 |
---|---|
[Program C#]Binary Serialization 을 이용한 소켓통신 - 콘솔버전 (0) | 2015.01.28 |
[Program C#]구조체를 이용한 소켓통신3 - 바이너리 포매터 방법 - 콘솔 버전 (0) | 2015.01.26 |
[Program C#]구조체를 이용한 소켓통신2 - 마샬링 방법 - 윈도우 버전 (0) | 2015.01.22 |
[Program C#]구조체를 이용한 소켓통신2 - 마샬링 방법 - 콘솔 버전 (0) | 2015.01.21 |