C#Socket 服务器&客户端学习笔记
学习代码中加入自己的注释和理解,完全可以复制使用,但是窗体要自己画,根据代码里的名称去修改窗体内空间的Name属性
*****************************************************************************************************************************服务器窗体
服务器代码
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace _06Socket监听
{
public partial class Form1 : Form
{
//窗体加载初始化
public Form1()
{
InitializeComponent();
}
//创建端口,开始监听
private void btnStart_Click(object sender, EventArgs e)//Protocol:协议,Stream:流
{
Socket socketWatch = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);//当电机开始监听的时候,在服务器端创建一个负责监IP地址和端口号的Socket
IPAddress ip = IPAddress.Any; //获取计算机的IP
IPEndPoint point = new IPEndPoint(ip, Convert.ToInt32(txtPort.Text)); //创建端口号对象,网络终结点表示为 IP 地址和端口号
socketWatch.Bind(point); //监听网络终结点的数据
ShowMsg("监听成功,等待客户端连接进来");
socketWatch.Listen(10); //限制连接数量为10,防止服务器过载
Thread th = new Thread(Listen); //新建线程,负责等待连接
th.IsBackground = true; //将线程设置为后台线程,特点是前台程序关闭后,后台线程自动关闭
th.Start(socketWatch); //启动线程,告诉CPU,我可以被执行了,具体什么时候执行,由CPU决定
}
//公共字段
/// <summary>
/// 等待客户端的连接,并创建与之通信用的Socket
/// </summary>
Socket socketSend;
//服务器监听方法
void Listen(object o) //如果线程有参数,参数类型必须是object类型
{
Socket socketWatch = o as Socket; //将object类型数据强转成Socket类型
while (true)
{
try //将网络连接中可能出错的时候,什么都不显示,相当于卡死状态
{
socketSend = socketWatch.Accept(); //一直等待客户端的连接
dicSocket.Add(socketSend.RemoteEndPoint.ToString(), socketSend); // 将远程连接的客户端的IP地址和Socket存入集合中
cboUser.Items.Add(socketSend.RemoteEndPoint.ToString()); //将远程连接的客户端的IP地址和端口存储在下拉筐中
ShowMsg(socketSend.RemoteEndPoint.ToString() + ":" + "连接成功");//连接成功后,显示客户端的IP和端口
Thread th = new Thread(Recive); //开一个新线程不停的去接受客户端发过来的数据
th.IsBackground = true; //设定线程为后台线程
th.Start(socketSend); //线程启动,线程内参数为连接的客户端
}
catch { }
}
}
//新建多客户端键值对结构
/// <summary>
/// 将远程连接的客户端的IP地址和Socket存入集合中
/// </summary>
Dictionary<string, Socket> dicSocket = new Dictionary<string, Socket>();//新建一个键值对集合,键是String类型数据,值是Socket类型数据
//数据接收方法
/// <summary>
/// 服务器端不停的接受客户端发过来的消息
/// </summary>
/// <param name="o"></param>
void Recive(object o)//持续接收客户端数据
{
Socket socketSend = o as Socket;
while (true)
{
try //将网络连接中可能出错的时候,设么都不显示,相当于卡死状态
{
byte[] buffer = new byte[1024 * 1024 * 2]; //新建一个字节数组做缓存,客户端连接成功后,服务器接受客户端发来的消息都存在这个缓存里
int r = socketSend.Receive(buffer); //实际接收到的有效字节数
if (r == 0) //判断接受到是数据长度为0时,代表客户端被关闭,退出接受数据的循环
{ break; } //客户端关闭,退出循环接收数据黄铜
string str = Encoding.UTF8.GetString(buffer, 0, r); //将接收到的数据按编码转换成字符串
ShowMsg(socketSend.RemoteEndPoint + ":" + str); //信息显示,显示客户端的IP地址和端口号
}
catch { }
}
}
//信息显示方法
void ShowMsg(string str) //信息显示方法,形参是显示的字符串类型数据
{
txtLog.AppendText(str + "\r\n"); //在文本中累计添加显示
}
//窗体加载开始事件
/// <summary>
/// 加载Form时,不检查多线程间的调用
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void Form1_Load(object sender, EventArgs e)
{
Control.CheckForIllegalCrossThreadCalls = false; //关闭系统中线程相互调用的检查报错
}
//消息发送事件
/// <summary>
/// 服务器给客户端发消息
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void btnSend_Click(object sender, EventArgs e)
{
try
{
string str = txtMsg.Text; //获取文本框中输入的信息
byte[] buffer = System.Text.Encoding.UTF8.GetBytes(str);//将获取的文本信息转换成字节数组
List<byte> list = new List<byte>(); //创建泛型集合去存放字节数组
list.Add(0); //在集合的第一位添加类型,0:文字,1:文件,2:震动
list.AddRange(buffer); //加入原有需要发送的内容
byte[] newBuffer = list.ToArray(); //将集合转换为新的字节数组
string ip = cboUser.SelectedItem.ToString(); // 获得用户在下拉框中选择的IP地址,键
dicSocket[ip].Send(newBuffer); //键对应的值,就是连接到的端口(socketWatch.Accept())去发送数据,发送的内容是新的带文件类型标志的字符数组
}
catch
{
}
}
//文件打开事件
/// <summary>
/// 选择要发送的文件
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void button2_Click(object sender, EventArgs e)
{
OpenFileDialog ofd = new OpenFileDialog(); //新建文件打开功能实例
ofd.InitialDirectory = @"C:\Users\admin\Desktop"; //文件打开的路径
ofd.Title = "请选择要发送的文件"; //文件打开窗口的标题
ofd.Filter = "所有文件|*.*"; //文件打开选择的文件类型
ofd.ShowDialog(); // 用默认的所有者运行通用对话框,返回结果: System.Windows.Forms.DialogResult.OK 如果用户单击确定在对话框中;否则为 System.Windows.Forms.DialogResult.Cancel。
txtPath.Text = ofd.FileName; //将文件的完整路径显示在文本框中
}
//发送文件事件
private void btnSendFile_Click(object sender, EventArgs e)
{
string path = txtPath.Text; //从上面显示的文本框中获取要发送的完整的文件路径
using (FileStream fsRead = new FileStream(path, FileMode.Open, FileAccess.Read)) //将文件转换成字节流,using在线程结束后会自动释放内存,
//新建文件流,参数包括,路径,文件模式(打开文件),访问方式(读取)
{
byte[] buffer = new byte[1024 * 1024 * 5]; //新建读取数据流的缓存大小
int r = fsRead.Read(buffer, 0, buffer.Length); //将数据流放入缓存,并返回一个读取的大小
List<byte> list = new List<byte>(); //新建一个泛型集合,用来组合文件类型的定义值和读取的文件的数据流字节数组
list.Add(1); //将文件类型放入泛型结合的第一位
list.AddRange(buffer); //将读取到的文件的字符数组加入到泛型集合中
byte[] newBuffer = list.ToArray(); //将组合后的泛型结合重新转换成一个字符数组,新数组的长度比原有的字符数组长度+1
dicSocket[cboUser.SelectedItem.ToString()].Send(newBuffer, 0, r + 1, SocketFlags.None); //下拉框中连接的客户端去发送新的字符数组内容,参数(新数组,从0开始,到原有字符数组长度+1,通讯标志:无)
}
}
//震动发送事件
private void btnZD_Click(object sender, EventArgs e)
{
byte[] buffer = new byte[1]; //新建一个字符数组,长度为1,因为只要发个标志数据给客户端就行
buffer[0] = 2; //定义了第一个字符的数据为2就代表震动
dicSocket[cboUser.SelectedItem.ToString()].Send(buffer); //还是将下拉菜单中选择的客户端发送字符数组
}
}
}
客户端窗体****************
客户端代码****************
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Net.Sockets;
using System.Net;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Threading;
using System.IO;
namespace _07Client
{
public partial class Form1 : Form
{
//窗体加载初始化
public Form1()
{
InitializeComponent();
}
//公共字段
Socket socketSend; //新建通讯实例对象
//按钮事件,创建通讯端口,开始接收服务器数据
private void btnStart_Click(object sender, EventArgs e) //客户端连接服务器按钮
{
try
{
socketSend = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);//创建Socket通讯连接
IPAddress ip = IPAddress.Parse(txtServer.Text); //将文本框中的数据转成IP地址类型数据
IPEndPoint point = new IPEndPoint(ip, Convert.ToInt32(txtPort.Text)); //新建网络终结点,包括了IP地址和端口号
socketSend.Connect(point); //连接服务器
showMsg("服务器连接成功"); //显示服务器连接成功的信息
Thread th = new Thread(Recive); //开启一个线程,不停的接收服务器发来的消息
th.IsBackground = true; //将线程设置为后台线程
th.Start(); //线程启动
}
catch { }
}
//数据接收方法
/// <summary>
/// 不停的接收服务器发来的消息
/// </summary>
void Recive() //接收服务器发来的信息
{
try
{
while (true)
{
byte[] buffer = new byte[1024 * 1024 * 3]; //新建一个用于接收的缓存字节数组
int r = socketSend.Receive(buffer); //将接收到的数据存入缓存,并返回一个值,代表接收数据的大小
if (r == 0) //如果接收到的数据长度为0,代表连接断开,则跳出循环,不接收数据
{ break; }
int n = buffer[0];//判断接受到的数据的第一位,去判断收到的是什么类型的数据,0:文字,1:文件,2:震动
if (n == 0) //如果第一个字符是0,代表接受到的数据类型是文字
{
string s = Encoding.UTF8.GetString(buffer, 1, r - 1); //将接收的数据解码成一个字符串,格式是UTF-8,从接收的0位开始到整个数据长度位置结束
showMsg(socketSend.RemoteEndPoint + ":" + s); //显示数据信息,包括服务器的IP地址和端口,+ 接收到的内容
}
else if (n == 1) //如果第一个字符是1,代表发送过来的数据时文件数据
{
SaveFileDialog sfd = new SaveFileDialog(); //新建一个文件保存的实例化对象
sfd.InitialDirectory = @"C:\Users\admin\Desktop"; //要保存文件的路径
sfd.Title = "请选择要保存的文件"; //弹出对话框的标题
sfd.Filter = "所有文件|*.*"; //弹出对话框的文件类型
sfd.ShowDialog(this); //开始弹出对话框
string path = sfd.FileName; //保存文件的路径,用于下面的方法中
using (FileStream fsWrite = new FileStream(path, FileMode.OpenOrCreate, FileAccess.Write))//using便于释放内存,新建一个文件对象,参数(路径,文件模式.打开或新建,文件方式.写文件
{
fsWrite.Write(buffer, 1, r - 1); //写文件的内容是接收到的字符数组,从第1位开始写文件(因为第0个代表的是文件类型),写的长度是接收长度-1(因为第一个文件类型不用写入)
}
MessageBox.Show("保存成功"); //显示写完成的信息
}
else if (n == 2) //如果第一个字符是2,代表接受到的数据类型是震动
{
ZD(); //调用震动方法
}
}
}
catch { }
}
//窗体震动方法
/// <summary>
/// 震动方法
/// </summary>
void ZD()
{
for (int i = 0; i < 500; i++) //循环震动,就是不停的变换文件框的位置
{
this.Location = new Point(200, 200);
this.Location = new Point(220, 220);
}
}
//信息显示方法
void showMsg(string str)
{ txtLog.AppendText(str + "\r\n"); } //信息显示
//发送信息给服务器事件
/// <summary>
/// 客户端给服务器发送消息
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void btnSend_Click(object sender, EventArgs e) //客户端给服务器发送数据
{
string str = txtMsg.Text.Trim(); //新建一个字符串,从文本框获得数据,并将字符串的开头和结尾删除所有空白字符后剩余的字符串
byte[] buffer = System.Text.Encoding.UTF8.GetBytes(str); //将字符串按UTF-8的格式转换成字节数组
socketSend.Send(buffer); //数据发送,
}
//窗体加载时,取消多线程调用的检查,防止保存
private void Form1_Load(object sender, EventArgs e)
{
Control.CheckForIllegalCrossThreadCalls = false; //在窗体加载时,关闭多线程之间的调用检查,解决报错问题
}
}
}