一.在我们讲解服务端编写之前,同样来看看该图。我们编写步骤,依此图为准。
07-01.png根据我们之前的说明Socket是一个网络编程接口,那么我们就得根据此特性去使用它,Socket的作者也是依据网络通信的协议进行的接口编写,所以使用思路也是做的一个继承吧。
二. 开始上干货 我们来编写吧
思路大纲:
1.1 首先我们需要创建一个负责监听的Socket,该Socket任务是开启一个Tcp/Udp服务器,设置监听队列长度,启动监听客户端服务。----这段描述,对比上图,按照顺序走过来socket()-->bind()-->listen()-->accept()-->等待客户机连接请求。
1.2 开启接收消息服务,该服务所使用的Socket对象是监听客户端成功后得到的用于和客户端通信的Socket。----这段描述,对比上图,按照顺序走过来 recv()
1.3 发送消息服务开启。这里消息发送的Socket也是用的监听客户端成功后得到的用于和客户端通信的Socket。----这段描述,对比上图,按照顺序走过来 send()
到此大家体会一下。我们开始编写。
2.1创建一个Socket负责监听的Socket对象---Socket()
07-02.png2.2给该Socket对象绑定IP和端口,同时Tcp服务也开启了,并且设置该TCP服务器最大可被 客户端连接数量为10--bind()-->listen();
07-03.png2.3 开启一个线程来执行监听客户端操作。--accept()
07-04.png如下是监听的一个完整步骤。
07-05.png2.4 接收客户端消息服务--recv()
07-06.png 07-07.png 07-08.png2.5 服务端发送消息服务--send()
关键代码逻辑:消息编码,调用Send方法;
07-09.png三.上述代码的界面:
这个界面做的逻辑只是为了不让我们服务器界面排版不那么杂乱而存在,这个界面就仅仅是传递地址信息到服务页面(服务端IP填写作为服务端的电脑ip,本机测试可以直接写127.0.0.1,端口号自定义不与其他端口冲突ip)
07-10.pngusing System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
namespace Server
{
/// <summary>
/// MainWindow.xaml 的交互逻辑
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void Window_MouseLeftButtonDown(object sender, RoutedEventArgs e)
{
this.DragMove();
}
/// <summary>
/// 启动服务器
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void Button_Click(object sender, RoutedEventArgs e)
{
//获取用户输入
string port = TxtboxPort.Text.Trim(),//测试账号 123456
ip = TxtboxIp.Text.Trim();//密码 pwd
//校验
if (string.IsNullOrEmpty(port) || string.IsNullOrEmpty(ip))
if (string.IsNullOrEmpty(port))
{
MessageBox.Show("Ip不能为空");
return;
}
else
{
MessageBox.Show("端口号不能为空");
return;
}
//传递Ip和端口到服务端界面
this.Hide();
ServerWindow server = new ServerWindow(port,ip);
server.ShowDialog();
this.Close();
}
}
}
07-11.png完整代码:(不带太多解释)
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Shapes;
namespace Server
{
/// <summary>
/// ServerWindow.xaml 的交互逻辑
/// </summary>
public partial class ServerWindow : Window
{
public ServerWindow()
{
InitializeComponent();
}
public ServerWindow(string port,string ip)
{
InitializeComponent();
MessageShow.VerticalScrollBarVisibility = ScrollBarVisibility.Visible;
ListenSocket(port, ip);
}
region
public Socket listenSocket = null;//负责监听的Socket在整个服务中是唯一的存在,把它放在类成员,使它可用范围最大化。
public Thread threadListen = null;//专门用于监听客户端程序的线程
region 1.创建一个负责监听的Socket。
/// <summary>
/// 开启一个监听的方法
/// </summary>
/// <param name="port"></param>
/// <param name="ip"></param>
public void ListenSocket(string port, string ip)
{
/*
//说明点1:创建负责监听的套接字(Socket),注意其中传递的参数说明:1.IPV4(是目前最广泛使用的网络通信协议第四版) 2.流式套接字 3.TCP
我们在创建前根据我们学习到的原理知识,我们创建的该Socket是Ipv4协议的(目前在开始ipv6建设,但是我们目前接触的web项目都是ipv4的,如果有幸接触到ipv6项目,那么一定在大厂了);
我们使用的是流式套接字,双工通信。
我们最后一个参数填写就是我们通信协议TCP了。
*/
listenSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
/*
说明点二:下面两部都是为了给该Socket进行IP端口绑定所做的操作,因为
执行Bind(),方法需要传递一个参数,类型为EndPoint类型,这是对Ip和端口进行说明的类型,而该类型的数据的创建,我们就new IPEndPoint,去传递进.
EndPoint类型是抽象类,而IPEndPoint是继承于它,所以我们就用它。
我们在用它是发现,需要传递一个IPAddress类型的参数,这个类是对IP地址的封装。所以我们下面这逻辑其实是根据Bind推导出来的。
*/
IPAddress address = IPAddress.Parse(ip);
//根据IPAddress以及端口号创建IPE对象
IPEndPoint endpoint = new IPEndPoint(address, int.Parse(port));
try
{
//给该Socket绑定一个Ip和端口,进而开启TCP服务
listenSocket.Bind(endpoint);
this.MessageShow.AppendText("开启服务成功!" + Environment.NewLine);
MessageBox.Show("开启服务成功!", "打开服务");
}
catch (Exception ex)
{
MessageBox.Show("开启服务失败" + ex.Message, "打开服务");
return;
}
//谁知监听客户端数量为10
listenSocket.Listen(10);
/*
因为我们监听方法是在客户端未开始连接服务端时,会一直停在Accept中,代码不会往下面执行,除非有客户端连接上来,为了保证主线程能够独立做自己该做的逻辑,我们就开启一个线程来专门做监听这个操作。
*/
//开启一个线程来创建一个不断监听的服务
threadListen = new Thread(ListenConnection);
threadListen.IsBackground = true;
threadListen.Start();
}
endregion 1.创建一个负责监听的Socket。
region 2.调用Accept方法不断去监听是否有客户端连接上来
//创建一个键值对集合,用于存放客户端 键存放该客户端的地址信息,值存放于该客户端通信的Socket
public Dictionary<string, Socket> DicSocket = new Dictionary<string, Socket>();
/// <summary>
/// 2.用于监听连接的方法
/// </summary>
public void ListenConnection()
{
while (true)
{
//一旦监听到一个客户端的连接,将会创建一个与该客户端连接的套接字
Socket sockClient = listenSocket.Accept();//如果没有客户端连接将会卡在这里
string client = sockClient.RemoteEndPoint.ToString();//获取客户端的Ip和端口
//
DicSocket.Add(client, sockClient);
this.ListBoxTxt.Dispatcher.Invoke(new Action(()=> { this.ListBoxTxt.Items.Add(client); }));//列表中添加
this.MessageShow.Dispatcher.Invoke(new Action(()=> { this.MessageShow.AppendText(client + "上线了!" + Environment.NewLine); }));
//开启一个新线程用于客户端于服务端进行通信
Thread thr = new Thread(ReceiveMsg);
thr.IsBackground = true;
thr.Start(sockClient);
}
}
endregion 2.调用Accept方法不断去监听是否有客户端连接上来
region 3.接收客户端消息
/// <summary>
/// 用于接收客户端传递过来的消息
/// </summary>
/// <param name="sockClient"></param>
public void ReceiveMsg(object sockClient)
{
//获取和客户端通信的Socket
Socket messageSocket = sockClient as Socket;
while (true)
{
//定义一个2M缓冲区 1024byte登录1kb,1024kb等于,1mb
//用于存放客户端传递过来的消息,因为网络通信进行传递的是二进制,所以我们需要byte。
byte[] arrMsgRec = new byte[1024 * 1024 * 2];
//定义消息长度,-1是未接受消息(我自己定义的),原因下面会讲
//0表示客户端停止了与服务端连接
//大于0表示客户端发送了消息
int length = -1;
try
{
//解释原因 : 如果客户端未发消息,代码不会往下面执行,会停在这里,所以只要往下面执行我们length就要么为0,要么为大于0,我们只是需要一个表示没有值的数据而已,所以给一个-1,只是一个含义。
length = messageSocket.Receive(arrMsgRec);
}
catch (Exception) //服务端下线客户端
{
//获取客户端地址信息
string str = messageSocket.RemoteEndPoint.ToString();
//给我们的界面进行消息展示
this.MessageShow.Dispatcher.Invoke(new Action(() => { this.MessageShow.AppendText(str + "下线了!" + Environment.NewLine); }));
//从列表中移除URL
this.ListBoxTxt.Dispatcher.Invoke(new Action(() => { ListBoxTxt.Items.Remove(str); }));
DicSocket.Remove(str);
break;
}
if (length == 0)
{
string str = messageSocket.RemoteEndPoint.ToString();
this.MessageShow.Dispatcher.Invoke(new Action(() => { this.MessageShow.AppendText(str + "下线了!" + Environment.NewLine); }));
//从列表中移除URL
this.ListBoxTxt.Dispatcher.Invoke(new Action(() => { ListBoxTxt.Items.Remove(str); }));
DicSocket.Remove(str);
break;
}
else
{
//因为我们客户端发送过来的是二进制,所以我们需要,进行编码准换
//我们把我们想要传递给服务端的数据,需要先编译为二进制,在这个步骤我们就需要先确定我们编码的格式,因为中文的二进制如果被Ascll格式编码那是不行的,因为该编码格式不认中文,我们就选择识别度最广的编码UTF8编码,来编译为二进制数据,我们服务端解码也同样如此。
//小插曲:乱码的由来,编码格式和解码格式不一致就导致乱码。
string strMsg = Encoding.UTF8.GetString(arrMsgRec,0, length);
string Msg = "[接收] " + messageSocket.RemoteEndPoint.ToString() + " " + strMsg;
this.MessageShow.Dispatcher.Invoke(new Action(() => { this.MessageShow.AppendText(Msg + Environment.NewLine); }));
}
}
}
endregion
region 单独发送消息
/// <summary>
/// 发送消息
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void SendOne_Click_1(object sender, RoutedEventArgs e)
{
string StrMsg = this.SendTxt.Text.Trim();
byte[] arrMsg = Encoding.Default.GetBytes(StrMsg);
byte[] arrSend = new byte[arrMsg.Length + 1];
arrSend[0] = 0;
Buffer.BlockCopy(arrMsg, 0, arrSend, 1, arrMsg.Length);
if (this.ListBoxTxt.SelectedItems.Count == 0)
{
MessageBox.Show("请选择你要发送的对象!", "发送提示");
return;
}
else
{
foreach (string item in this.ListBoxTxt.SelectedItems)
{
DicSocket[item].Send(arrSend);
string Msg = "[发送] " + item + " " + StrMsg;
this.MessageShow.Dispatcher.Invoke(new Action(() => { this.MessageShow.AppendText(Msg + Environment.NewLine); }));
}
}
}
endregion
region 群发消息
/// <summary>
/// 群发消息
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void SendAll_Click(object sender, RoutedEventArgs e)
{
string StrMsg = this.SendTxt.Text.Trim();
byte[] arrMsg = Encoding.UTF8.GetBytes(StrMsg);
byte[] arrSend = new byte[arrMsg.Length + 1];
arrSend[0] = 0;
Buffer.BlockCopy(arrMsg, 0, arrSend, 1, arrMsg.Length);
foreach (string item in this.DicSocket.Keys)
{
DicSocket[item].Send(arrSend);
string Msg = "[发送] " + item + " " + StrMsg;
this.MessageShow.Dispatcher.Invoke(new Action(() => { this.MessageShow.AppendText(Msg + Environment.NewLine); }));
}
this.MessageShow.Dispatcher.Invoke(new Action(() => { this.MessageShow.AppendText("[群发] 群发完毕!" + Environment.NewLine); }));
}
endregion
endregion
}
}
网友评论