学习Socket编写的聊天室小程序 1.前言
在学习Socket之前先来学习点网络相关的知识吧自己学习过程中的一些总结Socket是一门很高深的学问本文只是Socket一些最基础的东西大神请自觉绕路。
传输协议
TCPTransmission Control Protocol 传输控制协议TCP是一种面向连接连接导向的、可靠的、基于字节流的运输层Transport layer通信协议。
特点
面向连接的协议数据传输必须要建立连接所以在TCP中需要连接时间。
传输数据大小限制一旦连接建立双方可以按统一的格式传输大的数据。
一个可靠的协议确保接收方完全正确地获取发送方所发送的全部数据。
说到TCP就不得不说经典的三次握手。
在TCP/IP协议中TCP协议通过三次握手建立一个可靠的连接
第一次握手客户端尝试连接服务器向服务器发送syn包同步序列编号Synchronize Sequence Numberssynj客户端进入SYN_SEND状态等待服务器确认
第二次握手服务器接收客户端syn包并确认ackj1同时向客户端发送一个SYN包synk即SYNACK包此时服务器进入SYN_RECV状态
第三次握手第三次握手客户端收到服务器的SYNACK包向服务器发送确认包ACK(ackk1此包发送完毕客户端和服务器进入ESTABLISHED状态完成三次握手
UDP User Datagram Protocol的简称 中文名是用户数据包协议是 OSI 参考模型中一种无连接的传输层协议提供面向事务的简单不可靠信息传送服务。
特点
每个数据报中都给出了完整的地址信息因此无需要建立发送方和接收方的连接。
UDP传输数据时是有大小限制的每个被传输的数据报必须限定在64KB之内。
UDP是一个不可靠的协议发送方所发送的数据报并不一定以相同的次序到达接收方。
TCP协议就好比两个电话机 通过电话线进行数据交互的格式约定
HTTP协议就好比两个人 通过电话机 说话的语法。
1公认端口WellKnownPorts从0到1023它们紧密绑定binding于一些服务。通常这些端口的通讯明确表明了某种服务的协议。例如80端口实际上总是HTTP通讯。
2注册端口RegisteredPorts从1024到49151。它们松散地绑定于一些服务。也就是说有许多服务绑定于这些端口这些端口同样用于许多其它目的。例如许多系统处理动态端口从1024左右开始。
3动态和/或私有端口Dynamicand/orPrivatePorts从49152到65535。理论上不应为服务分配这些端口。实际上机器通常从1024起分配动态端口。
OSI网络7层模型
TCP/IPTransmission Control Protocol/Internet Protocol即传输控制协议/网间协议是一个工业标准的协议集它是为广域网WANs设计的。
UDPUser Data Protocol用户数据报协议是与TCP相对应的协议。它是属于TCP/IP协议族中的一种。
应用层 (Application)应用层是个很广泛的概念有一些基本相同的系统级 TCP/IP 应用以及应用协议也有许多的企业商业应用和互联网应用。
传输层 (Transport)传输层包括 UDP 和 TCPUDP 几乎不对报文进行检查而 TCP 提供传输保证。
网络层 (Network)网络层协议由一系列协议组成包括 ICMP、IGMP、RIP、OSPF、IP(v4,v6) 等。
链路层 (Link)又称为物理数据网络接口层负责报文传输。
IP地址
每台联网的电脑都有一个唯一的IP地址。
长度32位分为四段每段8位用十进制数字表示每段范围 0 ~ 255
特殊IP127.0.0.1 用户本地网卡测试
版本V4(32位) 和 V6(128位分为8段每段16位)
端口
在网络上有很多电脑这些电脑一般运行了多个网络程序。每种网络程序都打开一个Socket并绑定到一个端口上不同的端口对应于不同的网络程序。
常用端口21 FTP ,25 SMTP ,110 POP3 ,80 HTTP , 443 HTTPS
有两种常用Socket类型
流式SocketSTREAM
是一种面向连接的Socket针对于面向连接的TCP服务应用安全但是效率低
数据报式SocketDATAGRAM
是一种无连接的Socket,对应于无连接的UDP服务应用.不安全(丢失,顺序混乱,在接收端要分析重排及要求重发),但效率高.
说了那么多让我们来看看socket在网络7层协议中的位置。如下图所示
2.聊天室原理
Socket 流式(服务器端和客户端
服务器端的Socket(至少需要两个)
一个负责接收客户端连接请求(但不负责与客户端通信)
每成功接收到一个客户端的连接便在服务端产生一个对应的负责通信的Socket
在接收到客户端连接时创建.
为每个连接成功的客户端请求在服务端都创建一个对应的Socket(负责和客户端通信).
客户端的Socket
客户端Socket
必须指定要连接的服务端IP地址和端口。
通过创建一个Socket对象来初始化一个到服务器端的TCP连接
Socket的通讯过程
服务器端
申请一个socket
绑定到一个IP地址和一个端口上
开启侦听等待接授连接
客户端
申请一个socket
连接服务器指明IP地址和端口号
l服务器端接到连接请求后产生一个新的socket(端口大于1024与客户端建立连接并进行通讯原监听socket继续监听。
Socket常用的一些类和方法
IPAddress类包含了一个IP地址
IPEndPoint类包含了一对IP地址和端口号
Socket : 创建一个Socket
Bind: 绑定一个本地的IP和端口号(IPEndPoint)
Listen: 让Socket侦听传入的连接尝试并指定侦听队列容量
Connect: 初始化与另一个Socket的连接
Accept: 接收连接并返回一个新的socket
Send: 输出数据到Socket
Receive: 从Socket中读取数据
Close: 关闭Socket (销毁连接)
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 dictCon new Dictionary;
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和端口号。
端口号必须在 1 和 65535之间最好在1024以后。
要连接的远程主机必须正在监听指定端口也就是说你无法随意连接远程主机。
如
IPAddress addr IPAddress.Parse("127.0.0.1");
IPEndPoint endp new IPEndPoint(addr, 8989);
服务端先绑定serverWelcomeSocket.Bind(endp)
客户端再连接clientSocket.Connect(endp)
一个Socket一次只能连接一台主机。
Socket关闭后无法再次使用。
每个Socket对象只能一台远程主机连接. 如果你想连接到多台远程主机, 你必须创建多个Socket对象
5.扩展
l实现传送文件
如果接收数据是文件还是文字
设计"协议"
把要传递的字节数组前面都加上一个字节做为标识。0表示文字 1表示文件
即文字 0文字字节数组表示
文件1文件的二进制信息
原网站http://www.toutiao.com/i6323308820541997569/