1.前言
在学习Socket之前,先来学习点网络相关的知识吧,自己学习过程中的一些总结,Socket是一门很高深的学问,本文只是Socket一些最基础的东西,大神请自觉绕路。
传输协议
第一次握手:客户端尝试连接服务器,向服务器发送syn包(同步序列编号Synchronize Sequence Numbers),syn=j,客户端进入SYN_SEND状态等待服务器确认
第二次握手:服务器接收客户端syn包并确认(ack=j+1),同时向客户端发送一个SYN包(syn=k),即SYN+ACK包,此时服务器进入SYN_RECV状态
第三次握手:第三次握手:客户端收到服务器的SYN+ACK包,向服务器发送确认包ACK(ack=k+1),此包发送完毕,客户端和服务器进入ESTABLISHED状态,完成三次握手
![](https://i-blog.csdnimg.cn/blog_migrate/86fe1750cd58c4043a4d5d6d8ec8c8a2.jpeg)
TCP协议:就好比两个电话机 通过电话线进行数据交互的格式约定
HTTP协议:就好比两个人 通过电话机 说话的语法。
(1)公认端口(WellKnownPorts):从0到1023,它们紧密绑定(binding)于一些服务。通常这些端口的通讯明确表明了某种服务的协议。例如:80端口实际上总是HTTP通讯。
(2)注册端口(RegisteredPorts):从1024到49151。它们松散地绑定于一些服务。也就是说有许多服务绑定于这些端口,这些端口同样用于许多其它目的。例如:许多系统处理动态端口从1024左右开始。
(3)动态和/或私有端口(Dynamicand/orPrivatePorts):从49152到65535。理论上,不应为服务分配这些端口。实际上,机器通常从1024起分配动态端口。
OSI网络7层模型
![](https://i-blog.csdnimg.cn/blog_migrate/cc9b6b28fb2d9fab8ce0e902c611d4e7.jpeg)
是一种面向连接的Socket,针对于面向连接的TCP服务应用,安全,但是效率低
是一种无连接的Socket,对应于无连接的UDP服务应用.不安全(丢失,顺序混乱,在接收端要分析重排及要求重发),但效率高.
![](https://i-blog.csdnimg.cn/blog_migrate/9ac943b20bb07602d63f3b428bce79b7.png)
2.聊天室原理
![](https://i-blog.csdnimg.cn/blog_migrate/b228000e866ff974872cf3c81bb4a954.jpeg)
3.聊天室代码
服务器端代码:
using System.ComponentModel; using System.Data; using System.Drawing; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Windows.Forms; namespace Server { using System.Net.Sockets; using System.Net; using System.Threading; public partial class Form1 : Form {
public Form1() { InitializeComponent(); TextBox.CheckForIllegalCrossThreadCalls = false; }
//服务端 监听套接字 Socket socketWatch = null; //服务端 监听线程 Thread threadWatch = null; //字典集合:保存 通信套接字 Dictionary<string, Socket> dictCon = new Dictionary<string, Socket>();
private void btnStartListen_Click(object sender, EventArgs e) { try { //1.创建监听套接字 使用 ip4协议,流式传输,TCP连接 socketWatch = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); //2.绑定端口 //2.1获取网络节点对象 IPAddress address = IPAddress.Parse(txtIP.Text); IPEndPoint endPoint = new IPEndPoint(address, int.Parse(txtPort.Text)); //2.2绑定端口(其实内部 就向系统的 端口表中 注册 了一个端口,并指定了当前程序句柄) socketWatch.Bind(endPoint); //2.3设置监听队列 socketWatch.Listen(10); //2.4开始监听,调用监听线程 执行 监听套接字的 监听方法 threadWatch = new Thread(WatchConnecting); threadWatch.IsBackground = true; threadWatch.Start(); ShowMsg("枫伶忆,服务器启动啦!"); } catch (Exception ex) { MessageBox.Show(ex.Message); throw; } }
void WatchConnecting() { //2.4开始监听:此方法会阻断当前线程,直到有 其它程序 连接过来,才执行完毕 Socket sokMsg = socketWatch.Accept(); //将当前连接成功的 【与客户端通信的套接字】 的 标识 保存起来,并显示到 列表中 //将 远程客户端的 ip和端口 字符串 存入 列表 this.lbOnline.Items.Add(sokMsg.RemoteEndPoint.ToString()); //将 服务端的通信套接字 存入 字典集合 dictCon.Add(sokMsg.RemoteEndPoint.ToString(), sokMsg); ShowMsg("有客户端连接了!"); //2.5创建 通信线程 Thread thrMsg = new Thread(ReceiveMsg); thrMsg.IsBackground = true; thrMsg.Start(sokMsg); }
void ReceiveMsg(object obj) { try { Socket sokMsg = obj as Socket; //3.通信套接字 监听 客户端的 消息 //3.1创建 消息缓存区 byte[] arrMsg = new byte[1024 * 1024 * 1]; while (isReceive) { //3.2接收客户端的消息 并存入 缓存区,注意:Receive方法也会阻断当前的线程 sokMsg.Receive(arrMsg); //3.3将接收到的消息 转成 字符串 string strMsg = System.Text.Encoding.UTF8.GetString(arrMsg); //3.4将消息 显示到 文本框 ShowMsg(strMsg); } } catch (Exception ex) { MessageBox.Show(ex.Message); throw; } }
void ShowMsg(string strmsg) { this.txtShow.AppendText(strmsg + "\r\n"); }
private void btnSend_Click_1(object sender, EventArgs e) { string strClient = this.lbOnline.Text; if (string.IsNullOrEmpty(strClient)) { MessageBox.Show("请选择你要发送消息的客户端"); return; } if (dictCon.ContainsKey(strClient)) { string strMsg = this.txtInput.Text.Trim(); ShowMsg("\r\n向客户端【" + strClient + "】说:" + strMsg); //使用 指定的 通信套接字 将 字符串 发送到 指定的客户端 byte[] arrMsg = System.Text.Encoding.UTF8.GetBytes(strMsg); dictCon[strClient].Send(arrMsg); } this.txtInput.Text = ""; }
}
}
客户端代码:
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; namespace Client { using System.Net.Sockets; using System.Net; using System.Threading; public partial class Form1 : Form {
public Form1() { InitializeComponent(); TextBox.CheckForIllegalCrossThreadCalls = false; }
//客户端 通信套接字 Socket socketMsg = null; //客户端 通信线程 Thread threadMsg = null; bool isRec = true;//标记任务
private void btnConnect_Click(object sender, EventArgs e) { try { //1.创建监听套接字 使用 ip4协议,流式传输,TCP连接 socketMsg = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); //2.获取要连接的服务端 节点 //2.1获取网络节点对象 IPAddress address = IPAddress.Parse(txtIP.Text); IPEndPoint endPoint = new IPEndPoint(address, int.Parse(txtPort.Text)); //3.向服务端 发送链接请求 socketMsg.Connect(endPoint); ShowMsg("连接服务器成功~~!"); //4.开启通信线程 threadMsg = new Thread(RecevieMsg); threadMsg.IsBackground = true; threadMsg.Start(); } catch (Exception ex) { MessageBox.Show(ex.Message); throw; } }
void RecevieMsg() { try { //3.1创建 消息缓存区 byte[] arrMsg = new byte[1024 * 1024 * 1]; while (isRec) { socketMsg.Receive(arrMsg); string strMsg = System.Text.Encoding.UTF8.GetString(arrMsg); ShowMsg("\r\n服务器说:" + strMsg); } } catch (Exception ex) { MessageBox.Show(ex.Message); throw; } }
private void btnSend_Click_1(object sender, EventArgs e) { string strMsg = this.txtInput.Text.Trim(); byte[] arrMsg = System.Text.Encoding.UTF8.GetBytes(strMsg); socketMsg.Send(arrMsg); this.txtInput.Text = ""; }
void ShowMsg(string strmsg) { this.txtShow.AppendText(strmsg + "\r\n"); }
}
}
最终的效果图如下:
4.注意
至少要定义一个要连接的远程主机的IP和端口号。
服务端先绑定:serverWelcomeSocket.Bind(endp)
客户端再连接:clientSocket.Connect(endp)
5.扩展
比如Socket的分包,黏包问题,异步编程在后续的文章继续讨论