// 콜백 함수 void AcceptCallback(IAsyncResult ar) {// 클라이언트의 접속 요청이 Accept 되었을 때 호출되는 콜백함수 // 클라이언트의 연결 요청을 수락한다. Socket client = server_socket.EndAccept(ar); // 또 다른 클라이언트의 연결을 대기한다. server_socket.BeginAccept(AcceptCallback, null); // 비동기 통신을 위한 AsyncObject 객체 생성 및 소켓 지정 AsyncObject obj = new AsyncObject(4096); obj.WorkingSocket = client; // 클라이언트의 닉네임을 얻는다. GetNickname(client); // 연결된 클라이언트 딕셔너리에 추가해준다. connected_clients.Add(client_nickname, client); // 클라이언트에게 플레이어 번호를 할당해준다. SendPlayerNum(); // 현재 접속한 플레이어 수에 따라 이미지를 동기화 해준다. switch (player_count) { case 2: pb_human2.Image = img_pHuman.Images[0]; break; case 3: pb_human2.Image = img_pHuman.Images[0]; pb_human3.Image = img_pHuman.Images[0]; break; case 4: pb_human2.Image = img_pHuman.Images[0]; pb_human3.Image = img_pHuman.Images[0]; pb_human4.Image = img_pHuman.Images[0]; break; } // 모든 클라이언트에게 마지막 클라이언트가 연결되었다고 써준다. // 문자열을 utf8 형식의 바이트로 변환한다. // 현재 접속한 플레이어 수와 클라이언트 연결 메세지를 보낸다. string tts = string.Format("<System>{0}({1}P)님이 입장하였습니다.", client_nickname, player_count); byte[] bDts = Encoding.UTF8.GetBytes(player_count.ToString() + '\x01' + tts); // 연결된 모든 클라이언트에게 전송한다. //for(int i = connected_clients.Count - 1; i >= 0; i--) foreach (string nickname in connected_clients.Keys) { Socket socket = connected_clients[nickname]; try { socket.Send(bDts); } catch { // 오류 발생하면 전송 취소하고 리스트에서 삭제한다. try { socket.Dispose(); } catch { } connected_clients.Remove(nickname); } } // 전송 완료 후 텍스트 박스에 추가한다. AppendText(rtb_chat, string.Format("<System>{0}({1}P)님이 입장하였습니다.", client_nickname, player_count)); // 클라이언트의 데이터를 받는다. client.BeginReceive(obj.Buffer, 0, 4096, 0, DataReceived, obj); }
void DataReceived(IAsyncResult ar) {// 데이터 받기 - 서버, 클라이언트 둘 다 사용 // BeginReceive에서 추가적으로 넘어온 데이터를 Asyncobject 형식으로 변환한다. AsyncObject obj = (AsyncObject)ar.AsyncState; // 데이터 수신을 끝낸다. int received = obj.WorkingSocket.EndReceive(ar); // 클라 끄면 서버 꺼지는 이유 // 받은 데이터가 없으면(연결 끊어짐) 끝낸다. if (received <= 0) { obj.WorkingSocket.Close(); return; } // 텍스트로 변환한다. string text = Encoding.UTF8.GetString(obj.Buffer); // 0x01 기준으로 자른다. == '\x01' 기준으로 자른다. // tokens[0] - 보낸 사람 닉네임 // tokens[1] - 보낸 메세지 // tokens[2] - 보낸 사람 플레이어 번호 string[] tokens = text.Split('\x01'); if (tokens.Length == 3) // 토큰이 셋이면 채팅 메세지이다. { string nickname = tokens[0]; string msg = tokens[1]; string player_num = tokens[2].Trim('\0'); // 텍스트 박스에 추가해준다. // (비동기식으로 작업하기 때문에 폼이 UI 스레드에서 작업을 해줘야 한다. // 따라서 대리자를 통해 처리한다.(Invoke)) AppendText(rtb_chat, string.Format("({0}P){1}: {2}", player_num, nickname, msg)); } else if (tokens.Length == 2) // 토큰이 둘이면 <System> 메세지이다. { current_player_num = Int32.Parse(tokens[0]); string system_msg = tokens[1].Trim('\0'); // 텍스트 박스에 추가해준다. // (비동기식으로 작업하기 때문에 폼이 UI 스레드에서 작업을 해줘야 한다. // 따라서 대리자를 통해 처리한다.(Invoke)) AppendText(rtb_chat, system_msg); } if (is_server) // 함수를 호출한 프로세스가 서버이면 { // 모든 클라이언트에게 전송 foreach (string nickname in connected_clients.Keys) { Socket socket = connected_clients[nickname]; if (socket != obj.WorkingSocket) { try { socket.Send(obj.Buffer); } catch { // 오류 발생하면 전송 취소하고 리스트에서 삭제한다. try { socket.Dispose(); } catch { } connected_clients.Remove(nickname); } } } // 데이터를 받은 후엔 다시 버퍼를 비워주고 obj.ClearBuffer(); // 같은 방법으로 수신을 대기한다. obj.WorkingSocket.BeginReceive(obj.Buffer, 0, 4096, 0, DataReceived, obj); } else // 함수를 호출한 프로세스가 클라이언트이면 { // 플레이어의 접속 이미지를 동기화 한다. switch (current_player_num) { case 2: pb_chat.Image = img_pNumber.Images[1]; pb_human1.Image = img_pHuman.Images[0]; pb_human2.Image = img_pHuman.Images[0]; break; case 3: pb_chat.Image = img_pNumber.Images[2]; pb_human1.Image = img_pHuman.Images[0]; pb_human2.Image = img_pHuman.Images[0]; pb_human3.Image = img_pHuman.Images[0]; break; case 4: pb_chat.Image = img_pNumber.Images[3]; pb_human1.Image = img_pHuman.Images[0]; pb_human2.Image = img_pHuman.Images[0]; pb_human3.Image = img_pHuman.Images[0]; pb_human4.Image = img_pHuman.Images[0]; break; } // 클라이언트에선 데이터를 전달해줄 필요가 없으므로 바로 수신 대기한다. // 데이터를 받은 후엔 다시 버퍼를 비워주고 obj.ClearBuffer(); // 같은 방법으로 수신을 대기한다. obj.WorkingSocket.BeginReceive(obj.Buffer, 0, 4096, 0, DataReceived, obj); } }
private void btn_Client_Click(object sender, EventArgs e) { if (client_socket.Connected) { MsgBoxHelper.Error("이미 연결되어 있습니다!"); return; } int port; if (!int.TryParse(txt_port.Text, out port)) { lbl_loginAlert.ForeColor = Color.Red; lbl_loginAlert.Text = "잘못된 포트번호입니다."; txt_port.Focus(); txt_port.SelectAll(); return; } if (txt_nickname.Text == "") { lbl_loginAlert.ForeColor = Color.Red; lbl_loginAlert.Text = "잘못된 닉네임입니다."; txt_nickname.Focus(); txt_nickname.SelectAll(); return; } changePhase(PHASE.CONNECTING); try { client_socket.Connect(txt_ip.Text, port); } catch (Exception ex) { lbl_loginAlert.ForeColor = Color.Red; lbl_loginAlert.Text = "연결에 실패했습니다!"; changePhase(PHASE.LOGIN); return; } // 연결 완료되었다는 메세지를 띄워준다. //AppendText(rtb_chat, "서버와 연결되었습니다."); // 클라이언트 자신의 닉네임을 설정한다. my_nickname = txt_nickname.Text; // 서버에게 닉네임을 보낸다. SendNickname(); // 서버에게서 플레이어 번호를 할당받는다. GetPlayerNum(); //게임인터페이스로 이동 changePhase(PHASE.LOBBY); // 로비로 이동.(준비 전 상태) // 채팅창을 사용 가능하도록 변경 txt_send.Enabled = true; btn_send.Enabled = true; btn_ready.Enabled = true; // 연결 완료, 서버에서 데이터가 올 수 있으므로 수신 대기한다. AsyncObject obj = new AsyncObject(4096); // 데이터 송신을 대기할 소켓 지정 obj.WorkingSocket = client_socket; // 해당 소켓이 비동기로 데이터를 받는 함수 BeginReceive 호출 client_socket.BeginReceive(obj.Buffer, 0, obj.BufferSize, 0, DataReceived, obj); }