1.【死磕NIO】— 探索 SocketChannel 的核心原理
2.求一份unity中Socket-TCP从服务器接收数据方法的代码
3.一文从linux源码看socket的close基本概括
4.从Linux源码看Socket(TCP)的listen及连接队列
5.一篇搞懂TCP、HTTP、Socket、Socket连接池
6.从 Linux源码 看 Socket(TCP)的accept
【死磕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 网络编程的高级功能。
求一份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
}
}
一文从linux源码看socket的close基本概括
理解TCP关闭过程的关键在于四次挥手,这个过程是主动关闭、被动关闭和同时关闭的统一体现。在主动关闭close(fd)的过程中,通过C语言中的close(int fd)函数调用系统调用sys_close,进而执行filp_close方法。随后,fput函数处理多进程中的socket引用问题,确保父进程也正确关闭socket。在f_op->release的实现中,我们关注socket与file的关系以及close(fd)调用链。随着状态机的变迁,TCP从FIN_WAIT1变迁至FIN_WAIT2,设置一个TCP_FIN_WAIT2定时器,防止由于对端未回应导致的长时间等待。FIN_WAIT2状态等待对端的FIN,完成最后两次挥手。接收对端FIN后,wap游戏源码下载武侠状态变化至time_wait,原socket资源被回收,并在时间等待超时后从系统中清除。在被动关闭中,接收FIN进入close_wait状态,应用关闭连接时改变状态为last_ack,并发送本端的FIN。被动关闭的后两次挥手后,连接关闭。出现大量close_wait通常与应用检测到对端FIN时未及时关闭有关,解决方法包括调整连接池的参数或加入心跳检测。操作系统通过包活定时器在超时后强制关闭连接。进程退出时会关闭所有文件描述符,再次触发filp_close函数。在Java中,通过重写finalize方法,GC会在释放内存时关闭未被引用的socket,但不可完全依赖GC来管理socket资源,以避免潜在的内存泄露问题。总结,深入理解TCP关闭过程有助于优化网络应用程序的性能和稳定性,同时阅读Linux内核源代码需要耐心和系统性的方法。
从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"及连接队列机制,旨在帮助开发者更深入地理解网络编程。
一篇搞懂TCP、HTTP、Socket、Socket连接池
本文旨在帮助开发者理解TCP、HTTP、Socket和Socket连接池之间的关系和原理。首先,七层网络模型中的TCP/IP对应传输层,HTTP对应应用层,而Socket并非OSI模型的一部分,但将在后续章节结合代码进行详细阐述。
在TCP和UDP连接中,TCP以三次握手建立连接,四次分手断开,确保数据传输的可靠,而UDP则无需这些步骤,以更快的速度传输数据。TCP的三次握手包括:客户端发送SYN,服务器确认并发送SYN+ACK,客户端确认服务器的ACK。断开连接时,需经过客户端和服务器的FIN和ACK交互。
关于TCP并发连接数,虽然理论上受限于端口号数量,但实际操作中更取决于系统性能,通过调整文件句柄数来优化。TIME_WAIT状态的等待时间长达2MSL,是为了防止因网络问题导致的连接重发。
Socket长连接是指在数据传输后保持连接,常用于频繁操作的场景,如数据库连接。心跳包用于检测连接状态,确保数据传输的连续性。连接池则通过维护连接资源,提高并发处理效率,避免频繁创建和销毁Socket带来的性能损耗。
在实际应用中,如Node.js的generic-pool连接池模块,可以简化长连接的管理,提高服务端的性能和资源利用率。通过自定义协议和连接池,我们可以构建更高效、可靠的网络通信系统。
从 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 端性能,并有效解决"惊群"等问题,提升系统处理能力。
ãtcpãå ³äºtcp socketåºç°çâconnection reset by peerâåâbroken pipeâ
å¨socketéä¿¡è¿ç¨ä¸ï¼ç»å¸¸åç°å®¢æ·ç«¯æè æå¡å¨çæ¥å¿ä¸åºç°âbroken pipeâæè âconnection reset by peerâçé误æ示ã
以åä¸ç´ä»¥ä¸ºèªå·±ç解äºè¿ä¸¤ä¸ªé误å¼å¸¸æ示æå å«çæä¹ï¼èå®é ç解å®å ¨é误ã
æçé误ç解åä¸é¢è¿æ®µæ¥èªblogspotç表述差ä¸å¤ï¼
```
Maybe I'm just dumb, but I always thought "broken pipe" meant, "the other end of this socket closed before I finished sending something" and "connection reset by peer" meant, well, roughly the same thing. (As well as indicating some slightly more esoteric problems.)
Turns out though, "broken pipe" actually means "I just tried to send something and the socket was already closed to sending."
So in the following example, if the other end of (TCP) socket "sock" closes or dies before the write method, "connection reset by peer" will be raised. The next write will give a broken-pipe error, since the socket now knows that further sending is invalid.
```
```
try:
sock.write('foo')
except:
pass # connection reset by peer
sock.write('bar') # broken pipe
```
RSTçæ å¿ä½ï¼è¿ä¸ªæ è¯ä¸ºå¨å¦ä¸å ç§æ åµä¸ä¼è¢«è®¾ç½®ï¼ä»¥ä¸æ¯æäºè§£çæ åµï¼å¯è½è¿ææ´å¤çåºæ¯ï¼æ²¡æéªè¯ï¼
1. å½å°è¯åæªå¼æ¾çæå¡å¨ç«¯å£å»ºç«tcpè¿æ¥æ¶ï¼æå¡å¨tcpå°ä¼ç´æ¥å客æ·ç«¯åéresetæ¥æ
2. åæ¹ä¹åå·²ç»æ£å¸¸å»ºç«äºéä¿¡ééï¼ä¹å¯è½è¿è¡è¿äºäº¤äºï¼å½æä¸æ¹å¨äº¤äºçè¿ç¨ä¸åçäºå¼å¸¸ï¼å¦å´©æºçï¼å¼å¸¸çä¸æ¹ä¼å对端åéresetæ¥æï¼éç¥å¯¹æ¹å°è¿æ¥å ³é
3. å½æ¶å°TCPæ¥æï¼ä½æ¯åç°è¯¥æ¥æä¸æ¯å·²å»ºç«çTCPè¿æ¥å表å¯å¤ççï¼åå ¶ç´æ¥å对端åéresetæ¥æ
4. ackæ¥æ丢失ï¼å¹¶ä¸è¶ åºä¸å®çéä¼ æ¬¡æ°ææ¶é´åï¼ä¼ä¸»å¨å对端åéresetæ¥æéæ¾è¯¥TCPè¿æ¥
å ¶å®æ们javaå¼å¸¸éçå°çBroken pipeæè Connection reset by peerä¿¡æ¯ä¸æ¯jdkæè jvméå®ä¹çï¼æçå°è¿äºå ³é®åå¾å¾ä¼é¦å æç´¢ä¸jdkæè hotspotæºç æ¾å°ä½ç½®è¿è¡ä¸ä¸æåæï¼ä½æ¯æ²¡æ¾å°ï¼åé¢ææ³å°åºè¯¥æ¯Linuxæè glibcéå®ä¹çï¼æç¶å¨glibcéçå°äºå¦ä¸çæè¿°åå®ä¹ã
对äºBroken pipeå¨ç®¡éçå¦å¤ä¸ç«¯æ²¡æè¿ç¨å¨è¯»çæ¶åå°±ä¼æåºæ¤å¼å¸¸ï¼Connection reset by peerçæè¿°å ¶å®ä¸æ¯å¾æ£ç¡®ï¼ä»æçå®è·µæ¥çåªæè¿°äºä¸æ¹é¢ï¼å ¶å®å¨æä¸ç«¯æ£å¸¸closeä¹åï¼ä¹æ¯å¯è½ä¼ææ¤å¼å¸¸çã
connection reset by peerâåâbroken pipeâåºç°çåºæ¯ï¼
1ï¼å¾ä¸ä¸ªå¯¹ç«¯å·²ç»closeçééåæ°æ®çæ¶åï¼å¯¹æ¹çtcpä¼æ¶å°è¿ä¸ªæ¥æï¼å¹¶ä¸åé¦ä¸ä¸ªresetæ¥æãå½æ¶å°resetæ¥æçæ¶åï¼ç»§ç»åselect读æ°æ®çæ¶åå°±ä¼æåºConnect reset by peerçå¼å¸¸ï¼ã
2ï¼å½ç¬¬ä¸æ¬¡å¾ä¸ä¸ªå¯¹ç«¯å·²ç»closeçééåæ°æ®çæ¶åä¼åä¸é¢çæ åµä¸æ ·ï¼ä¼æ¶å°resetæ¥æãå½å次å¾è¿ä¸ªsocketåæ°æ®çæ¶åï¼å°±ä¼æåºBroken pipeäº ãæ ¹æ®tcpç约å®ï¼å½æ¶å°resetå çæ¶åï¼ä¸å±å¿ é¡»è¦ååºå¤çï¼è°ç¨å°socketæ件æ述符è¿è¡å ³éï¼å ¶å®ä¹æå³çpipeä¼å ³éï¼å æ¤ä¼æåºè¿ä¸ªé¡¾åæä¹çå¼å¸¸ã
ä»tcpåçè§åº¦ç解Broken pipeåConnection Reset by Peerçåºå«
/blog////tcp-broken-pipe
å ³äºtcp socketåºç°çâconnection reset by peerâåâbroken pipeâ
////%E5%%B3%E4%BA%8Etcp-socket%E5%%BA%E7%8E%B0%E7%9A%connection-reset-by-peer%E5%%8Cbroken-pipe
c#中使用TCP socket通信,第一次打开TCP连接成功,关闭后,再次打开就失败。
关闭socket.Close()后,socket对象就为null,再次打开socket时会发生异常。稍微修改一下:private void tcpStart_Click(object sender, EventArgs e){
//IPAddress ipAddr = IPAddress.Parse("...");
IPAddress ipAddr = IPAddress.Parse(IP);
IPEndPoint ipEnd = new IPEndPoint(ipAddr, );
//创建socket实例!加上这句!!!!!
socket = new Socket(AddressFamily.InterNetwork,
SocketType.Stream,
ProtocolType.Tcp)
try
{
socket.Connect(ipEnd);
btnStart.Enabled = true;
MessageBox.Show("TCP连接成功!!");
}
catch
{
MessageBox.Show("TCP连接失败!!");
}
}