9.1 Socket相关概念
IP地址
每台联网的电脑都有一个唯一的IP地址。
长度32位,分为四段,每段8位,用十进制数字表示,每段范围 0 ~ 255
特殊IP:127.0.0.1 用户本地网卡测试
版本:V4(32位) 和 V6(128位,分为8段,每段16位)
端口
在网络上有很多电脑,这些电脑一般运行了多个网络程序。每种网络程序都打开一个Socket,并绑定到一个端口上,不同的端口对应于不同的网络程序。
常用端口:21 FTP ,25 SMTP ,110 POP3 ,80 HTTP , 443 HTTPS
有两种常用Socket类型:
流式Socket(STREAM):
是一种面向连接的Socket,针对于面向连接的TCP服务应用,安全,但是效率低
数据报式Socket(DATAGRAM):
是一种无连接的Socket,对应于无连接的UDP服务应用.不安全(丢失,顺序混乱,在接收端要分析重排及要求重发),但效率高.
9.1.1 Socket 流式(服务器端和客户端)
服务器端的Socket(至少需要两个)
一个负责接收客户端连接请求(但不负责与客户端通信)
每成功接收到一个客户端的连接便在服务端产生一个对应的负责通信的Socket
在接收到客户端连接时创建.
为每个连接成功的客户端请求在服务端都创建一个对应的Socket(负责和客户端通信).
客户端的Socket
客户端Socket
必须指定要连接的服务端IP地址和端口。
通过创建一个Socket对象来初始化一个到服务器端的TCP连接
9.1.2 Socket的通讯过程
服务器端:
•申请一个socket
//1.创建监听套接字 使用 ip4协议,流式传输,TCP连接
socketWatch = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
•绑定到一个IP地址和一个端口上
//2.1 获取网络节点对象
IPAddress address = IPAddress.Parse(this.txt_IP.Text);
IPEndPoint endPoint = new IPEndPoint(address, int.Parse(txt_Port.Text));
//2.2 绑定端口(其实内部就像系统端口表中注册了一个端口,并指定了当前句柄)
socketWatch.Bind(endPoint);
//2.3 设置监听队列
socketWatch.Listen(10);
•开启侦听,等待接授连接
//2.4.开始监听 此方法会阻断当前线程。知道有其他程序连接,才能执行完毕
Socket socket = socketWatch.Accept(); //【通信套接字】注意这里,Accept返回类型Socket
客户端:
•申请一个socket
•连接服务器(指明IP地址和端口号)
//1.创建监听套接字 使用 ip4协议,流式传输,TCP连接
socketWatch = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
//绑定端口
//2.1 获取网络节点对象
IPAddress address = IPAddress.Parse(this.txt_IP.Text);
IPEndPoint endPoint = new IPEndPoint(address, int.Parse(txt_Port.Text));
//3.向服务端发送连接请求
socketWatch.Connect(endPoint);
服务器端接到连接请求后,产生一个新的socket(端口大于1024)与客户端建立连接并进行通讯,原监听socket继续监听。
9.2 使用线程调用监听套接字
监听demo
监听demo using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Net.Sockets;//套接字
using System.Net;
using System.Threading;//线程 namespace _02Server
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
TextBox.CheckForIllegalCrossThreadCalls = false;//检查非法跨线程调用
} //服务端 监听套接字
Socket socketWatch = null;
//服务端 监听线程
Thread thread = null; /// <summary>
/// 监听
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void btn_Listen_Click(object sender, EventArgs e)
{
//1.创建监听套接字 使用 ip4协议,流式传输,TCP连接
socketWatch = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
//绑定端口
//2.1 获取网络节点对象
IPAddress address = IPAddress.Parse(this.txt_IP.Text);
IPEndPoint endPoint = new IPEndPoint(address, int.Parse(txt_Port.Text));
//2.2 绑定端口(其实内部就像系统端口表中注册了一个端口,并指定了当前句柄)
socketWatch.Bind(endPoint);
//2.3 设置监听队列
socketWatch.Listen(10);
thread = new Thread(WathchConnection);
thread.IsBackground = true;
thread.Start();
ShowMsg("服务器启动。。");
} void WathchConnection()
{
//2.4.开始监听 此方法会阻断当前线程。知道有其他程序连接,才能执行完毕
socketWatch.Accept();
ShowMsg("有客户端连接~!");
} //显示监听消息方法
void ShowMsg(string strmsg)
{
this.txt_Show.AppendText(strmsg + "\r\n");
}
}
}
9.3 客户端连接服务端
向服务端发送连接请求demo
向服务端发送连接请求demo //客户端 监听套接字
Socket socketWatch = null; private void btn_Connction_Click(object sender, EventArgs e)
{
//1.创建监听套接字 使用 ip4协议,流式传输,TCP连接
socketWatch = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
//绑定端口
//2.1 获取网络节点对象
IPAddress address = IPAddress.Parse(this.txt_IP.Text);
IPEndPoint endPoint = new IPEndPoint(address, int.Parse(txt_Port.Text));
//3.向服务端发送连接请求
socketWatch.Connect(endPoint);
}
9.4 客户端向服务端发送消息
不足:点了一次发送后,再点就没反映了。
服务端
3.通信套接字 监听 客户端 消息
3.通信套接字 监听 客户端 消息 void WathchConnection()
{
//2.4.开始监听 此方法会阻断当前线程。知道有其他程序连接,才能执行完毕Socket socket = socketWatch.Accept();//【通信套接字】注意这里,Accept返回类型Socket
ShowMsg("有客户端连接~!");//------------------3.通信套接字 监听 客户端 消息------------------------
//3.1 创建缓存区
byte[] arrMsg = new byte[1024 * 1024 * 1];
//3.2 接收客户端的消息,并存如缓存区。注意:Receive会阻断当前的线程
socket.Receive(arrMsg);
//3.3 将接收的消息转化为字符串
string strMsg = System.Text.Encoding.UTF8.GetString(arrMsg);
//3.4 将消息 显示到 txt_show
ShowMsg(strMsg);
} //显示监听消息方法void ShowMsg(string strmsg)
{
this.txt_Show.AppendText(strmsg + "\r\n");
}
客户端
发送消息
发送消息 /// <summary>
/// 发送消息
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void btn_Send_Click(object sender, EventArgs e)
{
string strMsg = this.txt_Input.Text.Trim();
byte[] arrMsg = System.Text.Encoding.UTF8.GetBytes(strMsg);
socketWatch.Send(arrMsg);
}
9.5 服务端循环接收消息
解决:9.4的只能发送1次的问题
创建一个线程,还是在WathchConnection里修改
服务端循环接收消息demo
服务端循环接收消息demo void WathchConnection()
{
//2.4.开始监听 此方法会阻断当前线程。知道有其他程序连接,才能执行完毕Socket socket = socketWatch.Accept();//【通信套接字】注意这里,Accept返回类型Socket
ShowMsg("有客户端连接~!");Thread thread2 = new Thread(ReceiveMsg);
thread2.IsBackground = true;
//有参数的线程(参数只能是object)
thread2.Start(socket);
} bool isReceive = true;
void ReceiveMsg(object o)
{
Socket socket2 = o as Socket;
//------------------3.通信套接字 监听 客户端 消息------------------------
//3.1 创建缓存区byte[] arrMsg = new byte[1024 * 1024 * 1];
while (isReceive)
{//3.2 接收客户端的消息,并存如缓存区。注意:Receive会阻断当前的线程
socket2.Receive(arrMsg);//3.3 将接收的消息转化为字符串
string strMsg = System.Text.Encoding.UTF8.GetString(arrMsg);//3.4 将消息 显示到 txt_show
ShowMsg(strMsg);
}
9.6 服务端向客户端发送消息
服务端
服务端向客户端发送消息:Server
服务端向客户端发送消息:Server using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Net.Sockets;//套接字
using System.Net;
using System.Threading;//线程Z namespace _02Server
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
TextBox.CheckForIllegalCrossThreadCalls = false;//检查非法跨线程调用
}//服务端 监听套接字
Socket socketWatch =null;//服务端 监听线程Thread thread = null;
//字典集合(【与客户端通信的套接字】的标识保存起来)
Dictionary<string, Socket> dict = new Dictionary<string, Socket>();
/// <summary>
/// 监听
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>private void btn_Listen_Click(object sender, EventArgs e)
{//1.创建监听套接字 使用 ip4协议,流式传输,TCP连接
socketWatch = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);//绑定端口
//2.1 获取网络节点对象
IPAddress address = IPAddress.Parse(this.txt_IP.Text);
IPEndPoint endPoint = new IPEndPoint(address, int.Parse(txt_Port.Text));//2.2 绑定端口(其实内部就像系统端口表中注册了一个端口,并指定了当前句柄)
socketWatch.Bind(endPoint);//2.3 设置监听队列
socketWatch.Listen(10);
thread = new Thread(WathchConnection);
thread.IsBackground = true;
thread.Start();
ShowMsg("服务器启动。。");void WathchConnection()
}
{//2.4.开始监听 此方法会阻断当前线程。知道有其他程序连接,才能执行完毕
Socket socket = socketWatch.Accept();//【通信套接字】注意这里,Accept返回类型Socket//------------------------4.start-------------------------
//4.1将当前连接成功的【与客户端通信的套接字】的标识保存起来,并显示在列表中
//将远程客户端的 IP和端口 字符串 存入列表
lb_OnLine.Items.Add(socket.RemoteEndPoint.ToString());
//4.2 将服务器端的通信套接字 存入 字典集合
dict.Add(socket.RemoteEndPoint.ToString(), socket);
//-----------------------4.end---------------------------ShowMsg("有客户端连接~!");
Thread thread2 = new Thread(ReceiveMsg);
thread2.IsBackground = true;//有参数的线程(参数只能是object)
thread2.Start(socket);
} /// <summary>
/// 4.3服务端向客户端发送消息
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void btn_ServerSend_Click(object sender, EventArgs e)
{
string strClient = lb_OnLine.Text;
if (dict.ContainsKey(strClient))
{
string strMsg = txt_ServerInput.Text.Trim();
ShowMsg("向客户端【" + strClient + "】说:" + strMsg);
byte[] arrMsg = System.Text.Encoding.UTF8.GetBytes(strMsg);
dict[strClient].Send(arrMsg);
}
}
bool isReceive = true;
void ReceiveMsg(object o)
{
Socket socket2 = o as Socket;
//------------------3.通信套接字 监听 客户端 消息------------------------
//3.1 创建缓存区
byte[] arrMsg = new byte[1024 * 1024 * 1];
while (isReceive)
{
//3.2 接收客户端的消息,并存如缓存区。注意:Receive会阻断当前的线程
socket2.Receive(arrMsg);
//3.3 将接收的消息转化为字符串
string strMsg = System.Text.Encoding.UTF8.GetString(arrMsg).Trim();
//3.4 将消息 显示到 txt_show
ShowMsg(strMsg);
} } //显示监听消息方法void ShowMsg(string strmsg)
{
this.txt_Show.AppendText(strmsg + "\r\n");
}
}
}
客户端
服务端向客户端发送消息:Client
服务端向客户端发送消息:Client using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Net.Sockets;//套接字
using System.Net;
using System.Threading;//线程 namespace _02客户端
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
TextBox.CheckForIllegalCrossThreadCalls = false;
}
//客户端 通信套接字Socket socketWatch = null;
//客户端 通信线程
Thread threadMsg = null; /// <summary>
/// 连接服务器
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void btn_Connction_Click(object sender, EventArgs e)
{//1.创建监听套接字 使用 ip4协议,流式传输,TCP连接
socketWatch = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);//绑定端口
//2.1 获取网络节点对象
IPAddress address = IPAddress.Parse(this.txt_IP.Text);
IPEndPoint endPoint = new IPEndPoint(address, int.Parse(txt_Port.Text));//3.向服务端发送连接请求
socketWatch.Connect(endPoint);
ShowMsg("连接服务器成功!.."); //4.4.开启通信线程
threadMsg = new Thread(ReceiveMsg);
threadMsg.IsBackground = true;
threadMsg.Start();
} bool isRec = true;
void ReceiveMsg()
{
byte[] arrMsg = new byte[1024 * 1024 * 1];
while (isRec)
{
socketWatch.Receive(arrMsg);
string strMsg = System.Text.Encoding.UTF8.GetString(arrMsg);
ShowMsg("服务端说:" + strMsg);
}
} /// <summary>
/// 发送消息
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void btn_Send_Click(object sender, EventArgs e)
{
string strMsg = this.txt_Input.Text.Trim();
byte[] arrMsg = System.Text.Encoding.UTF8.GetBytes(strMsg);
socketWatch.Send(arrMsg);
} void ShowMsg(string strmsg)
{
this.txt_Show.AppendText(strmsg + "\r\n");
}
}
}
问题:
1.不换行
获取真实长度
//3.2 接收客户端的消息,并存如缓存区。注意:Receive会阻断当前的线程
int realLength = socket2.Receive(arrMsg);
//3.3 将接收的消息转化为字符串
string strMsg = System.Text.Encoding.UTF8.GetString(arrMsg,0,realLength);