效果展示
①客户端发送消息给服务器
②服务器发送消息给指定客户端
③服务器群发消息给客户端
③服务器发送文件给客户端
④服务器给客户端发送震动指令
1、什么是Socket网络编程
两台计算机相互通信靠的就是Socket,类似两个人要通信靠电话,也就是说Socket就是电脑间(程序间)的电话机
Socket英文原意是孔、插座,作为进程通信机制,取后一种意思,通常也称套接字,用于描述IP地址和端口。IP地址指向某平台服务器,端口用于连接到某一个应用程序。
Socket在通信过程中所处位置(作用)理解:
释义:男生要到女生宿舍找女朋友出去玩,不能直接进入女生宿舍去找,要经过宿管大妈,由宿管大妈打电话告知你的女朋友,电话打通后你们之间就能通话了。
这里的宿管大妈就是负责监听的Socket,如果有男生(客户端发送请求)来了就创建负责通信的Socket(电话机),从而使该男生(客户端)与对应女生(服务器
某个应用程序)可以进行通信了。
2、Socket支持的协议(TCP协议、UDP协议)
协议:类似与两个人打电话有一个默认协议就是都说普通话,如果张三是四川人李四是上海人他们都说家乡话(当地方言),可能他们都听不懂对方的话,在网络中常用的协议有:TCP协议、UDP协议
TCP/IP协议(Transmission Control Protocol/Internet Protocol):传输控制协议/网间协议,是一个工业标准的协议集,它是为广域网设计的。
TCP协议:属于TCP/IP协议中的一种,相对于UDP协议安全稳定,在TCP传输过程中要经过3次握手,传输效率相对低。
释义:客户端像服务器发送消息(你有空吗?),服务器回复(我有空),客户端再发给服务器(我晓得你有空了),经过了3次握手,客户端和服务器才开始通信。
UDP协议(User Data Protocol):用户数据协议,与TCPx相对应的协议。他也属于TCP/IP协议的一种,它只管发,不管对方有没有接收,相对于TCP协议快速、效率
高、不稳定,容易发生数据丢失。客户端直接发给服务器发送消息,不管服务器是否有空接收数据等,都发给服务器。类似于电报、电台广播。
协议的选择?
两种传输协议比较,无论好坏各有各自的优点。
当数据传输的性能必须让位于数据传输的完整性、可控性和可靠性时,TCP协议(安全稳定)是当然的选择。
当强调传输性能而不是传输完整性时,如:音频和多媒体应用,UDP协议是最好的选择。也就是说视频传输的时候用UDP协议(快速效率高),因为视频聊天时更希
望看到对方流畅不卡,但是清晰度可能低一点。
2、端口分类
公认端口:也称为常用端口,端口号为0~1023,它们紧密的绑定一些特殊服务。通常这些端口的通信明确了某种服务协议,不可再重新定义它的作用对象。如80端口
(http通信)、23端口(Telnet服务)。
注册端口:端口号为1024~49151,它们松散的绑定一些服务,也有许多服务绑定了这些端口,这些端口同样也用于其他目的,且多数没有明确定义对象,不同程序可以
根据需求自己定义,常用于大企业。这些端口对网络的安全十分重要,所以对于服务器一般来说要关闭这些端口。
动态端口/私有端口:端口号为49152~65535,理论上不应该把常用服务分配在这些端口上,但实际上有较为特殊的程序,特别是木马就非常喜欢这些端口,因为这些端口
常常不会引起人们注意,容易隐藏。
3、Socket通信的基本流程及如何创建Socke
①、Socket通信的基本流程
②、创建服务器界面
创建一个解决方案名称Socket网络编程,在其中创建Windows 窗体应用程序(.NET Framework)名称SocketChatServer,窗体改名为FrmSocketServer。
Ip:本地Ip地址
Port:端口号(动态端口/私有端口)
UserIp:客户端的Ip地址
服务器代码
public partial class FrmSocketServer : Form { public FrmSocketServer() { InitializeComponent(); // 取消捕获对错误线程的调用(解决跨域线程的问题) //Control.CheckForIllegalCrossThreadCalls = false; } //定义一个键值对集合用于存储客户端的Ip、端口及负责通信的Socket Dictionary<string, Socket> dicSocket = new Dictionary<string, Socket>(); List<Socket> clientCommunicationSocketList = new List<Socket>(); private void StartMonitor_Click(object sender, EventArgs e) { // 创建负责监听的Socket // 第一个参数AddressFamily设置网络寻址协议,InterNetwork表示IPV4 // 第二个参数SocketType设置数据传输方式(Socket类型),这个要根据第三个参数来设置,Stream此类型的 Socket 与单个对方主机进行通信,并且在通信开始之前需要远程主机连接 // 第三个参数为UDP协议时,第二个参数就要为Dgram(报文) Socket socketWatch = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); // 绑定监听的Ip和Port IPAddress ip = IPAddress.Parse(textIp.Text); int port = Convert.ToInt32(textPort.Text); IPEndPoint endPoint = new IPEndPoint(ip, port); socketWatch.Bind(endPoint); ShowMsg("监听成功"); StartMonitor.Enabled = false; // 设置监听队列数量(如果监听太多就要使用分流) socketWatch.Listen(10); // 创建一个线程去执行这个方法 Thread thread = new Thread(Listen); // 设置后台线程 thread.IsBackground = true; // 标记这个线程准备就绪了,可以随时被执行,具体什么时候执行这个线程由CPU决定 thread.Start(socketWatch); } Socket socketCommunication; /// <summary> /// 让线程去执行负责通信的Socket /// </summary> /// <param name="obj"></param> private void Listen(object obj) { Socket socketWatch = obj as Socket; // 不断地监听客户端有没有发送请求过来 while (true) { // 创建负责监听的Socket socketCommunication = socketWatch.Accept(); dicSocket.Add(socketCommunication.RemoteEndPoint.ToString(), socketCommunication); clientCommunicationSocketList.Add(socketCommunication); ShowMsg(socketCommunication.RemoteEndPoint.ToString() + "连接成功"); // 解决跨线程的访问 if (cboUser.InvokeRequired) { cboUser.Invoke(new Action(() => { cboUser.Items.Add(socketCommunication.RemoteEndPoint.ToString()); }), null); } else { cboUser.Items.Add(socketCommunication.RemoteEndPoint.ToString()); } // 创建一个线程去执行接收客户端发送过来消息这个方法 Thread thread = new Thread(Receive); // 设置后台线程 thread.IsBackground = true; // 标记这个线程准备就绪了,可以随时被执行,具体什么时候执行这个线程由CPU决定 thread.Start(socketCommunication); } } /// <summary> /// 接收客户端发送过来消息 /// </summary> /// <param name="obj"></param> private void Receive(object obj) { Socket socketWatch = obj as Socket; // 定义接收数据的大小 byte[] buffer = new byte[1024 * 1024 * 2]; while (true) { try { // r 表示实际接收数据的字节数 int r = socketWatch.Receive(buffer); // 解决(关闭窗口后 r=0) if (r == 0) { // break; socketWatch.Shutdown(SocketShutdown.Both); socketWatch.Close(); return; } // 字节数据转字符串 string str = Encoding.UTF8.GetString(buffer, 0, r); // 显示消息格式:客户端Ip端口,消息 ShowMsg(socketWatch.RemoteEndPoint.ToString() + ":" + str); } catch { } } } private void ShowMsg(string str) { // 解决跨线程的访问 if (textReceiveMsg.InvokeRequired) { textReceiveMsg.Invoke(new Action<string>(s => { textReceiveMsg.AppendText(s + "\r\n"); }), str); } else { //textReceiveMsg.Text = str + "\r\n" + textReceiveMsg.Text; textReceiveMsg.AppendText(str + "\r\n"); } } /// <summary> /// 发送消息 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void SendMsg_Click(object sender, EventArgs e) { string str = textSendMsg.Text.Trim(); // 字符串转字节数组 byte[] buffer = Encoding.UTF8.GetBytes(str); #region 第一种思路 byte[] newBuffer = new byte[buffer.Length + 1]; newBuffer[0] = 0; Buffer.BlockCopy(buffer, 0, newBuffer, 1, buffer.Length); #endregion #region 第二种思路 //List<byte> list = new List<byte>(); //list.Add(0); //list.AddRange(buffer); //byte[] newBuffer = list.ToArray(); #endregion string ipPort = cboUser.SelectedItem.ToString(); // 只能给最后一个客户端发送消息 //socketCommunication.Send(buffer); // 给指定客户端发送消息 dicSocket[ipPort].Send(newBuffer); textSendMsg.Clear(); } /// <summary> /// 群发消息 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void BulkMsg_Click(object sender, EventArgs e) { foreach (var socket in clientCommunicationSocketList) { if (socket.Connected) { string str = textSendMsg.Text.Trim(); // 字符串转字节数组 byte[] buffer = Encoding.UTF8.GetBytes(str); #region 第一种思路 //byte[] newBuffer = new byte[buffer.Length + 1]; //newBuffer[0] = 0; //Buffer.BlockCopy(buffer, 0, newBuffer, 1, buffer.Length); #endregion #region 第二种思路 List<byte> list = new List<byte>(); list.Add(0); list.AddRange(buffer); byte[] newBuffer = list.ToArray(); #endregion socket.Send(newBuffer); } } textSendMsg.Clear(); } /// <summary> /// 选择文件 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void SelectFile_Click(object sender, EventArgs e) { OpenFileDialog ofd = new OpenFileDialog(); // 初始目录 ofd.InitialDirectory = @"C:\Users\DHR\Desktop"; ofd.Title = "请选择要发送的文件"; ofd.Filter = "所有文件|*.*|txt文本文档|*.txt"; ofd.ShowDialog(); textPath.Text = ofd.FileName; } /// <summary> /// 发送文件 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void SendFile_Click(object sender, EventArgs e) { #region 第一种思路,使用文件流方式读取 string path = textPath.Text; using (FileStream fsRead = new FileStream(path, FileMode.Open, FileAccess.Read)) { // 定义发送文件的大小 byte[] buffer = new byte[1024 * 1024 * 3]; // 实际读取到的字节数(文件存储到buffer数组中) int r = fsRead.Read(buffer, 0, buffer.Length); List<byte> list = new List<byte>(); list.Add(1); list.AddRange(buffer); byte[] newBuffer = list.ToArray(); string ipPort = cboUser.SelectedItem.ToString(); // 为什么要加后面3个参数(newBuffer这里是3M+1byte 实际文件可能不会超过3M) dicSocket[ipPort].Send(newBuffer, 0, r + 1, SocketFlags.None); } #endregion #region 第二种思路,使用File.ReadAllBytes读取 //using (OpenFileDialog ofd = new OpenFileDialog()) //{ // if (ofd.ShowDialog() != DialogResult.OK) // { // return; // } // // 把文件写入到字节数组 // byte[] buffer = File.ReadAllBytes(ofd.FileName); // byte[] newBuffer = new byte[buffer.Length + 1]; // newBuffer[0] = 1; // Buffer.BlockCopy(buffer, 0, newBuffer, 1, buffer.Length); // string ipPort = cboUser.SelectedItem.ToString(); // dicSocket[ipPort].Send(newBuffer, SocketFlags.None); //} #endregion } private void Shock_Click(object sender, EventArgs e) { byte[] buffer = new byte[1]; buffer[0] = 2; string ipPort = cboUser.SelectedItem.ToString(); dicSocket[ipPort].Send(buffer); } }
③、创建客户端界面
客户端代码
public partial class FrmSocketClinet : Form { public FrmSocketClinet() { InitializeComponent(); // 取消捕获对错误线程的调用(解决跨域线程的问题) //Control.CheckForIllegalCrossThreadCalls = false; } Socket socketCommunication; private void StartConnect_Click(object sender, EventArgs e) { // 创建负责通信的Socket // 第一个参数AddressFamily设置网络寻址协议,InterNetwork表示IPV4 // 第二个参数SocketType设置数据传输方式(Socket类型),这个要根据第三个参数来设置,Stream此类型的 Socket 与单个对方主机进行通信,并且在通信开始之前需要远程主机连接 // 第三个参数为UDP协议时,第二个参数就要为Dgram(报文) socketCommunication = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); // 连接到服务器 IPAddress ip = IPAddress.Parse(textIp.Text); int port = Convert.ToInt32(textPort.Text); IPEndPoint endPoint = new IPEndPoint(ip, port); socketCommunication.Connect(endPoint); ShowMsg("连接成功"); StartConnect.Enabled = false; // 创建一个线程去执行接收服务器发送过来消息这个方法 Thread thread = new Thread(Receive); // 设置后台线程 thread.IsBackground = true; // 标记这个线程准备就绪了,可以随时被执行,具体什么时候执行这个线程由CPU决定 thread.Start(); } /// <summary> /// 接收服务器发送过来消息 /// </summary> /// <param name="obj"></param> private void Receive() { // 定义接收数据的大小 byte[] buffer = new byte[1024 * 1024 * 2]; while (true) { try { // r 表示实际接收数据的字节数 int r = socketCommunication.Receive(buffer); // 解决(服务器关闭 r=0) if (r == 0) { // break; socketCommunication.Shutdown(SocketShutdown.Both); socketCommunication.Close(); return; } int n = buffer[0]; // 文本d if (n == 0) { // 字节数据转字符串 //string str = Encoding.UTF8.GetString(buffer, 0, r); string str = Encoding.UTF8.GetString(buffer, 1, r - 1); // 显示消息格式:客户端Ip端口,消息 ShowMsg(socketCommunication.RemoteEndPoint.ToString() + ":" + str); } // 文件 else if (n == 1) { #region 第一种思路,使用文件流写 SaveFileDialog sfd = new SaveFileDialog(); sfd.InitialDirectory = @"C:\Users\DHR\Desktop"; sfd.Title = "请选择要保存文件的位置"; sfd.Filter = "所有文件|*.*|txt文本文档|*.txt"; // 解决跨线程的访问 if (this.InvokeRequired) { this.Invoke(new Action(() => { sfd.ShowDialog(this); }), null); } else { sfd.ShowDialog(this); } string path = sfd.FileName; using (FileStream fsWrite = new FileStream(path, FileMode.OpenOrCreate, FileAccess.Write)) { fsWrite.Write(buffer, 1, r - 1); } MessageBox.Show("保存成功"); #endregion #region 第二种思路 使用File.ReadAllBytes写 //using (SaveFileDialog sfd = new SaveFileDialog()) //{ // sfd.InitialDirectory = @"C:\Users\DHR\Desktop"; // sfd.Title = "请选择要保存文件的位置"; // sfd.Filter = "所有文件|*.*|txt文本文档|*.txt"; // sfd.DefaultExt = "txt"; // // 解决跨线程的访问 // if (this.InvokeRequired) // { // this.Invoke(new Action(() => // { // if (sfd.ShowDialog(this) != DialogResult.OK) // { // return; // } // }), null); // } // else // { // if (sfd.ShowDialog(this) != DialogResult.OK) // { // return; // } // } // byte[] newBuffer = new byte[r - 1]; // Buffer.BlockCopy(buffer, 1, newBuffer, 0, r - 1); // File.WriteAllBytes(sfd.FileName, newBuffer); // MessageBox.Show("保存成功"); //} #endregion } // 震动 else if (n == 2) { Shock(); } } catch (Exception e) { } } } /// <summary> ///震动方法 /// </summary> private void Shock() { // 记录窗体的初始位置 Point p = new Point(this.Location.X, this.Location.Y); // 初始化Random对象 Random r = new Random(); // 解决跨线程的访问 if (this.InvokeRequired) { this.Invoke(new Action(() => { for (int i = 0; i < 3000; i++) { this.Location = new Point(p.X + r.Next(-2, 2), p.Y + r.Next(-2, 2)); } }), null); } else { for (int i = 0; i < 3000; i++) { this.Location = new Point(p.X + r.Next(-10, 10), p.Y + r.Next(-10, 10)); } } } private void ShowMsg(string str) { // 解决跨线程的访问 if (textReceiveMsg.InvokeRequired) { textReceiveMsg.Invoke(new Action<string>(s => { textReceiveMsg.AppendText(s + "\r\n"); }), str); } else { //textReceiveMsg.Text = str + "\r\n" + textReceiveMsg.Text; textReceiveMsg.AppendText(str + "\r\n"); } } /// <summary> /// 发送消息 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void SendMsg_Click(object sender, EventArgs e) { string str = textSendMsg.Text.Trim(); // 字符串转字节数组 byte[] buffer = Encoding.UTF8.GetBytes(str); socketCommunication.Send(buffer); textSendMsg.Clear(); } }
需要代码的关注私聊哦!
后续会陆续更新其他资料,喜欢请关注哦!