网络编程 ------ 连接关闭(close,shutdown)

close函数和shutdown函数都用于关闭一个四元组标识的连接。接下来简单介绍一下
1)close函数

#include <unistd.h>
int close(int sockfd);
                           返回:成功返回0,出错则返回-1;

close的默认行为是将套接字标记成关闭,然后立即返回,此后不能使用此套接字调用read和write函数,调用close后在内核层面会将套接字发送缓冲区里面的数据全部发送到对端,这部分数据确认后然后执行四次挥手。我们可以通过setsocket()函数设置 SO_LINGER选项,修改参数,来改变close的默认行为。
在并发编程时,由于父子进程共用一个套接字,父进程调用close套接字时只是导致套接字描述符的引用计数减一,当引用计数为0的时候才会终止连接,

struct linger{
   int  l_onoff;
   int  l_linger;
}
  1. 当l_onoff为0时,LINGER选项被关闭,此时执行tcp的默认选项。 在这里插入图片描述

  2. 当l_onoff非0且l_linger为0时,tcp将丢弃保留在套接字发送缓冲区中的任何数据,然后发送一个RST到对端,这样就避免了time_wait状态,如果此时在2MSL内使用相同的四元组建立一个新的连接,旧的数据就会被传送到这个新的连接。

  3. 当l_onoff非0且l_linger非0时,当套接字关闭的时候,如果套接字缓冲区仍然残留有数据,那么进程会被睡眠,直到所有的数据都被对端确认或者睡眠时间到。我们可以通过close的返回值来区分这两种情况,如果返回EWOULDBLOCK错误则代表迟滞时间到,并且此时套接字发送缓冲区的任何残留数据都会被丢弃。在这里插入图片描述

其实SO_LINGER参数控制的实际是close函数在四次挥手阶段不同阶段的返回,对于情况1,则是立即发出FIN后立即返回,所以不能保证数据缓冲区的数据是否正确被对端tcp接收到,情况3则是在收到发出数据和FIN的响应后返回。可以保证数据被对端tcp收到。
2)shutdown函数
关闭一个连接通常使用close,但是close有两个限制,可以使用shutdown来解决。

  1. 使用close的时候会把套接字的引用计数减一,只有计数变为0时才关闭套接字,shutdown可以不用管计数就可以直接关闭套接字
  2. close关闭的是读和写两个方向的传送,当一方完成了数据传送,但是另一方仍然有数据需要传送的时候,需要使用shutdown来达到半关闭的状态,使得对方的数据能够正确到达。
#include <sys/socket.h>
    int shutdown(int sockfd,int howto);
                    返回:若成功则为0,若出错则为-1;

函数的行为依赖于howto参数的值:

  1. SHUT_RD: 关闭连接的读这一半,将读缓冲区里面的所有数据都进行确认(向对端发送ack),然后丢弃这部分数据。
  2. SHUT_WR:关闭连接的写这一半,这成为半关闭,当前留在套接字发送缓冲区的数据将被发送掉,然后正常终止序列,此时不管引用计数是否等于0。
  3. SHUT_RDWR:这与调用两次shutdown函数相等,第一次使用SHUT_RD,第二次使用SHUT_WR。
    我们使用close关闭连接时,设置SO_LINGERCNA参数,close的成功返回只能保证传输层层面的数据确认,并不能保证应用进程也已经读取数据。要确保应用进程读取数据,有两种方法:
    1)shutdown+read,在调用shutdown函数后加一个阻塞的read函数,read函数会在第三次挥手的时候收到对端的FIN后返回,保证应用程序端可以处理完数据
    在这里插入图片描述
    2)应用ACK,在调用closed之前先使用阻塞read函数从对端读取一个字节的数据作为应用ACK,这个字节用全零作为标识。
    在这里插入图片描述
    这两个函数对tcp的影响具体入下图:
    在这里插入图片描述
在C#中进行socket网络编程可以实现一个服务器与多个客户端的通信,以下是一个简单的示例: 服务器端代码: ```csharp using System; using System.Net; using System.Net.Sockets; using System.Text; namespace Server { class Program { static void Main(string[] args) { // 创建一个TCP/IP socket对象 Socket listener = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); // 绑定IP地址和端口号 IPAddress ipAddress = IPAddress.Parse("127.0.0.1"); IPEndPoint localEndPoint = new IPEndPoint(ipAddress, 8888); listener.Bind(localEndPoint); // 开始监听连接请求 listener.Listen(10); Console.WriteLine("等待客户端连接..."); while (true) { // 接受来自客户端的连接请求 Socket handler = listener.Accept(); // 接收客户端发送的数据 byte[] buffer = new byte[1024]; int bytesRead = handler.Receive(buffer); string data = Encoding.ASCII.GetString(buffer, 0, bytesRead); Console.WriteLine($"接收到客户端数据:{data}"); // 向客户端发送响应数据 byte[] responseBytes = Encoding.ASCII.GetBytes("收到你的消息了!"); handler.Send(responseBytes); // 关闭连接 handler.Shutdown(SocketShutdown.Both); handler.Close(); } } } } ``` 客户端代码: ```csharp using System; using System.Net; using System.Net.Sockets; using System.Text; namespace Client { class Program { static void Main(string[] args) { // 创建一个TCP/IP socket对象 Socket sender = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); // 连接服务器 IPAddress ipAddress = IPAddress.Parse("127.0.0.1"); IPEndPoint remoteEP = new IPEndPoint(ipAddress, 8888); sender.Connect(remoteEP); Console.WriteLine("连接服务器成功!"); // 向服务器发送数据 string message = "Hello, Server!"; byte[] bytes = Encoding.ASCII.GetBytes(message); sender.Send(bytes); // 接收服务器的响应数据 byte[] buffer = new byte[1024]; int bytesRead = sender.Receive(buffer); string response = Encoding.ASCII.GetString(buffer, 0, bytesRead); Console.WriteLine($"接收到服务器响应:{response}"); // 关闭连接 sender.Shutdown(SocketShutdown.Both); sender.Close(); Console.ReadKey(); } } } ``` 以上代码可以实现一个简单的服务器与客户端通信,如果想实现多个客户端同时连接服务器,可以使用多线程或异步编程来处理。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值