|
|
EDA365欢迎您登录!
您需要 登录 才可以下载或查看,没有帐号?注册
x
, K& q4 I" w- p/ M0 c功能描述:
1 H! u: D8 n* `+ N 获取或者设置与某个套接字关联的选 项。选项可能存在于多层协议中,它们总会出现在最上面的套接字层。当操作套接字选项时,选项位于的层和选项的名称必须给出。为了操作套接字层的选项,应该 将层的值指定为SOL_SOCKET。为了操作其它层的选项,控制选项的合适协议号必须给出。例如,为了表示一个选项由TCP协议解析,层应该设定为协议 号TCP。3 d- G" ^4 G7 d1 H
$ @* |& r! R0 N6 O5 o; J
5 ?) c; b+ B* C2 m4 p( W' c- S用法:( S: W: i. D% w+ R% U+ i
#include <sys/types.h>
m U0 } }' b6 j8 V2 l#include <sys/socket.h>
8 X" n/ L9 O; `8 {$ E' t$ p- K. q$ [/ w7 t R
int getsockopt(int sock, int level, int optname, void *optval, socklen_t *optlen);
3 V" \% M# d% o6 ]' l2 b
) h: w* g! z0 Sint setsockopt(int sock, int level, int optname, const void *optval, socklen_t optlen);. h8 u) e3 n# x
% }9 ?7 O. q5 W3 S参数: 1 v# T/ e# i" D2 p: S* W
sock:将要被设置或者获取选项的套接字。0 D3 H. y& P" `0 L4 F! j) f
level:选项所在的协议层。
+ D @1 h. r% m% l/ foptname:需要访问的选项名。目前仅支持SOL_SOCKET和IPPROTO_TCP层次。
$ I8 N" u6 G: t9 z7 p" K; x% soptval:对于getsockopt(),指向返回选项值的缓冲。对于setsockopt(),指向存放新选项值的缓冲。, a2 }# O4 _0 b5 ?+ v! K/ A# X
optlen:对于getsockopt(),作为入口参数时,选项值的最大长度。作为出口参数时,选项值的实际长度。对于setsockopt(),现选项的长度,optval缓冲区长度。
2 O& P0 T: q8 \' U+ R
0 O; n5 w k. A# ^* j; F4 x) Z2 T& Y" t2 v
返回说明: ) n6 d/ R2 O1 P" K5 l. O
( d& K; L; F) w5 h
9 d0 R4 B6 `* G0 {7 c9 S成功执行时,返回0。失败返回-1,errno被设为以下的某个值
% {4 I0 p8 e$ n% N+ f; rEBADF:sock不是有效的文件描述词
8 x4 U. k, f2 g7 R4 N' p0 s1 L% DEFAULT:optval指向的内存并非有效的进程空间
* t9 q( @! z) X+ hEINVAL:在调用setsockopt()时,optlen无效8 |- I' M6 x4 k, \/ O m* d6 g
ENOPROTOOPT:指定的协议层不能识别选项
# k% h6 G/ m# W# a2 b& B/ ~& xENOTSOCK:sock描述的不是套接字0 V) g# [& a& {; S* u/ X
6 P/ X3 w/ u5 x+ o0 Z
7 D1 x' `8 b/ b
参数详细说明:
- z+ v9 Z+ x2 j! w# Y! O, l
) {. Y* ]$ }8 t* Y( `2 R" X8 r: |level指定控制套接字的层次.可以取三种值:(即在哪一层对套接字进行控制)
* Q' I) u& N8 N) @1)SOL_SOCKET:通用套接字选项.8 _( R4 B$ P0 ]' c4 x$ K% q
2)IPPROTO_IP:IP选项.
: G; I! Z7 W$ D( E0 x7 z& i0 B/ J3)IPPROTO_TCP:TCP选项. 7 o9 r6 G5 c5 X# h( k9 b
optname指定控制的方式(选项的名称),我们下面详细解释 ) W" s* t8 ]% d* ]: a% ~
2 l" L0 q+ k* q$ j
optval获得或者是设置套接字选项.根据选项名称的数据类型进行转换 1 D4 U2 J. S$ d5 @
- `/ D2 H* e: O* X2 q* i $ H8 o1 D# y$ |7 o3 R
* l* N( h% E7 ], H* R
选项名称 说明 数据类型
' }0 F6 ?5 G( Z! O2 Z: j5 T========================================================================
( m' w0 }2 u" ?* W3 d9 E/ k# S7 S8 g SOL_SOCKET
2 `* `- x" c5 l------------------------------------------------------------------------2 }: I$ ~8 f# b3 D" p5 M
SO_BROADCAST 允许发送广播数据 int
3 w6 k9 x, o( m- x8 N! b! ESO_DEBUG 允许调试 int3 J1 r( Z2 O) J7 q. m
SO_DONTROUTE 不查找路由 int
`6 \/ C4 _0 q2 w" q9 JSO_ERROR 获得套接字错误 int6 ?4 q4 k- ]4 V2 o
SO_KEEPALIVE 保持连接 int; l* D9 O! \ t% ~' b
SO_LINGER 延迟关闭连接 struct linger
5 j" z ]- F' d7 oSO_OOBINLINE 带外数据放入正常数据流 int* @. h2 h/ ^# W) `1 J. p6 s
SO_RCVBUF 接收缓冲区大小 int
5 `: k& O+ R/ m+ L7 c: i( I* @SO_SNDBUF 发送缓冲区大小 int* `9 b O' [' J. B6 X) u
SO_RCVLOWAT 接收缓冲区下限 int
+ k9 r" C3 t/ ]" QSO_SNDLOWAT 发送缓冲区下限 int
# k. _: K+ n) J1 ~& h8 }" SSO_RCVTIMEO 接收超时 struct timeval6 D8 M. f0 _# D4 o4 f6 y7 `6 V$ a- S
SO_SNDTIMEO 发送超时 struct timeval
/ p% `/ f# f+ wSO_REUSERADDR 允许重用本地地址和端口 int
6 O4 v' t3 ^( W: ~SO_TYPE 获得套接字类型 int8 B/ T8 V6 h4 ]& ?
SO_BSDCOMPAT 与BSD系统兼容 int" C# n$ v! W) r$ o1 f7 q
========================================================================
/ q7 k* h' K3 x( L IPPROTO_IP
' b$ S. Z( s* L0 c/ _, U9 n------------------------------------------------------------------------
- A9 l4 a* }' K) H5 H& y( iIP_HDRINCL 在数据包中包含IP首部 int! m* q% r! R) K g/ A! x
IP_OPTINOS IP首部选项 int1 C9 p. z7 ]" ?" Z; o* z. d! X7 {
IP_TOS 服务类型
X% `! ?% e1 p$ IIP_TTL 生存时间 int
/ E% e" O7 C$ o8 W========================================================================
: Q u5 p6 u" F4 V* w IPPRO_TCP
3 R0 ~; m! L7 h------------------------------------------------------------------------
! ]) s9 u; x' k; ETCP_MAXSEG TCP最大数据段的大小 int+ a2 [" w) z; e/ ~% J
TCP_NODELAY 不使用Nagle算法 int0 M( [) c7 k+ {+ D+ d8 z
========================================================================
/ H; E: S0 z k( L3 B4 e
1 [, z% O2 @$ S6 {$ c" z8 g
0 v E6 W1 ^# W3 G! _* k7 a. X7 C- e I- C9 Z- U
setsockopt()函数用于任意类型、任意状态套接口的设置选项值。尽管在不同协议层上存在选项,但本函数仅定义了最高的“套接口”层次上的选项。选项影响套接口的操作,诸如加急数据是否在普通数据流中接收,广播数据是否可以从套接口发送等等。
: Z" E& ^3 B& c. `0 c6 z& T% G a 有两种套接口的选项:一种是布尔型选项,允许或禁止一种特性;另一种是整形或结构选项。允许一个布尔型选项,则将optval指向非零整形数;禁止一个选项optval指向一个等于零的整形数。对于布尔型选项,optlen应等于sizeof(int);对其他选项,optval指向包含所需选项的整形数或结构,而optlen则为整形数或结构的长度。SO_LINGER选项用于控制下述情况的行动:套接口上有排队的待发送数据,且closesocket()调用已执行。参见closesocket()函数中关于SO_LINGER选项对closesocket()语义的影响。应用程序通过创建一个linger结构来设置相应的操作特性:( t8 z9 N+ n, J7 p" @0 i
struct linger {
5 F/ p: `) a% [; ~- Y8 L$ yint l_onoff;& e0 n. j6 X0 Q; J1 H8 v0 I$ D6 [/ U
int l_linger;
: R x) ?: ^) c; ^/ P1 X$ r1 S };
. o% t* |% x+ S6 r' R& ?5 \ 为了允许SO_LINGER,应用程序应将l_onoff设为非零,将l_linger设为零或需要的超时值(以秒为单位),然后调用setsockopt()。为了允许SO_DONTLINGER(亦即禁止SO_LINGER),l_onoff应设为零,然后调用setsockopt()。
3 S" w. B( l; a& n1 w: D# S 缺省条件下,一个套接口不能与一个已在使用中的本地地址捆绑(参见bind())。但有时会需要“重用”地址。因为每一个连接都由本地地址和远端地址的组合唯一确定,所以只要远端地址不同,两个套接口与一个地址捆绑并无大碍。为了通知WINDOWS套接口实现不要因为一个地址已被一个套接口使用就不让它与另一个套接口捆绑,应用程序可在bind()调用前先设置SO_REUSEADDR选项。请注意仅在bind()调用时该选项才被解释;故此无需(但也无害)将一个不会共用地址的套接口设置该选项,或者在bind()对这个或其他套接口无影响情况下设置或清除这一选项。 g9 [, A+ \$ S$ u& P! _
一个应用程序可以通过打开SO_KEEPALIVE选项,使得WINDOWS套接口实现在TCP连接情况下允许使用“保持活动”包。一个WINDOWS套接口实现并不是必需支持“保持活动”,但是如果支持的话,具体的语义将与实现有关,应遵守RFC1122“Internet主机要求-通讯层”中第4.2.3.6节的规范。如果有关连接由于“保持活动”而失效,则进行中的任何对该套接口的调用都将以WSAENETRESET错误返回,后续的任何调用将以WSAENOTCONN错误返回。4 Z: m- K# G, e/ w
TCP_NODELAY选项禁止Nagle算法。Nagle算法通过将未确认的数据存入缓冲区直到蓄足一个包一起发送的方法,来减少主机发送的零碎小数据包的数目。但对于某些应用来说,这种算法将降低系统性能。所以TCP_NODELAY可用来将此算法关闭。应用程序编写者只有在确切了解它的效果并确实需要的情况下,才设置TCP_NODELAY选项,因为设置后对网络性能有明显的负面影响。TCP_NODELAY是唯一使用IPPROTO_TCP层的选项,其他所有选项都使用SOL_SOCKET层。
; \/ D& D0 d7 ` 如果设置了SO_DEBUG选项,WINDOWS套接口供应商被鼓励(但不是必需)提供输出相应的调试信息。但产生调试信息的机制以及调试信息的形式已超出本规范的讨论范围1 q S& H/ i- W' c
6 `8 s8 G; o/ K5 y: s5 y. Y 2 g \4 q, f$ {& m4 b5 N0 H, s
/ w1 E+ d5 G1 g* l9 m2 I" a7 F( |+ b
如:SO_RCVBUF和SO_SNDBUF每个套接口都有一个发送缓冲区和一个接收缓冲区,使用这两个套接口选项可以改变缺省缓冲区大小。2 }" }3 @8 E6 ~% n( c( V
! ^! _; h- c5 ?+ h// 接收缓冲区, }' m& u) S* a( _+ Y0 r
int nRecvBuf=32*1024; //设置为32K
2 O( O+ |5 h/ u7 w0 M( b+ esetsockopt(s,SOL_SOCKET,SO_RCVBUF,(const char*)&nRecvBuf,sizeof(int));
& w6 T g/ J( |) q& i3 p' K0 K4 j2 d, H# j5 M5 }
8 q8 X& L2 w( l5 K$ `* q//发送缓冲区; z( P) c$ A* x8 I6 G; r8 _* _% F
int nSendBuf=32*1024;//设置为32K
|* T# I- P4 G% _setsockopt(s,SOL_SOCKET,SO_SNDBUF,(const char*)&nSendBuf,sizeof(int));
6 e1 J5 Y0 X, C3 l' _, m- m, V5 d# A/ v7 w8 ]
注意:- C3 p0 d0 a( y6 r0 s! G
1 S+ P. E. K3 U7 y) B 当设置TCP套接口接收缓冲区的大小时,函数调用顺序是很重要的,因为TCP的窗口规模选项(窗口大小表示客户端上用来存储从服务器发送来的传入段的缓冲区的大小)是在建立连接时用SYN与对方互换得到的。对于客户,SO_RCVBUF选项必须在connect之前设置;对于服务器,SO_RCVBUF选项必须在listen前设置。* o( b6 J _2 R8 v9 g
U1 s f# p& p5 g结合原理说明:1 P0 |0 `1 k% n( X8 ~
% P! ]6 w6 G B% q$ ~ n
1.每个套接口都有一个发送缓冲区和一个接收缓冲区。 接收缓冲区被TCP和UDP用来将接收到的数据一直保存到由应用进程来读。 TCP:TCP向另一端通告自己的窗口大小。 TCP套接口接收缓冲区不可能溢出,因为对方不允许发出超过所通告窗口大小的数据。 这就是TCP的流量控制,如果对方无视窗口大小而发出了超过窗口大小的数据,则接 收方TCP将丢弃它。 UDP:当接收到的数据报装不进套接口接收缓冲区时,此数据报就被丢弃。UDP是没有流量控制的;快的发送者可以很容易地就淹没慢的接收者,导致接收方的UDP丢弃数据报。
- f7 X# d7 @2 E* W( ~, `* J 2.我们经常听说tcp协议的三次握手,但三次握手到底是什么,其细节是什么,为什么要这么做呢?% m8 P- M0 P! [/ ]- y- v' g
第一次:客户端发送连接请求给服务器,服务器接收;3 S/ W, H7 E9 Q1 j
第二次:服务器返回给客户端一个确认码,附带一个从服务器到客户端的连接请求,客户机接收,确认客户端到服务器的连接.7 F2 X" I( ?! E$ Z# O
第三次:客户机返回服务器上次发送请求的确认码,服务器接收,确认服务器到客户端的连接.4 Q2 h/ P; R- z& ?6 X; m5 N
我们可以看到:# T R2 W3 N# `1 t3 B8 j& z
1. tcp的每个连接都需要确认.8 l+ Q n6 ]9 \& c0 X
2. 客户端到服务器和服务器到客户端的连接是独立的.
. f6 _# i$ o( n2 ` 我们再想想tcp协议的特点:连接的,可靠的,全双工的,实际上tcp的三次握手正是为了保证这些特性的实现.
' u" I6 z; i) J
. L/ U. x, G2 h/ L4 m5 c/ {4 bsetsockopt的用法
7 {# q& X; G. X$ @3 S" C! W
5 m& ?% [! M4 i$ ?" T) z1 R! d7 W1.closesocket(一般不会立即关闭而经历TIME_WAIT的过程)后想继续重用该socket:( u y, @' ]* `6 L8 ?
BOOL bReuseaddr=TRUE;
1 O0 C% h5 D/ V" \setsockopt(s,SOL_SOCKET ,SO_REUSEADDR,(const char*)&bReuseaddr,sizeof(BOOL));6 g$ q: F, N2 V) T
8 \' O0 k6 S0 N: S% G Y% C* s4 h0 j4 ~; ^( i5 w+ G+ W* y- I7 i
2. 如果要已经处于连接状态的soket在调用closesocket后强制关闭,不经历TIME_WAIT的过程:. y( J/ T( c) M8 J8 N! A+ r4 b
BOOL bDontLinger = FALSE;
1 p' {) Z# R% R) B! t& W7 u! l1 [setsockopt(s,SOL_SOCKET,SO_DONTLINGER,(const char*)&bDontLinger,sizeof(BOOL));
! D( x3 u) @$ T9 z) J6 D9 f2 N* l4 j& o+ \6 e9 I' t- e, w q
8 g6 |4 j8 N; E# p. f7 M/ r3.在send(),recv()过程中有时由于网络状况等原因,发收不能预期进行,而设置收发时限:* s1 e- \% {( O/ |+ Q6 \8 d% K# z$ N
int nNetTimeout=1000;//1秒
9 Y5 V2 z$ k& H- u+ T, C( `//发送时限
4 w) Z6 v# Z) ]% v- z! Q9 y- Nsetsockopt(socket,SOL_S0CKET,SO_SNDTIMEO,(char *)&nNetTimeout,sizeof(int));
) Q- c# d0 @" J2 w8 H//接收时限
* g& v% N1 Z* ~- B- W) Ksetsockopt(socket,SOL_S0CKET,SO_RCVTIMEO,(char *)&nNetTimeout,sizeof(int));" e. w1 ^: [$ g. N. I
5 `7 ~1 m) w$ L2 D9 E) L$ b
/ q9 Q e. \& f) |7 B( j; b! Y4.在send()的时候,返回的是实际发送出去的字节(同步)或发送到socket缓冲区的字节
3 ~( p ~; R/ G$ C: i(异步);系统默认的状态发送和接收一次为8688字节(约为8.5K);在实际的过程中发送数据; L- _9 p5 b' f9 E
和接收数据量比较大,可以设置socket缓冲区,而避免了send(),recv()不断的循环收发:
K4 S) W4 s- s8 w; I" f. w. j// 接收缓冲区8 S7 d) [8 H7 F2 y( P9 o% t
int nRecvBuf=32*1024;//设置为32K
5 m1 E6 b9 U+ qsetsockopt(s,SOL_SOCKET,SO_RCVBUF,(const char*)&nRecvBuf,sizeof(int));
/ r5 U& ]7 e6 }& o//发送缓冲区
& x% T& P4 T2 F! B5 iint nSendBuf=32*1024;//设置为32K
Q; T) t& V# j" {2 Qsetsockopt(s,SOL_SOCKET,SO_SNDBUF,(const char*)&nSendBuf,sizeof(int));$ ?0 u: C9 i% a. |. W+ B
/ }8 O$ W) l, J) M1 _: F1 ?- @. l; A
5 k5 A3 I) ?8 C% h5 s5. 如果在发送数据的时,希望不经历由系统缓冲区到socket缓冲区的拷贝而影响0 b7 Y! d6 R g* [' ]0 X; q6 C
程序的性能:
5 N, \ u3 \" k8 J% fint nZero=0;
, N# _/ x) z) P" o5 ^- M- z2 Nsetsockopt(socket,SOL_S0CKET,SO_SNDBUF,(char *)&nZero,sizeof(nZero));
7 p8 _7 g1 W# i3 D7 ?
\2 n, {7 B9 Q& C" ]" l: B% G! z; u/ ^: k* C! S7 @4 Q. c
6.同上在recv()完成上述功能(默认情况是将socket缓冲区的内容拷贝到系统缓冲区):; h. z+ H% [" e7 S7 n
int nZero=0;: |6 [( O& o8 z! i
setsockopt(socket,SOL_S0CKET,SO_RCVBUF,(char *)&nZero,sizeof(int));
0 \, [8 H' [" ^& D! w! F& i4 m: S
. \* f; u' T/ J; z I
7.一般在发送UDP数据报的时候,希望该socket发送的数据具有广播特性:9 O1 O a" _) D# D4 q$ d
BOOL bBroadcast=TRUE;8 L0 |9 T; ?! Q2 W% ~, n( b* m
setsockopt(s,SOL_SOCKET,SO_BROADCAST,(const char*)&bBroadcast,sizeof(BOOL));, E, [/ M8 k3 c- Y
4 h' F3 I: R( R" F$ k. b
+ m+ H) O2 C& @" x7 f3 f
8.在client连接服务器过程中,如果处于非阻塞模式下的socket在connect()的过程中可以设置connect()延时,直到accpet()被呼叫(本函数设置只有在非阻塞的过程中有显著的作用,在阻塞的函数调用中作用不大)
" L# A* c- h G6 B1 f1 HBOOL bConditionalAccept=TRUE;
+ g& }: b0 q3 g, Qsetsockopt(s,SOL_SOCKET,SO_CONDITIONAL_ACCEPT,(const char*)&bConditionalAccept,sizeof(BOOL));
6 N% Y( G. ]5 B4 ]" d
, J( ^( v7 f. H# Y* Z4 s4 a+ l! e4 @/ O% n0 O& C. w+ f
9.如果在发送数据的过程中(send()没有完成,还有数据没发送)而调用了closesocket(),以前我们一般采取的措施是"从容关闭"shutdown(s,SD_BOTH),但是数据是肯定丢失了,如何设置让程序满足具体应用的要求(即让没发完的数据发送出去后在关闭socket)?) j+ ^# G% `2 \' T. g* J" ]) Q! q
struct linger {
0 B- t8 D6 k4 J+ \& y) c: T$ su_short l_onoff;! N+ d& U- S0 U6 J# J; X# {/ ^
u_short l_linger;$ t: C2 K9 S$ M& |1 L7 ?* i, G
};
2 O+ b% k$ e: d5 g# J+ Zlinger m_sLinger;" }8 L) Q% l9 _# e8 T6 r
m_sLinger.l_onoff=1;//(在closesocket()调用,但是还有数据没发送完毕的时候容许逗留)
1 R+ M: S1 O+ {4 H5 I8 h2 E// 如果m_sLinger.l_onoff=0;则功能和2.)作用相同;( l) ^4 n r' c5 G
m_sLinger.l_linger=5;//(容许逗留的时间为5秒), [0 ?+ i& d8 C# M
setsockopt(s,SOL_SOCKET,SO_LINGER,(const char*)&m_sLinger,sizeof(linger)); |
|