在计算机通信领域,Socket被译为“套接字”。它是计算机之间进行通信的一种约定或一种方式。通过Socket这种约定可以接收到其他计算机的数据,也可以向其他计算机发送数据。
是编程接口(API),对于TCP/IP的封装,TCP/IP也提供了可供程序员做网络开发所用的接口
Socket的英文原意是“插座”,的意思,通常在计算机编程中称作套接字,可以用来实现不同虚拟机或不同计算机之间的通信
(1)Socket的应用之一就是Web服务器和浏览器:浏览器获取用户输入的URL地址,向服务器发起请求,服务器分析接收到的URL请求,将对应的网页内容返回给浏览器,浏览器再经过解析和渲染,将文字、图片、视频等元素呈现给用户
(2)QQ或者微信或者默默等聊天社交工具等:本地的QQ或微信程序就是客户端,登录过程就是连接服务器的过程,聊天过程就是Socket的发送和接受信息过程
特点:
(1)人们可以很容易的讨论和学习协议的规范细节。
(2)层间的标准接口方便了工程模块化。
(3)创建了一个更好的互连环境。
(4)降低了复杂度,使程序更容易修改,产品开发的速度更快。
(5)每层利用紧邻的下层服务,更容易记住个层的功能。
网络编程的本质是两个设备之间的数据交换,当然,在计算机网络中,设备主要指计算机。
现在的网络编程基本上都是基于请求/响应方式的,也就是一个设备发送请求数据给另外一个,然后接收另一个设备的反馈。
在网络编程中,发起连接程序,也就是发送第一次请求的程序,被称作客户端(Client),等待其他程序连接的程序被称作服务器(Server)。客户端程序可以在需要的时候启动,而服务器为了能够时刻相应连接,则需要一直启动。例如以打电话为例,首先拨号的人类似于客户端,接听电话的人必须保持电话畅通类似于服务器。
连接一旦建立以后,客户端和服务器端就可以进行数据传递了,而且两者的身份是等价的。
在一些程序中,程序既有客户端功能也有服务器端功能,最常见的软件就是BT、emule这类软件了。
IP地址是一个规定,现在使用的是IPv4,既由4个0-255之间的数字组成,在计算机内部存储时只需要4个字节即可。在计算机中,IP地址是分配给网卡的,每个网卡有一个唯一的IP地址,如果一个计算机有多个网卡,则该台计算机则拥有多个不同的IP地址,在同一个网络内部,IP地址不能相同。IP地址的概念类似于电话号码、身份证这样的概念。
由于IP地址不方便记忆,所以有专门创造了 域名(Domain Name) 的概念,其实就是给IP取一个字符的名字,例如163.com、sina.com等。IP和域名之间存在一定的对应关系。如果把IP地址类比成身份证号的话,那么域名就是你的姓名。
其实在网络中只能使用IP地址进行数据传输,所以在传输以前,需要把域名转换为IP,这个由称作DNS的服务器专门来完成。
所以在网络编程中,可以使用IP或域名来标识网络上的一台设备。
可以认为是设备与外界通讯交流的出口。端口可分为虚拟端口和物理端口,其中虚拟端口指计算 机内部或交换机路由器内的端口,不可见。例如计算机中的80端口、21端口、23端口等。物理端口又称为接口,是可见端口,计算机背板的RJ45网口,交换机路由器集线器等RJ45端口。电话使用RJ11插口也属于物理端口的范畴。
规定一个设备有216个,也就是65536个端口,每个端口对应一个唯一的程序。每个网络程序,无论是客户端还是服务器端,都对应一个或多个特定的端口号。由于0-1024之间多被操作系统占用 ,所以实际编程时一般采用1024以后的端口号。
(1) 面向无连接,将数据及源封装在数据包中,不需要建立连接
(2)每个数据报的大小限制在64K内
(3)因无连接,是不可靠协议
(4)不需要连接连接,速度快
(1) 建立连接,形成传输数据的通道
(2)在连接中进行大数据量传输,以字节流的形式
(3)通过三次握手(四次挥手)完成连接,是可靠协议
(4)必须建立连接,效率会稍低
一般需要了解一下几个字段:
所谓三次握手(Three-Way Handshake)即建立TCP连接,就是指建立一个TCP连接时,需要客户端和服务端总共发送3个包以确认连接的建立。在socket编程中,这一过程由客户端执行connect来触发,整个流程如下图所示:
第一次握手:Client将标志位SYN置为1,随机产生一个值seq=J,并将该数据包发送给Server,Client进入SYN_SEND状态,等待Server确认。
第二次握手:Server收到数据包后由标志位SYN=1知道Client请求建立连接,Server将标志位SYN和ACK都置为1,ACK=J+1,随机产生一个seq=K,并将该数据包发送给Client以确认连接请求,Server进入SYN_RECV状态。
第三次握手:Client收到确认后,检查ACK是否为J+1,ACK是否为1,如果正确则将标志位ACK置为1,ACK=K+1,并将数据包发送给Server,Server检查ACK是否为K+1,如果正确则连接建立成功,Client和Server进入ESTABLISHED状态,完成三次握手,随后Client与Server之间就可以开始传输数据了。
SYN攻击:在三次握手过程中,Server发送SYN-ACK后,收到Client的ACK之前的TCP连接称为半连接,此时Serve处于SYN_RECV状态,当收到ACK后,Server转入ESTABLISHED状态。SYN攻击就是Client在短时间内伪造大量不存在的IP地址,并向Server不断的发送SYN包,Server回复确认包,并等待Client的确认,由于源地址不存在,因此Server需要不断重发直至超时,这些伪造的SYN包将长时间占用未连接队列,导致正常的SYN请求因为队列满而被丢弃,从而引起网络阻塞甚至系统瘫痪。SYN攻击就是一种典型的DDOS攻击,检测SYN攻击方式也很简单,即当有大量半连接状态且源地址是随机的,则可以断定遭到SYN攻击了,使用如下命令让其无处可逃:netstat -nap|grep SYN_RECV
所谓四次挥手即终止TCP连接,就是指断开一个TCP连接时,需要客户端和服务端总发送三个包以确认连接的断开。在Socket编程中,这一过程由客户端或服务端任一方执行close来触发,流程如下:
由于TCP连接是全双工的,因此每个方向都必须要单独进行关闭,这一原则是当一方完成数据发送任务后,发送一个FIN来终止这一方向的连接,收到一个FIN只是意味着这一方向上没有数据流动了,即不会再收到数据了,但是在这个TCP连接上仍然能够发送数据,直到这一方向也发送了FIN。首先进行关闭的一方将执行主动关闭,而另一方则执行被动关闭。
第一次挥手:Client发送一个FIN,用来关闭Client到Server的数据传输,Client进入FIN_WAIT_1状态。
第二次挥手:Server收到FIN后,发送一个ACK给Client,确认序号为收到序号+1(与SYN相同,一个FIN占用一个序号),Server进入CLOSE_WAIT状态
第三次挥手:Server发送一个FIN,用来关闭Server到Client的数据传输,Server进入LAST_ACK状态
第四次挥手:Client收到FIN后,Client进入TIME_WAIT状态,接着发送一个ACK给Server,确认序号为收到序号+1,Server进入CLOSED状态,完成四次挥手
Socket起源于Unix,而Unix/Linux基本哲学之一就是“一切皆文件”,都可以用“打开Open->读写write/read->关闭close”模式来操作文件。Socket就是该模式的一个实现,Socket即是一种特殊的文件,一些Socket函数就是对其进行操作(读写IO、打开、关闭)。因此Socket也提供了类似于连接Connect、关闭连接Close、发送、接收等方法的调用
常用stream和dgram
表示面向连接的数据传输方式,数据可以准确无误地到达另一台计算机,如果丢失或损坏,可以重新发送,但是相对效率低
表示无连接的数据传输方式,计算机只管数据传输,不做数据校验,DGRAM所做的校验工作少,所以效率比STREAM高
例如
:QQ视频聊天和语音聊天使用的就是DGRAM传输数据,因为首先需要保证通信的效率,尽量减少延迟,而数据的正确性是次要的,即使丢失很小的一部分数据视频和音频也可以正常解析,最多出现噪点或杂音,不会对通信质量有实质影响
服务器端
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; //IPAddress的命名空间
using System.Threading; //线程的命名空间
namespace FrmServer
{
public partial class 服务器 : Form
{
public 服务器()
{
InitializeComponent();
toolStripStatusLabel1.Text = "服务器已关闭";
}
//1.1声明套接字
Socket serverSocket = null;
//3.2 创建用来专门作为监听来电等待工作的线程
Thread listenThread = null;
private void btnStartServer_Click(object sender, EventArgs e)
{
if (btnStartServer.Tag.ToString() == "open")
{
btnStartServer.Tag = "close";
btnStartServer.Text = "关闭服务器";
//1.2调用Socket()函数 用于通信的套接字
serverSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); //第一个参数为寻找地址的方式,此时选定为IPV4的地址; 第二个参数为数据传输的方式,此时选择的是Stream传输(能够准确无误的将数据传输到);第三个参数为执行的协议,此时选择的是TCP协议;
//2.套接字绑定端口号,设置套接字的地址调用bind()因为此函数需要EndPoint 所以创建2.1和2.2
//2.1 设置地址 IPaddress 在using System.Net;下此时需引入
IPAddress address = IPAddress.Parse(txtIP.Text.Trim());
//2.2 设置地址和端口
IPEndPoint endPoint = new IPEndPoint(address, int.Parse(txtPort.Text.Trim())); //第一个参数为要设置的IP地址,第二参数为端口号
try
{
//2.套接字绑定端口号和IP
serverSocket.Bind(endPoint);
toolStripStatusLabel1.Text = "服务已开启!";
MessageBox.Show("开启服务成功", "开启服务");
}
catch (Exception ex)
{
MessageBox.Show("开启服务失败:" + ex.Message, "开启服务失败");//ex.Message为出现异常的消息
return;
}
//3.1监听套接字,等待
serverSocket.Listen(10); //参数为最大监听的用户数
listenThread = new Thread(ListenConnectSocket);
listenThread.IsBackground = true; //关闭后天线程
listenThread.Start();
}
else if (btnStartServer.Tag.ToString() == "close")
{
toolStripStatusLabel1.Text = "服务器已关闭";
btnStartServer.Tag = "open";
btnStartServer.Text = "开启服务器";
isOpen = false;
serverSocket.Close();
serverSocket.Dispose();
}
}
//3.3 用于判断用户是否链到服务器
bool isOpen = true;
//3.4 监听用户来电 等待
void ListenConnectSocket()
{
while (isOpen)
{
try
{
Socket ClientSocket = serverSocket.Accept();
byte[] buffer = Encoding.Default.GetBytes("成功连接到服务器!");
ClientSocket.Send(buffer);
string client = ClientSocket.RemoteEndPoint.ToString();
listBox1.Invoke(new Action<string>((msg) =>
{
listBox1.Items.Add(DateTime.Now + ": " + msg);
}), client);
Thread thr = new Thread(ReceiveCkientMsg);
thr.IsBackground = true;
thr.Start(ClientSocket);
}
catch (Exception ex)
{
listenThread.Abort(ex.Message);
}
}
}
///
/// 服务器解释用户消息
///
///
private void ReceiveCkientMsg(object clientSocket)
{
Socket client = clientSocket as Socket;
while (true)
{
byte[] recBuffer = new byte[1024 * 1024 * 2];
int length = -1;
try
{
length = client.Receive(recBuffer);
}
catch (Exception ex)
{
string str = client.RemoteEndPoint.ToString();
this.Invoke(new Action(() =>
{
listBox1.Items.Add($"{str}:{str}");
listBox1.Items.Remove(str);
}));
break;
}
if (length==0)
{
string str = client.RemoteEndPoint.ToString();
this.Invoke(new Action(() =>
{
listBox1.Items.Add($"{str}:下线了!");
listBox1.Items.Remove(str);
}));
break;
}
else
{
string msg = Encoding.Default.GetString(recBuffer,0, length);
string msgStr = $"{DateTime.Now}【接收{client.RemoteEndPoint.ToString()}】{msg}";
this.Invoke(new Action(() =>
{
txtReceive.AppendText(msgStr + Environment.NewLine);
}));
}
}
}
}
}
客户端
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;
using System.Threading;
namespace FrmClient
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
//1.1创建套接字
Socket clientSocket = null;
Thread clientThread = null;
//连接服务器
private void btnConnectStart_Click(object sender, EventArgs e)
{
//1.2
clientSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);//地址类型,数据传输方式,协议
//2.1 设置IP地址
IPAddress address = IPAddress.Parse(txtIP.Text.Trim());
//2.2 设置IP地址和端口号
IPEndPoint endPoint = new IPEndPoint(address, int.Parse(txtPort.Text.Trim()));
try
{
txtRecive.AppendText("与服务器连接中..." + Environment.NewLine);
//3与服务器建立连接
clientSocket.Connect(endPoint); // 与服务器连接
}
catch (Exception ex)
{
MessageBox.Show("连接失败:"+ex.Message,"友情提示");
return;
}
txtRecive.AppendText("与服务器连接成功!" + Environment.NewLine);
//4.接收或发送消息 使用线程来实现
clientThread = new Thread(ReceiveMsg);
clientThread.IsBackground = true; //开启后台线程
clientThread.Start();
}
private void ReceiveMsg()
{
while (true)
{
byte[] recBuffer = new byte[1024 * 1024 * 2];//声明最大字符内存
int length = -1; //字节长度
try
{
length = clientSocket.Receive(recBuffer);//返回接收到的实际的字节数量
}
catch (SocketException ex)
{
break;
}
catch (Exception ex)
{
this.Invoke(new Action(() =>
{
txtRecive.AppendText($"与服务器断开连接:{ex.Message}{Environment.NewLine}");
}));
break;
}
//接收到消息
if (length>0)
{
string msg = Encoding.Default.GetString(recBuffer, 0, length);//转译字符串(字符串,开始的索引,字符串长度)
string str = $"{DateTime.Now}【接收】{msg}{Environment.NewLine}";//接收的时间,内容,换行
this.Invoke(new Action(() =>
{
txtRecive.AppendText(str);//添加到文本
}));
}
}
}
//发送消息
private void btnSend_Click(object sender, EventArgs e)
{
string str = txtSendBox.Text.Trim();
byte[] buffer = Encoding.Default.GetBytes(str);
clientSocket.Send(buffer);
this.Invoke(new Action(() =>
{
txtRecive.AppendText($"{DateTime.Now}【发送】{str}{Environment.NewLine}");
}));
}
}
}
参考链接1
参考链接2