文章目录
Socket
简单介绍一下Socket
Socket是什么呢?
先简单了解一下,下面是项目实战
Socket是应用层与TCP/IP协议族通信的中间软件抽象层,它是一组接口。在设计模式中,Socket其实就是一个门面模式,它把复杂的TCP/IP协议族隐藏在Socket接口后面,对用户来说,一组简单的接口就是全部,让Socket去组织数据,以符合指定的协议。
Scoket项目实战
项目介绍
本项目为自己做的一个小程序,客户端与服务器连接通过Socket传输字符串消息、文件以及闪屏。
此程序为C# winform窗体应用程序,分为客户端与服务器端,废话不多说,直接上干货
服务器端
1. 窗体设计
这个窗体设计的可能有些丑陋,但是实现的功能是齐全的,适用于初学Socket的学者,接下来进入代码编写阶段
2. 服务器功能实现
下面这个是服务器整体代码结构,一一展示出来,注释都写的很清楚不明白的慢慢理解,私信我也可以
下面这个是整个服务器端的代码,有些长,每一个方法,每一个重要的代码我都用 #region #endregion,认真查看,执行时如有报错或其他问题私信我!!!
using System; using System.Collections.Generic; using System.IO; using System.Net; using System.Net.Sockets; using System.Text; using System.Threading; using System.Windows.Forms; namespace ChatDemo { public partial class 服务器端_MainFrm : Form { #region 属性定义 //代理Socket对象(客户端点击发送时,ClentProxSocketList发送到服务器端) //存储客户端发送的数据 List<Socket> ClentProxSocketList = new List<Socket>(); #endregion public 服务器端_MainFrm() { InitializeComponent(); } #region 启动服务(创、绑、开、接) private void btnStart_Click(object sender, EventArgs e) { #region 1. 创建Socket(注释) /* * AddressFamily.InterNetwork 寻址的方式(IPV4) * SocketType.Stream 传播方式 * ProtocolType.Tcp 传输协议 */ #endregion Socket socket = new Socket(AddressFamily.InterNetwork,SocketType.Stream,ProtocolType.Tcp); #region 2. 绑定端口IP(注释) /* * 指定地址和端口号 * txtIP.Text IP * txtPort.Text 端口号 * **/ #endregion socket.Bind(new IPEndPoint(IPAddress.Parse(txtIP.Text), int.Parse(txtPort.Text))); #region 3. 开启侦听(注释) /* * 链接:同时来了100链接请求,只能处理1个链接,队列里面放10个等待链接的客户端,其他的返回错误消息 * 连接等待连接队列 * **/ #endregion socket.Listen(10); #region 4. 接收客户端连接(注释) /* * 开始接收客户端链接 (proxSocket 代理客户端执行对象) * 启动了一个新的线程,不断的接收客户端的连接,并且服务器端参数socket,传到委托里 * * ThreadPool(思ruai的胖)创建线程池 * **/ //AcceptClientConnect 委托方法 接收客户端数据 //Accept() 每被执行一次,就代表一个客户端连接上来 阻塞当前线程 //new WaitCallback 声明一个委托 把客户端方法传到委托里 将服务器端参数socket #endregion ThreadPool.QueueUserWorkItem(new WaitCallback(this.AcceptClientConnect),socket);//线程池 } #endregion #region 向客户端发送数据 public void AcceptClientConnect(object socket) { //对socket进行类型转换 var serverSocket = socket as Socket; //提示 this.AppendTextToTxtLog("服务情开始接收客户端连接"); //调用Accept方法,接收客户端连接 //循环接收客户端发送的数据 while (true) { //proxSocket 代理Socket对象 var proxSocket = serverSocket.Accept(); this.AppendTextToTxtLog(string.Format("客户端:{0}链接上了", proxSocket.RemoteEndPoint.ToString())); ; ClentProxSocketList.Add(proxSocket);//只要一个客户端连接就把Socket代理放到集合中 #region QueueUserWorkItem(异步线程池) //不停的接收当前连接的客户端发送来的消息 /* * Receive 阻塞当前线程 * WaitCallback 委托方法 * **/ //使用异步线程池线程池 #endregion ThreadPool.QueueUserWorkItem(new WaitCallback(ReceiveData), proxSocket); } } #endregion #region 文本框显示数据 //往(日志)文本框上追加数据 public void AppendTextToTxtLog(string txt) { //考虑跨线程访问控件 if (txtLog.InvokeRequired) { //传入一个字符串不需要返回值 txtLog.Invoke(new Action<string>(s => { this.txtLog.Text = string.Format("{0}\r\n{1}", s, txtLog.Text); }), txt); } else { this.txtLog.Text = string.Format("{0}\r\n{1}", txt, txtLog.Text);//不考虑跨线程访问控件 } } #endregion #region 接收客户端消息 public void ReceiveData(object socket) { var proxSocket = socket as Socket;//转换Socket 对象 //创建缓冲区 byte[] data = new byte[1024 * 1024]; while (true) { //客户端发送过来的消息 len实际接收字节数(数据) int len = 0; #region 客户端异常退出 try { /* * data:从什么时候开始写入数据 * 0:从哪开始写 * data.Length:写入最大长度 * **/ len = proxSocket.Receive(data, 0, data.Length, SocketFlags.None); } catch (Exception) { //客户端异常退出 AppendTextToTxtLog(string.Format("客户端:{0}非正常退出", proxSocket.RemoteEndPoint.ToString())); ClentProxSocketList.Remove(proxSocket);//移除当前代理Socket对象 StopContent(proxSocket); return;//让方法结束,终结当前接收客户端数据的异步线程 } #endregion #region 客户端正常退出 if (len <= 0) { //客户端正常退出 AppendTextToTxtLog(string.Format("客户端:{0}正常退出", proxSocket.RemoteEndPoint.ToString())); ClentProxSocketList.Remove(proxSocket);//移除当前代理Socket对象 StopContent(proxSocket); return;//让方法结束,终结当前接收客户端数据的异步线程 } #endregion #region 接收到的数据放到文本框(注释) //把接收到的数据放到文本框上去 //把字节数组转换成字符串放到日志文本框进行显示 /* * data, 0 从哪个地方开始转 * len 一共转多少个长度 * **/ #endregion string str =Encoding.Default.GetString(data, 0, len); AppendTextToTxtLog(string.Format("接收到客户端:{0}的消息是:{1}", proxSocket.RemoteEndPoint.ToString(),str)); } } #endregion #region 发送消息(字符串) private void btnSendMsg_Click(object sender, EventArgs e) { foreach (var proxSocket in ClentProxSocketList) { if (proxSocket.Connected)//判断Socket 是否是连接状态 { //获取需要发送到客户端的数据,转换成字节数组,进行发送 byte[] data = Encoding.Default.GetBytes(txtMsg.Text); #region 验证发送类型 //对原始的数据数组加上协议的头部字节 byte[] result = new byte[data.Length+1]; //设置当前的协议头部字节 是1:1代表字符串 result[0] = 1; #region BlockCopy(注释) //把原始的数据放到最终的字节数组里去 /* * Buffer.BlockCopy:块拷贝 * data:从哪里烤 *:0:从data那个地方开始烤:从0开始烤 * result:拷到result里去 * 1:从1(result)开始烤 * data.Length:一共烤多少个字节数,把整个data.Length考进去 * **/ #endregion Buffer.BlockCopy(data,0,result,1,data.Length); #endregion proxSocket.Send(result, 0, result.Length, SocketFlags.None);//从0开始发送 SocketFlags.None? } } } #endregion #region 发送闪屏 private void btnSendShake_Click(object sender, EventArgs e) { //循环遍历ClentProxSocketList 集合 foreach (var proxSocket in ClentProxSocketList) { if (proxSocket.Connected)//判断Socket 是否是连接状态 { //每个Socket对象发送一个,new byte字节数组,初始化2 proxSocket.Send(new byte[] { 2},SocketFlags.None); } } } #endregion #region 发送文件 private void btnSendFile_Click(object sender, EventArgs e) { //把要发送的文件读取出来,打开文件 using (OpenFileDialog ofd = new OpenFileDialog()) { if (ofd.ShowDialog()!=DialogResult.OK) { return; } //读取文件数据 byte[] data = File.ReadAllBytes(ofd.FileName); //将文件数据拷贝到result中 byte[] result = new byte[data.Length + 1]; //添加前缀 设置当前的协议头部字节 是3:3代表文件 result[0] = 3; Buffer.BlockCopy(data, 0, result, 1, data.Length); //循环遍历ClentProxSocketList 集合 foreach (var proxSocket in ClentProxSocketList) { if (!proxSocket.Connected)//判断Socket 是否是连接状态 { continue; } //发送到客户端 //每个Socket对象发送一个,new byte字节数组,初始化2 proxSocket.Send(result, SocketFlags.None); } } } #endregion #region 关闭连接 private void StopContent(Socket proxSocket) { try { if (proxSocket.Connected) { proxSocket.Shutdown(SocketShutdown.Both); proxSocket.Close(100);//如果100秒没有自动关闭,那就强行关闭 } } catch (Exception) { throw new Exception("连接关闭失败!"); } } #endregion } }
客户端
1. 窗体设计
这个窗体设计的可能有些丑陋,但是实现的功能是齐全的,适用于初学Socket的学者,接下来进入代码编写阶段
2. 客户端功能实现
using System; using System.Drawing; using System.IO; using System.Net; using System.Net.Sockets; using System.Text; using System.Threading; using System.Windows.Forms; namespace SocketClient { public partial class 客户端_MainFrm : Form { #region 属性定义 //绑定当前Socket(全局) public Socket ClientSocket { get; set; } #endregion public 客户端_MainFrm() { InitializeComponent(); //不捕获调用线程错误,此处应该使用异步调用线程池 Control.CheckForIllegalCrossThreadCalls = false; } #region 客户端连接服务器端 private void btnConnect_Click(object sender, EventArgs e) { #region 1. 创建Socket对象 Socket socket = new Socket (AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); ClientSocket = socket;//赋值全局socket #endregion #region 2. 连接服务器端 try { //获取IP地址和端口 socket.Connect(IPAddress.Parse(txtIP.Text), int.Parse(txtPort.Text)); } catch (Exception err) { MessageBox.Show("重新连接"); //Thread.Sleep(500);//设置连接等待时间 //btnConnect_Click(this,e); return; } #endregion #region 3. 发送消息(接收服务器端消息) Thread thread = new Thread(new ParameterizedThreadStart(ReceiveData)); thread.IsBackground = true; thread.Start(ClientSocket); #endregion } #endregion #region 客户端接收数据 public void ReceiveData(object socket) { var proxSocket = socket as Socket;//转换Socket 对象 //创建缓冲区 byte[] data = new byte[1024 * 1024]; while (true) { //客户端发送过来的消息 len实际接收字节数(数据) int len = 0; #region 客户端异常退出 try { /* * data:从什么时候开始写入数据 * 0:从哪开始写 * data.Length:写入最大长度 * **/ len = proxSocket.Receive(data, 0, data.Length, SocketFlags.None); } catch (Exception) { //客户端异常退出 //AppendTextToTxtLog(string.Format("服务器端:{0}非正常退出", // proxSocket.RemoteEndPoint.ToString())); StopContent();//关闭连接 return;//让方法结束,终结当前接收客户端数据的异步线程 } #endregion #region 客户端正常退出 if (len <= 0) { //服务器端正常退出 AppendTextToTxtLog(string.Format("服务器端:{0}正常退出", proxSocket.RemoteEndPoint.ToString())); StopContent();//关闭连接 return;//让方法结束,终结当前接收客户端数据的异步线程 } #endregion #region 验证接收类型 #region 接收的是字符串(1 是字符串) if (data[0] == 1) { string strMes = ProcessRecieveString(data); AppendTextToTxtLog(string.Format("接收到服务器端:{0}的消息是:{1}", proxSocket.RemoteEndPoint.ToString(), strMes)); } #endregion #region 接收闪屏(2 是闪屏) else if (data[0] == 2)//判断字节头是否为2 { SHake();//抖动窗口 } #endregion #region 接收文件(3 是文件) else if (data[0] == 3)//判断字节头是否为3 { ProcessRecieveFile(data,len);//接收文件 } #endregion #endregion } } #endregion #region 发送 private void btnSendMsg_Click(object sender, EventArgs e) { if (ClientSocket.Connected) { byte[] data = Encoding.Default.GetBytes(txtMsg.Text);//获取需要发送的数据,转换成字节数组 ClientSocket.Send(data, 0, data.Length, SocketFlags.None);//从0开始发送 SocketFlags.None? } } #endregion #region 向服务器端发送数据 public void AppendTextToTxtLog(string txt) { //考虑跨线程访问控件 if (txtLog.InvokeRequired) { //传入一个字符串不需要返回值 txtLog.Invoke(new Action<string>(s => { this.txtLog.Text = string.Format("{0}\r\n{1}", s, txtLog.Text); }), txt); } else { this.txtLog.Text = string.Format("{0}\r\n{1}", txt, txtLog.Text);//不考虑跨线程访问控件 } } #endregion #region 发送闪屏(抖动窗口) private void SHake() { //把窗体最原始的点记住 Point o = this.Location; //随机设定在某个位置开始左右上下抖动 Random r = new Random(); for (int i = 0; i < 50; i++) { //设置抖动位置 this.Location = new Point(r.Next(o.X-10,o.X+10), r.Next(o.Y-10,o.Y+10)); Thread.Sleep(200); //还原初始位置 this.Location = o; } } #endregion #region 处理接收到的文件 public void ProcessRecieveFile(byte[] data,int len) { using (SaveFileDialog sfd=new SaveFileDialog()) { //设置发送文件后缀 sfd.DefaultExt = "txt"; //设置文件类型 sfd.Filter = "文本文件(*.txt)|*.txt|所有文件(*.*)|*.*"; if (sfd.ShowDialog(this) != DialogResult.OK) { return; } #region BlockCopy(注释) /* * Buffer.BlockCopy:块拷贝 * data:从哪里烤 *:0:从data那个地方开始烤:从0开始烤 * result:拷到result里去 * 1:从1(result)开始烤 * data.Length:一共烤多少个字节数,把整个data.Length考进去 * **/ #endregion byte[] fileData = new byte[len - 1]; Buffer.BlockCopy(data, 1, fileData, 0, len - 1); File.WriteAllBytes(sfd.FileName, fileData); } } #endregion #region 处理接收到的字符串 private string ProcessRecieveString(byte[] data) { //把实际的字符串拿到 string str = Encoding.Default.GetString(data, 1, data.Length-1); return str; } #endregion #region 关闭客户端事件(如果未关闭,自动调用关闭方法) private void MainFrm_FormClosing(object sender, FormClosingEventArgs e) { //判断是否已连接,如果连接,那么就关闭连接 StopContent(); } #endregion #region 关闭连接 private void StopContent() { try { if (ClientSocket.Connected) { ClientSocket.Shutdown(SocketShutdown.Both); ClientSocket.Close(100);//如果100秒没有自动关闭,那就强行关闭 } } catch (Exception) { throw new Exception("关闭连接失败!"); } } #endregion } }
这就是整个程序代码实现与窗体设计,不明白或有报错的地方私信!!!