概述
C#学习TCP/IP之SOCKET通信应用
基本知识
OSI七层网络模型与TCP/IP四层网络模型
由上图可以看出OSI参考模型网络由下往上分为:
物理层、数据链路层、网络层、传输层、会话层、表示层和应用层。
图1-1 网络模型
而TCP/IP通讯协议采用了4层的层级结构,IP协议对应于网络层,TCP协议对应于传输层,而HTTP协议对应于应用层。socket则是对TCP/IP协议的封装和应用。
1.链路层(数据链路层/网络接口层):包括操作系统中的设备驱动程序、计算机中对应的网络接口卡
2.网络层(互联网层):处理分组在网络中的活动,比如分组的选路。
3.运输层:主要为两台主机上的应用提供端到端的通信。
4.应用层:负责处理特定的应用程序细节。
网络层与运输层的区别:
在TCP/TP协议族中,
网络层IP提供的是一种不可靠的服务。它只是尽可能快地把分组从源节点送到目的节点,但不提供任何可靠性的保证。
Tcp在不可靠的ip层上,提供了一个可靠的运输层,为了提供这种可靠的服务,TCP采用了超时重传、发送和接受端到端的确认分组等机制。
TCP/IP网络通信
以下简单介绍TCP/IP中的协议都具备什么样的功能,都是如何工作的:
1. IP 网际协议IP是TCP/IP的心脏,也是网络层中最重要的协议。
IP层接收由更低层(网络接口层例如以太网设备驱动程序)发来的数据包,并把该数据包发送到更高层—TCP或UDP层;相反,IP层也把从TCP或 UDP层接收来的数据包传送到更低层。IP数据包是不可靠的,因为IP并没有做任何事情来确认数据包是按顺序发送的或者没有被破坏。IP数据包中含有发送它的主机的地址(源地址)和接收它的主机的地址(目的地址)。
高层的TCP和UDP服务在接收数据包时,通常假设包中的源地址是有效的。也可以这样说,IP地址形成了许多服务的认证基础,这些服务相信数据包是从一个有效的主机发送来的。IP确认包含一个选项,叫作IP source routing,可以用来指定一条源地址和目的地址之间的直接路径。对于一些TCP和UDP的服务来说,使用了该选项的IP包好象是从路径上的最后一个系统传递过来的,而不是来自于它的真实地点。这个选项是为了测试而存在的,说明了它可以被用来欺骗系统来进行平常是被禁止的连接。那么,许多依靠IP源地址做确认的服务将产生问题并且会被非法入侵。
2. TCP
如果IP数据包中有已经封好的TCP数据包,那么IP将把它们向‘上’传送到TCP层。TCP将包排序并进行错误检查,同时实现虚电路间的连接。TCP数据包中包括序号和确认,所以未按照顺序收到的包可以被排序,而损坏的包可以被重传。
TCP将它的信息送到更高层的应用程序,例如Telnet的服务程序和客户程序。应用程序轮流将信息送回TCP层,TCP层便将它们向下传送到IP层,设备驱动程序和物理介质,最后到接收方。
面向连接的服务(例如Telnet、FTP、rlogin、X Windows和SMTP)需要高度的可靠性,所以它们使用了TCP。DNS在某些情况下使用TCP(发送和接收域名数据库),但使用UDP传送有关单个主机的信息。
服务器端的建立和客户端的建立
一、TCP连接的三次握手
第一次握手:客户端发送syn包(syn=j)到服务器,并进入SYN_SEND状态,等待服务器确认;
第二次握手:服务器收到syn包,必须确认客户的SYN(ack=j+1),同时自己也发送一个SYN包(syn=k),即SYN+ACK包,此时服务器进入SYN_RECV状态;
第三次握手:客户端收到服务器的SYN+ACK包,向服务器发送确认包ACK(ack=k+1),此包发送完毕,客户端和服务器进入ESTABLISHED状态,完成三次握手。
握手过程中传送的包里不包含数据,三次握手完毕后,客户端与服务器才正式开始传送数据。
理想状态下,TCP连接一旦建立,在通信双方中的任何一方主动关闭连接之前,TCP 连接都将被一直保持下去。断开连接时服务器和客户端均可以主动发起断开TCP连接的请求,断开过程需要经过“四次握手”(过程就不细写了,就是服务器和客户端交互,最终确定断开)。
图1-2 TCP连接的三次握手
二、利用Socket建立网络连接的步骤
建立Socket连接至少需要一对套接字,其中一个运行于客户端,称为ClientSocket ,另一个运行于服务器端,称为ServerSocket 。套接字之间的连接过程分为三个步骤:服务器监听,客户端请求,连接确认。
1、服务器监听:服务器端套接字并不定位具体的客户端套接字,而是处于等待连接的状态,实时监控网络状态,等待客户端的连接请求。
2、客户端请求:指客户端的套接字提出连接请求,要连接的目标是服务器端的套接字。为此,客户端的套接字必须首先描述它要连接的服务器的套接字,指出服务器端套接字的地址和端口号,然后就向服务器端套接字提出连接请求。
3、连接确认:当服务器端套接字监听到或者说接收到客户端套接字的连接请求时,就响应客户端套接字的请求,建立一个新的线程,把服务器端套接字的描述发给 客户端,一旦客户端确认了此描述,双方就正式建立连接。而服务器端套接字继续处于监听状态,继续接收其他客户端套接字的连接请求。
因此,TCP/IP下建立连接首先要有一个服务器,它是被动的,它只能等待别人跟它建立连接,自己不会去主动连接,而客户端则需要根据IP地址和Port端口去连接,一个服务器可以接受多个客户端的连接,但是一个客户端只能连接一台服务器,在连接后,服务器自动划分内存区域以分配各个客户端的通讯。
服务器端的建立和客户端的建立,如图所示:
图1-3 服务端的建立
图1-4 客户端的建立
C#中SOCKET通信应用
套接字(socket)概念
套接字(socket)是通信的基石,是支持TCP/IP协议的网络通信的基本操作单元。它是网络通信过程中端点的抽象表示,包含进行网络通信必须的五种信息:连接使用的协议,本地主机的IP地址,本地进程的协议端口,远地主机的IP地址,远地进程的协议端口。
服务器端
- 服务端创建一个socket对象
参数 :寻址方案,ip版本4 ;套接字类型,字节流 ;协议,TCP
Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp)
; - 在外边创建new一个空的socket对象比如serverSocket.来保存上面创建的对象。一个服务器端可以接受N个客户端的连接,因此,在服务器端,有必要对每个连接上来的客户端进行登记,创建一个
List<Socket>list
来保存连客户端socket对象。 - socket绑定:socket.Bind(new IPEndPoint(IPAddress.Parse(txtIP.Text.ToString()), int.Parse(txtPort.Text)));
- 设置连接等待队列的数量 socket.Listen(10);
- 开始服务,等待客户端连接,
- 登记客户端
ClientList.Add(proxSocket)
;
开始接收新的消息ThreadPool.QueueUserWorkItem(new WaitCallback(this.ReceiveData), proxSocket)
; - 读取接收数据,判断连通性
readLen = proxSocket.Receive(data, 0, data.Length, SocketFlags.None)
; - 关闭socket proxSocket.Close(100);
private static byte[] result = new byte[1024];
private static int myProt = 8888 ; //端口
static Socket serverSocket;
List<Socket> ClientList = new List<Socket>(); //客户端列表
private bool state = false;
private void button1_Click(object sender, EventArgs e)
{
Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
if (!state)
{
serverSocket = socket;
//ip port
socket.Bind(new IPEndPoint(
IPAddress.Parse(txt_IPAddress.Text.ToString()),int.Parse(txt_Port.Text)));
//listen
socket.Listen(10);//连接等待队列
ThreadPool.QueueUserWorkItem(new WaitCallback(this.AcceptClientConnect), socket);
state = true;
button1.Text = "关闭服务";
}
else
{
try
{
serverSocket.Close();
serverSocket.Shutdown(SocketShutdown.Both);
}
catch (Exception )
{
}
state =false ;
serverSocket.Close();
button1.Text = "开启服务";
}
}
//日志文本框追加数据
public void AppendTextToTxtLog(string txt)
{
if (lb_Log.InvokeRequired)
{
lb_Log.BeginInvoke(new Action<string>(s =>
{
this.lb_Log.Text = string.Format("{0}rn{1}", s, lb_Log.Text);
}), txt);
}
else
{
this.lb_Log.Text = string.Format("{0}rn{1}", txt, lb_Log.Text);
}
}
private void AcceptClientConnect(object socket)
{
var serverSocket = socket as Socket;
this.AppendTextToTxtLog("服务器端开始接受客户端的链接");
while (true)
{
try
{
var proxSocket = serverSocket.Accept();
this.AppendTextToTxtLog(string.Format(
"客户端{0}连接上了",proxSocket.RemoteEndPoint.ToString()));
ClientList.Add(proxSocket);
//接受消息
ThreadPool.QueueUserWorkItem(new WaitCallback(this.ReceiveData), proxSocket);
}
catch (Exception)
{
}
}
}
public void ReceiveData(object obj)
{
Socket proxSocket = obj as Socket;
byte[] data = new byte[1024 * 1024];
while (true)
{
int readLen = 0;
try
{
readLen = proxSocket.Receive(data, 0, data.Length, SocketFlags.None);
}
catch (Exception ex)
{
//异常退出时
AppendTextToTxtLog(string.Format(
"客户端{0}非正常退出", proxSocket.RemoteEndPoint.ToString()));
ClientList.Remove(proxSocket);
StopConnetct(proxSocket);
return;
}
if (readLen <= 0)
{
//客户端正常退出
AppendTextToTxtLog(string.Format(
"客户端{0}正常退出", proxSocket.RemoteEndPoint.ToString()));
ClientList.Remove(proxSocket);
StopConnetct(proxSocket);
return;//方法结束->终结当前接受客户端数据的异步线程
}
string txt = Encoding.Default.GetString(data, 0, readLen);
AppendTextToTxtLog(string.Format(
"接收到客户端{0}的消息{1}", proxSocket.RemoteEndPoint.ToString(), txt));
}
}
private void StopConnetct(Socket proxSocket)
{
try
{
if (proxSocket.Connected)
{
proxSocket.Shutdown(SocketShutdown.Both);
proxSocket.Close(100);
}
}
catch (Exception )
{
}
}
private void btn_Send_Click(object sender, EventArgs e)
{
foreach (Socket s in this.ClientList)
{ //服务端广播式发送给客户端
(s as Socket).Send(Encoding.UTF8.GetBytes(txtLog.Text));
}
}
客户端
- 要创建一个客户端socket对象名称为ClientSocket.(命名空间在System.Net.Sockets),接着确定连接类型等,调用对象下的方法 socket.Connect(txt_IP.Text.ToString(), int.Parse(txt_Port.Text.ToString()));调用完毕这个函数,系统将进行尝试连接服务器。
- 连接成功后,启动数据侦听, Thread thread = new Thread(new ParameterizedThreadStart(ReceiveData));
- 根据收到的数据判断连接正常还是连接异常 readlen = proxSocket.Receive(data, 0, data.Length, SocketFlags.None);如果获取的readlen为0表示客户端已经断开连接。
private bool state = false;
public Socket ClientSocket;
private void btn_OpenClose_Click(object sender, EventArgs e)
{
if (!state)
{
Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
ClientSocket = socket;
try
{
socket.Connect(txt_IP.Text.ToString(), int.Parse(txt_Port.Text.ToString()));
}
catch (Exception)
{
MessageBox.Show("连接失败");
return;
}
state = true;
btn_OpenClose.Text = "客户端已连接";
Thread thread = new Thread(new ParameterizedThreadStart(ReceiveData));
thread.IsBackground = true;
thread.Start(ClientSocket);
}
else
{
try
{
state = false ;
btn_OpenClose.Text = "客户端已断开";
StopConnetct();
}
catch (Exception)
{
}
}
}
private void ReceiveData(object obj)
{
Socket proxSocket = obj as Socket;
byte[] data = new byte[1024 * 1024];
while (true)
{
int readlen = 0;
try
{
readlen = proxSocket.Receive(data, 0, data.Length, SocketFlags.None);
}
catch (Exception)
{
//异常退出时
StopConnetct();
return;
}
if (readlen <= 0)
{
//客户端正常退出
StopConnetct();
return;//方法结束->终结当前接受客户端数据的异步线程
}
string strMsg = ProcessRecieveString(data, readlen);
AppendTextToTxtLog(string.Format(
"接收到服务端{0}的消息{1}",proxSocket.RemoteEndPoint.ToString(), strMsg));
}
}
public void AppendTextToTxtLog(string txt)
{
if (this.txt_rec.InvokeRequired)
{
this.txt_rec.BeginInvoke(new Action<string>(s => { this.txt_rec.Text = String.Format("{0}rn{1}", s, txt_rec.Text); }), txt);
}
else
{
this.txt_rec.Text = string.Format("{0}rn{1}", txt, txt_rec.Text);
}
}
public string ProcessRecieveString(byte[] data, int readLen)
{
string str = Encoding.Default.GetString(data, 0, readLen);
return str;
}
private void StopConnetct()
{
try
{
if (ClientSocket.Connected)
{
ClientSocket.Shutdown(SocketShutdown.Both);
ClientSocket.Close(100);
}
}
catch (Exception ex)
{
}
}
private void btn_Send_Click(object sender, EventArgs e)
{
if (ClientSocket == null)
{
return;
}
if (ClientSocket.Connected)
{
byte[] data = Encoding.Default.GetBytes(txt_sendSMG.Text);
ClientSocket.Send(data, 0, data.Length, SocketFlags.None);
}
}
注 :
f (this.InvokeRequired)
{
this.BeginInvoke(new MethodInvoker(LoadGlobalImage));
return;
}
是什么意思
答: c#中禁止跨线程直接访问控件,InvokeRequired是为了解决这个问题而产生的
当一个控件的InvokeRequired属性值为真时,说明有一个创建它以外的线程想访问它,此时它将会在内部调用new MethodInvoker(LoadGlobalImage)来完成下面的步骤,这个做法保证了控件的安全。
测试效果
简单的测试,
服务端发送数据采用的是广播式发送,就是在开启多个客户端情况下,服务器发送的数据客户端会同时收到
图 1-5 测试效果图
参考地址:
C# Socket TCP 编程,客户端与服务端连接,发送字符串,文件
C#中Socket通信编程的同步实现
C# socket通讯
最后
以上就是神勇秋天为你收集整理的C #应用SOCKET实现TCP/IP协议的通讯C#学习TCP/IP之SOCKET通信应用的全部内容,希望文章能够帮你解决C #应用SOCKET实现TCP/IP协议的通讯C#学习TCP/IP之SOCKET通信应用所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复