前回作成したチャットソフトに個別送信機能を追加してみる。
やりたいこと。
・ユーザーは名前を付けられる。
・ユーザーは自分の発言に色を使用出来る。
・現在ログインしているユーザー一覧を表示する。なお、ユーザーの増減が生じた場合は自動で更新されること。※ただし異常終了は除く。
・クライアントが異常終了しても、サーバーが落ちないこと。
・MessagePackを通信フォーマットに使用したい。
・ユーザーはアイコンを設定できる。
・シェイクを送信できる。
・「他のユーザが入力中」とか表示できる。
・特定のユーザだけにメッセージを送信できる。←NEW
チャットメッセージクラスを変更
/// <summary>
/// チャットメッセージ
/// </summary>
public class MessageInfo : ExtensionsClass.ExtensionsClass.IMessage
{
public string _1TOGUID { get; set; }
public int _2COLOR { get; set; }
public string _3MESSAGESTRING { get; set; }
}
_1TOGUID変数を追加して、送信先のGUIDを指定できるようにします。尚、送信先が全員の場合は空で送るようにします。
C#チャットメッセージ送信部分を変更
SendMessage関数内でチャットメッセージの送信データ作成時に送信先のGUIDを指定するようにします。
/// チャットメッセージ送信
case Interfaces.COMMAND.CMDSENDSTRING:
{
var data = new Interfaces.MessageInfo();
data._1TOGUID = "";
if (cBoxToUser.Text != "全員")
{
var sendUser = _userList.Where(p => p.Value._2NAME == cBoxToUser.Text);
if (sendUser.Any())
{
data._1TOGUID = sendUser.First().Key;
}
else
{
MessageBox.Show("送信先エラーです。");
cBoxToUser.SelectedIndex = 0;
return;
}
}
data._2COLOR = pictureBoxStringColor.BackColor.ToArgb();
data._3MESSAGESTRING = textBoxMessage.Text;
cmdif._4SENDDATA = data.Object2MessagePack();
}
break;
cBoxToUserとしてユーザ名が入ったコンボボックスコントロールを追加します。送信時にこのコンボボックスに選択されている名前が送信先になります。デフォルトは”全員”とします。
_userListはGUIDとInterfaces.UserInfoデータクラスのDictionaryなので、送信先の名前からGUIDを特定して_1TOGUIDに指定しています。
ちなみに、名前がかぶった場合の処理が考えてありません。。。
Python側受信処理変更
connection_handle()のメッセージ受信ループの中でチャットメッセージコマンド受信の時の処理を追加します。
while True:
msg = ws.receive()
print "Receive Message"
if msg is None:
break
msgpack_msg = msgpack.unpackb(msg)
cmd = msgpack_msg[0]
guid = msgpack_msg[1]
timestr = getTimeStr()
print "\tcmd = " + str(cmd)
print "\tmsg len = " + str(len(msg))
sendmsg = []
sendmsg.append(cmd)
sendmsg.append(guid)
sendmsg.append(timestr)
sendmsg.append(msgpack_msg[3])
sendmsg_pack = msgpack.packb( sendmsg )
if cmd == CMDLOGIN:
_userInfo[ id(ws) ] = msgpack_msg[1]
_userData[ id(ws) ] = msgpack.unpackb( msgpack_msg[3] )
sendAllUserInfo(ws, SENDONE)
if cmd == CMDMESSAGESTRING:
data = msgpack.unpackb( msgpack_msg[3] )
touser = wsIdToWs(guidToWsId( data[0] ))
if touser != None:
sendMessage(ws, str2bytearray(sendmsg_pack), SENDONE)
sendMessage(touser, str2bytearray(sendmsg_pack), SENDONE)
elif len(data[0]) == 0:
sendMessage(ws, str2bytearray(sendmsg_pack), SENDALL)
else:
sendMessage(ws, str2bytearray(sendmsg_pack), SENDALL)
ヘッダー部分のコマンド番号を解析し、CMDMESSAGESTRING(0x12)の処理を追加します。データ部分をさらに分解し、配列の1番目のGUIDを解析して該当のユーザを特定します。そして、送信ユーザと該当のユーザのみにチャットメッセージを送信します。
guidToWsId()はGUIDからWebSocketのインスタンスIDを特定し、wsIdToWs()はWebSocketのインスタンスIDからWebSocketのインスタンスを返します。
C#メッセージ受信処理変更
RecvMessage()のチャットメッセージ受信処理を変更します。
/// チャットメッセージ受信
case Interfaces.COMMAND.CMDSENDSTRING:
{
var msg = cmdif._4SENDDATA.MessagePack2Object<Interfaces.MessageInfo>();
var userData = _userList[ cmdif._2GUID];
CtrlAction = new Action(() =>
{
AddChatMessage(userData.GetIconImage(), userData._2NAME,
msg._3MESSAGESTRING, Color.FromArgb(msg._2COLOR), cmdif._3TIME,msg._1TOGUID);
});
}
break;
実際にはAddChatMessage関数に引数toguidを追加し、GUIDにより該当ユーザの名前を画面に表示させるようにします。
private void AddChatMessage(Image icon, string name, string msg, Color msgcolor, string time, string toguid = "")
{
var str = time + " :";
if (toguid != "")
{
str += _userList[toguid]._2NAME;
}
else
{
str += "全員";
}
DataGridViewRow dgvrow = new DataGridViewRow();
dgvChatView.Rows.Add(icon, name, str);
dgvChatView.Rows.Add(null, msg, null);
dgvChatView.Rows[dgvChatView.Rows.Count - 2].Height = 25;
dgvChatView.Rows[dgvChatView.Rows.Count - 1].Cells[1].Style.ForeColor = msgcolor;
if (toguid != "")
{
dgvChatView.Rows[dgvChatView.Rows.Count - 2].DefaultCellStyle.BackColor = Color.Beige;
dgvChatView.Rows[dgvChatView.Rows.Count - 1].DefaultCellStyle.BackColor = Color.Beige;
}
dgvChatView.FirstDisplayedScrollingRowIndex = dgvChatView.Rows.Count-1;
}
特定ユーザからのメッセージは背景色を変えています。
実行画面

まとめ
今回の機能追加は、サーバ・クライアント間の処理をヘッダー部、データ部に分離してあることによりコアな処理に変更を入れる必要は生じなかったものの、元々拡張性を考えて作っていた割りには、汚いソースとなってしまいました。送信先情報はヘッダー部に入れるべきだったなぁ。サーバ側の元々のサンプルが全てのメッセージをブロードキャストするイメージだったので、個別送信機能は後付になっちゃたなぁ。
やっぱ、設計って大事だなー。
今回作成したソースはこちら。