SOCKET
scoket: 程序间的通讯方式,就像人们用【电话】通讯,程序用【SCOKET】通讯。
SOCKET俗称【套接字】,是用来描述【IP地址】和【端口】,是一个通讯链的句柄。
IP地址: 用于查找计算机在网络中的位置
端口号: 用于查找程序在计算机的位置
协议: 计算机之间联系的语言,需要统一的语言方式,计算机才能有效沟通。
UDP协议: 效率高,但不稳定,容易数据丢失
TCP协议: 安全,稳定,但效率较低
协议概念
客户端与服务器之间的【传输规则】与【解析规则】,博主写的服务器和客户端也存在着协议,不然服务器和客户端就解析不了接收到的消息。
【TCP协议】和【UDP协议】是【国际通用】的传输规则和解析规则,采用这套协议我们可以在国际上与其他【使用相同协议】的服务器、客户端进行交互。
创建Scoket服务器
知识点:
- 创建一个负责监听IP地址和端口号的SOCKET:
Socket socketWatch = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
- 获取本地IP地址:
IPAddress ip = IPAddress.Any;
- 创建客户端链接IP和端口地址的对象:
IPEndPoint point = new IPEndPoint(ip,Convert.ToInt32(textBox_Port.Text));
- SOCKET监听客户端:
socketWatch.Bind(point);
- 等待客户的连接,并且创建与之通信的SOCKET(等待状态程序会假死,所以安排线程来执行):
socketSend = socketWatch.Accept();
- 接受客户端发送的消息,返回值为有效字节数(用线程循环接受消息):
int r = socketSend.Receive(buffer);
- 向客户端发送消息:
dic[ip](链接客户端的SOCKET).Send(newBuffer);
- 将多个【链接客户端的SOCKET】存放在键值对中。
- 多用
try{ }catch{ }
,可以减少程序使用时出现的异常报错,影响用户体验。(当然调试阶段少于try-catch)
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 _03_demo
{
public partial class Form1 : Form
{
Dictionary<string, Socket> dic = new Dictionary<string, Socket>();
public Form1()
{
InitializeComponent();
}
/// <summary>
/// 开启客户端连接的监听
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void Button_Monitor_Click(object sender, EventArgs e)
{
//创建一个负责监听IP地址和端口号的SOCKET
Socket socketWatch = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
IPAddress ip = IPAddress.Any;
//创建端口号对象
IPEndPoint point = new IPEndPoint(ip,Convert.ToInt32(textBox_Port.Text));
//监听
socketWatch.Bind(point);
ShowMsg("监听成功!");
//同时监听人数
socketWatch.Listen(10);
//创建新线程执行监听客户端的连接,避免socket等待客户连接是造成假死
Thread th = new Thread(Listen);
th.IsBackground = true;
th.Start(socketWatch);
}
/// <summary>
/// 等待客户的连接,并且创建与之通信的SOCKET
/// </summary>
///
Socket socketSend;
void Listen(object o)
{
Socket socketWatch = o as Socket;
//不断地等待与客户的连接
while (true)
{
//等待客户的连接,并且创建与之通信的SOCKET
socketSend = socketWatch.Accept();
//把客户端地址加入键值对
dic.Add(socketSend.RemoteEndPoint.ToString(),socketSend);
//把客户端的地址加入下拉框
comboBox_IP.Items.Add(socketSend.RemoteEndPoint.ToString());
ShowMsg(socketSend.RemoteEndPoint.ToString() + ":连接成功!");
//新建线程执行接收客户发来的消息
Thread th = new Thread(Receive);
th.IsBackground = true;
th.Start(socketSend);
}
}
/// <summary>
/// 接收客户端发来的消息,并输出到文本框
/// </summary>
/// <param name="o"></param>
void Receive(object o)
{
while (true)
{
try
{
Socket socketSend = o as Socket;
//客户端连接成功后,服务器应该接收客户端发来的消息
byte[] buffer = new byte[1024 * 1024 * 2];
//返回值为实际接收的有效字节
int r = socketSend.Receive(buffer);
//判断客户端是否停止运行,如果客户端停止运行,就退出循环
if (r == 0)
{
break;
}
//将字节数据转成字符串
string s = Encoding.UTF8.GetString(buffer, 0, r);
ShowMsg(socketSend.RemoteEndPoint + ":" + s);
}
catch
{
}
}
}
/// <summary>
/// 打印文本在日志框中
/// </summary>
/// <param name="str"></param>
void ShowMsg(string str)
{
textBox_Log.AppendText(str +"\r\n");
}
/// <summary>
/// 关闭跨线程操作的检查
/// </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 Button4_Click(object sender, EventArgs e)
{
Thread th = new Thread(Send);
th.IsBackground = true;
th.Start();
}
/// <summary>
/// 获取文本框内容,转成字符发送给客户端
/// </summary>
void Send()
{
string s = textBox_Input.Text.Trim();
byte[] buffer = Encoding.UTF8.GetBytes(s);
List<byte> list = new List<byte>();
//第一个字节为0,表示传送的数据是文本
list.Add(0);
list.AddRange(buffer);
byte[] newBuffer = list.ToArray();
string ip = comboBox_IP.SelectedItem.ToString();
dic[ip].Send(newBuffer);
}
/// <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\shen\Desktop";
ofd.Filter = "所有文件|*.*";
ofd.ShowDialog();
textBox_File.Text = ofd.FileName;
}
/// <summary>
/// 发送文件给客户端
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void Button3_Click(object sender, EventArgs e)
{
string path = textBox_File.Text;
using(FileStream fsr = new FileStream(path, FileMode.Open, FileAccess.Read))
{
byte[] buffer = new byte[1024 * 1024 * 5];
int r = fsr.Read(buffer, 0, buffer.Length);
List<byte> list = new List<byte>();
//第一个字节为0,表示传送的是文件
list.Add(1);
list.AddRange(buffer);
byte[] newBuffer = list.ToArray();
string ip = comboBox_IP.SelectedItem.ToString();
dic[ip].Send(newBuffer,0,r+1,SocketFlags.None);
}
}
/// <summary>
/// 发送震动按钮
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void Button5_Click(object sender, EventArgs e)
{
byte[] buffer = new byte[1];
//第一个字节为0,表示传送的是震动指令
buffer[0] = 2;
string ip = comboBox_IP.SelectedItem.ToString();
dic[ip].Send(buffer);
}
}
}
创建Socket客户端
知识点:
- 运行完服务器后,右键客服端项目,【调试-启动新实例】,可以打开多个客户端
- CMD控制台输入
ipconfig
,可以查询本机IP地址 - 创建负责通讯的SOCKET:
socketSend = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
- 将字符串ip地址转成ip地址实例:
IPAddress ip = IPAddress.Parse(textBox_IP.Text);
- 创建服务器IP和端口地址的对象:
IPEndPoint point = new IPEndPoint(ip, Convert.ToInt32(textBox_Port.Text));
- 连接服务器:
socketSend.Connect(point);
- 发送消息给服务器:
socketSend.Send(buffer);
- 接受服务器发送的消息,返回值为有效字节数(用线程循环接受消息):
int r = socketSend.Receive(buffer);
- 在当前窗体打开【保存文件对话框】:
sfd.ShowDialog(this);
- 获取当前窗体的坐标:
int X = this.Location.X; int Y = this.Location.Y;
- 重新赋值当前窗体的新坐标:
this.Location = new Point(this.Location.X - 10, this.Location.Y - 10);
- 多用try{ }catch{ },可以减少程序使用时出现的异常报错,影响用户体验。(当然调试阶段少于try-catch)
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 _04_demo
{
public partial class Form1 : Form
{
Socket socketSend;
public Form1()
{
InitializeComponent();
}
/// <summary>
/// 客户端给服务器发消息
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void Button2_Click(object sender, EventArgs e)
{
//获取输入框的字符串
string s = textBox_input.Text.Trim();
//把字符串转成字节数组
byte[] buffer = Encoding.UTF8.GetBytes(s);
//发送消息给服务器
socketSend.Send(buffer);
}
/// <summary>
/// 把文本输出到日志框
/// </summary>
/// <param name="s"></param>
void ShowMsg(string s)
{
textBox_Log.AppendText(s + "\n");
}
/// <summary>
/// 连接服务器
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void Button_Link_Click(object sender, EventArgs e)
{
//创建负责通讯的SOCKET
socketSend = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
//将字符串ip地址转成ip地址实例
IPAddress ip = IPAddress.Parse(textBox_IP.Text);
//获取IP地址和端口号
IPEndPoint point = new IPEndPoint(ip, Convert.ToInt32(textBox_Port.Text));
//连接服务器
socketSend.Connect(point);
ShowMsg("连接成功!");
//创建线程,执行接收服务器消息
Thread th = new Thread(Receive);
th.IsBackground = true;
th.Start();
}
/// <summary>
/// 不断接收服务器的消息
/// </summary>
void Receive()
{
while (true)
{
//创建字节数组,接收服务器的消息
byte[] buffer = new byte[1024 * 1024 * 2];
//接收服务器的消息,返回值为有效字节数
int r = socketSend.Receive(buffer);
//判断服务器是否停止,如果停止,就退出循环
if (r == 0)
{
break;
}
if (buffer[0] == 0)
{
//将字节数组转成字符串
string s = Encoding.UTF8.GetString(buffer, 1, r-1);
ShowMsg(socketSend.RemoteEndPoint + ":" + s);
}
else if (buffer[0] == 1)
{
SaveFileDialog sfd = new SaveFileDialog();
sfd.InitialDirectory = @"C:\Users\shen\Desktop";
sfd.Filter = "所有文件|*.*";
sfd.Title = "保存文件";
sfd.ShowDialog(this);
string path = sfd.FileName;
using(FileStream fsw = new FileStream(path, FileMode.OpenOrCreate, FileAccess.Write))
{
fsw.Write(buffer, 1, r - 1);
}
MessageBox.Show("保存成功!");
}
else if (buffer[0] == 2)
{
Shake();
}
}
}
/// <summary>
/// 关闭跨线程操作的检查
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void Form1_Load(object sender, EventArgs e)
{
Control.CheckForIllegalCrossThreadCalls = false;
}
/// <summary>
/// 窗体震动效果
/// </summary>
void Shake()
{
for(int i = 0; i < 500; i++)
{
this.Location = new Point(this.Location.X - 10, this.Location.Y - 10);
this.Location = new Point(this.Location.X + 20, this.Location.Y + 20);
this.Location = new Point(this.Location.X - 10, this.Location.Y - 10);
}
}
}
}