1.一文从linux源码看socket的close基本概括
2.求一份unity中Socket-TCP从服务器接收数据方法的代码
3.2024年度Linux6.9内核最新源码解读-网络篇-server端-第一步创建--socket
4.从Linux源码看Socket(TCP)的listen及连接队列
5.从 Linux源码 看 Socket(TCP)的accept
6.死磕NIO— 探索 SocketChannel 的核心原理
一文从linux源码看socket的close基本概括
理解TCP关闭过程的关键在于四次挥手,这个过程是主动关闭、被动关闭和同时关闭的统一体现。在主动关闭close(fd)的过程中,通过C语言中的close(int fd)函数调用系统调用sys_close,进而执行filp_close方法。麻将源码node随后,fput函数处理多进程中的socket引用问题,确保父进程也正确关闭socket。在f_op->release的实现中,我们关注socket与file的关系以及close(fd)调用链。随着状态机的变迁,TCP从FIN_WAIT1变迁至FIN_WAIT2,设置一个TCP_FIN_WAIT2定时器,防止由于对端未回应导致的长时间等待。FIN_WAIT2状态等待对端的FIN,完成最后两次挥手。接收对端FIN后,状态变化至time_wait,原socket资源被回收,并在时间等待超时后从系统中清除。在被动关闭中,接收FIN进入close_wait状态,应用关闭连接时改变状态为last_ack,并发送本端的FIN。被动关闭的后两次挥手后,连接关闭。出现大量close_wait通常与应用检测到对端FIN时未及时关闭有关,解决方法包括调整连接池的微帐源码参数或加入心跳检测。操作系统通过包活定时器在超时后强制关闭连接。进程退出时会关闭所有文件描述符,再次触发filp_close函数。在Java中,通过重写finalize方法,GC会在释放内存时关闭未被引用的socket,但不可完全依赖GC来管理socket资源,以避免潜在的内存泄露问题。总结,深入理解TCP关闭过程有助于优化网络应用程序的性能和稳定性,同时阅读Linux内核源代码需要耐心和系统性的方法。
求一份unity中Socket-TCP从服务器接收数据方法的代码
接收的字节使用了protubuf反序列化,处理的时候要注意和服务器发送消息类型、大小定义一致。如果不需要可以直接删除,用服务器发送字符串也是一样
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System.Net.Sockets;
using System;
using UnityEngine.Networking;
using System.Text;
using Google.Protobuf;
using Pb;
using System.Net;
using System.IO;
namespace Net
{
public class SocketClient
{
#region Public Variables
public static SocketClient Instance { get; private set; }
[Header("Network")]
public string ipAdress = ".0.0.1";
public int port = ;
public float waitingMessagesFrequency = 1;
public bool loggedIn { get; private set; }
public bool FullLog = true;
#endregion
#region Private m_Variables
private TcpClient m_Client;
private NetworkStream m_NetStream = null;
private byte[] m_Buffer = new byte[];
private NetworkStream m_OutStream;
[Tooltip("This value should be >= to Server waitingMessagesFrequency")]
[Min(0)] private float m_DelayedCloseTime = 0.5f;
#endregion
#region Delegate Variables
protected Action OnClientStarted = null; //Delegate triggered when client start
protected Action OnClientClosed = null; //Delegate triggered when client close
#endregion
private void Awake()
{
if (Instance == null)
Instance = this;
}
//Start client and stablish connection with server
public void StartClient()
{
//Early out
if (m_Client != null)
{
ClientLogError($"There is already a runing client on { ipAdress}::{ port}");
return;
}
try
{
//Create new client
m_Client = new TcpClient();
//Set and enable client
m_Client.BeginConnect(ipAdress, port, new AsyncCallback(OnConnect), null);
ClientLogInfo($"Client Started on { ipAdress}::{ port}");
OnClientStarted?.Invoke();
}
catch (SocketException)
{
ClientLogError("Socket Exception: Start Server first");
OnClose();
}
}
private void OnConnect(IAsyncResult asr)
{
ClientLogInfo("Connect Sucessful.");
m_NetStream = m_Client.GetStream();
m_Client.GetStream().BeginRead(m_Buffer, 0, m_Buffer.Length, new AsyncCallback(OnRead), m_Client);
}
#region Receive Message
private void OnRead(IAsyncResult result)
{
OnReceivedMessage(m_Buffer);
NetworkStream stream = m_Client.GetStream();
lock (stream)
{
Array.Clear(m_Buffer, 0, m_Buffer.Length);
m_Client.GetStream().BeginRead(m_Buffer, 0, m_Buffer.Length, new AsyncCallback(OnRead), m_Client);
}
}
private void OnReceivedMessage(byte[] bytes)
{
ByteBuffer buffer = new ByteBuffer(bytes);
OnRecieveMessageDeal(buffer, 0);
}
private void OnRecieveMessageDeal(ByteBuffer buffer, ushort length = 0)
{
// 判断传参length是否为0,如果不为0则代表非首次调用,不再取length值而使用传递的length
ushort nextLength;
if(length != 0)
{
nextLength = length;
}
else
{ // 判断传参length是否为0,如果为0则为首次调用,直接取出length后进行处理
nextLength = buffer.ReadUInt();
}
uint pId = buffer.ReadUInt();
ClientLogInfo("Length:" + nextLength + ".id:" + pId);
byte[] bytes = buffer.ReadBytes(nextLength);
NetLogic(pId, bytes);
// 取出下一个length,如果为0则没有数据了,不为0则递归调用,并且传递已经取出的长度值
nextLength = buffer.ReadUInt();
if (nextLength != 0)
{
OnRecieveMessageDeal(buffer, nextLength);
}
}
#endregion
#region Process
private void NetLogic(uint pid, byte[] bytes)
{
ClientLogInfo("Get Msg Id :" + pid);
if (pid == 1)
{
SyncPid syncPid = SyncPid.Parser.ParseFrom(bytes);
ClientLogInfo("sync pid:"+syncPid.Pid);
}
if (pid == )
{
BroadCast broadCast = BroadCast.Parser.ParseFrom(bytes);
ClientLogInfo("broadCast-pid:" + broadCast.Pid);
ClientLogInfo("broadCast-Tp:" + broadCast.Tp);
ClientLogInfo("broadCast-Position-x:" + broadCast.P.X);
ClientLogInfo("broadCast-Position-y:" + broadCast.P.Y);
ClientLogInfo("broadCast-Position-z:" + broadCast.P.Z);
ClientLogInfo("broadCast-Position-v:" + broadCast.P.V);
}
}
#endregion
#region Send Message
private void WriteMessage(byte[] message)
{
MemoryStream memoryStream2;
MemoryStream memoryStream = memoryStream2 = new MemoryStream();
try
{
memoryStream.Position = 0L;
BinaryWriter binaryWriter = new BinaryWriter(memoryStream);
ushort num = (ushort)message.Length;
binaryWriter.Write(message);
binaryWriter.Flush();
if (m_Client != null && this.m_Client.Connected)
{
byte[] array = memoryStream.ToArray();
m_OutStream.BeginWrite(array, 0, array.Length, new AsyncCallback(OnWrite), null);
}
else
{
ClientLogError("client.connected----->>false");
}
}
finally
{
if (memoryStream2 != null)
{
((IDisposable)memoryStream2).Dispose();
}
}
}
private void OnWrite(IAsyncResult r)
{
try
{
m_OutStream.EndWrite(r);
}
catch (Exception ex)
{
ClientLogError("OnWrite--->>>" + ex.Message);
}
}
public void SendMessage(ByteBuffer buffer)
{
this.SessionSend(buffer.ToBytes());
buffer.Close();
}
private void SessionSend(byte[] bytes)
{
this.WriteMessage(bytes);
}
#endregion
#region Close Client
//Close client connection
public void Close()
{
if (m_Client != null)
{
if (m_Client.Connected)
{
m_Client.Close();
}
m_Client = null;
}
loggedIn = false;
}
public void OnClose()
{
ClientLogError("Client Closed");
//Reset everything to defaults
if (m_Client.Connected)
m_Client.Close();
if (m_Client != null)
m_Client = null;
OnClientClosed?.Invoke();
}
#endregion
#region ClientLog
// Custom Client Log With Text Color
public void ClientLogInfo(string msg)
{
if (FullLog)
{
Debug.Log($"<color=green>Client:</color>{ msg}");
}
}
public void ClientLogWarning(string msg)
{
if (FullLog)
{
Debug.LogWarning($"<color=yellow>Client:</color>{ msg}");
}
}
public void ClientLogError(string msg)
{
if (FullLog)
{
Debug.LogError($"<color=red>Client:</color>{ msg}");
}
}
//Custom Client Log Without Text Color
public void ClientLog(string msg)
{
if (FullLog)
{
Debug.Log($"Client:{ msg}");
}
}
#endregion
}
}
年度Linux6.9内核最新源码解读-网络篇-server端-第一步创建--socket
深入解析年Linux 6.9内核的网络篇,从服务端的第一步:创建socket开始。理解用户空间与内核空间的交互至关重要。当我们在用户程序中调用socket(AF_INET, SOCK_STREAM, 0),实际上是触发了从用户空间到内核空间的系统调用sys_socket(),这是暗黑挂机源码创建网络连接的关键步骤。 首先,让我们关注sys_socket函数。这个函数在net/socket.c文件的位置,无论内核版本如何,都会调用__sys_socket_create函数来实际创建套接字,它接受地址族、类型、协议和结果指针。创建失败时,会返回错误指针。 在socket创建过程中,参数解析至关重要:网络命名空间(net):隔离网络环境,每个空间有自己的配置,如IP地址和路由。
协议族(family):如IPv4(AF_INET)或IPv6(AF_INET6)。
套接字类型(type):如流式(SOCK_STREAM)或数据报(SOCK_DGRAM)。
协议(protocol):如TCP(IPPROTO_TCP)或UDP(IPPROTO_UDP),默认值自动选择。
结果指针(res):指向新创建的socket结构体。
内核标志(kern):区分用户空间和内核空间的socket。
__sock_create函数处理创建逻辑,调用sock_map_fd映射文件描述符,支持O_CLOEXEC和O_NONBLOCK选项。每个网络协议族有其特有的create函数,如inet_create处理IPv4 TCP创建。 在内核中,安全模块如LSM会通过security_socket_create进行安全检查。sock_alloc负责内存分配和socket结构初始化,成长记录源码协议族注册和动态加载在必要时进行。RCU机制保护数据一致性,确保在多线程环境中操作的正确性。 理解socket_wq结构体对于异步IO至关重要,它协助socket管理等待队列和通知。例如,在TCP协议族的inet_create函数中,会根据用户请求找到匹配的协议,并设置相关的操作集和数据结构。 通过源码,我们可以看到socket和sock结构体的关系,前者是用户空间操作的抽象,后者是内核处理网络连接的实体。理解这些细节有助于我们更好地编写C++网络程序。 此外,原始套接字(如TCP、UDP和CMP)的应用示例,以及对不同协议的深入理解,如常用的IP协议、专用协议和实验性协议,是进一步学习和实践的重要部分。从Linux源码看Socket(TCP)的listen及连接队列
了解Linux内核中Socket (TCP)的"listen"及连接队列机制是深入理解网络编程的关键。本文将基于Linux 3.内核版本,从源码角度解析Server端Socket在进行"listen"时的具体实现。
建立Server端Socket需要经历socket、bind、listen、accept四个步骤。股价指标源码本文聚焦于"listen"步骤,深入探讨其内部机理。
通过socket系统调用,我们可以创建一个基于TCP的Socket。这里直接展示了与TCP Socket相关联的操作函数。
接着,我们深入到"listen"系统调用。注意,glibc的INLINE_SYSCALL对返回值进行了封装,仅保留0和-1两种结果,并将错误码的绝对值记录在errno中。其中,backlog参数至关重要,设置不当会引入隐蔽的陷阱。对于Java开发者而言,框架默认backlog值较小(默认),这可能导致微妙的行为差异。
进入内核源码栈,我们发现内核对backlog值进行了调整,限制其不超过内核参数设置的somaxconn值。
核心调用程序为inet_listen。其中,除了fastopen外的逻辑(fastopen将在单独章节深入讨论)最终调用inet_csk_listen_start,将sock链入全局的listen hash表,实现对SYN包的高效处理。
值得注意的是,SO_REUSEPORT特性允许不同Socket监听同一端口,实现内核级的负载均衡。Nginx 1.9.1版本启用此功能后,性能提升3倍。
半连接队列与全连接队列是连接处理中的关键组件。通常提及的sync_queue与accept_queue并非全貌,sync_queue实际上是syn_table,而全连接队列为icsk_accept_queue。在三次握手过程中,这两个队列分别承担着不同角色。
在连接处理中,除了qlen与sk_ack_backlog计数器外,qlen_young计数器用于特定场景下的统计。SYN_ACK的重传定时器在内核中以ms为间隔运行,确保连接建立过程的稳定。
半连接队列的存在是为抵御半连接攻击,避免消耗大量内存资源。通过syn_cookie机制,内核能有效防御此类攻击。
全连接队列的最大长度受到限制,超过somaxconn值的连接会被内核丢弃。若未启用tcp_abort_on_overflow特性,客户端可能在调用时才会察觉到连接被丢弃。启用此特性或增大backlog值是应对这一问题的策略。
backlog参数对半连接队列容量产生影响,导致内核发送cookie校验时出现常见的内存溢出警告。
总结而言,TCP协议在数十年的演进中变得复杂,深入阅读源码成为分析问题的重要途径。本文深入解析了Linux内核中Socket (TCP)的"listen"及连接队列机制,旨在帮助开发者更深入地理解网络编程。
从 Linux源码 看 Socket(TCP)的accept
从 Linux 源码角度探究 Server 端 Socket 的 Accept 过程(基于 Linux 3. 内核),以下是一系列关键步骤的解析。
创建 Server 端 Socket 需依次执行 socket、bind、listen 和 accept 四个步骤。其中,socket 系统调用创建了一个 SOCK_STREAM 类型的 TCP Socket,其操作函数为 TCP Socket 所对应的 ops。在进行 Accept 时,关键在于理解 Accept 的功能,即创建一个新的 Socket 与对端的 connect Socket 进行连接。
在具体实现中,核心函数 sock->ops->accept 被调用。关注 TCP 实现即 inet_stream_ops->accept,其进一步调用 inet_accept。核心逻辑在于 inet_csk_wait_for_connect,用于管理 Accept 的超时逻辑,避免在超时时惊群现象的发生。
EPOLL 的实现中,"惊群"现象是由水平触发模式下 epoll_wait 重新塞回 ready_list 并唤醒多个等待进程导致的。虽然 epoll_wait 自身在有中断事件触发时不惊群,但水平触发机制仍会造成类似惊群的效应。解决此问题,通常采用单线程专门处理 accept,如 Reactor 模式。
针对"惊群"问题,Linux 提供了 so_reuseport 参数,允许多个 fd 监听同一端口号,内核中进行负载均衡(Sharding),将 accept 任务分散到不同 Socket 上。这样,可以有效利用多核能力,提升 Socket 分发能力,且线程模型可改为多线程 accept。
在 accept 过程中,accept_queue 是关键成员,用于填充添加待处理的连接。用户线程通过 accept 系统调用从队列中获取对应的 fd。值得注意的是,当用户线程未能及时处理时,内核可能会丢弃三次握手成功的连接,导致某些意外现象。
综上所述,理解 Linux Socket 的 Accept 过程需要深入源码,关注核心函数与机制,以便优化 Server 端性能,并有效解决"惊群"等问题,提升系统处理能力。
死磕NIO— 探索 SocketChannel 的核心原理
深入探索 SocketChannel 的核心原理,首先,我们需要了解 Socket 的基本概念。Socket 是计算机网络中用于进程间通信的抽象层,它结合了 IP 地址、协议和端口信息,以实现应用程序间的通信。TCP/IP 协议族通过三元组(IP地址、协议、端口)来指明数据应发送至哪个应用程序,而 Socket API(如 UNIX BSD 的套接字(socket))允许应用程序实现网络通信。
在 TCP/IP 四层模型中,Socket 作为一种抽象接口,连接了应用层与传输层,使得应用层无需直接关注复杂的 TCP/IP 协议细节。SocketChannel 是针对 TCP 网络Socket 的一种通道改进,支持非阻塞的读写操作。它具有以下特点:创建、校验连接、读取数据、写入数据、设置 I/O 模式和关闭通道。
使用 SocketChannel 涉及创建通道、校验连接状态、读取和写入数据等操作。创建 SocketChannel 通常通过 open() 方法实现,而连接服务器则通过 connect() 方法。读取数据时,SocketChannel 会使用 read() 方法将数据读入到 ByteBuffer 中;写入数据则使用 write() 方法。此外,SocketChannel 支持阻塞和非阻塞两种 I/O 模式,可通过 configureBlocking() 方法进行切换。当完成通信后,应通过 close() 方法关闭 SocketChannel 实例。
深入 SocketChannel 的源码,可以看到其核心子类 SocketChannel 实现了大部分功能。创建 SocketChannel 实例时,通过 SelectorProvider 创建并调用 openSocketChannel() 方法。SocketChannelImpl 作为 SocketChannel 的实现类,在构造函数中实例化 SocketChannel 对象。文件描述符(fd)用于与操作系统进行文件或网络连接的交互,状态变量指示通道当前的连接状态。连接服务器、读取和写入数据等核心操作通过调用相关方法实现,这些操作在底层通常会与系统调用或 native 方法交互。
了解 SocketChannel 的工作原理和使用方法对于构建高效、可靠的网络应用程序至关重要。深入研究 SocketChannel 的实现细节,能够帮助开发者更好地利用其非阻塞特性,优化网络通信性能。在完成 SocketChannel 相关内容后,接下来的文章将开始探索第三个组件:Selector,以进一步深入了解 Java 网络编程的高级功能。