Socket
简单介绍一下Socket
Socket是什么呢?
先简单了解一下,下面是项目实战
Socket是应用层与TCP/IP协议族通信的中间软件抽象层,它是一组接口。在设计模式中,Socket其实就是一个门面模式,它把复杂的TCP/IP协议族隐藏在Socket接口后面,对用户来说,一组简单的接口就是全部,让Socket去组织数据,以符合指定的协议。
Scoket项目实战
项目介绍
本项目为自己做的一个小程序,客户端与服务器连接通过Socket传输字符串消息、文件以及闪屏。
此程序为C# winform窗体应用程序,分为客户端与服务器端,废话不多说,直接上 干货
代码直接复制粘贴,无任何费用,点个赞加个收藏吧,Thank you!!!
服务器端
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
}
}
这就是整个程序代码实现与窗体设计,不明白或有报错的地方私信!!!