接黑马程序员学习日记(9),服务端与客户端已创建连接,以下的增加功能及改进代码均以此为基础
服务端向客户端发送数据
服务端新增发送信息文本框,与发送按钮,发送按钮点击事件如下:(发送完成后在txt_msg文本框中显示)
private void txt_send_Click(object sender, EventArgs e)
{
//向客户端发送信息
string msg = txt_msgsend.Text.Trim();
byte[] sendmsg = System.Text.Encoding.UTF8.GetBytes(msg);//将要发送的字符串转换成UTF8数组
socConnection.Send(sendmsg);//直接发送,暂时不考虑接收方
ShowMsg("服务端发送:"+msg);
}
客户端:改成多线程操作,将接受信息封装成方法,供多线程调用:
(客户端有一个msg文本框专门用来显示接收到的信息)
public MyChetClient()
{
InitializeComponent();
TextBox.CheckForIllegalCrossThreadCalls = false;//解除限制,可以跨线程调用
}
Thread threadClient = null;
Socket socketConn = null;//客户端的套接字
private void btn_con_Click(object sender, EventArgs e)
{
IPAddress address=IPAddress.Parse(txt_IP.Text.Trim());//IP
IPEndPoint endpoint=new IPEndPoint(address,int.Parse(txt_port.Text.Trim()));//端点包含IP和端口
//创建套接字
socketConn=new Socket(AddressFamily.InterNetwork,SocketType.Stream,ProtocolType.Tcp);
//socketConn.Accep服务端专用,负责接收
socketConn.Connect(endpoint);//由套接字向此端口发出请求
threadClient = new Thread(ReciveMsg);
threadClient.IsBackground = true;
threadClient.Start();
}
/// <summary>
/// 监听服务器发来的信息
/// </summary>
void ReciveMsg()
{
while (true)//定义死循环,不断接收
{
//定义用于接收的空间:定义一个接受用的缓存区2M空间
byte[] reciveMsg = new byte[1024 * 1024 * 2];
int reciveLength = socketConn.Receive(reciveMsg);//将接收到字节数组存储到reciveMsg数组,并返回接收到的数据的长度,用于处理多余的分配的存储空间而产生的换行不正确或者其他的异常
string msg = System.Text.Encoding.UTF8.GetString(reciveMsg, 0, reciveLength);//只将用户真正发送的数据(去除多余分配的缓冲区空间)转换成string类型
ShowMsg(msg);
}
}
void ShowMsg(string msg)
{
txt_msg.AppendText(msg + "\r\n");
}
}
服务端向多个客户端分别发送信息(有选择的发送)
改进服务端
增加键值对存储已经连接的客户端的endpoint,同时前台新增控件listbox
Dictionary<string, Socket> dirn = new Dictionary<string, Socket>();//键值对用于存储客户端endpoint //键:存储套接字的远程IP和端口;值存的是套接字
修改两个函数分别如下
void AcceptConn()
{
while (true)//一直监听并创建连接
{
socConnection = socketWatch.Accept();//监听到连接并创建新的socket
dirn.Add(socConnection.RemoteEndPoint.ToString(), socConnection);//将套接字存储到键值对中去
lb_client.Items.Add(socConnection.RemoteEndPoint.ToString());//同时将套接字存储到listbox中
ShowMsg("客户端连接成功"+socConnection.RemoteEndPoint.ToString());
}
}
void ServerSendMsg()
{
string strmsg = txt_msgsend.Text.Trim();
//byte[] msgByte = System.Text.Encoding.UTF8.GetBytes(strmsg);//将指定的字符串转换成字节序列
byte[] msgByte = Encoding.UTF8.GetBytes(strmsg);
string selectedEndpoint = lb_client.Text;//从listbox中选择已经与服务器连接上的客户端
dirn[selectedEndpoint].Send(msgByte);//按照用户选择,从键值对中取出其对应的endpoint
ShowMsg("服务端发送了消息:"+strmsg);
}
服务端要为服务端的每个通信套接字创建一个单独的通信套接字,负责调用通信套接字的receive方法,监听 客户端的数据,这些线程也应该装在字典集合中Dictionary<string,Thread>dictThread=newDictionary<string,Thread>();线程相当于把代码拷出去单独运行
在服务端新增:
//保存服务端所有负责调用通信套接字Receive方法的线程
Dictionary<string, Thread> dirReciveThread = new Dictionary<string, Thread>();
Thread thrRec = null;
/// <param name="socRecive"></param>
void ReceiveMsg(object socRecive)//新增接受方法,通过传递过来的参数(套接字)确定从哪个客户端接受信息
{
Socket socServerRec = socRecive as Socket;//将object型的参数转换成socket型
while (true)
{
//通信套接字根据字典(键)来拿,字典根据客户端的端口来拿,todo:那要字典存储通信线程还要有什么用?
byte[] recByte = new byte[1024];//创建缓冲空间字节组
int recLength = socServerRec.Receive(recByte);//存储接收到的字节组并返回长度
string strRec = Encoding.UTF8.GetString(recByte, 0, recLength);//将接受到的字节组转换成字符串
ShowMsg("由" +socServerRec.LocalEndPoint.ToString() + "发来:" + strRec);
}
}
Socket socConnection = null;//负责连接的套接字
/// <summary>
/// socConnectio为监听到客户端连接胡创建的socket,用它来发送信息,所以在外部定义
/// </summary>
void AcceptConn()
{
while (true)//一直监听并创建连接
{
socConnection = socketWatch.Accept();//监听到连接并创建新的socket
dirn.Add(socConnection.RemoteEndPoint.ToString(), socConnection);//将套接字存储到键值对中去
lb_client.Items.Add(socConnection.RemoteEndPoint.ToString());//同时将套接字存储到listbox中
thrRec = new Thread(ReceiveMsg);//通过委托调用接受信息的方法
thrRec.IsBackground = false;//设置为后台线程
thrRec.Start(socConnection);//启动线程,同时把间听到的连接的套接字传过去
dirReciveThread.Add(socConnection.RemoteEndPoint.ToString(),thrRec);//将接收指定套接字的线程存入键值对,
//todo:存储到键值对后是什么时候用的呢?
ShowMsg("客户端连接成功"+socConnection.RemoteEndPoint.ToString());
}
客户端发送按钮事件
同服务端一样:private void btn_sendMsg_Click(object sender, EventArgs e)
{
string strMsg = txt_msgSend.Text.Trim();
byte[] byteMsg = System.Text.Encoding.UTF8.GetBytes(strMsg);
socketClient.Send(byteMsg);
ShowMsg("本地发送了:"+strMsg);
}
服务器端群发信息:
客户端之间的信息发送实际上是先有一个客户端发出信息到服务端(其中包含自己的IP、端口,接收端的IP和端口),然后再由服务端发送给接收端
Socket有待解决的事情:
(1)客户端之间发送接收信息
(2)对一些致命bug修复,比如一些按钮点击事件触发错误
(3)真正电脑间联网如何确定端口号
(4)完善出一个完整地聊天系统