相关文章推荐
干练的水桶  ·  让lua脚本等待/暂停/睡眠/阻塞几秒钟的最 ...·  4 月前    · 
一身肌肉的荒野  ·  解决:Unable to open ...·  1 月前    · 
爱笑的汉堡包  ·  域套接字sendto errno ...·  1 月前    · 
想表白的茶壶  ·  Spring ...·  1 月前    · 
悲伤的茄子  ·  ps进程命令 - Gityuan博客 | ...·  10 月前    · 
从未表白的香菇  ·  【Windows】如何对显示器进行简单校色- ...·  1 年前    · 
高大的灯泡  ·  联合国糖尿病日,践行健康生活方式,学习知识“ ...·  1 年前    · 
千年单身的红烧肉  ·  首选谁?上汽大通MAXUS新途V70 VS ...·  2 年前    · 
无聊的猕猴桃  ·  皮卡堂龙宫宝箱是什么 皮卡堂龙宫宝箱介绍 ...·  2 年前    · 
Code  ›  C#을 이용하여 간단한 1:1 비동기 채팅 프로그램을 만들어보자 - 서버편 :: 꿈꾸는 프로그래머
ao bind socket
https://slaner.tistory.com/40
豪气的葡萄酒
8 月前

꿈꾸는 프로그래머

.NET 내부 구현 / Windows API / Windows Native 에 관심이 많습니다. DirectX 를 이용한 게임 및 UI 렌더링 / C# 기반 플러그인 플랫폼 구축하고 싶어요..

  • 분류 전체보기 (146)
    • API Reference (64)
    • .NET (43)
      • C# (19)
      • C# Renewal (0)
      • VB (1)
      • 프로젝트 (0)
        • BossHunter (0)
        • Utilities (7)
        • 게임 (6)
          • Apex Legends (6)
          • 잡담 (23)
          • Win Internal (1)
          • Trouble Shooting (2)
  • API 문서 목록
  • IsWow64Process 이름을 헷갈리게 지어서 착각하기쉬운데 T⋯
  • 제가 까먹을까봐 쓰는 댓글 // TextBox 없이 옮기기 IntPtr⋯
  • 감사합니다
  • 잘보고있습니다. 혹시 프로젝트 파일이 다운 받아도 파일이 없다 떠서 그⋯
  • 네이버 검색API를 C# winform에서 사용중인데, 글을 끝까지 읽⋯
  • 꿈 많은 개발자가 되자! Tae-hwan
  • 검은곰의 아카이브
  • 가벼운 여행
  • 마구드림 스케치
  • 세상을 보는 눈
  • 야심찬 블로그
  • 크스타
  • DAMIN Home
  • 未来日記
  • 유낙
  • 저장유낙
  • sol's spine journal review
  • 꿈꾸는 삶
  • 최대한 객관적으로
  • 할 것만 합시다.
  • JustDoIt
  • PCB
  • 예니블로그

C#을 이용하여 간단한 1:1 비동기 채팅 프로그램을 만들어보자 - 서버편

2014. 9. 25. 20:09
C# , socket , 비동기 소켓 , 소켓 , 소켓통신 , 채팅 , 채팅프로그램 , 채팅프로그램만들기

.NET 에서는 아주 다양한 구조체, 클래스, 인터페이스 등을 제공하고 있습니다.

그 양은 정말 다 쓰기가 힘들 정도로 방대하지요.

이 많고 많은 클래스들 중에서도 네트워크 통신을 담당하는 녀석이 있습니다.

네트워크와 관련된 클래스나 열거형 등은 모두 System.Net 네임스페이스에 정의가 되어 있는데요.

이번 글에선 소켓을 이용한 간단한 1:1 비동기 채팅 프로그램을 만들어볼까 합니다.

비동기(Asynchronous)란, 동기화를 하지 않는다는 뜻으로! 자료를 전송이나 수신, 연결 수락 등의 작업들이 완료되기를 기다리는 것이 아니라 개별적인 쓰레드를 생성하여 그 쓰레드 내에서 처리하게끔 만들어놓은 것입니다.

즉, 사용자로 하여금 동시에 여러 가지 작업을 할 수 있도록 작업의 동기화를 하지 않는 것입니다.


비동기 소켓 모델은 이렇게 표현할 수 있습니다.

Server

Client

소켓 통신을 하기 위해선 System.Net.Sockets 네임스페이스를 임포트 해주어야 합니다.

그리고, 같이 사용되는 EndPoint, IPAddress 등의 클래스를 사용하기 위해서 System.Net 네임스페이스 역시 임포트를 해주어야 합니다.

그럼 서버쪽부터 차근차근 설명해보도록 하겠습니다.

서버의 경우, 클라이언트가 접속 요청을 할 수 있도록 통신에 사용할 특정 포트를 바인딩한 후 들어올 연결 요청을 대기한 상태로 기다려야 합니다.

포트(Port)란, 네트워크 통신에 사용되는 통로라고 보시면 됩니다. 일상 생활에 빗대어 표현하자면 집 주소는 IP 주소가 되는 것이고 번지 수와 동/호 등을 포트로 비유할 수 있겠습니다.


요청을 대기하는 것은 어렵지 않습니다.

Bind, Listen 이 두 개의 메서드를 성공적으로 호출한 후에 BeginAccept 메서드를 사용하여 비동기적으로 들어오는 연결 요청을 처리하게 하면 됩니다.

Bind 메서드 ( Socket.Bind 메서드 문서 , MSDN)

Bind 메서드는 1개의 EndPoint 형 매개 변수를 필요로 합니다.

이 매개 변수는 어느 주소에서 연결 요청을 대기할 것인지를 나타냅니다.

EndPoint란 단어를 풀어서 해석한 그대로 '끝 점' 이라는 의미를 가지고 있습니다.

(더 자세한 설명은 저도 잘 몰라서.. 위X백과사전한테 그 임무를 넘겨주겠습니다 ㅠㅠ)

제가 이해하고 있는 EndPoint란 네트워크 상의 주소를 나타내는 것입니다.

IP 주소 및 포트 번호를 저장하고 있기 때문입니다. 또한 이 EndPoint 클래스는 원격 호스트에 연결할 때에도 사용되게 됩니다.

이만, EndPoint에 대한 제 개인적인 생각은 그만 말하고 본론으로 돌아가겠습니다.

Bind 메서드에 넘겨줄 EndPoint 클래스는 정말로 단순합니다.

Socket. Bind ( new IPEndPoint (IPAddress.Any, /* 사용할 포트 번호 */ 1000 ));


IPEndPoint 클래스의 생성자에 IPAddress.Any 와 사용할 포트 번호를 넘겨주면 끝입니다.

IPAddress 클래스에 정의된 정적 필드에 대한 설명 및 목록은 [여기] 를 참고하시면 되겠습니다.

Bind 메서드를 성공적으로 호출하였다면, 이제 Listen 메서드를 호출할 차례입니다.

Listen 메서드는 Bind 메서드보다 더 .. 더 ! 쉽습니다.

Listen 메서드 ( Socket.Listen 메서드 문서 , MSDN)

Listen 메서드는 Bind 메서드와 마찬가지로 1개의 매개 변수를 필요로 하지만, 매개 변수의 형식이 int 형입니다.

이 매개 변수는 클라이언트의 연결 요청을 동시에 몇 개 까지 처리할 것인가를 나타냅니다.

(5 로 입력한 경우, 최대 5개의 동시 요청을 처리할 수 있게 됩니다)

Listen 메서드에 넘겨줄 매개 변수는 이렇습니다.

Socket. Listen ( /* 백로그 큐 갯수 */ 5 ));


Listen 메서드 또한 성공적으로 호출되었다면, 이제 BeginAccept 메서드를 호출할 차례입니다.

여기부터는 과정이 약간 복잡해지니 잘 따라오셔야 합니다.

일단, BeginAccept 메서드에 대한 설명이 있는 [여기] 를 참고하시기 바랍니다.

(귀찮으시더라도 이 메서드에 대한 설명은 반드시 숙지하셔야 하기에, 링크로 이동하여 꼭 설명을 볼 것을 권장합니다)

BeginAccept 메서드는 2개의 매개 변수를 필요로 합니다.

Socket. BeginAccept (AsyncCallback, Object);


첫번째 매개 변수는 AsyncCallback 형식으로 대리자 라고 칭해지는데, 함수 포인터로 보시면 되겠습니다.

두번째 매개 변수는 Object 형식으로, 비동기 작업에 대한 추가 정보를 넘겨주는 것입니다.

AsyncCallback 형식은 이렇게 정의가 되어 있습니다.

delegate void AsyncCallback (IAsyncResult ar);


IAsyncResult 인터페이스는 BeginAccept 등의 비동기 메서드에서 넘겨준 추가 정보 및 작업에 대한 정보를 저장하는 인터페이스로써, 비동기 작업을 할 때 아주 유용하게 사용되니 꼭! 숙지하시기 바랍니다 .

모든 대리자는 생성자를 호출할 때 대리자와 호환되는 매개 변수 및 반환 형식을 가진 메서드를 필요로 합니다.

대리자는 다시 말하면 메서드 호출을 대신 해주는 사람으로도 표현할 수가 있겠네요.

그럼 어떻게 대리자의 새 개체를 초기화하는지 한번 보시겠습니다.

delegate void EmptyDelegate ();
private EmptyDelegate ed = new EmptyDelegate (dummyProc);
private void dummyProc () { } // 성공
private Int32 dummyProc () { } // 오류, 반환 형식이 맞지 않음
private void dummyProc (Int32 dummy) { } // 오류, 매개 변수가 맞지 않음

대리자는 그냥 메서드 그 자체입니다. 단지, 메서드를 캡슐화 할 수 있다는 것이 다릅니다.

그래서 메서드처럼 반환 형식이나 매개 변수를 프로그래머 입맛에 맞게끔 설정할 수 있습니다.

그럼 이제.. BeginAccept를 어떻게 호출하는지 한번 볼까요?

일단 어떤 대리자를 요구하는 매개 변수던 2가지 방법을 사용할 수가 있습니다.

  1. 대리자를 변수로 만들어서 변수를 넘겨주는 방법
  2. 즉시 대리자의 새 개체를 만들고 만들어진 개체를 넘겨주는 방법

/* 1. 대리자 변수를 하나 만들고 대리자 변수를 넘겨주는 방법 */
private AsyncCallback fnAsyncAccept = new AsyncCallback (asyncAcceptProc);
Socket. BeginAccept (fnAsyncAccept, /* 추가 정보 */ null );
/* 2. 즉시 대리자의 새 개체를 만들고 만들어진 개체를 넘겨주는 방법 */
Socket. BeginAccept ( new AsyncCallback (asyncAcceptProc), /* 추가 정보*/ null );
// 대리자가 사용하는 메서드
private void asyncAcceptProc (IAsyncResult ar) {

첫 번째 방법의 경우, 변수를 선언해야 하기 때문에 코드가 길어지면 다소 복잡하게 보일 수도 있습니다만,

첫 번째 방법으로 하는 것이 두 번째 방법보다 안전합니다.

두 번째 방법의 경우, 다소 단순해서 코드가 길어져도 그냥 그러려니 합니다만,

그래도 첫 번째 방법보다는 불안정한게 현실입니다.

여기서 왜 제가 안정성을 논하느냐.. 저는 C#, VB.NET 이 두 개를 이용해서 웹 서버를 만들어본 적이 있는데 성능을 시험하던 도중 예외가 발생했고(어떤 예외가 발생했는지는 자세히 기억이 나지가 않습니다 ㅠㅠ) 그 예외에 대한 해결 방법은 1번 방법처럼 대리자 변수를 선언하고, 그 선언한 대리자 변수를 넘겨줌으로써 해결 가능한 것이였기 때문입니다.


대게 일반적으로 클라이언트의 연결을 1회만 허용할 것이 아니라면 BeginAccept 메서드를 호출할 때 추가 정보엔 서버 소켓을 전달해주어야 합니다. 서버 소켓을 전달해 줌으로써, 들어온 클라이언트의 연결을 수락하고 또 다른 클라이언트의 연결 요청을 수락할 수 있기 때문입니다. 하지만 여기선 매우 간단한 통신 프로그램을 만들 것이기에, 추가 정보는 전달하지 않겠습니다.

그럼 1번 단계를 코드로 짜보겠습니다.

private Socket m_ServerSocket = null ;
private AsyncCallback m_fnAcceptHandle = new AsyncCallback (handleClientConnectionRequest);
public void startServer () {
// TCP 통신을 위한 소켓을 생성합니다.
m_ServerSocket = new Socket (AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.IP);
// 특정 포트에서 모든 주소로부터 들어오는 연결을 받기 위해 포트를 바인딩합니다.
// 사용한 포트: 1234
m_ServerSocket. Bind ( new IPEndPoint (IPAddress.Any, 1234 ));
// 연결 요청을 받기 시작합니다.
m_ServerSocket. Listen ( 5 );
// BeginAccept 메서드를 이용해 들어오는 연결 요청을 비동기적으로 처리합니다.
// 연결 요청을 처리하는 함수는 handleClientConnectionRequest 입니다.
m_ServerSocket. BeginAccept (m_fnAcceptHandler, null );
private void handleClientConnectionRequest (IAsyncResult ar) {

이제~ 기본 골격이 만들어 졌습니다.

그럼 2단계로 넘어가서 EndAccept 로 연결 요청을 수락하고, BeginReceive 메서드를 이용해서 자료를 수신받는 것을 만들 차례입니다.

우선, EndAccept 메서드 설명부터 하도록 하겠습니다.

EndAccept 메서드 ( Socket.EndAccept 메서드 문서 , MSDN)

이 메서드는 한 개의 매개 변수를 필요로 하고 있습니다. 매개 변수의 형식은 IAsyncResult 형식인데요..

어디서 본듯한 형식이 아닌가요? 네 맞습니다. 대리자에 사용될 함수의 매개 변수도 IAsyncResult 로 되어 있었지요~!!

이 메서드도 호출하는 방법은 매우 간단합니다. 그냥 집어넣으면 됩니다. 그러면 쨘~!!! 소켓이 만들어집니다.

private void someAsyncAcceptHandlerProc (IAsyncResult ar) {
Socket sockClient = sockServer. EndAccept (ar);

보시면 아시겠지만, sockServer 은 서버의 소켓, sockClient 는 클라이언트의 소켓을 의미합니다.

그럼, EndAccept는 이정도로 설명해도 이전에 설명한 것이 있기 때문에 이해하셨으리라 믿고 BeginReceive 메서드를 설명하도록 하겠습니다. 이 메서드를 사용할땐 이제 자료형을 하나 만들어서 사용할 것입니다~! 잘 보시고 따라오세요!!

BeginReceive 메서드 ( Socket.BeginReceive 메서드 문서 , MSDN)

이 메서드는 무려 여섯 개 의 매개 변수를 필요로 합니다. 하지만, 겁먹으실 필요 없습니다. 하나 하나 살펴보도록 하겠습니다.

byte[] buffer - 자료를 수신받을 바이트 배열을 입력하면 됩니다.

int offset - 바이트 배열의 몇 번째 요소부터 자료를 수신할 것인지를 입력하면 됩니다. 주로 0 을 사용합니다.

int size - 수신받을 자료의 크기를 입력하면 됩니다. 주로 배열의 크기를 사용합니다.

SocketFlags socketFlags - 소켓 옵션을 입력하면 됩니다. 주로 SocketFlags.None 을 사용합니다.

AsyncCallback callback - 자료를 수신하면 호출되는 비동기 대리자입니다.

Object state - 추가로 넘겨줄 정보를 입력하면 됩니다.


그럼 이제, 사용할 자료형에 대해서 설명을 해보도록 하겠습니다.

Begin 으로 시작하는 비동기 메서드를 보시면 아시겠지만, 모두 마지막 매개 변수는 Object 형으로 추가 정보를 받고 있습니다.

하지만, 일반적인 자료를 사용하면 한 가지 정보밖에 넘길 수가 없기 때문에 여기서 자료형 하나를 정의하고 정의한 자료형에 값을 할당함으로써 여러 가지 정보를 넘겨줄 것입니다.

그럼, 사용할 자료형을 정의해보도록 하겠습니다.

public class AsyncObject {
public Byte[] Buffer;
public Socket WorkingSocket;
public AsyncObject (Int32 bufferSize) {
this .Buffer = new Byte[bufferSize];

생성자로 하나의 매개 변수를 필요로 하고 있습니다. 바로 바이트 배열의 크기를 결정해주는 것입니다.

이제 자료형도 정의했겠다, BeginReceive 메서드를 어떻게 호출하는지 보도록 하겠습니다.

AsyncObject ao = new AsyncObject ( 4096 );
Socket. BeginReceive (ao.Buffer, 0 , ao.Buffer.Length, SocketFlags.None, fnReceiveHandler, ao);


이렇게 하면 연결된 클라이언트가 서버로 자료를 전송한 경우 fnReceiveHandler 대리자를 만들 때 넘겨준 메서드가 호출되고, ao.Buffer 에 수신한 자료가, fnReceiveHandler 대리자 메서드에서 넘겨준 ao 변수를 받을 수 있게 됩니다.

이제 보니깐 BeginAccept 메서드하고 크게 차이는 없습니다. 추가 정보를 넘겨주고 자료를 받는 것 외엔 비슷비슷 하니까요!

그럼 설명은 이제 입아프죠. 바로 2번 단계를 코드로 짜보도록 하겠습니다.

/* 1번 단계 코드는 생략하였습니다. */
public class AsyncObject {
public Byte[] Buffer;
public Socket WorkingSocket;
public AsyncObject (Int32 bufferSize) {
this .Buffer = new Byte[bufferSize];
private AsyncCallback m_fnReceiveHandler = new AsyncCallback (handleDataReceive);
private void handleClientConnectionRequest (IAsyncResult ar) {
// 클라이언트의 연결 요청을 수락합니다.
Socket sockClient = m_ServerSocket. EndAccept (ar);
// 4096 바이트의 크기를 갖는 바이트 배열을 가진 AsyncObject 클래스 생성
AsyncObject ao = new AsyncObject ( 4096 );
// 작업 중인 소켓을 저장하기 위해 sockClient 할당
ao.WorkingSocket = sockClient;
// 비동기적으로 들어오는 자료를 수신하기 위해 BeginReceive 메서드 사용!
sockClient. BeginReceive (ao.Buffer, 0 , ao.Buffer.Length, SocketFlags.None, m_fnReceiveHandler, ao);
private void handleDataReceive (IAsyncResult ar) {

이제 어느정도 끝이 보이려고 합니다. (ㅋㅋㅋ)

그럼 이제 마지막으로 서버/클라이언트 공통적인 수신할 자료가 있을 때 EndReceive 메서드를 이용해 자료를 수신하고,

다시 비동기적으로 들어오는 데이터를 수신받는 작업을 해보도록 하겠습니다.

여기선 뭐 설명할게 없네요. 그래도 EndReceive 메서드를 새로 사용하니 설명하도록 하겠습니다.

EndReceive 메서드 ( Socket.EndReceive 메서드 문서 , MSDN)

이 메서드는 한 개의 매개 변수를 필요로 합니다. EndAccept 와 똑같습니다.

다만 반환 값이 Socket이 아닌 int 형으로 수신받은 자료의 바이트 수를 반환합니다.

이게 끝입니다 . 설명할게 없군요 ㅋㅋ;

그럼 이제 설명은 그만하고 마지막 단계를 코드로 짜보도록 하겠습니다.

private void handleDataReceive (IAsyncResult ar) {
// 넘겨진 추가 정보를 가져옵니다.
// AsyncState 속성의 자료형은 Object 형식이기 때문에 형 변환이 필요합니다~!
AsyncObject ao = (AsyncObject) ar.AsyncState;
// 자료를 수신하고, 수신받은 바이트를 가져옵니다.
Int32 recvBytes = ao.WorkingSocket. EndReceive (ar);
// 수신받은 자료의 크기가 1 이상일 때에만 자료 처리
if ( recvBytes > 0 ) {
여기에 자료를 처리하는 작업을 하시면 됩니다.
// 자료 처리가 끝났으면~
// 이제 다시 데이터를 수신받기 위해서 수신 대기를 해야 합니다.
// Begin~~ 메서드를 이용해 비동기적으로 작업을 대기했다면
// 반드시 대리자 함수에서 End~~ 메서드를 이용해 비동기 작업이 끝났다고 알려줘야 합니다!
ao.WorkingSocket. BeginReceive (ao.Buffer, 0 , ao.Buffer.Length, SocketFlags.None, m_fnReceiveHandler, ao);

BeginReceive 메서드에 넘겨준 대리자 함수의 본문에서 비동기 수신 작업을 처리하고, 다시 수신을 비동기적으로 시작하는 코드가 완성되었습니다.

이번 장은 서버의 전체 코드를 작성함으로써 마무리를 짓고, 다음 장엔 클라이언트 쪽을 설명하도록 하겠습니다.

using System;
using System.Text;
using System.Net;
using System.Net.Sockets;
namespace simpleChat {
public class chatServer {
public class AsyncObject {
public Byte[] Buffer;
public Socket WorkingSocket;
public AsyncObject (Int32 bufferSize) {
this .Buffer = new Byte[bufferSize];
private Socket m_ServerSocket = null ;
private AsyncCallback m_fnReceiveHandler;
private AsyncCallback m_fnSendHandler;
private AsyncCallback m_fnAcceptHandler;
public void StartServer () {
// 비동기 작업에 사용될 대리자를 초기화합니다.
m_fnReceiveHandler = new AsyncCallback (handleDataReceive);
m_fnSendHandler = new AsyncCallback (handleDataSend);
m_fnAcceptHandler = new AsyncCallback (handleClientConnectionRequest);
// TCP 통신을 위한 소켓을 생성합니다.
m_ServerSocket = new Socket (AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.IP);
// 특정 포트에서 모든 주소로부터 들어오는 연결을 받기 위해 포트를 바인딩합니다.
// 사용한 포트: 1234
m_ServerSocket. Bind ( new IPEndPoint (IPAddress.Any, 1234 ));
// 연결 요청을 받기 시작합니다.
m_ServerSocket. Listen ( 5 );
// BeginAccept 메서드를 이용해 들어오는 연결 요청을 비동기적으로 처리합니다.
// 연결 요청을 처리하는 함수는 handleClientConnectionRequest 입니다.
m_ServerSocket. BeginAccept (m_fnAcceptHandler, null );
public void StopServer () {
// 가차없이 서버 소켓을 닫습니다.
m_ServerSocket. Close ();
public void SendMessage (String message) {
// 추가 정보를 넘기기 위한 변수 선언
// 크기를 설정하는게 의미가 없습니다.
// 왜냐하면 바로 밑의 코드에서 문자열을 유니코드 형으로 변환한 바이트 배열을 반환하기 때문에
// 최소한의 크기르 배열을 초기화합니다.
AsyncObject ao = new AsyncObject ( 1 );
// 문자열을 바이트 배열으로 변환
ao.Buffer = Encoding.Unicode. GetBytes (message);
// 사용된 소켓을 저장
ao.WorkingSocket = m_ServerSocket;
// 전송 시작!
m_ServerSocket. BeginSend (ao.Buffer, 0 , ao.Buffer.Length, SocketFlags.None, m_fnSendHandler, ao);
private void handleClientConnectionRequest (IAsyncResult ar) {
// 클라이언트의 연결 요청을 수락합니다.
Socket sockClient = m_ServerSocket. EndAccept (ar);
// 4096 바이트의 크기를 갖는 바이트 배열을 가진 AsyncObject 클래스 생성
AsyncObject ao = new AsyncObject ( 4096 );
// 작업 중인 소켓을 저장하기 위해 sockClient 할당
ao.WorkingSocket = sockClient;
// 비동기적으로 들어오는 자료를 수신하기 위해 BeginReceive 메서드 사용!
sockClient. BeginReceive (ao.Buffer, 0 , ao.Buffer.Length, SocketFlags.None, m_fnReceiveHandler, ao);
private void handleDataReceive (IAsyncResult ar) {
// 넘겨진 추가 정보를 가져옵니다.
// AsyncState 속성의 자료형은 Object 형식이기 때문에 형 변환이 필요합니다~!
AsyncObject ao = (AsyncObject) ar.AsyncState;
// 자료를 수신하고, 수신받은 바이트를 가져옵니다.
Int32 recvBytes = ao.WorkingSocket. EndReceive (ar);
// 수신받은 자료의 크기가 1 이상일 때에만 자료 처리
if ( recvBytes > 0 )
Console. WriteLine ( "메세지 받음: {0}" , Encoding.Unicode. GetString (ao.Buffer));
// 자료 처리가 끝났으면~
// 이제 다시 데이터를 수신받기 위해서 수신 대기를 해야 합니다.
// Begin~~ 메서드를 이용해 비동기적으로 작업을 대기했다면
// 반드시 대리자 함수에서 End~~ 메서드를 이용해 비동기 작업이 끝났다고 알려줘야 합니다!
ao.WorkingSocket. BeginReceive (ao.Buffer, 0 , ao.Buffer.Length, SocketFlags.None, m_fnReceiveHandler, ao);
private void handleDataSend (IAsyncResult ar) {
// 넘겨진 추가 정보를 가져옵니다.
AsyncObject ao = (AsyncObject) ar.AsyncState;
// 자료를 전송하고, 전송한 바이트를 가져옵니다.
Int32 sentBytes = ao.WorkingSocket. EndSend (ar);
if ( sentBytes > 0 )
Console. WriteLine ( "메세지 보냄: {0}" , Encoding.Unicode. GetString (ao.Buffer));

긴 글 읽어주시느라 수고하셨습니다 ^^;

정말 ' 간단한 ' 채팅 프로그램이기에, 소켓 상태를 확인하는 부분도 없고.. 채팅만 가능합니다.

이 글을 읽으시는 개발자분들이 응용해 보시는건 어떨까요?

다음 장엔 클라이언트 편으로 만나길 고대합니다! 감사합니다!

Powered by Tistory , Designed by wallel
Rss Feed and Twitter , Facebook , Youtube , Google+

티스토리툴바

 
推荐文章
干练的水桶  ·  让lua脚本等待/暂停/睡眠/阻塞几秒钟的最简单方法?开发者社区
4 月前
一身肌肉的荒野  ·  解决:Unable to open debugger port (127.0.0.1:55017): java.net.SocketException "Socket closed"开发者社区
1 月前
爱笑的汉堡包  ·  域套接字sendto errno -11分析 - StepForwards
1 月前
想表白的茶壶  ·  Spring WebSocket中关于WebSocket配置类的注意事项 - 一念错·误终生
1 月前
悲伤的茄子  ·  ps进程命令 - Gityuan博客 | 袁辉辉的技术博客
10 月前
从未表白的香菇  ·  【Windows】如何对显示器进行简单校色-百度经验
1 年前
高大的灯泡  ·  联合国糖尿病日,践行健康生活方式,学习知识“保护明天” - 湖南省卫生健康委员会
1 年前
千年单身的红烧肉  ·  首选谁?上汽大通MAXUS新途V70 VS 江铃福特新全顺V362_搜狐汽车_搜狐网
2 年前
无聊的猕猴桃  ·  皮卡堂龙宫宝箱是什么 皮卡堂龙宫宝箱介绍 - 游戏吧
2 年前
今天看啥   ·   Py中国   ·   codingpro   ·   小百科   ·   link之家   ·   卧龙AI搜索
删除内容请联系邮箱 2879853325@qq.com
Code - 代码工具平台
© 2024 ~ 沪ICP备11025650号