C socket

TCP

在TCP/IP协议族中,网络层IP提供的是一种不可靠的服务。也就是说,它只是尽可能快地把分组从源结点送到目的结点,但是并不提供任何可靠性保证。而另一方面,TCP在不可靠的IP层上提供了一个可靠的运输层。为了提供这种可靠的服务,TCP采用了超时重传、发送和接收端到端的确认分组等机制。由此可见,运输层和网络层分别负责不同的功能。

在四层网络结构,由下之上分别是:数据链路层网络层传输层应用层

数据链路层 协议典型如:Ethernet、ARP、ICMP

网络层 协议典型如:IP

传输层 协议典型如:TCP、UDP

应用层 协议典型如:HTTP、FTP等

TCPIP-01

图1-4 TCP/IP协议族中不同层次的协议

为协议ICMP和IGMP定位一直是一件很棘手的事情。在图1-4中,把它们与IP放在同一层上,那是因为事实上它们是IP的附属协议。但是在这里,又把它们放在IP层的上面,这是因为ICMP和IGMP报文都被封装在IP数据报中。

使用TCP/IP协议的应用程序通常采用两种应用编程接口(API):socket和TLI(运输层接口:Transport Layer Interface)。前者有时称作“Berkeley Socket”,表明它是从 伯克利 版发展而来的。后者起初是由AT&T开发的,有时称作XTI(X/Open运输层接口),以承认X/Open这个自己定义标准的国际计算机生产商所做的工作。XTI实际上是TLI的一个超集。

TCP Introduce

TCP(Transmission Control Protocol)传输控制协议,是主机对主机层的传输控制协议,提供可靠的连接服务,采用三次握手确认建立一个连接。

TCP Structure20210218-03

20210218-02
Definitions Length Explain
源端口 Source Port 16bits(2bytes)
目的端口 Destination Port 16bits(2bytes)
序号 Sequence Number 32bits(4bytes) 指定了当前数据分片中分配给 第一字节数据 的序列号。在TCP传输流中每一个字节为一个序号。如果TCP报文中flags标志位为SYN,该序列号表示 初始化序列号(ISN),此时第一个数据应该是从序列号ISN+1开始。
确认号 Acknowledgement Number 32bits(4bytes) 表示TCP发送者期望接受下一个数据分片的序列号。该序号在TCP分片中Flags标志位为ACK时生效。序列号分片的方向和流的方向同方向,而确认序列号分片方向和流方向反方向。
偏移量 Data-Offset 4bits 数据偏移也叫首部长度。
因为首部长度实际也说明了数据区在分片中的起始偏移值。它表示TCP头包含了多少个32-bit的words。因为4bits在十进制中能表示的最大值为15,32bits表示4个字节,那么Data Offset的最大可表示15*4=60个字节(bytes)。
所以TCP报头长度最大为60字节。如果options field为0的话,报文头长度为20个字节。
保留域 Reserved field 3bits(或6bits) 值全为零
标志位 Flags 9bits(或6bits) 表示TCP包特定的连接状态,一个标签位占一个bit。
窗口 Window 16bits(2bytes) 表示滑动窗口的大小,用来告诉发送端接收端的buffer space的大小。接收端buffer大小用来控制发送端的发送数据数率,从而达到流量控制。最大值为65535.
检验和 Checksum 16bits(2bytes) 用来检查TCP头在传输中是否被修改。
紧急指针 Urgent Pointer 16bits(2bytes) 表示TCP片中第一个紧急数据字节的指针。只有当URG标志置1时紧急指针才有效。
可变部分 Options Field填充部分 Padding Field 可变长度。 表示TCP可选选项以及填充位。当选项不足32bits时,填充字段加入额外的0填充。

TCP Flags

对于旧版本的TCP头定义,Flags有6bits,新版TCP头对flags扩展了3bits。每个TCP flag对应于1bit 。所以旧版TCP头flags值有6个,新版扩展了3个值。

从低位到高位分别是:

Definition Declaration
FIN
(finished 结束)
表示发送者以及发送完数据。通常用在发送者发送完数据的最后一个包中。
SYN
(synchronous 建立联机)
RST
(reset 重置)
重置连接标志,用于重置由于主机崩溃或其他原因而出现错误的连接。
或者发送包发送到一个不是期望的目的主机时,接收端发送reset 重置连接标志的包。
PSH
(push 传送)
通知接收端处理接收的报文,而不是将报文缓存到buffer中。
ACK
(acknowledgement 确认)
URG
(urgent 紧急)
通知接收端处理在处理其他包前优先处理接收到的紧急报文。
ECE
(Explicit Congestion Notification Echo) 【新】
表示TCP peer有ECN能力。
CWR
(Congestion Window Reduced)【新】
发送者在接收到一个带有ECE flag包时,将会使用CWR flag。
NS
(Nonce Sum)【新】
该标签用来保护不受发送者发送的突发的恶意隐藏报文的侵害。

TCP 连接的建立都是采用客户-服务器(Client-Server)方式:

  • 主动发起连接建立的应用进程叫做客户(client)。
  • 被等待连接建立的应用进程叫做服务器(server)。

传输连接就有三个阶段,即:连接建立数据传输连接释放

TCP 采用全双工模式,在连接建立后和连接释放前进行数据传输。数据传输是单向的,从发送端传输给接受端。TCP通过序列号能够保证数据被接受端接受。TCP建立连接是通过三次握手的方式来建立连接的。

TCP Three-Way Handshake

注意: 不管是大小写,ack ( 或 Ack )和 ACK 都是 确认 的意思, 不同之处在于:

  • 在TCP首部中,ACK为确认标志位 ——占 1 字节,只有当 ACK = 1 时确认号字段才有效。当 ACK = 0 时,确认号无效。;
  • Ack为确认号字段(Ack Number)——占 4 字节,是期望收到对方的下一个报文段的数据的第一个字节的序号。

The Steps of Three-Way Handshake

第一次握手:Client发送位码为SYN=1 ,随机产生 seq number=1234567 的数据包到服务器,Server收到 SYN=1,知道Client要求建立联机;

第二次握手:Server收到请求后要确认联机信息,向Client发送ack number=(Client的seq+1)SYN=1ACK=1 ,随机产生 seq number=7654321 的包;

第三次握手:Client 收到后检查 ack number 是否正确,即第一次发送的seq number+1,以及位码 ACK是否为 1,若正确,Client 会再发送 ack number=(主机B的seq+1)ACK=1,Server 收到后确认seq number 值与ACK=1则连接建立成功。

完成三次握手,主机A与主机B开始传送数据。

sequenceDiagram
Client ->> Server: SYN=1  [seq number=1234567]
Server -->> Client: SYN = 1, ACK = 1  [ack number = 1234568,seq number = 7654321]
Client ->> Server: ACK = 1 [ack number = 7654322]

在TCP/IP协议中,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状态,完成三次握手。完成三次握手,客户端与服务器开始传送数据.

The Meaning of Three-way handshake

向客户端确认这个请求,这两个数据包(前两次握手)足以证明客户端与服务器之间的网络是畅通的,并且协商数据通信所需要的参数。比如协商接收窗口大小,所支持的数据包最大字节数等。

如果没有最后一个数据包确认(第三次握手),A先发出一个建立连接的请求数据包,由于网络原因绕远路了。A经过设定的超时时间后还未收到B的确认数据包,于是发出第二个建立连接的请求数据包,这次网路通畅,数据包很快到达B,B的确认数据包也很快就到达A。于是A与B开始传输数据,过了一会A第一次发出的建立连接的请求数据包到达了B,B以为是再次建立连接,所以又发出一个确认数据包。由于A已经收到了一个确认数据包,所以会忽略B发来的第二个确认数据包,但是B发出确认数据包之后就要一直等待A的回复,而A永远也不会回复。由此造成服务器资源浪费,这种情况多了B计算机可能就停止响应了。

第三次握手(第三个数据包)作用在于,告诉B计算机,B第二次握手发给A的确认数据包A收到了,是有效的。避免B计算机等待造成资源浪费。随后A与B可进行下一步的通信。

连接建立 过程中要解决以下三个问题:

  • 要使每一方能够确知对方的存在。
  • 要允许双方协商一些参数(如 最大报文段长度最大窗口大小服务质量 等)。
  • 能够对运输实体资源(如 缓存大小连接表中的项目 等)进行分配。

连接建立过程中,客户端存在以下状态:

SYN-SENT:在未与目标服务器建立连接之前始终处于此状态,并将不断向目标服务器发送请求,直到 收到回复 来自服务器的信息后,方进入下一个状态。 ESTABLISHED:稳定连接状态。

连接建立过程中,服务器存在以下状态:

LISTEN: 在未与客户端建立连接之前,始终处于此状态,在收到客户端的连接请求后答复其请求,并进入下一个状态。 SYN-RCVD: 等待SYN信息到达后进入下一个状态。 ESTABLISHED:稳定连接状态。

20210218-01

连接建立文字图解:

Client 发出 同步数据包(请求建立连接的数据包) 并进入SYN-SENT状态。

  • SYN = 1, 表示该为一个连接建立请求数据包;
  • ACK = 0,说明数据包确认号无效,省略;
  • Seq=x,x为所传送数据的第一个字节的序号。

Server 收到Client发出的 同步数据包 后结束LISTEN状态,进入SYN-RCVD状态并向A发出 确认同步数据包

  • SYN=1;
  • ACK=1;
  • seq=y,y的值由B指定表示B发送数据时的 第一个数据字节的序号
  • ack=x+1,表示已经收到A发送的x个字节数据,并告诉A下次应从数据的第x+1个字节开始发送。

Client 收到确认同步数据包之后,向B答复 确认数据包 ,结束 SYN-SENT 状态,进入 ESTABLISHED 状态。

  • SYN=0,表示双方已同意建立连接;
  • ACK=1,表示收到B的确认数据包;
  • seq=x+1,表示发出的数据包就是数据的第x+1个字节;
  • ack=y+1,表示收到了B发送y字节数据,并告诉B下次应从数据的第y+1个字节开始发送。

Server 收到 Client 的 确认数据包 之后,结束SYN-RCVD状态,进入ESTABLISHED状态。

TCP Four-Way-Wavehand

结束连接时,不管是Client或Server均可以主动发起结束信标FIN或RST。此处称主动发起结束信标的一方为主动方,另一方为被动方。

The Meaning of Four-Way-Wavehand

当被动方收到主动方的FIN报文通知时,它仅仅表示主动方没有数据再发送给被动方了。

但未必被动方所有的数据都完整的发送给了主动方,所以 被动方不会马上关闭SOCKET,它可能还需要发送一些数据给主动方后,再发送FIN报文给主动方,告诉主动方同意关闭连接,所以这里的ACK报文和FIN报文多数情况下都是分开发送的。

The Steps of Four-Way-Wavehand

  1. 第一次挥手:Client发送一个FIN,用来关闭Client到Server的数据传送,Client进入 FIN_WAIT_1 状态。

  2. 第二次挥手:Server收到FIN后,发送一个ACK给Client,确认序号为收到序号+1(与SYN相同,一个FIN占用一个序号),Server进入`CLOSE_WAIT 状态。

  3. 第三次挥手:Server发送一个FIN,用来关闭Server到Client的数据传送,Server进入 LAST_ACK 状态。

  4. 第四次挥手:Client收到FIN后,Client进入`TIME_WAIT 状态,接着发送一个ACK给Server,确认序号为收到序号+1,Server进入 CLOSED 状态,完成四次挥手

RST

Differences between FIN and RST

关键字: 主动释放、有序释放、终止释放

释放 即发送方和接收方终止连接,解除连接状态。

发送方和接收方均可主动释放。最常见的是,主动释放端会发送FIN包,并且因为TCP是双工的, 仅关闭一个方向上的数据流,从而TCP连接处于半关闭状态,继续完成四次挥手完成连接释放。

上述挥手是一种 有序释放 , 即,标志位为FIN的TCP报文会在之前所有排队的数据发送完之后,才会发送,在socket缓冲区和窗口中的数据也能保证发送成功。

通常我们调用 shutdown() , close() 函数后, TCP会发送FIN报文。

shutdown 和 close 的区别:

  • shutdown是关闭一个socket, 可以关闭读、写、读写;
  • close是关闭一个linux系统的文件描述符fd。

除了“有序释放”, 还有一种 终止释放 ,比如进程异常退出,用来关闭异常连接使用,是通过 RST 标志位实现的。 标志位为 RST 的TCP报文,会立即发送,而之前所有在缓存区排队的数据都将被RST发送方丢弃。

FIN报文需要应答 ACK , RST报文不需要应答 ACK

半开连接:如果发生断点,或网络条件很差,其中一端发送 RST 后会立马关闭连接;而另一端可能感知不到,仍然认为连接正常,造成一种半开连接的状态。

Why do we send RST?

RST标志位被发送,通常是因为一下几种原因:

  1. A向B发起连接,但B之上并没有应用监听相应的端口,这时B操作系统上的TCP处理程序会发RST包。
  2. 请求超时,即由于主动连接端连接请求超时,主动发起RST关闭连接。
  3. 在一个已关闭的socket上收到数据
  4. 字节流接收不完全

情况二: 有89、27两台主机。主机89向主机27发送了一个SYN,表示希望连接8888端口,主机27回应了主机89一个SYN表示可以连接。但是主机89却很不友好,莫名其妙的发送了一个RST表示我不想连接你了。 后来经过排查发现,在主机89上的程序在建立了socket之后,用setsockopt的SO_RCVTIMEO选项设置了recv的超时时间为100ms。而我们看上面的抓包结果表示,从主机89发出SYN到接收SYN的时间多达110ms。(从15:01:27.799961到15:01:27.961886, 小数点之后的单位是微秒)。因此主机89上的程序认为接收超时,所以发送了RST拒绝进一步发送数据。

情况三: 比如,AB正常建立连接了,正在通讯时,A向B发送了FIN包要求关连接,B发送ACK后,网断了,A通过若干原因排查后放弃了这个连接(例如进程重启)。网通了后,B又开始发数据包,A收到后表示压力很大,不知道这野连接哪来的,就发了个RST包强制把连接关了,B收到后会出现connect reset by peer错误。

Wireshark Tutorial

Wireshark是一款可以监听和捕捉网络通信数据的软件,对捕获的数据自下而上进行解码和分析至已知协议。如(Ethernet II -- IP -- TCP 或 Ethernet II -- IP -- TCP -- HTTP)。

Wireshark主要通过 捕获过滤显示过滤 对所有数据进行 捕获时过滤捕获后显示过滤

Capturing Filters 捕获过滤

Filtering packets while capturing 捕获时可用的过滤组件

Capture Filters are used to filter out uninteresting packets already at capture time. This is done to reduce the size of the resulting capture (file) and is especially useful on high traffic networks or for long term capturing.

Wireshark uses the pcap (libpcap/WinPcap) filter language for capture filters. This language is explained in the tcpdump man page under "expression" (http://www.tcpdump.org and search for "selects which").

Note: This capture filter language is different from the one used for the Wireshark display filters!

捕获过滤器的使用方式为:菜单栏上的 捕获 --> 选项 --> 所选择接口的捕获过滤器 中输入对应的过滤条件。


Some common examples 一些通用示例

Example Ethernet: capture all traffic to and from the Ethernet address 08:00:08:15:ca:fe

ether host 08:00:08:15:ca:fe

Example IP: capture all traffic to and from the IP address 192.168.0.10

host 192.168.0.10

Example TCP: capture all traffic to and from the TCP port 80 (http) of all machines

tcp port 80

Examples combined: capture all traffic to and from 192.168.0.10 except http

host 192.168.0.10 and not tcp port 80

Beware: if you capture TCP/IP traffic with the primitives "host" or "port", you will not see the ARP traffic belonging to it!


Capture Filter Syntax 捕获过滤器标志

The following is a short description of the capture filter language syntax. For a further reference, have a look at: http://www.tcpdump.org/tcpdump_man.html

A capture filter takes the form of a series of primitive expressions, connected by conjunctions (and/or) and optionally preceded by not:

[not] primitive [and|or [not] primitive ...]

A primitive is simply one of the following:

[src|dst] host

This primitive allows you to filter on a host IP address or name. You can optionally precede the primitive with the keyword src|dst to specify that you are only interested in source or destination addresses. If these are not present, packets where the specified address appears as either the source or the destination address will be selected.

ether [src|dst] host

This primitive allows you to filter on Ethernet host addresses. You can optionally include the keyword src|dst between the keywords ether and host to specify that you are only interested in source or destination addresses. If these are not present, packets where the specified address appears in either the source or destination address will be selected.

gateway host

This primitive allows you to filter on packets that used host as a gateway. That is, where the Ethernet source or destination was host but neither the source nor destination IP address was host.

[src|dst] net [{mask }|{len }]

This primitive allows you to filter on network numbers. You can optionally precede this primitive with the keyword src|dst to specify that you are only interested in a source or destination network. If neither of these are present, packets will be selected that have the specified network in either the source or destination address. In addition, you can specify either the netmask or the CIDR (Classless Inter-Domain Routing) prefix for the network if they are different from your own.

[tcp|udp] [src|dst] port

This primitive allows you to filter on TCP and UDP port numbers. You can optionally precede this primitive with the keywords src|dst and tcp|udp which allow you to specify that you are only interested in source or destination ports and TCP or UDP packets respectively. The keywords tcp|udp must appear before src|dst. If these are not specified, packets will be selected for both the TCP and UDP protocols and when the specified address appears in either the source or destination port field.

less|greater

This primitive allows you to filter on packets whose length was less than or equal to the specified length, or greater than or equal to the specified length, respectively.

ip|ether proto

This primitive allows you to filter on the specified protocol at either the Ethernet layer or the IP layer.

ether|ip broadcast|multicast

This primitive allows you to filter on either Ethernet or IP broadcasts or multicasts.

relop

This primitive allows you to create complex filter expressions that select bytes or ranges of bytes in packets. Please see the tcpdump man pages for more details.

Capturing 捕获

This section will explain the capturing options and give hints on what to do in some special cases.

Capture options 捕获设置

The capture options can be logically divided into the following categories:

-input -filtering -stop conditions -storing -display while capturing

Input options 输入设置

-Interface: You have to choose which interface (network card) will be used to capture packets from. Be sure to select the correct one, as it's a common mistake to select the wrong interface.

-Link-layer header type: unless you are in the rare case that you will need this, just keep the default.

Filtering options 过滤设置

-Capture packets in promiscuous mode: Usually a network card will only capture the traffic to its own network address. If you want to capture all traffic that the network card can "see", mark this option. See the FAQ for some more details of capturing packets from a switched network.

-Limit each packet to xy bytes: Will limit the maximum size to be captured of each packet, this includes the link-layer header and all subsequent headers. This can be useful when an error is known to be in the first 20 bytes of a packet, for example, as the size of the resulting capture file will be reduced.

-Capture Filter: Use a capture filter to reduce the amount of packets to be captured. See "Capture Filters" in this help for further information how to use it.

Storing options 存储设置

-File: You can choose the file to which captured data will be written. If you don't enter something here a temporary file will be used.

-Use multiple files: Instead of using a single capture file, multiple files will be created. The generated filenames will contain an incrementing number and the start time of the capture. For example, if you choose "/foo.cap" in the "File" field, files like "/foo_00001_20040205110102.cap", "/foo_00002_20040205110102.cap", ... will be created. This feature can be useful if you do long term capturing, as working with a single capture file of several GB usually isn't very fast.

Stop condition options 终止设置

These three fields should be obvious; the capture process will be automatically stopped if one of the selected conditions is exceeded.

Display while capturing options

-Update list of packets in real time: Using this will show the captured packets immediately on the main screen. Please note: this will slow down capturing, so increased packet drops might appear.

-Automatic scrolling in live capture: This will scroll the "Packet List" automatically to the latest captured packet, when the "Update List of packets in real time" option is used.

-Name resolution: perform the corresponding name resolution while capturing.

High performance capturing 高性能模式捕获

When your network traffic is high, you might need to take some steps to ensure Wireshark doesn't get behind on its capture, particularly if you're running it on a slow computer.

When Wireshark cannot keep up, packets are dropped. To help avoid this as much as possible:

  1. Don't use the "Update list of packets in real time" option (see above). This has a significant performance penalty.

  2. Close other programs that might slow down your system, such as virus scanner software, server processes, etc.

  3. It might be a good idea not to use a capture filter. This will depend on the task you have to do. As a rule of thumb: if you want to see most of the packets and only filter a small number out, don't use a capture filter (you can use a display filter later). If you only want to capture a small proportion of the packets, it might be better to set a capture filter, as this will reduce the number of packets that have to be saved.

  4. If you still get packet drops, it might be an idea to use a tool dedicated to packet capturing and only use Wireshark for displaying and analyzing the packets.

Have a look at tshark, the command line variant of wireshark, which is included in this package. XXX: add a list of possibly useful standalone capture programs.

Long term capturing 长期捕获

By "Long term capturing", it's meant to capture data from a network for several hours or even days. Long term capturing will usually result in huge capture files, being hundreds of MB's or even several GB's in size!

Before doing a long term capture, get familiar with the options to use for it, as you might not get what you desire. Doing a long term capture not getting the results needed, is usually wasting a lot of time. ;-)

Rules of thumb for this task: -Use the ring buffer feature when you expect very large capture files. -Don't use the "Update list of packets in real time" option. -Set an appropriate capture filter, when you are only interested in some special packets from the net.

Display Filter 显示过滤器

Filtering packets while viewing 观察时的过滤器组件

After capturing packets or loading some network traffic from a file, Wireshark will display the packet data immediately on the screen.

Using display filters, you can choose which packets should (not) be shown on the screen. This is useful to reduce the "noise" usually on the network, showing only the packets you want to. So you can concentrate on the things you are really interested in.

The display filter will not affect the data captured, it will only select which packets of the captured data are displayed on the screen.

Every time you change the filter string, all packets will be reread from the capture file (or from memory), and processed by the display filter "machine". Packet by packet, this "machine" is asked, if this particular packet should be shown or not.

Wireshark offers a very powerful display filter language for this. It can be used for a wide range of purposes, from simply: "show only packets from a specific IP address", or on the other hand, to very complex filters like: "find all packets where a special application specific flag is set".

Note: This display filter language is different from the one used for the Wireshark capture filters!

特殊字符

constains 和 matches

contains 用来判断是否包含一个值,matches 用来判断是否匹配一个表达式


Some common examples 一些通用示例

Example Ethernet: display all traffic to and from the Ethernet address 08.00.08.15.ca.fe

eth.addr==08.00.08.15.ca.fe

Example IP: display all traffic to and from the IP address 192.168.0.10

ip.addr==192.168.0.10

Example TCP: display all traffic to and from the TCP port 80 (http) of all machines

tcp.port==80

Examples combined: display all traffic to and from 192.168.0.10 except http

ip.addr==192.168.0.10 && tcp.port!=80

Beware: The filter string builds a logical expression, which must be true to show the packet. The && is a "logical and", "A && B" means: A must be true AND B must be true to show the packet (it doesn't mean: A will be shown AND B will be shown).


Hint

Filtering can lead to side effects, which are sometimes not obvious at first sight. Example: If you capture TCP/IP traffic with the primitive "ip", you will not see the ARP traffic belonging to it, as this is a lower protocol layer than IP!

WIRESHARK中的各种标志(TCP)

OUT OF ORDER

TCP Out-of-Order

正常情况:在TCP传输过程中,同一台主机发出的数据段应该是连续的,即后一个包的 Seq 号等于前一 个包的 Seq + Len (三次握手和四次挥手是例外)。

异常情况:当Wireshark发现后一个包的 Seq小于 前一个包的 Seq + Len 时,就会认为是乱序了,因此提示 TCP Out-of-Order

Previous segment not captured

Previous segment not capturedd ,即报文缺失,指存在未抓取的数据包

正常情况:在TCP传输过程中,同一台主机发出的数据段应该是连续的,即后一个包的 Seq 号等于前一个包的 Seq + Len (三次握手和四次挥手是例外)。

异常情况:如果Wireshark发现后一个包的 Seq 号大于前一个包的 Seq + Len ,就知道中间缺失了一段数据。假如缺失的那段数据在整个网络包中都找不到(即排除了乱序),就会提示TCP Previous segment not captured

TCP Previous segment lost - Occurs when a packet arrives with a sequence number greater than the "next expected sequence number" on that connection, indicating that one or more packets prior to the flagged packet did not arrive. This event is a good indicator of packet loss and will likely be accompanied by "TCP Retransmission" events.

-- Wireshark

TCP DUP ACK

Tcp Dup Ack xxx#y ,即重复确认。

当乱序或者丢包发生时,接收方会收到一些Seq号比期望值大的包。

接收方每收到一个这种包就会进行答复,Ack 一次期望的 Seq 值,以此方式来提醒发送方,于是就产生了一些重复的 Ack

Wireshark会在这种重复的Ack上标记 TCP Dup ACK ,代表了数据段丢失 TCP 状态,xxx 代表数据丢失的位置, y 后代表第几次丢失报文。

TCP Fast Retransmission

快速重传,当发送方收到来自接收方的3个或以上TCP Dup ACK ,就意识到之前发的包可能丢了,于是发送方快速重传该数据(这是RFC的规定)。

TCP Spurious Retransmission

TCP Retransmission

超时重传。

如果一个包真的丢了,且无后续包,则可以在接收方触发 Dup Ack ,就不会快速重传。

这种情况下发送方只好等到超时了再重传,此类重传包就会被Wireshark标上 TCP Retransmission

TCP ACKed unseen segment

抓取遗漏。

当Wireshark发现被Ack的那个包没被抓到,就会提示 TCP ACKed unseen segment, 即此为由于抓包不到造成的报错。

这可能是最常见的Wireshark提示了,幸好它几乎是永远可以忽略的。

以图3为例,32号包的Seq=6889 Len=1448 ,相加得 8337,说明服务器发出的下一个包应该是 Seq=8337。而我们看到的却是35号包的Seq=11233,这意味着 8337~11232 这段数据没有被抓到。这段数据本应该出现在34号之前,所以Wireshark提示了TCP ACKed unseen segment

TCP Zerowindow

窗口清零。

TCP包中的 win= 代表接收窗口的大小,即表示这个包的发送方当前还有多少缓存区可以接收数据。

当Wireshark在一个包中发现 win=0 时,就会给它打上 TCP zerowindow 的标志,表示缓存区已满,不能再接受数据了。下图就是服务器的缓存区已满,所以通知客户端不要再发数据了。我们甚至可以在3258~3263这几个包中看出它的窗口逐渐减少的过程,即从 win=15872 减小到 win=1472

TCP Window Update

其他

Sockets API和Internet在许多 竞争性协议族(包括 IPXAppleTalkDECNetOSISNATCP/IP)的世界中逐渐成长起来,并且 Sockets被设计成支持所有这些协议

IPv4映射

IPv4映射的地址是通过在IPv4地址前添加4个字节的前缀 ::fff: 而构成。

如,132.3.23.7 的IPv4地址映射至IPv6即 ::ffff:132.3.32.7

协议互操作性

回送地址

IPv4的回送地址是127.0.0.1

IPv6的回送地址是 0:0:0:0:0:0:0:1

专用网络地址

10192.168172.16~172.31开头的地址最初被指定在不属于全球Internet的专用网络中使用。

Name

Hostname是指 计算机名称 ,Domain Name是指 域名

通常情况下,一个name都是指Hostname。

Socket in Posix

Headerfiles

在Posix标准中,以下文件被用于在Unix/Linux/Mac中进行网络编程。

1
2
3
4
5
6
7
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <unistd.h>
#include <errno.h>

各文件包含的内容如下:

  • <sys/types.h> 定义了在socket网络编程中用到的数据类型;

  • <sys/socket.h> 定义了接口类型(socket types)、接口标志(socket flags)、额外设置(addtional options)、地址簇(address family)和地址存储结构体(address storage structure)特定机器的一些设定。

  • <netinet/in.h> 定义了RFC协议(protocol),和IP网络层相关的宏定义。其中 in 是 internet 的缩写。

  • <arpa/inet.h> 则定义了对网络层操作的函数,例如,“将host地址与net地址互相转换” 的 htonl()htons()ntohl()ntohs() 都定义在里面,还有 inet_addr()*inet_ntoa()*inet_ntop()inet_pton() 等操作

  • <netdb.h> 定义了网络数据库(network database)的操作, 相关手册可以查看 Michael 的 在线手册

  • <unistd.h> 定义了系统线程、文件读取等的操作和宏定义。

  • <errno.h> 定义了 普通文件操作和网路文件操作的错误码(error codes)。

Socket in Texas Instrument’s SysBIOS

NETCTRL.H 及 SOCKET.H

以下两句话是TI官方对<netctrl.h> 的描述,即简易控制网络开断的包装函数,以此类方法实现接口的目的是隐藏可以被调用的HAL/STACK功能

  • Shell functions for simplified net startup and shutdown
  • The idea behind this API is to hide the user callable HAL/STACK functions

<netctrl.h> 是用于初始化和维护服务的。为了完成此功能,其调用了NETTOOLS库提供的配置管理器。要注意的是,此处的配置定义和结构声明是对针对NETCTRL 的,而不是针对 CONFIG。

NETCTRL is used to initialize the stack and maintain services. To accomplish this, it makes use of the configuration manager provided in the NETTOOLS library. Note that the configuration definitions and structures defined here are specific to NETCTRL, not CONFIG.

NETCTRL API

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
// 初始化运行环境
_extern int NC_SystemOpen( int Priority, int OpMode );

//第一个参数可选择任务等级高或任务等级低
#define NC_PRIORITY_LOW OS_SCHEDULER_LOWPRI
#define NC_PRIORITY_HIGH OS_SCHEDULER_HIGHPRI

//定义打开模式为POLLING或中断
#define NC_OPMODE_POLLING 1
#define NC_OPMODE_INTERRUPT 2

// NC_SystemOpen()的返回结果
#define NC_OPEN_SUCCESS 0
#define NC_OPEN_ILLEGAL_PRIORITY -1
#define NC_OPEN_ILLEGAL_OPMODE -2
#define NC_OPEN_MEMINIT_FAILED -3
#define NC_OPEN_EVENTINIT_FAILED -4

// 关闭运行环境
_extern void NC_SystemClose();

// 使用提供的配置信息开启网络
_extern int NC_NetStart( HANDLE hCfg, void (*NetStart)(),
void (*NetStop)(), void (*NetIP)(IPN,uint,uint) );

// 断开网络
_extern void NC_NetStop( int rc );

// 当Boot线程完成时被调用
_extern void NC_BootComplete();

// 当IP地址被添加或移除时被调用
_extern void NC_IPUpdate( IPN IPAddr, uint IfIdx, uint fAdd );

SOCKET API

UNIVERSAL_TCP

defined in <ws2def.h>

1
2
3
4
5
6
7
8
9
10
11
12
typedef struct addrinfo
{
int ai_flags; // AI_PASSIVE, AI_CANONNAME, AI_NUMERICHOST
int ai_family; // PF_xxx
int ai_socktype; // SOCK_xxx
int ai_protocol; // 0 or IPPROTO_xxx for IPv4 and IPv6
size_t ai_addrlen; // Length of ai_addr
char * ai_canonname; // Canonical name for nodename
_Field_size_bytes_(ai_addrlen) struct sockaddr * ai_addr; // Binary address
struct addrinfo * ai_next; // Next structure in linked list
}
ADDRINFOA, *PADDRINFOA;

defined in TI <socket.h>

IPv4套接字地址数据结构
1
2
3
4
5
6
7
8
// AF_INET family (IPv4) Socket address data structure.
struct sockaddr_in {
UINT8 sin_len; // total length
UINT8 sin_family; // address family
UINT16 sin_port; // port
struct in_addr sin_addr;
INT8 sin_zero[8]; // fixed length address value
};
内核用地址存储数据结构

这个套娃里还有个套娃 in_addr ,这个结构体供内核调用,以储存更多地址数据。

1
2
3
4
// Structure used by kernel to store most addresses.
struct in_addr {
UINT32 s_addr; // 32 bit long IP address, net order
};
通用套接字地址储存数据结构
1
2
3
4
5
6
7
8
9
// Generic Socket address storage data structure.
struct sockaddr {
UINT8 sa_len; // Length
UINT8 sa_family; // address family
char sa_data[14]; // socket data
};

typedef struct sockaddr SA;
typedef struct sockaddr *PSA;

以下为Socket 接口协议簇接口类型接口协议

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
//
// Socket address families
//
#define AF_TASK 1 // Intertask Communication
#define AF_INET 2 // Internet: UDP, TCP, etc.
#define AF_INET6 10 // IPV6
#ifdef _INCLUDE_NIMU_CODE
#define AF_RAWETH 12 // Raw Ethernet Protocol
#endif

//
// Socket Types
//
#define SOCK_STREAM 1 // stream socket
#define SOCK_DGRAM 2 // datagram socket
#define SOCK_RAW 3 // raw-protocol interface
#define SOCK_STREAMNC 4 // non-copy stream socket
#ifdef _INCLUDE_NIMU_CODE
#define SOCK_RAWETH 5 // non-copy raw eth socket
#endif

//
// Protocols
//
#define IPPROTO_IP 0 // IP Placeholder
#define IPPROTO_ICMP 1 // ICMP
#define IPPROTO_IGMP 2 // IGMP
#define IPPROTO_TCP 6 // TCP
#define IPPROTO_UDP 17 // UDP
#define IPPROTO_IPV6 41 // IPV6
#define IPPROTO_ICMPV6 58 // ICMPV6 Header.

以下Socket接口均以Ti NDK为载体,以C为实现方式。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// Socket Oriented Functions
SOCKET accept( SOCKET s, PSA pName, int *plen ); //接受一个套接字的连接
int bind( SOCKET s, PSA pName, int len ); //给套接字绑定一个名字
int connect( SOCKET s, PSA pName, int len ); //在一个套接字上初始化连接
int getpeername( SOCKET s, PSA pName, int *plen ); //在已连接的peer上返回名称地址
int getsockname( SOCKET s, PSA pName, int *plen ); //返回套接字的本地名称地址
int getsockopt( SOCKET s, int level, int op, void *pbuf, int *pbufsize ); //获取套接字设置信息
int listen( SOCKET s, int maxcon ); //监听数据
int recv( SOCKET s, void *pbuf, int size, int flags ); //接收数据
int recvfrom( SOCKET s, void *pbuf, int size, int flags, PSA pName, int *plen ); //从指定对象处接收信息

int recvnc( SOCKET s, void **ppbuf, int flags, HANDLE *pHandle ); //
int recvncfrom( SOCKET s, void **ppbuf, int flags,
PSA pName, int *plen, HANDLE *pHandle );
void recvncfree( SOCKET Handle );

int send( SOCKET s, void *pbuf, int size, int flags ); //发送信息
int sendto( SOCKET s, void *pbuf, int size, int flags, PSA pName, int len ); //在未连接的套接字上往指定目的地发送数据
int setsockopt( SOCKET s, int level, int op, void *pbuf, int bufsize ); //设置套接字设置
int shutdown( SOCKET s, int how ); //关闭一半的套接字连接
SOCKET socket( int domain, int type, int protocol ); //创建套接字

SOCKET API IN CLIENT


第一步 创建套接字

SOCKET socket( int domain, int type, int protocol );

如果创建成功,则返回一个代表套接字的文件描述符。否则就返回一个 INVALID_SOCKET 值,并且可以调用 fdError() 来诊断错误原因。

domain是指链路层类型IPv4还是IPv6,分别书写为 AF_INET | AF_INET6

type是指传输层套接字类型,共有 报文数据流式数据原始数据 三种可选,分别是 SOCK_DGRAM | SOCK_STREAM | SOCK_RAW

protocol是指网络层协议类型,IPPROTO_TCP | IPPROTO_UDP,在套接字类型是原始数据时可以任意指定,如果是套接字类型是 SOCK_STREAM,则协议需要指定为 IPPROTO_TCP

第二步 设定套接字参数

通常在套接字创建之后,使用Pv4套接字地址数据结构 sockaddr_in设定参数,以下实例是下位机做客户端时的代码。

1
2
3
4
5
bzero(&sin1, sizeof(struct sockaddr_in));            /* Set Port, IP address = IPAddrSend */
sin1.sin_family = AF_INET;
sin1.sin_len = sizeof(sin1);
sin1.sin_addr.s_addr = inet_addr(REMOTE_IPADDR_STRING); //连接服务器的地址
sin1.sin_port = htons(TCP_CLIENT_PORT);
第三步 连接

int connect( SOCKET s, PSA pName, int len );

PSAsockaddr 结构体的 指针 对象类型,定义为 typedef struct sockaddr *PSA;

另,SAsockaddr 结构体的对象类型。

sockaddr 的定义如下:

1
2
3
4
5
6
// 通用套接字地址存储数据结构
struct sockaddr {
UINT8 sa_len; //套接字长度
UINT8 sa_family; //套接字类型,AF_INET
char sa_data[14]; //套接字数据
};

实际连接时,通常设置一定时间的任务休眠以等待网络稳定后再行连接,且连接次数自定。

1
2
3
4
5
6
7
8
9
10
11
for(count = 0; count < 30; count ++){
res = connect(stcp, (PSA) &sin1, sizeof(sin1));
if(res < 0){
ConsoleWarning("网络连接失败!\n");
TaskSleep(SLEEPTIME);
}
else{
ConsoleWarning("网络连接成功!\n");
break;
}
}

通过特定配置设置,打开网络。

1
_extern int NC_NetStart( HANDLE hCfg,   void (*NetStart)(),   void (*NetStop)(),    void (*NetIP)(IPN,uint,uint) );

套接字

HANDLE is a void pointer* defined in the <usertype.h>.

<socket.h>中,SOCKET类型其实是个HANDLE,而HANDLE其实是void*数据。

typedef HANDLE SOCKET; // OS Socket Type

typedef void * HANDLE;

文件描述符

文件描述符,即 File Descriptor,其实一个是 void*类型

FD集
1
2
3
4
5
6
7
8
9
10
11
//
// Select uses bit masks of file descriptors. These macros
// manipulate handle lists. FD_SETSIZE can be modified as
// needed.
//
#define FD_SETSIZE 16

typedef struct _fd_set {
uint count;
HANDLE fd[FD_SETSIZE];
} fd_set;

字节顺序转换函数

在C/C++写网络程序的时候,往往会遇到字节的网络顺序和主机顺序的问题。这是就可能用到htons() , ntohl() , ntohs()htons()这4个函数。

网络字节顺序与本地字节顺序之间的转换函数:

htonl()--"Host to Network Long" ntohl()--"Network to Host Long" htons()--"Host to Network Short" ntohs()--"Network to Host Short"

网络字节顺序(NBO, Network Byte Order): 按从高到低的顺序存储,在网络上使用统一的网络字节顺序,可以避免兼容性问题。

主机字节顺序(HBO, Host Byte Order): 不同的机器HBO不相同,与CPU设计有关,数据的顺序是由cpu决定的,而与操作系统无关。

如 Intel x86结构下, short型数 0x1234 表示为34 12, int型数 0x12345678 表示为78 56 34 12

如 IBM power PC结构下, short型数0x1234 表示为 12 34 , int型数 0x12345678 表示为 12 34 56 78

由于这个原因不同体系结构的机器之间无法通信,所以要转换成一种约定的数序,也就是网络字节顺序,其实就是如同power pc那样的顺序. 在PC开发中有ntohl和htonl函数可以用来进行网络字节和主机字节的转换.

timeval结构体

Ti SysBios中,timeval 是在 <SOCKET.H> 中被定义,被 fdSelect() 使用的;

结构体定义与Linux C中的一致

1
2
3
4
struct timeval {
INT32 tv_sec; //Second Level
INT32 tv_usec; //Microsecond Level
};

Linux C 中,timevaltimezone 结构体都隶属于 sys/time.h 头文件

1
2
3
4
struct timezone{
int tz_minuteswest;
int tz_dsttime;
}

Linux C 中对 TIMEVAL 使用的补充资料:

1
int gettimeofday(struct timeval*tv, struct timezone *tz);

其参数tv是保存获取时间结果的结构体,参数tz用于保存时区结果,tz 参数若不使用则传入NULL即可。

gettimeofday() 使用举例-1:

1
2
3
4
struct timeval tv_begin, tv_end ;
gettimeofday(&tv_begin, NULL);
foo();
gettimeofday(&tv_end, NULL);

gettimeofday() 使用举例-2:

1
2
3
4
5
6
7
8
9
10
11
int main(int argc,char * argv[])
{
struct timeval tv;
while(1)
{
gettimeofday(&tv,NULL);
printf("time %u:%u\n",tv.tv_sec,tv.tv_usec);
sleep(2);
}
return 0;
}

源自:http://www.cnblogs.com/Neddy/archive/2012/01/31/2332957.html

SOCKET API IN SERVER

第一步 创建套接字

同client,略

第二步 监听套接字

int listen( SOCKET s, int maxcon );

maxcon 参数用于定义最大的阻塞数,如果阻塞值最高,则会发送一个 ECONNREFUSED 错误给客户端

  • listen() 监听套接字上的连接请求。为了连接请求,需要先由socket() 函数创建套接字。

  • listen() 函数用于等待设备接入并声明有限接入数的队列。

  • 新连接接入时需要调用 accept() 函数。

第三步 从套接字接收数据

int recv( SOCKET s, void *pbuf, int size, int flags );

pbuf 参数用于储存数据

size 为欲接收数据的大小

flags 为接收不到数据时的行为定义

FLAGS CONDITIONS
MSG_DONTWAIT Requests that the operation not block when no data is available
MSG_OOB Requests receipt of out-of-band data that would not be received in the normal data stream. Some protocols place expedited data at the head of the normal data queue, and thus, this flag cannot be used with such protocols.
MSG_PEEK Causes the receive operation to return data from the beginning of the receive queue without removing that data from the queue. Thus, a subsequent receive call will return the same data.
MSG_WAITALL Requests that the operation block until the full request is satisfied. However, the call may still return less data than requested if an error or disconnect occurs, or the next data to be received is of a different type than that returned.

SOCKET API IN SERVER (LINUX C)

第一步 创建套接字
1
2
int servSock;  //Socket descriptor for server
if( ( servSock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP ) ) < 0 ) printf("socket() failed.\n");
第二步 套接字地址初始化
1
2
3
4
5
struct sockaddr_in servAddr;
memset(&servAddr, 0, sizeof( servAddr ) );
servAddr.sin_family = AF_INET;
servAddr.sin_addr.s_addr = htonl( INADDR_ANY );
servAddr.sin_port = htons( servPort );
第三步 绑定套接字

注意: 客户端把服务器的地址提供给 connect() 以供连接至服务器,而 服务器 必须将自己的地址指定给 bind() 进行绑定。

1
if( ( bind( servSock, (struct sockaddr*) &servAddr, sizeof( servAddr ) ) ) < 0) printf("bind() failed.\n");
第四步 监听套接字
1
2
short MAXPENDING = 5;
if( ( listen( servSock, MAXPENDING ) ) < 0 ) printf("listen() failed.\n");
第四步 处理程序
1
2
3
4
5
6
7
8
9
10
11
12
    for(;;){
struct sockaaddr_in cintAddr;
socklen_t clntAddrLen = sizeof( clntAddr );
int clntSock = accept( servSock, (struct sockaddr*)&clntAddr, &clntAddrLen );
if( clntSock < 0 ) printf("accept() failed.\n");

char clntName[ INET_ADDRSTRLEN ];
if( inet_ntop( AF_INET, &clntAddr.sin_addr.s_addr, clntName, sizeof( clntName ) ) != NULL ) printf("Handling client %s/%d.\n", clntName, ntohs( clntAddr.sin_port ));
else printf("Unable to get client address.\n");

HandleTCPClient(clntSock);
}

Daemon API

DAEMON,即TCP/UDP Server Daemon Support。

A server daemon is a single network task that monitors the socket status of multiple network servers. When activity is detected, the daemon creates a task thread specifically to handle the new activity. This is more efficient than having multiple servers, each with their own listening thread.

要使用服务器守护,首先要创建入口(entry),创建成功时会返回一个句柄,失败则返回NULL。

1
2
void *DaemonNew(uint32_t Type, uint32_t LocalAddress, uint32_t LocalPort, int(*pCb)(SOCKET,uint32_t), 
uint32_t Priority, uint32_t StackSize, uint32_t Argument, uint32_t MaxSpawn);

Type Socket type (SOCK_STREAM, SOCK_STREAMNC, or SOCK_DGRAM) LocalAddress Local IP address (set to NULL for wildcard) LocalPort Local Port to serve (cannot be NULL) pCb Pointer to callback to handle server event (connection or activity) Priority Priority of new task to create for callback function StackSize Stack size of new task to create for callback function Argument Argument (besides socket) to pass to callback function MaxSpawn Maximum number of callback function instances (must be 1 for UDP)

在TCP环境中,当新连接稳定时,新任务线程会被创建,套接字Session会被打开。在新的任务线程上,用户的回调函数会被调用以供新连接上的套接字和调用的指定参数使用。回调函数可以一直维护套接字和任务线程。一旦完成连接它将从回调任务中返回。该任务能够判断是否关闭套接字(或翻译成:该任务能够在需要时关闭套接字)。

In the case of TCP, when a new connection is established, a new task thread is created, and a socket session is opened. Then the user's callback function is called on the new task thread, being supplied with both the socket to the new connection and the caller specified argument (as supplied to DaemonNew()). The callback function can keep the socket and task thread for as long as necessary. It returns from the callback once it is done with the connection. The function can choose to close the socket if desired. The return code informs the daemon whether the socket has been closed (0) or is still open (1).

NETCFG.H

CI means Configuration Item , and CFG means Configuration .

Data-type UINT32 and IPN are both defined in the header file <usertype.h> .

1
2
typedef unsigned int   UINT32;
typedef UINT32 IPN; // IP Address in NETWORK format

Structure CI_IPNET and structure CI_ROUTE are different from each other.

Structure CI_IPNET is as follow:

1
2
3
4
5
6
7
8
// IPNet Instance
typedef struct _ci_ipnet {
uint NetType; // 网络地址类型标志
IPN IPAddr; // 32bits地址((2^8)*4)
IPN IPMask; // 子网掩码
HANDLE hBind; // 绑定句柄
char Domain[CFG_DOMAIN_MAX]; // 域名
} CI_IPNET;

Structure CI_ROUTE is as follow:

1
2
3
4
5
6
7
// Route Instance
typedef struct _ci_route {
IPN IPDestAddr; // 目的地址
IPN IPDestMask; // 目的地址掩码
IPN IPGateAddr; // 默认网关地址
HANDLE hRoute; // Route handle (resets to NULL)
} CI_ROUTE;

CONFIGIF.H

请先阅读 <spru524k.pdf>

Configuration 特性

  • 任何对(已激活)配置的作用都将立即生效。

The configuration is based on an active database. That is, any change to the database can cause an immediate reaction in the system. For example, if a route is added to the configuration, it is added to the system route table. If the route is then removed from the configuration, it is removed from the system route table.

  • 配置存在激活与失效两种状态。

Configurations can be set active or inactive. When a configuration is active, any change to the configuration results in a change in the system. When a configuration is inactive, it behaves like a standard database. Part of the main initialization sequence is to make the system configuration active, and then inactive when shutting down.

  • 配置(Configurations)和配置入口(Configuration Entries)都使用句柄(handle)来映射,但不同的是,配置使用CfgHandle,而配置入口使用CfgEntryHandle,所以不能混淆。

Both the configurations and configuration entries are referenced by a generic handle. Configuration functions (named as CfgXxx()) take a configuration handle parameter, while configuration entry functions (name as CfgEntryXxx()) take a configuration entry handle parameter. These handles are not interchangeable.

  • 配置条目(Entry)包含着(contains) 一个内部引用计数(Internal Reference Count),即如果有任务想使用它,它就不能被其他任务销毁。配置条目被引用一次,引用计数就会加一。

Configuration entry handles are referenced. This means that each handle contains an internal reference count so that the handle is not destroyed by one task while another task expects it to stay valid. Functions that return a configuration entry handle supply a referenced handle in that its reference count has already been incremented for the caller.

  • 理论上句柄能够被无限持有,一旦释放则将被dereference。

The caller can hold this handle indefinitely, but should dereference it when it is through.

IF 是指 Interface。ifconfig 是unix系统上的ip接口查看语句。而 CONFIGIF 是配置管理接口的意思。

DeRef ,即 Dereference 之意。

1
_extern int  CfgAddEntry( HANDLE hCfg, uint Tag, uint Item, uint Mode, int Size, UINT8 *pData, HANDLE *phCfgEntry );

HANDLE hCfg

uint Tag

1
2
3
4
5
6
7
8
9
// Defined Configuration Tags
#define CFGTAG_OS 0x0001 // OS Configuration
#define CFGTAG_IP 0x0002 // IP Stack Configuration
#define CFGTAG_SERVICE 0x0003 // Service
#define CFGTAG_IPNET 0x0004 // IP Network
#define CFGTAG_ROUTE 0x0005 // Gateway Route
#define CFGTAG_CLIENT 0x0006 // DHCPS Client
#define CFGTAG_SYSINFO 0x0007 // System Information
#define CFGTAG_ACCT 0x0008 // User Account

uint Item

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//
// Currently Used DHCP Compatible Items
// Multiple instances are always to be stored as multiple config entries, not a concatenated byte string in a single config entry.
//
#define CFGITEM_DHCP_DOMAINNAMESERVER 6 // Stack's DNS servers
#define CFGITEM_DHCP_HOSTNAME 12 // Stack's host name
#define CFGITEM_DHCP_NBNS 44 // Stack's NBNS servers
#define CFGITEM_DHCP_CLIENT_OPTION 61 // Stack DHCP Client Identifier

#define CFGITEM_SYSINFO_REALM1 256 // Realm Name 1 (max 31 chars)
#define CFGITEM_SYSINFO_REALM2 257 // Realm Name 2 (max 31 chars)
#define CFGITEM_SYSINFO_REALM3 258 // Realm Name 3 (max 31 chars)
#define CFGITEM_SYSINFO_REALM4 259 // Realm Name 4 (max 31 chars)
#define CFGITEM_SYSINFO_REALMPPP 260 // Realm Name PPP (max 31 chars)
#define CFGITEM_SYSINFO_EVALCALLBACK 261 // Callback function to notify
// application 5 min before
// end of stack evaluation period

uint Mode

1
2
3
4
// Add Entry Flags
#define CFG_ADDMODE_UNIQUE 0x0001 // Replace all previous instances
#define CFG_ADDMODE_DUPLICATE 0x0002 // Allow duplicate data entry
#define CFG_ADDMODE_NOSAVE 0x0004 // Don't include this entry in CfgSave

Socket in Windows

Windows Sockets可以保证应用程序在任何支持Windows Sockets API 的网络内正常通信。

流式Socket:基于TCP,数据无差错且无重复发送;

数据报Socket:基于UDP,不能保证数据按发送顺序接收,可能丢失或重复。

真正与客户端Socket对象通信都不是服务器 Socket对象,而是新创建的“临时”Socket对象

构造函数:CAsyncSocket();

Create();【成功返回非0,失败返回0】 //SOCK_DGRAM 数据报

GetSockName(); //用于获取Socket对象的本地名称(自己的信息),ip地址及端口号,或

GetPeerName(); //用于获取连接的Socket对象名称(对方的信息)

Listen(); //面向流式使用,参数范围1~5,表示等待连接队列的最大长度

Accept(); //用以接收等待队列中存在的连接请求

Connect(); //建立连接请求函数

Send(); //

除字符型与布尔型外,其余整型用于表示(可能)不同尺寸的整数。

C++规定一个int(最小16bit)至少和一个short(16bit)一样大,一个long(最小32bit)至少和一个int一样大,一个long long(64bit)至少和一个long一样大。

其中,long long 是在C++ 11中新定义的。

尽管字符型char有三种(char、signed char、unsigned char),但是字符的表现形式只有2种,signed或unsigned。类型char实际上会表现为其中一种,具体由编译器决定。

字面值常量

20 //十进制

020 //0开头的为8进制

0x20 //0x开头的为16进制

SEND() BLOCKING

In some cases where send() would block, it instead returns without copying all of the data as requested.

In this case, the return value of send() indicates how many bytes were actually copied. One example of this is if your program is blocking on send() and then receives a signal from the operating system.

In these cases, it is up to caller to try again with any remaining data.

套接字及流输入输出接口(IO)

TOPIC

  • 文件描述符环境
  • 文件描述符编程接口
  • 套接字编程接口
  • 元以太网套接字编程接口
  • 全双工管道编程接口
  • 因特网群组管理协议(IGMP)

在各嵌入式系统中,对文件描述符的支持都大相径庭。大部分情况,都只支持基本功能(bare minimum functionality), 通常都以通用名称命名和提供被修剪过的函数(trimmed down support functions)。

TI NDK支持标准套接字接口函数,这些函数也要求文件描述符的支持,堆栈提供一个小型文件系统。

在堆栈代码内部的基本构建块是一个对象句柄。在其内部,套接字和管道都通过对象句柄寻址。然而,在应用层,套接字和管道都被当做文件描述符看待。文件描述符内涵附加状态信息(additional state information),允许根据套接字活动阻塞和解除阻塞任务。

注意:尽管文件描述符能够在传统函数中使用,如select(), 但在这种实现方式中,他们仍然是句柄,而不是整型。

出于兼容性考虑,网络程序必须使用NDK头文件,然后使用INVALID_SOCKET作为错误情况判定,并且在检查SOCKET有效性时,不要直接与(<0)比较。

使用文件描述符前,需要一个任务首先创建一个文件描述符表格(FD table / session)。只需要在应用层调用文件描述符函数 fdOpenSession() 来完成。

当任务结束使用FD接口,或被关闭时,调用 fdCloseSession()

为确保堆栈操作正确,每个任务在使用FD相关的函数前,都应创建FD session,在使用完毕后关闭它。

Open FD Session

  1. 最简单的方式就是用 TaskCreate() ,他可以(自动)在内部打开和关闭FD session。

  2. 另一种方式就是在函数调用的最开头进行session创建,在函数结束的末尾进行session关闭。如下:

1
2
3
4
5
6
7
8
9
void socket_task(int IPAddr, int TcpPort)
{
SOCKET s;
// Open the file session
fdOpenSession(TaskSelf());
// < socket application code >
// Close the file session
fdCloseSession(TaskSelf());
}
  1. 另一种方式就是创建子线程。注意的是,父线程需要保证子线程的任务被执行前打开子线程的session。可通过任务优先级或信标来完成,但会增加任务创建复杂度,并不是理想方案。
  2. 也可以通过让子任务调用session创建函数,并且由父线程来监控和关闭子线程门。

栈库支持一些通常被认为是文件函数的功能,因此套接字应用程序可以在更传统的意义上编程。

The stack library supports a handful of what are normally considered file functions, so that sockets applications can be programmed in a more traditional sense.

fdPoll() 远比 fdSelect() 来得更有效率。它轮询提供的套接字列表,并指定以毫秒为单位的超时(或使用POLLINFTIM 来设置无限超时)。拥有 fdSelect() 的优点,即对原始文件描述符列表(或者套接字)的检验不会被结果所改写,因此可以不用重建便多次使用。

1
2
3
4
5
typedef struct _fdpollitem {
void *fd; //the fd or socket to check
uint16_t eventsRequested; //a set of flags for requested events
uint16_t eventsDetected; //a set of resulting flags for a detected event
} FDPOLLITEM;

参考

  1. Michael Kerrisk