1:在服务端创建了一个负责监听的sokcet
//三个:采用TCP协议.
ListenSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
IPAddress ipAddress = IPAddress.Parse(this.txtIP.Text);//获取IP地址.
IPEndPoint point = new IPEndPoint(ipAddress, Convert.ToInt32(this.txtPort.Text));
//通信节点.包含了IP地址与端口号.
ListenSocket.Bind(point);//将通信节点(IP地址与端口号)与负责监听的Socket进行绑定。
ListenSocket.Listen(10);//设置监听队列.(将当前的Socket设置侦听状态)
2:解决服务端Accept方法占用UI线程的问题.(必做)
ThreadStart start = new ThreadStart(AcceptConnection);
Thread thread = new Thread(start);
thread.IsBackground = true;
//设置后台线程.如果整个的服务端窗体关闭,也就没有必要再等带客户端了,
所以在这里把该线程设置为后台线程。
thread.Start();
3:解决多个客户端请求,针对不同的客户端创建单独的newSocket(可选)
public void AcceptConnection()
{
while (true)//由于每个客户端必须都有一个单独的newSocket实例与之进行交流,
所以我们服务端得Accept()方法一致等待客户端有没有链接过来。
{
Socket newSocket = ListenSocket.Accept();
//获取到一个客户端的请求,创建一个新的Socket,负责与客户端进行交流
。每个客户都要创建一个单独的newSocket.
ShowMsg("客户端链接成功");
}
}
4:客户端怎样连接了服务端.
在客户端中添加如下代码:
Socket clientSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
IPAddress ipAddress = IPAddress.Parse(this.txtIP.Text);//获取IP地址.
IPEndPoint endPoint = new IPEndPoint(ipAddress,Convert.ToInt32(this.txtPort.Text));
//clientSocket.Bind(endPoint);
clientSocket.Connect(endPoint);//客户端根据通信节点去链接服务端。在客户端不能进行Bind操作.
5:服务端怎样向客户端发送文本数据.
首先在服务添加如下代码
/// <summary> /// 服务端向客户端发送文本数据(发送按钮) /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void btnSendMsg_Click(object sender, EventArgs e) { string sendMsg = this.txtSendMsg.Text; byte[] buffer=System.Text.Encoding.UTF8.GetBytes(sendMsg);//send发送的是字节数组. newSocket.Send(buffer);//服务端向客户端发送数据. ShowMsg("发送成功!"); }
以上代码中我们将newSocket定义了一个全局变量,这样会有问题。
客户端的代码(放在了客户端的 private void btnStartListen_Click(object sender, EventArgs e)方法中)
byte[] buffer=new byte[1024*1024*2];
clientSocket.Receive(buffer);
//接收服务端发过来的数据,该法会让客户端的Socket一直处于一种接收状态。
所以该方法也会占用UI线程。
string receiveMsg=System.Text.Encoding.UTF8.GetString(buffer);
ShowMsg(receiveMsg);
6:解决客户端接收数据的Receive方法占用UI线程问题,以及newSocket全局问题
客户端解决Receive占用UI线程问题。 ThreadStart start = new ThreadStart(ReciveClient); //解决客户端接收Receive方法占用UI线程的问题。 Thread thread = new Thread(start); thread.IsBackground = true; thread.Start(); public void ReciveClient() { byte[] buffer = new byte[1024 * 1024 * 2]; clientSocket.Receive(buffer); //接收服务端发过来的数据,该法会让客户端的Socket一直处于一种接收状态。 所以该方法也会占用UI线程。 string receiveMsg = System.Text.Encoding.UTF8.GetString(buffer); ShowMsg(receiveMsg); }
7:解决newSocket全局问题
//将与客户端进行数据交流的newSocket放入该集合中。 Dictionary<string, Socket> dictSocket = new Dictionary<string, Socket>(); public void AcceptConnection() { while (true)//由于每个客户端必须都有一个单独的newSocket实例与之进行交流, 所以我们服务端得Accept()方法一致等待客户端有没有链接过来。 { Socket newSocket = ListenSocket.Accept();//获取到一个客户端的请求, 创建一个新的Socket,负责与客户端进行交流。每个客户都要创建一个单独的newSocket. //将每个与客户端进行交流的newSocket添加到集合中, 我们用远程的客户端的IP地址与端口号作为集合的key. dictSocket.Add(newSocket.RemoteEndPoint.ToString(), newSocket); //将客户端的IP地址与端口号放在了列表中 this.listOnline.Items.Add(newSocket.RemoteEndPoint.ToString()); ShowMsg("客户端链接成功"); } } /// <summary> /// 服务端向客户端发送文本数据 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void btnSendMsg_Click(object sender, EventArgs e) { string sendMsg = this.txtSendMsg.Text; byte[] buffer=System.Text.Encoding.UTF8.GetBytes(sendMsg);//send发送的是字节数组. string clientIp = this.listOnline.Text; if (!string.IsNullOrEmpty(clientIp)) { //newSocket.Send(buffer);//服务端向客户端发送数据. //根据用户在listBox列表中选择的客户端的iP地址与端口号,找到该客户端对应的newSocket. dictSocket[clientIp].Send(buffer); ShowMsg("发送成功!"); } else { MessageBox.Show("请选择要发送的客户端"); } }
8:客户端一直处于接收状态.
while (true)//也要让客户端一直处于接收状态.(只是增加了改循环) { byte[] buffer = new byte[1024 * 1024 * 2]; clientSocket.Receive(buffer); //接收服务端发过来的数据,该法会让客户端的Socket一直处于一种接收状态。 所以该方法也会占用UI线程。 string receiveMsg = System.Text.Encoding.UTF8.GetString(buffer); ShowMsg(receiveMsg); }
9:解决回车换行问题.
public void ReciveClient() { while (true)//也要让客户端一直处于接收状态. { byte[] buffer = new byte[1024 * 1024 * 2]; int receiveLength=clientSocket.Receive(buffer); //接收服务端发过来的数据,该法会让客户端的Socket一直处于一种接收状态 。所以该方法也会占用UI线程。 //如果接收的服务端发过来的数据不足2M,那么剩余的存储单元都用"\0"来填充, 而"\0"表示字符串的结束,所以我们在接受的字符串后面加回车换行时无效。 // string receiveMsg = System.Text.Encoding.UTF8.GetString(buffer); //sokecet中的Receive方法会将接收到的数据填充到字节数组中,同时返回一个整数, 表示实际接收的数据长度。那么我们在将字节数组转成字符串时,从字节数组中的第一位开始, 一直实际接收的数据长度接收。这时不包含\0, string receiveMsg = System.Text.Encoding.UTF8.GetString(buffer, 0, receiveLength); ShowMsg(receiveMsg); } }