服务器端:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Net.Sockets;//套接字所在的命名空间
using System.Net;//Ip地址和网络终端节点的引用空间
using System.Threading;//多线程
namespace socket通信
{
public partial class Form1 : Form
{
//1.声明监听套接字 服务器端
private Socket socketWatch = null;
//监听线程
private Thread threadWatch = null;
//服务器端保存的通信套接字字典列表
Dictionary<string, SocketMsgManager> dictClients = new Dictionary<string,SocketMsgManager>();
public Form1()
{
InitializeComponent();
Control.CheckForIllegalCrossThreadCalls = false;
}
private void button1_Click(object sender, EventArgs e)
{
//2.创建监听套接字
//AddressFamily.InterNetwork:指明通信所使用的地址格式,InterNetwork指的是 IPv4
//SocketType.Stream :以流的形式传输数据,流式数据传输
//ProtocolType.Tcp:使用TCP协议
//127.0.1:指的是本机
//.Trim():清空前后的空格 自动对齐
socketWatch = new Socket(AddressFamily.InterNetwork,SocketType.Stream,ProtocolType.Tcp);
//3.获取Ip地址
IPAddress address = IPAddress.Parse(this.textBox1.Text.Trim());
//4.获取网络终端节点对象
IPEndPoint endPoint = new IPEndPoint(address, Convert.ToInt32(textBox2.Text));
//5.绑定IP和Port到套接字 ,socketWatch是
socketWatch.Bind(endPoint);
//6.设置监听队列,并让套接字处于监听状态
socketWatch.Listen(10);
threadWatch = new Thread(WatchConnection);
threadWatch.IsBackground = true;
threadWatch.Start();
ShowMsg("服务器已经启用监听");
this.button1.Enabled = false;//不能再点击
}
private void WatchConnection()
{
try
{
//7.开启监听
//该方法会阻断当前线程,等待客户端来联接他。直到有客户端联接他了,Accept()才能执行结束。
//等待呼叫
//多次循环,可以接受多个客户端连接
while (true)
{
//8.Accept()方法返回一个Socket对象,这个对象就是用来跟当前连接进来的用户客户端通信套接字进行互发信息的服务器端通信套接字
Socket socketMsg = socketWatch.Accept();
ShowMsg("有客户连接进来了");
//获取客户端终端节点对象(IP地址和port)
EndPoint clientEndPoint = socketMsg.RemoteEndPoint;
//创建通信管理对象
SocketMsgManager smm =new SocketMsgManager(socketMsg,this.ShowMsg,this.CloseConnection);
//把客户端IP地址和端口号,显示到服务器端的客户列表中
listBoxliebiao.Items.Add(clientEndPoint.ToString());
//把与客户端通信的 那个通信管理对象,添加到客户端字典中
dictClients.Add(clientEndPoint.ToString(), smm);
//启动一个通信套接字
Thread threadMsg = new Thread(ReceiveMsg);
threadMsg.IsBackground = true;
threadMsg.Start(socketMsg);
}
}
catch (Exception ex)
{
this.ShowMsg("监听异常" + ex.Message);
}
}
private void ReceiveMsg(object socket)
{
Socket socketMsg = socket as Socket;
//9.创建服务器端通信套接字的信息缓存区,1Byte = 8bit
byte[] buffer = new byte[1024 * 1024];//预分配1M的缓存区
//获取客户端的ip和port
string clientIpPort = socketMsg.RemoteEndPoint.ToString();
try
{
//接受客户端发来的信息,为了给通信线程调用
while (true)
{
//10.接受客户端发来的信息,然后存储到缓存区。注意:recive()方法也会阻断当前线程
//13.获取有效信息长度
int realLength = socketMsg.Receive(buffer);
//11.使用UTF-8编码格式 ,将客户发来的数据流换成字符串
string msg = Encoding.UTF8.GetString(buffer, 0, realLength);
//12.将客户发送的信息发送到文本框中
this.ShowMsg("来自客户端["+clientIpPort+"]消息:" + msg);
}
}
catch
{
this.CloseConnection(clientIpPort);
this.ShowMsg("客户端断开连接");
}
}
private void ShowMsg(string msg)
{
this.textBox3.AppendText(msg + "\r\n");
}
private void Form1_Load(object sender, EventArgs e)
{
}
private void btnMsg_Click(object sender, EventArgs e)
{
//获取客户端IP和port
string clientIp = listBoxliebiao.Text;
//判读用户列表字典中是不是 有这个ip和port
if (dictClients.ContainsKey(clientIp))
{
//读取出来对应的value,也就是通信套接字
SocketMsgManager smm = dictClients[clientIp];
//获取服务端要发送给客户端的信息
string msg = txtMsg.Text;
//把信息转换为字节数组
smm.SendText(msg);
}
}
//客户端断开连接,移除listboxliebiao的客户端项,移除字典中的通信套接字
private void CloseConnection(string ClientAddress)
{
//移除listboxliebiao的客户端项
listBoxliebiao.Items.Remove(ClientAddress);
//移除字典中的通信套接字
this.dictClients.Remove(ClientAddress);
}
private void button3_Click(object sender, EventArgs e)
{
string clientIpPort = listBoxliebiao.Text;
if (dictClients.ContainsKey(clientIpPort))
{
SocketMsgManager smm = dictClients[clientIpPort];
smm.SendShake();
}
}
private void wenjian_Click(object sender, EventArgs e)
{
//打开文件对话框
OpenFileDialog ofd = new OpenFileDialog();
//如果选中了要发送的文件
if (ofd.ShowDialog() == DialogResult.OK)
{
//获取待发送文件的完整路径
string filePath = ofd.FileName;
string clientIpPort = listBoxliebiao.Text;
SocketMsgManager smm = dictClients[clientIpPort];
smm.SendFile(filePath);
}
}
}
}
下面是客户端
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Net;
using System.Net.Sockets;
using System.Threading;
namespace ClientFrm
{
public partial class Form1 : Form
{
//声明通信套接字
private Socket SocketMsg = null;
public Form1()
{
InitializeComponent();
Control.CheckForIllegalCrossThreadCalls = false;
}
private void Form1_Load(object sender, EventArgs e)
{
}
private void button1_Click(object sender, EventArgs e)
{
//创建通信套接字,客户端
SocketMsg = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
//服务器端的IP地址
IPAddress address = IPAddress.Parse(this.textBox1.Text.Trim());
//服务器所在的终端节点对象
IPEndPoint endPoint = new IPEndPoint(address, Convert.ToInt32(this.textBox2.Text));
//向服务器端发送请求联接
SocketMsg.Connect(endPoint);
this.ShowMsg("连接服务器成功!!!!! ");
this.button1.Enabled = false;
//准备接收来自服务器的信息
Thread threadMsg = new Thread(ReceiveMsg);
threadMsg.IsBackground = true;
//win7,win8如果没有这条语句,将无法打开保存文件
threadMsg.SetApartmentState(ApartmentState.STA);
threadMsg.Start();
}
private void ReceiveMsg()
{
//信息缓存区
byte[] buffer = new byte[1024 * 1024];
while (true)
{
int realLength = SocketMsg.Receive(buffer);
//读取标识位信息
switch (buffer[0])
{
case 0 :
ReceiveText(buffer,realLength);
break;
case 1:
ReceiveFile(buffer,realLength );
break;
case 2:
ReceiveShake();
break;
}
}
}
/// <summary>
/// 把接收到的字节流,处理为字符串
/// </summary>
/// <param name="buffer"></param>
/// <param name="realLength"></param>
private void ReceiveText(byte[]buffer,int realLength)
{
//一定要从下标1开始转换
string msg = Encoding.UTF8.GetString(buffer, 1, realLength);
this.ShowMsg("来自服务器信息:" + msg);
}
/// <summary>
/// 把字节流保存为文件
/// </summary>
/// <param name="buffer">字节流</param>
/// <param name="realLength">实际有效数据长度</param>
private void ReceiveFile(byte[] buffer,int realLength)
{
//创建一个保存文件对话框
SaveFileDialog sfd = new SaveFileDialog();
//判断是否选择了 保存路径(只有选择了保存路径,并且点击了保存按钮,返回值才是ok)
if (sfd.ShowDialog() == DialogResult.OK)
{
//读取用户选择的保存路径
string filePath = sfd.FileName;
//使用文件流,把字节数组写入到用户选中的完全路径(已经带有文件名)
//使用using会自动释放掉资源,在使用过后
using (System.IO.FileStream fs = new System.IO.FileStream(filePath, System.IO.FileMode.OpenOrCreate))
{
fs.Write(buffer, 1, realLength);
this.ShowMsg("保存文件到:" + filePath);
}
}
}
private void ReceiveShake()
{
//初始位置
Point odlPoint = this.Location;
Point newPoint = new Point(odlPoint.X + 10, odlPoint.Y + 10);
for (int i = 0; i < 5; i++)
{
this.Location = newPoint;
Thread.Sleep(50);
this.Location = odlPoint;
Thread.Sleep(50);
}
}
private void ShowMsg(string msg)
{
this.textBox3.AppendText(msg + "\r\n");
}
private void textBox2_TextChanged(object sender, EventArgs e)
{
}
private void button2_Click(object sender, EventArgs e)
{
string msg = this.textBoxMsg.Text;
//把字符串转换成字节数
byte[] buffer = Encoding.UTF8.GetBytes(msg);
//使用客户端通信套接字,把信息发送出去
SocketMsg.Send(buffer);
this.ShowMsg("发送信息:" + msg);
this.textBoxMsg.Clear();
}
}
}
封装函数:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Threading;
using System.Net;
using System.Net.Sockets;
namespace socket通信
{
public class SocketMsgManager
{
#region 封装的属性
//服务器端 通信套接字
public Socket SocketMsg { get; private set; }
//通信子线程
public Thread ThreadMsg { get; private set; }
//显示消息的委托
public Action<string> DelShowMsg { get; private set; }
//连接关闭后要做操作的委托
public Action<string> DelCloseConnection { get; private set; }
#endregion
#region 构造函数
/// <summary>
/// 构造函数
/// </summary>
/// <param name="sokMsg">通信套接字</param>
public SocketMsgManager(Socket sokMsg, Action<string> delShowMsg, Action<string> delCloseConn)
{
this.SocketMsg = sokMsg;
this.DelShowMsg = delShowMsg;
this.DelCloseConnection = delCloseConn;
this.SocketMsg = sokMsg;
ThreadMsg = new Thread(ReceiveMsg);
this.ThreadMsg.IsBackground = true;
this.ThreadMsg.Start();
}
#endregion
#region 接收信息 void ReceiveMsg()
/// <summary>
/// 接收消息
/// </summary>
private void ReceiveMsg()
{
//9.创建服务器端通信套接字的信息缓存区,1Byte = 8bit
byte[] buffer = new byte[1024 * 1024];//预分配1M的缓存区
//获取客户端的ip和port
string clientIpPort = this.SocketMsg.RemoteEndPoint.ToString();
try
{
//接受客户端发来的信息,为了给通信线程调用
while (true)
{
//10.接受客户端发来的信息,然后存储到缓存区。注意:recive()方法也会阻断当前线程
//13.获取有效信息长度
int realLength = this.SocketMsg.Receive(buffer);
//11.使用UTF-8编码格式 ,将客户发来的数据流换成字符串
string msg = Encoding.UTF8.GetString(buffer, 0, realLength);
//12.将客户发送的信息发送到文本框中
this.DelShowMsg("来自客户端[" + clientIpPort + "]消息:" + msg);
}
}
catch
{
this.DelCloseConnection.Invoke(clientIpPort);
this.DelShowMsg("客户端断开连接");
}
}
#endregion
#region 给待发消息添加协议标识位 byte[] MarkMsg(byte[] arrOriginal, string msgType)
/// <summary>
/// 给待发消息添加协议标识位
/// </summary>
/// <param name="arrOriginal">原始消息</param>
/// <param name="msgType">消息类型</param>
/// <returns>标记过的消息</returns>
private byte[] MarkMsg(byte[] arrOriginal, string msgType)
{
//创建标记过的消息的字节数组,长度为原始数组的长度+1
byte[] marked = new byte[arrOriginal.Length + 1];
//根据信息类型,给0号索引位置添加协议标识
switch (msgType.ToLower())
{
case "text":
marked[0] = 0;
break;
case "file":
marked[0] = 1;
break;
default:
marked[0] = 2;
break;
}
//把有效数据拷贝到mared里,从1号索引位置开始拷贝
arrOriginal.CopyTo(marked, 1);
return marked;
}
#endregion
#region 发送字符串消息 void SendText(string msg)
/// <summary>
/// 发送字符串消息
/// </summary>
/// <param name="msg">消息</param>
public void SendText(string msg)
{
//把消息转换为字节数组
byte[] buffer = Encoding.UTF8.GetBytes(msg);
//给头部添加协议标识位
byte[] marked = this.MarkMsg(buffer, "text");
//使用对应的服务器端通信套接字,把信心发送给对应的客户端
try
{
this.SocketMsg.Send(marked);//二进制
//把发送给客户端的信息,显示到文本框
string clientIp = this.SocketMsg.RemoteEndPoint.ToString();
this.DelShowMsg("向客户端【" + clientIp + "】发送信息:" + msg);
}
catch (Exception ex)
{
this.DelShowMsg("发送消息异常:" + ex.Message);
}
}
#endregion
#region 发送文件
public void SendFile(string filePath)
{
//把文件读入内存,读取为字节数组
byte[] buffer = System.IO.File.ReadAllBytes(filePath);
byte[] mared = this.MarkMsg(buffer, "file");
try
{
this.SocketMsg.Send(mared);//发送文件
string clientIp = this.SocketMsg.RemoteEndPoint.ToString();
this.DelShowMsg("向客户端【" + clientIp + "】发送文件完毕!");
}
catch (Exception ex)
{
this.DelShowMsg("发送文件异常:" + ex.Message);
}
}
#endregion
#region MyRegion
public void SendShake()
{
//定义一个长度为1的byte数组,并且把0号元素初始化为2(标记位,代表闪屏)
byte[] buffer = new byte[1] { 2 };
try
{
this.SocketMsg.Send(buffer);
}
catch (Exception ex)
{
this.DelShowMsg("发送闪屏异常:" + ex.Message);
}
}
#endregion
}
}