1. 程式人生 > >Socket學習記錄

Socket學習記錄

Soket網路程式設計學習記錄

文章目錄

TCP狀態

在這裡插入圖片描述
closing狀態產生

SIGPIPE

  • 往一個已經接收FIN的套接字中寫是允許的,接收到FIN僅僅代表對方不再發送資料。
  • 在收到RST段之後,如果再呼叫write就會產生SIGPIPE訊號,對於這個訊號的處理通常忽略即可。
    signal(SIGPIPE,SIG_IGN)

網路地址

#include <netinet/in.h>
struct sockaddr_in {
   sa_family_t    sin_family; /* address family: AF_INET */
   in_port_t      sin_port;   /* port in network byte order */
   struct in_addr sin_addr;   /* internet address */
};
struct sockaddr {
   sa_family_t sa_family;
   char        sa_data[14];
}

/* Internet address. */
struct in_addr {
   uint32_t       s_addr;     /* address in network byte order */
};

位元組序

主機位元組序

不同主機擁有不同的位元組序,主要是大端位元組序或者小端位元組序,windows主要是小端位元組序

網路位元組序

網路位元組序規定為大端位元組序

位元組序轉換函式

#include <arpa/inet.h>

uint32_t htonl(uint32_t hostlong);

uint16_t htons(uint16_t hostshort);

uint32_t ntohl(uint32_t netlong);

uint16_t ntohs(uint16_t netshort);
介紹
htonl主機位元組序轉換成網路位元組序,long

htons主機位元組序轉換成網路位元組序,short

ntohl網路位元組序轉換成主機位元組序,long

ntohs網路位元組序轉換為主機位元組序,short

地址轉換函式

#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>


INET_ADDRSTRLEN - 16


int inet_aton(const char *cp, struct in_addr *inp);

in_addr_t inet_addr(const char *cp);

in_addr_t inet_network(const char *cp);

char *inet_ntoa(struct in_addr in);

struct in_addr inet_makeaddr(in_addr_t net, in_addr_t host);

in_addr_t inet_lnaof(struct in_addr in);

in_addr_t inet_netof(struct in_addr in);

介紹
inet_aton將點分十進位制的IP地址轉換成十進位制(網路位元組序),儲存在inp指標物件中。返回非0值表示成功,0表示失敗。

inet_aton返回值為1表示解析成功,返回值為0表示失敗。


inet_addr將點分十進位制IPV4字串轉換成十進位制網路位元組序。輸入不合法,返回值為INADDR_NONE(-1)。使用該函式需要注意255.255.255.255表示一個不合法地址。使用inet_aton,inet_pton或者getaddrinfo來避免這一個問題,這些函式提供了清晰地錯誤返回。

inet_lnaof區域性網路地址部分返回網際網路地址。返回的值按主機位元組順序排列。

typedef uint32_t in_addr_t;

struct in_addr {
   in_addr_t s_addr;
};
#include <arpa/inet.h>

int inet_pton(int af, const char *src, void *dst);


const char *inet_ntop(int af, const void *src,char *dst, socklen_t size);


介紹
inet_pton:成功返回1,src非法返回0,出錯返回-1
inet_ntop:成功返回非NULL值,失敗返回NULL值
size表示dst的長度

協議 地址型別
AF_INET struct in_addr
AF_INET6 struct in6_addr

套接字主要型別

  • 流式套接字(SOCK_STREAM)提供面向連線的、可靠的資料傳輸服務,資料無差錯,無重複的傳送,且按傳送順序接收
  • 資料報式套接字(SOKC_DGRAM)
    提供無連線服務。不提供無措保證,資料可能丟失或重複,並且接收順序混亂。
  • 原始套接字(SOCK_RAW)

socket函式

#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>

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

介紹
socket建立通訊端點並返回描述符。

domain指定通訊域,並選擇進行通訊的協議。<sys/socket.h>。

套接字具有指定點分型別,該型別指定通訊語義。

 
protocol指定用於socket的特定協議.通常只有一個協議對應socket的對應協議族,
在這種情況下,協議可以指定為0。但是某些協議族可能對應多種協議,這時需要
指定協議.   The  protocol number to use is specific to the “communication
domain” in which communication is to take place; see protocols(5).  See
getprotoent(3) on how to map protocol name strings to protocol numbers.

返回值
成功:檔案描述符
失敗:-1
domian Name Purpose Man page
AF_UNIX, AF_LOCAL Unix域協議 unix(7)
AF_INET IPv4 ip(7)
AF_INET6 IPv6 ipv6(7)
AF_IPX IPX - Novell protocols
AF_NETLINK Kernel user interface device netlink(7)
AF_X25 ITU-T X.25 / ISO-8208 protocol x25(7)
AF_AX25 Amateur radio AX.25 protocol
AF_ATMPVC Access to raw ATM PVCs
AF_APPLETALK AppleTalk ddp(7)
AF_PACKET Low level packet interface packet(7)
AF_ALG Interface to kernel crypto API
type 介紹
SOCK_STREAM 位元組流套接字
SOCK_DGRAM 資料報套接字
SOCK_RAW 原始套接字
SOCK_RDM Provides a reliable datagram layer that does not guarantee ordering.
protocol 說明
IPPROTO_TCP TCP傳輸協議
IPPROTO_UDP UDP傳輸協議
IPPROTO_SCTP SCTP傳輸協議
型別 AF_INET AF_INET6 AF_LOCAL AF_ROUTE AF_KEY
SOCK_STREAM TCP/SCTP TCP/SCTP - -
SOCK_DGRAM UDP UDP - -
SOCK_SEQPACKET SCTP SCTP - -
SOCK_RAW IPV4 IPV6 -

bind函式

#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>

int bind(int sockfd, const struct sockaddr *addr,socklen_t addrlen);

介紹

struct sockaddr {
   sa_family_t sa_family;
   char        sa_data[14];
}
返回值:
成功:0
失敗:-1

memset/bzero函式

#include <string.h>

void *memset(void *s, int c, size_t n);
void bzero(void *s, size_t n);

返回值
s的指標

listen函式

#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>

int listen(int sockfd, int backlog);

介紹
backlog為連個佇列的連線總數

SOMAXCONN:128 表示連線佇列長度
返回值
成功:0
失敗:-1

listen函式應該在socket和bind函式呼叫之後,accept函式呼叫之前呼叫

對於給定的監聽套介面,核心需要維護兩個佇列:

  • 已由客戶端發出併到達伺服器,伺服器正在等待完成相應TCP三次握手過程
  • 已完成連線的佇列

accept函式

 #include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>

int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);

介紹
如果sockfd為非阻塞,而且連線佇列中沒有連線,errno值設定為EAGAIN或EWOULDBLOCK.


返回值
成功:檔案描述符
失敗:-1

getsockopt/setsockopt

#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>

int getsockopt(int sockfd, int level, int optname,void *optval, socklen_t *optlen);
int setsockopt(int sockfd, int level, int optname,const void *optval, socklen_t optlen);

介紹
optval是指向某個變數的指標,socklen_t表示該變數的長度u32_int

返回值
成功:0
失敗:-1

SOL_SOCKET

選項名稱 說明 資料型別
SO_BROADCAST 允許傳送廣播資料 int
SO_DEBUG 允許除錯 int
SO_DONTROUTE 不查詢路由 int
SO_ERROR 獲得套接字錯誤 int
SO_KEEPALIVE 週期性的測試連線是否存活 int
SO_LINGER 若有資料 struct linger
SO_OOBINLINE 帶外資料放入正常資料流 int
SO_RCVBUF 接收緩衝區大小 int
SO_SNDBUF 傳送緩衝區大小 int
SO_RCVLOWAT 接收緩衝區下限 int
SO_SNDLOWAT 傳送緩衝區下限 int
SO_RCVTIMEO 接收超時 struct timeval
SO_SNDTIMEO 傳送超時 struct timeval
SO_REUSERADDR 允許重用本地地址和埠 int
SO_TYPE 獲得套接字型別 int
SO_BSDCOMPAT 與BSD系統相容 int

SO_BROADCAST

本選項開啟或者禁止程序傳送廣播的能力。只有資料報套接字支援廣播,並且還必須是在支援廣播訊息的網路上。

SO_DEBUG

本選項支援TCP。當給一個TCP套接字開啟本選項時,核心將為TCP在該套接字傳送和接收所有分組保留詳細的跟蹤資訊。這些資訊將儲存在核心的某個環形緩衝區中,並可使用trpt程式進行檢查。

SO_DONTROUTE

規定外出的分組將繞過底層協議的正常路由機制。舉例來說IPv4情況下外出分組將被定向到適當的本地介面,也就是由其目的地址的網路和子網部分確定的本地介面。如果這樣的本地介面無法由目的地址確定,那麼返回ENETUNREACH錯誤。

給函式send、sendto或sendmsg使用MSG_DONTROUTE標誌也能在個別的資料報上取得與本地選項相同的效果。

路由守護程序(routed和gated)經常使用本選項來繞過路由表,以強制將分組從特定介面送出

SO_ERROR

只能獲取不能設定該欄位

一個套接字上發生錯誤時,核心中的協議模組將該套接字的名為so_error的變數設定為標準Unix Exxx值中的一個,我們稱它為該套接字的待處理錯誤。

  • 如果程序阻塞在對該套接字的select呼叫上,無論是檢查可讀條件還是可寫條件,select軍返回並設定其中一個或所有兩個條件
  • 如果程序使用訊號驅動IO模型,就會給程序組產生一個SIGIO訊號

程序然後可以通過訪問SO_ERROR套接字選項獲取so_error值。由getsockopt返回整數值就是該套接字的待處理錯誤。so_error隨後由核心復位為0 。
當程序呼叫read且沒有資料返回時,如果so_error值為非0,那麼read返回-1,且errno被置為so_error的值。so_error隨後被複位為0.如果該套接字上有資料在排隊等待讀取,那麼read返回那些資料而不是返回錯誤條件。如果在程序呼叫write時so_error為非0值,那麼write返回-1且errno被設為so_error值。so_error隨後被複位為0 。

SO_KEEPALIVE

TCP Keepalive HOWTO

SO_KEEPALIVE 保持連線檢測對方主機是否崩潰,避免(伺服器)永遠阻塞於TCP連線的輸入。

設定該選項後,如果2小時內在此套介面的任一方向都沒有資料交換,TCP就自動給對方 發一個保持存活探測分節(keepalive probe)。這是一個對方必須響應的TCP分節.它會導致以下三種情況:

  • 對方接收一切正常:以期望的ACK響應,2小時後,TCP將發出另一個探測分節。

  • 對方已崩潰且已重新啟動:以RST響應。套介面的待處理錯誤被置為ECONNRESET,套接 口本身則被關閉。

  • 對方無任何響應:源自berkeley的TCP傳送另外8個探測分節,相隔75秒一個,試圖得到一個響應。在發出第一個探測分節11分鐘15秒後若仍無響應就放棄。套介面的待處理錯誤被置為ETIMEOUT,套介面本身則被關閉。如ICMP錯誤是“host unreachable(主機不可達)”,說明對方主機並沒有崩潰,但是不可達,這種情況下待處理錯誤被置為 EHOSTUNREACH。

有關SO_KEEPALIVE的三個引數詳細解釋如下:

  • tcp_keepalive_intvl,保活探測訊息的傳送頻率。預設值為75s。傳送頻率tcp_keepalive_intvl乘以傳送次數tcp_keepalive_probes,就得到了從開始探測直到放棄探測確定連線斷開的時間,大約為11min。
  • tcp_keepalive_probes,TCP傳送保活探測訊息以確定連線是否已斷開的次數。預設值為9(次)。
    注意:只有設定了SO_KEEPALIVE套介面選項後才會傳送保活探測訊息。
  • tcp_keepalive_time,在TCP保活開啟的情況下,最後一次資料交換到TCP傳送第一個保活探測訊息的時間,即允許的持續空閒時間。預設值為7200s(2h)。
SOL_TCP

TCP_KEEPCNT->tcp_keepalive_probes

TCP_KEEPIDLE->tcp_keepalive_time

TCP_KEEPINTVL->tcp_keepalive_intvl

檢測各種TCP條件的方法

情形 對端程序崩潰 對端主機崩潰 對端主機不可達
本端TCP正主動傳送資料 對端TCP傳送一個FIN,這通過使用select判斷可讀條件立即能檢測數來。如果本端TCP傳送另一個分節,對端TCP以RST響應。如果在本端TCP收到RST後應用程序仍然試圖寫套接字,套接字實現會給該程序傳送一個SIGPIPE訊號 本端TCP將超市,且套接字的待處理錯誤被設定成ETIMEOUT 本端TCP將超時,且套接字的待處理錯誤被設定為EHOSTUNRECH
本端TCP正主動接收資料 對端TCP將傳送一個FIN,我們將把它作為一個EOF讀入 停止接收資料 停止接收資料
連線空閒,保持存活選項已設定 對端TCP傳送一個FIN,這通過使用select判斷可讀條件立即能檢測 在毫無動靜2h之後,傳送9個保持存活探測節,然後套接字的待處理錯誤設定為ETIMEOUT 在毫無動靜2h之後,傳送9個保持存活探測節,然後套接字的待處理錯誤設定為EHOSTUNRECH
連線空閒,保持存活未設定 對端TCP傳送一個FIN,這通過使用select判斷可讀條件立即能檢測出來 (無) (無)

SO_LINGER

指定close函式面向連線協議如何操作。預設操作是close立即返回,但是如果有殘留的資料在套接字傳送緩衝區,系統將試著把這些資料傳送給對端。

struct linger{
    int l_onoff;
    int l_linger
}
函式 說明
shutdown,SHUT_RD 在套接字上不能再發出接收請求;程序可以往套接字傳送資料;套接字接收緩衝中所有資料丟棄;在接收到任何資料由TCP丟棄;對套接字傳送緩衝區沒有任何影響。
shutdown,SHUT_WR 在套接字上不能再發出發送請求,程序可從套接字接收資料,套接字傳送緩衝區中的內容被髮送到對端,後跟正常的TCP連線終止序列,對套接字接收緩衝區沒有任何影響
close,l_onoff=0(預設情況) 在套接字上不能再發出發送或者接收請求;套接字傳送緩衝區的內容被髮送到對端。如果描述符引用計數為0:在傳送完傳送緩衝區中的資料後,跟著傳送正常的TCP終止序列;套接字接收緩衝區中的內容被丟棄。
close,l_onoff=1,l_linger=0 在套接字上不能再發送或者接收請求。如果描述符引用計數變為0;RST被髮送給對端;連線的狀態被置為CLOSED(沒有TIME_WAIT狀態);套接字傳送緩衝區和接收緩衝區資料都被丟棄
close,l_onoff=1,l_linger!=0 在套接字上不能再發送或接收請求;套接字傳送緩衝區中的資料被髮送到對端。如果描述符引用計數變為0:在傳送完傳送緩衝區中的資料後,跟以正常的TCP連線終止序列;套接字接收緩衝區資料中資料被丟失;如果在連線變為CLOSED狀態前延滯時間到,那麼close返回EWOULDBLOCK錯誤

SO_RCVBUF&SO_SNDBUF

每個套介面都有一個傳送緩衝區和一個接收緩衝區,使用SO_SNDBUF & SO_RCVBUF可以改變預設緩衝區大小。

對於客戶端,SO_RCVBUF選項須在connect之前設定.
對於伺服器,SO_RCVBUF選項須在listen前設定.

接收緩衝區把資料快取入核心,應用程序一直沒有呼叫read進行讀取的話,此資料會一直快取在相應socket的接收緩衝區內。read所做的工作,就是把核心緩衝區中的資料拷貝到應用層使用者的buffer裡面,僅此而已。

接收緩衝區被TCP和UDP用來快取網路上來的資料,一直儲存到應用程序讀走為止。
TCP
對於TCP,如果應用程序一直沒有讀取,buffer滿了之後,發生的動作是:通知對端TCP協議中的視窗關閉。這個便是滑動視窗的實現。
保證TCP套介面接收緩衝區不會溢位,從而保證了TCP是可靠傳輸。因為對方不允許發出超過所通告視窗大小的資料。 這就是TCP的流量控制,如果對方無視視窗大小而發出了超過視窗大小的資料,則接收方TCP將丟棄它。
UDP
當套介面接收緩衝區滿時,新來的資料報無法進入接收緩衝區,此資料報就被丟棄。UDP是沒有流量控制的;快的傳送者可以很容易地就淹沒慢的接收者,導致接收方的UDP丟棄資料報。

SO_RCVLOWAT&SOSNDLOWAT

傳送低水位和接收低水位,由select函式使用。
接收低水位表示select返回“可讀”時套接字接收緩衝區所需的資料量,TCP/UDP預設值為1
傳送低水位表示select返回“可寫”時套接字傳送緩衝區所需的可用空間。對於TCP套接字淶水,預設值為2048

SO_RCVTIMEO&SO_SNDTIMEO

struct timeval{
     time_t    tv_sec;   //秒
     suseconds_t  tv_usec; //微秒:百萬分之一秒
};

套接字的接收和傳送設定一個超時值。可以使用秒和微秒規定超時。設定值為0秒和0微秒禁止超時。預設兩個超時都是禁止的。
接收超時影響5個輸入函式:read,readv,recv,recvfrom,recvmsg
傳送超時影響5個輸出函式:write,writev,send,sendto,sendmsg

SO_REUSEADDR

  • 伺服器端儘可能使用REUSEADDR
  • 在繫結之前儘可能呼叫setsockopt設定REUSEADDR套接字選項
  • 使用REUSEADDR選項可以使得不必等待TIME_WAIT狀態消失就可以了重啟伺服器

SO_REUSEADDR可以用在以下四種情況下。
(摘自《Unix網路程式設計》卷一,即UNPv1)

  1. 當有一個有相同本地地址和埠的socket1處於TIME_WAIT狀態時,而你啟
    動的程式的socket2要佔用該地址和埠,你的程式就要用到該選項。
  2. SO_REUSEADDR允許同一port上啟動同一伺服器的多個例項(多個程序)。但
    每個例項繫結的IP地址是不能相同的。在有多塊網絡卡或用IP Alias技術的機器可
    以測試這種情況。
  3. SO_REUSEADDR允許單個程序繫結相同的埠到多個socket上,但每個soc
    ket繫結的ip地址不同。這和2很相似,區別請看UNPv1。
  4. SO_REUSEADDR允許完全相同的地址和埠的重複繫結。但這隻用於UDP的

IPPROTO_IP

選項名稱 說明 資料型別
IP_HDRINCL 在資料包中包含IP首部 int
IP_OPTINOS IP首部選項 int
IP_TOS 服務型別
IP_TTL 生存時間 int

IPPRO_TCP

選項名稱 說明 資料型別
TCP_MAXSEG TCP最大資料段的大小 int
TCP_NODELAY 不使用Nagle演算法 int

粘包問題
本質是要在應用層維護訊息與訊息的邊界

  • 定長包
  • 包尾加\r\n(ftp)
  • 包頭加上包體長度
  • 更復雜的應用層協議

readn/writen

ssize_t readn(int fd,void* buf,size_t count){
	size_t nleft = count;
	ssize_t nread;
	char* bufp = (char*)buf;
	while(nleft>0){
		if((nread = read(fd,bufp,nleft))<0){
			if(errno == EINTR){
				continue;
			}
			return -1;
		}else if(nread==0){
			return count-nleft;
		}
		bufp += nread;
		nleft -=nread;
	}
	return count;
}

ssize_t writen(int fd,const void* buf,size_t count){
	size_t nleft = count;
	ssize_t nwritten;
	char* bufp = (char*)buf;
	while(nleft>0){
		if((nwritten = write(fd,bufp,nleft))<0){
			if(errno == EINTR)
				continue;
			return -1;
		}else if(nwritten==0){
			continue;
		}
		bufp += nwritten;
		nletf -= nwritten;
	}
}

getsockname函式

#include <sys/socket.h>
int getsockname(int sockfd, struct sockaddr *addr, socklen_t *addrlen);

介紹

getsockname() returns the current address to which the socket sockfd is
bound, in the buffer pointed to by addr.  The addrlen  argument  should
be initialized to indicate the amount of space (in bytes) pointed to by
addr.  On return it contains the actual size of the socket address.

The returned  address is truncated if the buffer provided is too  small;
in  this case, addrlen will return a value greater than was supplied to
the call.

返回值
成功:0
失敗:-1

getpeername函式

#include <sys/socket.h>
int getpeername(int sockfd, struct sockaddr *addr, socklen_t *addrlen);

介紹
getpeername()  returns  the address of the peer connected to the socket
sockfd, in the buffer pointed to by addr.  The addrlen argument  should
be  initialized to indicate the amount of space pointed to by addr.  On
return it contains the actual size of the  name  returned  (in  bytes).
The name is truncated if the buffer provided is too small.

The  returned address is truncated if the buffer provided is too small;
in this case, addrlen will return a value greater than was supplied  to
the call.

返回值
成功:0
失敗:-1

sethostname&gethostname

#include <unistd.h>
int gethostname(char *name, size_t len);
int sethostname(const char *name, size_t len);

介紹
These  system calls are used to access or to change the hostname of the
current processor.

sethostname() sets the hostname to the value  given  in  the  character
array  name.   The  len argument specifies the number of bytes in name.
(Thus, name does not require a terminating null byte.)

gethostname() returns the null-terminated  hostname  in  the  character
array  name,  which  has a length of len bytes.  If the null-terminated
hostname is too large to fit, then the name is truncated, and no  error
is  returned  (but see NOTES below).  POSIX.1 says that if such trunca‐
tion occurs,  then  it  is  unspecified  whether  the  returned  buffer
includes a terminating null byte.

返回值
成功:0
失敗:-1

五種IO模型

  • 阻塞IO
  • 非阻塞IO
  • IO複用
  • 訊號驅動IO
  • 非同步IO

非阻塞IO

應用範圍很窄

fcntl(fd,F_SETFL,flag|0_NONBLOCK)

IO複用

#include <sys/select.h>

void
FD_CLR(fd, fd_set *fdset);

void
FD_COPY(fd_set *fdset_orig, fd_set *fdset_copy);

int
FD_ISSET(fd, fd_set *fdset);

void
FD_SET(fd, fd_set *fdset);

void
FD_ZERO(fd_set *fdset);

int
select(int nfds, fd_set *restrict readfds, 
                 fd_set *restrict writefds,
                 fd_set *restrict errorfds, 
                 struct timeval *restrict timeout);

FD_SETSIZE
引數
nfds:最大描述符值+1
timeout

struct timeval {
   long    tv_sec;         /* seconds */
   long    tv_usec;        /* microseconds */
};
tv_sec-0 tv_usec-0 select立即返回
NULL-select阻塞

返回值:
失敗:-1 
成功:集合中準備好的IO數量

select限制

  • 一個程序能開啟的最大檔案描述符限制。可以通過調整核心引數
  • select中的fd_set集合容量的限制(FD_SETSIZE),需要重新編譯核心

設定最大檔案描述符

#include <sys/resource.h>

 int
 getrlimit(int resource, struct rlimit *rlp);

 int
 setrlimit(int resource, const struct rlimit *rlp);
 
 struct rlimit {
  rlim_t rlim_cur;
  rlim_t rlim_max;
};

resource 說明
RLIMIT_AS 程序的最大虛記憶體空間,位元組為單位。
RLIMIT_CORE 核心轉存檔案的最大長度。
RLIMIT_CPU 最大允許的CPU使用時間,秒為單位。當程序達到軟限制,核心將給其傳送SIGXCPU訊號,這一訊號的預設行為是終止程序的執行。然而,可以捕捉訊號,處理控制代碼可將控制返回給主程式。如果程序繼續耗費CPU時間,核心會以每秒一次的頻率給其傳送SIGXCPU訊號,直到達到硬限制,那時將給程序傳送 SIGKILL訊號終止其執行。
RLIMIT_DATA 程序資料段的最大值。
RLIMIT_FSIZE 程序可建立的檔案的最大長度。如果程序試圖超出這一限制時,核心會給其傳送SIGXFSZ訊號,預設情況下將終止程序的執行。
RLIMIT_LOCKS 程序可建立的鎖和租賃的最大值。
RLIMIT_MEMLOCK 程序可鎖定在記憶體中的最大資料量,位元組為單位。
RLIMIT_MSGQUEUE 程序可為POSIX訊息佇列分配的最大位元組數。
RLIMIT_NICE 程序可通過setpriority() 或 nice()呼叫設定的最大完美值。
RLIMIT_NOFILE 指定比程序可開啟的最大檔案描述詞大一的值,超出此值,將會產生EMFILE錯誤。
RLIMIT_NPROC 使用者可擁有的最大程序數。
RLIMIT_RTPRIO 程序可通過sched_setscheduler 和sched_setparam設定的最大實時優先順序。
RLIMIT_SIGPENDING 使用者可擁有的最大掛起訊號數。
RLIMIT_STACK 最大的程序堆疊,以位元組為單位。

可讀事件發生條件

  • 套介面緩衝區有可讀資料
  • 連線的一半關閉,即受到FIN段,讀操作將返回0
  • 如果是監聽套介面,已完成連線佇列不為空時
  • 套介面上發生了一個錯誤待處理,錯誤可以銅鼓getsockopt指定SO_ERROR選項獲取

可寫事件發生條件

  • 套介面傳送緩衝區有空間容納資料
  • 連線的寫一半關閉。即受到RST段之後,再次呼叫write操作
  • 套介面上發生一個錯誤待處理,錯誤可以通過getsockopt指定SO_ERROR選項來獲取

異常事件發生條件

  • 套介面存在外帶資料

close與shutdown區別

  • close終止了資料傳送的兩個方向
  • shutdown可以有選擇的終止某個方向的資料傳送或者終止資料傳送的兩個方向
  • shutdown how=1可以保證對等方接收到一個EOF字元,而不管其他程序是否已經打開了套接字。而close不能保證,直到套接字引用計數減為0時才傳送。也就是說直到所有的程序都關閉了套接字。

在這裡插入圖片描述
收到RST的B產生SIGPIPE訊號

#include <sys/socket.h>

int shutdown(int socket, int how);

返回值
成功:0
失敗:-1,errno具體錯誤
how 介紹
SHUT_RD 關閉讀
SHUT_WR 關閉寫
SHUT_RDWR 關閉讀寫

IO超時設定

方案一

SIGALRM

void handler(int sig){
    return;
}

signal(SIGALRM,handler);
alarm(5);
int ret = read(fd,buf,sizeof(buf));
if(ret==-1&&errno=EINTR){
    errno = ETIMEOUT;
}else if(ret>=0){
    alarm(0);
}

方案二

setsockopt(sock,SOL_SOCKET,SO_RCVTIMEO,5);
int ret = read(sock,buf,sizeof(buf));
if(ret==-1&&errno = EWOULDBLOCK){
    errno = ETIMEOUT;
}

poll

#include <poll.h>

int poll(struct pollfd fds[], nfds_t nfds, int timeout);

struct pollfd {
         int    fd;       /* file descriptor */
         short  events;   /* events to look for */
         short  revents;  /* events returned */
};
fd             File descriptor to poll.
events         Events to poll for.  (See below.)
revents        Events which may occur or have occurred.  (See below.)

nfds表示fds陣列大小
timeout:等待毫秒數,0-無阻塞等待,-1-阻塞等待

返回值
>0:準備好的IO
-1:出現錯誤
0:超時

event 介紹
POLLIN 有資料可讀。
POLLRDNORM 有普通資料可讀。
POLLRDBAND 有優先資料可讀。
POLLPRI 有緊迫資料可讀。
POLLOUT 寫資料不會導致阻塞。
POLLWRNORM 寫普通資料不會導致阻塞。
POLLWRBAND 寫優先資料不會導致阻塞。
POLLMSGSIGPOLL 訊息可用。

此外,revents域中還可能返回下列事件

revents 說明
POLLER 指定的檔案描述符發生錯誤。
POLLHUP 指定的檔案描述符掛起事件。
POLLNVAL 指定的檔案描述符非法。

epoll

#include <sys/epoll.h>
int epoll_create(int size);
int epoll_create1(int flags);

int epoll_ctl(int epfd,int op,int fd,struct epoll_event* event);
- epfd:epoll_create返回的描述符。
- op:表示op操作
- fd:是需要監聽的fd(檔案描述符)
- epoll_event:是告訴核心需要監聽什麼事

int epoll_wait(int epfd,struct epoll_event* events,int maxevents,int timeout);

struct epoll_event {
  __uint32_t events;  /* Epoll events */
  epoll_data_t data;  /* User data variable */
};

epoll_create

建立一個epoll的控制代碼,size用來告訴核心這個監聽的數目一共有多大,這個引數不同於select()中的第一個引數,給出最大監聽的fd+1的值,引數size並不是限制了epoll所能監聽的描述符最大個數,只是對核心初始分配內部資料結構的一個建議。
當建立好epoll控制代碼後,它就會佔用一個fd值,在linux下如果檢視/proc/程序id/fd/,是能夠看到這個fd的,所以在使用完epoll後,必須呼叫close()關閉,否則可能導致fd被耗盡。

epoll_create1

其實它和epoll_create差不多,不同的是epoll_create1函式的引數是flag,當flag是0時,表示和epoll_create函式完全一樣,不需要size的提示了。

當flag = EPOLL_CLOEXEC,建立的epfd會設定FD_CLOEXEC

當flag = EPOLL_NONBLOCK,建立的epfd會設定為非阻塞

一般用法都是使用EPOLL_CLOEXEC.

關於FD_CLOEXEC
它是fd的一個標識說明,用來設定檔案close-on-exec狀態的。當close-on-exec狀態為0時,呼叫exec時,fd不會被關閉;狀態非零時則會被關閉,這樣做可以防止fd洩露給執行exec後的程序。關於exec的用法,大家可以去自己查閱下,或者直接man exec。

epoll_ctl

op 說明
EPOLL_CTL_ADD 新增
EPOLL_CTL_DEL 刪除
EPOLL_CTL_MOD 修改
events 說明
EPOLLIN 表示對應的檔案描述符可以讀(包括對端SOCKET正常關閉);
EPOLLOUT 表示對應的檔案描述符可以寫;
EPOLLPRI 表示對應的檔案描述符有緊急的資料可讀(這裡應該表示有帶外資料到來);
EPOLLER 表示對應的檔案描述符發生錯誤;
EPOLLHUP 表示對應的檔案描述符被結束通話;
EPOLLET 將EPOLL設為邊緣觸發(Edge Triggered)模式,這是相對於水平觸發(Level Triggered)來說的。
EPOLLONESHOT 只監聽一次事件,當監聽完這次事件之後,如果還需要繼續監聽這個socket的話,需要再次把這個socket加入到EPOLL佇列裡

epoll_wait

等待epfd上的io事件,最多返回maxevents個事件。
引數events用來從核心得到事件的集合,maxevents告之核心這個events有多大,這個maxevents的值不能大於建立epoll_create()時的size,引數timeout是超時時間(毫秒,0會立即返回,-1將不確定,也有說法說是永久阻塞)。該函式返回需要處理的事件數目,如返回0表示已超時。

epoll與select、poll區別

  • 相比於select、poll、epoll最大好處在於他不會隨著監聽fd數目的增長而效率降低
  • 核心中的select與poll的實現採用輪詢來處理的,輪詢的fd數目越多,消耗越多
  • epoll的實現是基於回撥的,如果有fd期望的時間發生就通過回撥函式將其加入epoll就緒佇列中,也就是說它只關心活躍的fd,與fd數目無關
  • 核心/使用者控制元件記憶體拷貝問題,如何讓核心把fd訊息通知給使用者空間?該問題上select、poll採取了記憶體拷貝方法。而epoll採用了共享記憶體的方式
  • epoll不僅會告訴應用程式有IO時間到來,還會告訴應用程式相關的資訊,這些資訊是應用程式填充的,因此根據這些資訊應用程式就能直接定位到事件,而不必遍歷整個fd集合

epoll兩種觸發模式

EPOLLLT

完全靠kernel epoll驅動,應用程式只需要處理從epoll_wait返回的fds,這些fds認為處於就緒狀態

EPOLLET

  • 在此模式下,系統僅僅通知應用程式哪些fds變成了就緒狀態,一旦fd變成就緒狀態,epoll將不再關注fd的任何狀態資訊,(從epoll佇列移除)知道應用程式通過讀寫操作觸發EAGAIN狀態,epoll認為這個fd變為空閒狀態,那麼epoll又重新關注這個fd的狀態變化(重新加入epoll佇列)
  • 隨著epoll_wait的返回,佇列中的fds是在減少的,所以在大併發系統中,EPOLLET更有優勢。但是對程式設計師的要求更高。

epoll框架

#define MAX_EVENTS 10
struct epoll_event ev, events[MAX_EVENTS];
int listen_sock, conn_sock, nfds, epollfd;

/* Code to set up listening socket, 'listen_sock',
  (socket(), bind(), listen()) omitted */

epollfd = epoll_create1(0);
if (epollfd == -1) {
   perror("epoll_create1");
   exit(EXIT_FAILURE);
}

ev.events = EPOLLIN;
ev.data.fd = listen_sock;
if (epoll_ctl(epollfd, EPOLL_CTL_ADD, listen_sock, &ev) == -1) {
   perror("epoll_ctl: listen_sock");
   exit(EXIT_FAILURE);
}

for (;;) {
   nfds = epoll_wait(epollfd, events, MAX_EVENTS, -1);
   if (nfds == -1) {
       perror("epoll_wait");
       exit(EXIT_FAILURE);
   }

   for (n = 0; n < nfds; ++n) {
       if (events[n].data.fd == listen_sock) {
           conn_sock = accept(listen_sock,
                           (struct sockaddr *) &local, &addrlen);
           if (conn_sock == -1) {
               perror("accept");
               exit(EXIT_FAILURE);
           }
           setnonblocking(conn_sock);
           ev.events = EPOLLIN | EPOLLET;
           ev.data.fd = conn_sock;
           if (epoll_ctl(epollfd, EPOLL_CTL_ADD, conn_sock,
                       &ev) == -1) {
               perror("epoll_ctl: conn_sock");
               exit(EXIT_FAILURE);
           }
       } else {
           do_use_fd(events[n].data.fd);
       }
   }
}
for( ; ; )
{        
 	nfds = epoll_wait(epfd,events,20,500);        
 	for(i=0;i<nfds;++i)
 	{            
 		if(events[i].data.fd==listenfd) //有新的連線            
 		{                
 			connfd = accept(listenfd,(sockaddr *)&clientaddr, &clilen); //accept這個連線                
 			ev.data.fd=connfd;                
 			ev.events=EPOLLIN|EPOLLET;                
 			epoll_ctl(epfd,EPOLL_CTL_ADD,connfd,&ev); //將新的fd新增到epoll的監聽佇列中            
		}else if( events[i].events&EPOLLIN ) //接收到資料,讀socket
		{ 
			n = read(sockfd, line, MAXLINE)) < 0    //讀                
			ev.data.ptr = md;     //md為自定義型別,新增資料                
			ev.events=EPOLLOUT|EPOLLET;                
			epoll_ctl(epfd,EPOLL_CTL_MOD,sockfd,&ev);//修改識別符號,等待下一個迴圈時傳送資料,非同步處理的精髓            
		}else if(events[i].events&EPOLLOUT) //有資料待發送,寫socket            
		{                
			struct myepoll_data* md = (myepoll_data*)events[i].data.ptr;    //取資料                
			sockfd = md->fd;                
			send( sockfd, md->ptr, strlen((char*)md->ptr), 0 );        //傳送資料                
			ev.data.fd=sockfd;                
			ev.events=EPOLLIN|EPOLLET;                
			epoll_ctl(epfd,EPOLL_CTL_MOD,sockfd,&ev); //修改識別符號,等待下一個迴圈時接收資料            
		}else{ 
		               //其他的處理            
		}        
	}    
}

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <sys/socket.h>
#include <netdb.h>
#include <fcntl.h>
#include <sys/epoll.h>
#include <string.h> 
#define MAXEVENTS 64 
//函式:
//功能:建立和繫結一個TCP socket
//引數:埠
//返回值:建立的socket
static int create_and_bind (char *port)
{  
	struct addrinfo hints;  
	struct addrinfo *result, *rp;  
	int s, sfd;   
	memset (&hints, 0, sizeof (struct addrinfo));  
	hints.ai_family = AF_UNSPEC;     /* Return IPv4 and IPv6 choices */  
	hints.ai_socktype = SOCK_STREAM; /* We want a TCP socket */  
	hints.ai_flags = AI_PASSIVE;     /* All interfaces */   
	s = getaddrinfo (NULL, port, &hints, &result);  
	if (s != 0){      
		fprintf (stderr, "getaddrinfo: %s\n", gai_strerror (s));      
		return -1;
	}   
	for (rp = result; rp != NULL; rp = rp->ai_next){
		sfd = socket (rp->ai_family, rp->ai_socktype, rp->ai_protocol);
		if (sfd == -1)        
			continue;       
		s = bind (sfd, rp->ai_addr, rp->ai_addrlen);

		if (s == 0){          
			/* We managed to bind successfully! */         
			 break;        
	 	}
	 	close (sfd);    
 	}   
 	if (rp == NULL)
 	{      
 		fprintf (stderr, "Could not bind\n");
 		return -1;
	}   
	freeaddrinfo (result);   
	return sfd;
}  
//函式
//功能:設定socket為非阻塞的
static int
make_socket_non_blocking (int sfd){  
	int flags, s;
	//得到檔案狀態標誌  
	flags = fcntl (sfd, F_GETFL, 0);  
	if (flags == -1)
	{      
		perror ("fcntl");      
		return -1;    
	}   
	//設定檔案狀態標誌  
	flags |= O_NONBLOCK;
	s = fcntl (sfd, F_SETFL, flags);  
	if (s == -1)
	{      
		perror ("fcntl");      
		return -1;    
	}   
	return 0;
} 
//埠由引數argv[1]指定
int main (int argc, char *argv[]){  
	int sfd, s;  
	int efd;  
	struct epoll_event event;  
	struct epoll_event *events;
	if (argc != 2){      
		fprintf (stderr, "Usage: %s [port]\n", argv[0]);      
		exit (EXIT_FAILURE);    
	}   
	sfd = create_and_bind(argv[1]);  
	if (sfd == -1)
		abort ();   
	s = make_socket_non_blocking (sfd);  
	if (s == -1)    
		abort ();   
	s = listen (sfd, SOMAXCONN);  
	if (s == -1){      
		perror ("listen");
		abort ();    
	}   
	//除了引數size被忽略外,此函式和epoll_create完全相同  
	efd = epoll_create1 (0);  
	if (efd == -1){      
		perror ("epoll_create");      
		abort ();    
	}   
	event.data.fd = sfd;  
	event.events = EPOLLIN | EPOLLET;
	//讀入,邊緣觸發方式  
	s = epoll_ctl (efd, EPOLL_CTL_ADD, sfd, &event);  
	if (s == -1){      
		perror ("epoll_ctl");      
		abort ();    
	}   
	/* Buffer where events are returned */  
	events = calloc (MAXEVENTS, sizeof event);
	/* The event loop */  
	while (1){      
		int n, i;       
		n = epoll_wait (efd, events, MAXEVENTS, -1);      
		for (i = 0; i < n; i++){          
			if ((events[i].events & EPOLLERR)
				||(events[i].events & EPOLLHUP)
				||(!(events[i].events & EPOLLIN))){
				/* An error has occured on this fd, or the socket is not 
				ready for reading (why were we notified then?) */              
	            fprintf (stderr, "epoll error\n");              
	            close (events[i].data.fd);              
	            continue;            
            }else if(sfd == events[i].data.fd){              
            	/* We have a notification on the listening socket, 
            	which means one or more incoming connections. */              
            	while (1){                  
            		struct sockaddr in_addr;                  
            		socklen_t in_len;                  
            		int infd;                  
            		char hbuf[NI_MAXHOST], sbuf[NI_MAXSERV];                   
            		in_len = sizeof in_addr;                  
            		infd = accept (sfd, &in_addr, &in_len);                  
            		if (infd == -1){                      
            			if ((errno == EAGAIN)||(errno == EWOULDBLOCK)){                          
            				/* We have processed all incoming connections. */                          
            				break;                        
            			}else{ 
            			    perror ("accept");                          
            			    break;                        
        			    }                    
    			    }                                   
    			    //將地址轉化為主機名或者服務名                  
    			    s = getnameinfo (&in_addr, in_len,hbuf, 
    			    	sizeof(hbuf),sbuf, sizeof(sbuf),
    			    	NI_NUMERICHOST | NI_NUMERICSERV);
    			    //flag引數:以數字名返回                                  
    			    //主機地址和服務地址                   
    			    if (s == 0){                      
    			    	printf("Accepted connection on descriptor %d "
    			    		"(host=%s, port=%s)\n", infd, hbuf, sbuf);                    
    			    }                   
    			    /* Make the incoming socket 
    			    non-blocking and add it to the list of fds to monitor. */                  
    			    s = make_socket_non_blocking (infd);                  
    			    if (s == -1)
    			    	abort ();                   
			    	event.data.fd = infd;                  
			    	event.events = EPOLLIN | EPOLLET;                  
			    	s = epoll_ctl (efd, EPOLL_CTL_ADD, infd, &event);                  
			    	if (s == -1)
			    	{                      
			    		perror ("epoll_ctl");                      
			    		abort ();                    
			    	}                
			    }              
			    continue;            
			}
			else            
			{              
				/* We have data on the fd waiting to be read. Read and 
				display it. We must read whatever data is available                 
				completely, as we are running in edge-triggered mode                 
				and won't get a notification again for the same                 
				data. */              
				int done = 0;               
				while (1)
				{                  
					ssize_t count;                  
					char buf[512];                   
					count = read (events[i].data.fd, buf, sizeof(buf));
					if (count == -1)
					{                      
						/* If errno == EAGAIN, that means we have read all                         
						data. So go back to the main loop. */                      
						if (errno != EAGAIN){                         
							perror ("read");                          
							done = 1;                        
						}                      
						break;                    
					}else if (count == 0){                      
						/* End of file. The remote has closed the                         
						connection. */                      
						done = 1;                      
						break;                    
					}                   
					/* Write the buffer to standard output */                  
					s = write (1, buf, count);                  
					if (s == -1)                    
					{                      
						perror ("write");                      
						abort ();                    
					}                
				}               
				if (done)                
				{                  
					printf ("Closed connection on descriptor %d\n",                          
					events[i].data.fd);                   
					/* Closing the descriptor will make epoll remove it                     
					from the set of descriptors which are monitored. */                  
					close (events[i].data.fd);                
				}            
			}        
		}   
	}   
	free (events);   
	close (sfd);   
	return EXIT_SUCCESS;
}

UDP

UDP特點

  • 無連線
  • 基於訊息的資料傳輸服務
  • 不可靠
  • UDP更加高效

UDP注意點

  • UDP報文可能會丟失、重複
  • UDP報文可能會亂序
  • UDP缺乏流量控制
  • UDP協議資料報文截斷
  • recvfrom返回0,不代表連線關閉,因為UDP是無連線的
  • ICMP非同步錯誤
  • UDP connect(呼叫connect函式,維護資訊,connect後對方地址在sendo中不需要指定)
  • UDP外出介面的確定(sendto確定)

UDP客戶/服務基本模型
在這裡插入圖片描述

UDP使用connect

  • 不能給使用sendto而改使用write或者send。寫到已連線UDP套接字上的任何內容都自動傳送到由connect指定的協議地址,其實也是可以使用sendto,但是不可以指定地址及埠
  • 不必使用recvfrom以獲得資料報傳送者,改用read,recv或者recvmsg。在一個已連線UDP套接字上,由核心為輸入操作返回的資料報只有那些來自connect所指定協議地址的資料報。目的地為該已連線套接字本地協議地址,發源卻不是由connect連線到的對端地址不會發送到該套接字。A沒有connect到C,C卻往A傳送資料,A將不會接收C的資料。限制已連線UDP套接字只能和一個對端交換資料。
  • 由已連線UDP套接字引發的非同步錯誤會返回所在程序,而未連線套接字不會接收任何非同步錯誤。
  • 效能優於未連線
套接字型別 write或send 不指定目的地址sendto 指定目的地址sendto
TCP套接字 可以 可以 EISCONN
UDP套接字,已連線 可以 可以 EISCONN
UDP套接字,未連線 EDESTADDRREQ EDESTADDRREQ 可以

UDP套接字多次呼叫connect

  • 指定新的IP地址和埠號
  • 斷開套接字,將connect地址協議族設定為AF_UNSPEC,返回一個EAFNOSUPPORT錯誤。

注意:TCP套接字中的connect只能呼叫一次

recvfrom

#include <sys/types.h>
#include <sys/socket.h>

ssize_t recv(int sockfd, void *buf, size_t len, int flags);

ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,
                struct sockaddr *src_addr, socklen_t *addrlen);

#define SO_EE_ORIGIN_NONE    0
#define SO_EE_ORIGIN_LOCAL   1
#define SO_EE_ORIGIN_ICMP    2
#define SO_EE_ORIGIN_ICMP6   3

struct sock_extended_err
{
  uint32_t ee_errno;   /* error number */
  uint8_t  ee_origin;  /* where the error originated */
  uint8_t  ee_type;    /* type */
  uint8_t  ee_code;    /* code */
  uint8_t  ee_pad;     /* padding */
  uint32_t ee_info;    /* additional information */
  uint32_t ee_data;    /* other data */
  /* More data may follow */
};

struct sockaddr *SO_EE_OFFENDER(struct sock_extended_err *);

flags 解釋
MSG_DONTWAIT 操作不會被阻塞
MSG_ERRQUEUE 指示應該從套接字的錯誤佇列上接收錯誤值,依據不同的協議,錯誤值以某種輔佐性訊息的方式傳遞進來,使用者應該提供足夠大的緩衝區。導致錯誤的原封包通過msg_iovec作為一般的資料來傳遞。導致錯誤的資料報原目標地址作為msg_name被提供。 錯誤以sock_extended_err結構形態被使用。
MSG_PEEK 指示資料接收後,在接收佇列中保留原資料,不將其刪除,隨後的讀操作還可以接收相同的資料。
MSG_TRUNC 返回封包的實際長度,即使它比所提供的緩衝區更長,只對packet套接字有效。
MSG_WAITALL 要求阻塞操作,直到請求得到完整的滿足。然而,如果捕捉到訊號,錯誤或者連線斷開發生,或者下次被接收的資料型別不同,仍會返回少於請求量的資料。
MSG_OOB 指示接收到out-of-band資料(即需要優先處理的資料)。

sendto

#include <sys/types.h>
#include <sys/socket.h>

ssize_t send(int sockfd, const void *buf, size_t len, int flags);

ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,
              const struct sockaddr *dest_addr, socklen_t addrlen);


recvmsg&sendmsg

recvmsg和sendmsg函式

#include <sys/socket.h>
ssize_t recvmsg(int sockfd, struct msghdr *msg, int flags);
ssizt_t sendmsg(int sockfd, struct msghdr *msg, int flags);

struct msghdr {
    void          *msg_name;            /* protocol address */
    socklen_t     msg_namelen;          /* sieze of protocol address */
    struct iovec  *msg_iov;             /* scatter/gather array */
    int           msg_iovlen;           /* # elements in msg_iov */
    void          *msg_control;         /* ancillary data ( cmsghdr struct) */
    socklen_t     msg_conntrollen;      /* length of ancillary data */
    int           msg_flags;            /* flags returned by recvmsg() */
}

#include <sys/uio.h>
struct iovec {
    void    *iov_base;      /* starting address of buffer */
    size_t  iov_len;        /* size of buffer */
}

#include <sys/socket.h>
struct cmsghdr {
    socklen_t   cmsg_len;   /* length in bytes, including this structure */
    int         cmsg_level; /* originating protocol */
    int         cmsg_type;  /* protocol-specific type */
    /* followed by unsigned char cmsg_data[] */
}

#include <sys/socket.h>
#include <sys/param.h>
struct cmsghdr *CMSG_FIRSTHDR(struct msghdr *mhdrptr);
    //返回:指向第一個cmsghdr結構的指標,若無輔助資料則為NULL
struct cmsghdr *CMSG_NXTHDR(struct msghdr *mhdrptr, struct cmsghdr *cmsghdr);
    //返回:指向下一個cmsghdr結構的指標,若不再有輔助資料物件則為NULL
unsigned char *CMSG_DATA(struct cmsghdr *cmsgptr);
    //返回:指向與cmsghdr結構關聯的資料的第一個位元組的指標
unsigned char *CMSG_LEN(unsigned int length);
    //返回:給定資料量下存放到cmsg_len中的值
unsigned char *CMSG_SPACE(unsigned int length);
    //返回:給定資料量下一個輔助資料物件總的大小



char contorl[CMSG_SPACE(size_of_struct1) + CMSG_SPACE(size_of_struct2)];
struct msghdr msg;
struct cmsghdr *cmsgptr;
for ( cmsgptr = CMSG_FIRSTHDR(&