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_INETstruct in_addr
AF_INET6struct 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 NamePurposeMan page
AF_UNIX, AF_LOCALUnix域协议unix(7)
AF_INETIPv4ip(7)
AF_INET6IPv6ipv6(7)
AF_IPXIPX - Novell protocols
AF_NETLINKKernel user interface devicenetlink(7)
AF_X25ITU-T X.25 / ISO-8208 protocolx25(7)
AF_AX25Amateur radio AX.25 protocol
AF_ATMPVCAccess to raw ATM PVCs
AF_APPLETALKAppleTalkddp(7)
AF_PACKETLow level packet interfacepacket(7)
AF_ALGInterface to kernel crypto API
type介绍
SOCK_STREAM字节流套接字
SOCK_DGRAM数据报套接字
SOCK_RAW原始套接字
SOCK_RDMProvides a reliable datagram layer that does not guarantee ordering.
protocol说明
IPPROTO_TCPTCP传输协议
IPPROTO_UDPUDP传输协议
IPPROTO_SCTPSCTP传输协议
类型AF_INETAF_INET6AF_LOCALAF_ROUTEAF_KEY
SOCK_STREAMTCP/SCTPTCP/SCTP--
SOCK_DGRAMUDPUDP--
SOCK_SEQPACKETSCTPSCTP--
SOCK_RAWIPV4IPV6-

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_OPTINOSIP首部选项int
IP_TOS服务类型
IP_TTL生存时间int

IPPRO_TCP

选项名称说明数据类型
TCP_MAXSEGTCP最大数据段的大小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套接字,未连接EDESTADDRREQEDESTADDRREQ可以

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(&msg); cmsgptr != NULL; 
    cmsgptr = CMSG_NXTHDR(&msg, cmsgptr) ) {
    /* 判断是否是自己需要的msg_level和msg_type */
    u_char *ptr;
    ptr = CMSG_DATA(cmsgptr); /* 获取辅助数据 */
}

msg_name和msg_namelen用于套接字未连接的时候(主要是未连接的UDP套接字),用来指定接收来源或者发送目的的地址。两个成员分别是套接字地址及其大小,类似recvfrom和sendto的第二和第三个参数。对于已连接套接字,则可直接将两个参数设置为NULL和0。而对于recvmsg,msg_name是一个值-结果参数,会返回发送端的套接字地址。

其中iov_base就是一个缓冲区元素,事实上也是一个数组,而iov_len则是指定该数据的大小。也就是说,缓冲区是一个二维数组,并且每一维长度不是固定的。猜测这样子设置应该是方便传递多个结构类型不同,并且长度也是不固定的数据吧,这样子客户端就可以直接对每个位置的数据进行转换获取就行了。如果只是当存传送一个字符串,那只需要将msg_iovlen设置成1,然后将数据赋给iov[0].iov_base就行了。无论是sendmsg和recvmsg,都需要提前设置好这两项并且分配好内存。
msg_control和msg_controllen是用来设置辅助数据的位置和大小的,辅助数据(ancillary data)也叫作控制信息(control infomation)。这两个成员可以用来返回关于数据报文的其他指定信息,不过需要通过setsockopt函数指定要返回的辅助信息。对于sendmsg,这两项需要都设置成0,否则会导致发送数据失败。还未研究过sendmsg的辅助数据能够做什么。
关于两个函数的flags参数和msghdr的msghdr的msg_flags成员,目前没有研究。

cmsghdr中实际上只有三个元素,而cmsg_data成员实际上并不存在,只是用来表明接下来都是数据,并且实际上数据和结构中还存在着填充数据。填充数据可能是为了对齐(unp中讲到msg_control指向的辅助数据必须为cmsghdr结构适当的对齐),在两个cmsghdr之间也存在着填充数据。
看到这里的时候我是很郁闷的,那我要怎么获取到辅助数据呢?一开始以为要自己手动给cms_data分配内存,但是我连cmsg_data成员都获取不到啊!然后仔细看了unp中的内容才发现可以通过下面5个CMSG_XXX宏来获取和设置辅助数据。

在这里插入图片描述

UNIX域协议

UNIX域协议特点

  • UNIX域套接字与TCP套接字相比,在同一主机的传输速度前者是后者的两倍
  • UNIX域套接字可以在同一台主机上各进程之间传递描述符
  • UNIX域套接字与传统套接字的区别是用路径名来表示协议族的描述

UNIX域套接字编程注意点

  • bind成功将会创建一个文件,权限为0777&~umask
  • sun_path最好用一个绝对路径
  • UNIX域协议支持流式套接口与报式套接口
  • unix域流式套接字connect发现监听队列满时,会立刻返回一个ECONNREFUSED,这和TCP不同如果监听队列满,会忽略到来的SYN,这导致对方重传SYN

UNIX域地址结构

#define UNIX_PATH_MAX 108
struct sockaddr_un{
    sa_family_t sun_family; /*AF_UNIX*/
    char        sun_path[UNIX_PATH_MAX];/*pathname*/ 
}

socketpair

创建一个全双工的流管道

#include <sys/socket.h>
int socketpair(int domain, int type, int protocol, int socket_vector[2]);

domain:协议家族
type:套接字类型
protocol:协议类型
sv:返回套接字对
两端都可以进行读写

返回值:
成功:0
失败:-1
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值