Unix网络编程第一卷学习总结

简述和TCP/IP

如果要写一个简单的ipv6客户端程序,需要sockaddr_in6地址,其余用法和ipv4的用法一致,
先指定地址族,端口,ip地址,connect成功后,就可以发包收包

int inet_pton(int af, const char *restrict src, void *restrict dst);
其中af可以是ipv4,也可以是ipv6,此函数的目的是把字符串转换为dst二进制流

如果加了锁 pthread_cond_wait 会保证 比 pthread_cond_signal 先解锁

32位系统中size_t是一个32位值, 64系统中size_t是一个64位值
UDP不保证各个数据报顺序跨网络后保持不变,也不保证每个数据报只到达一次

传输层

UDP是无连接的,以下的代码是客户端给服务端进行发送时的处理, 其中dest_addr可以动态的切换
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,
const struct sockaddr *dest_addr, socklen_t addrlen);

SCTP 在客户端与服务器之间提供关联(一个关联是指两个系统间的一次通信),可能因为SCTP支持多宿(单个SCTP结点能够支持多个ip地址)而涉及不止两个地址
SCTP是面向消息的
TIME_WAIT的时间一般是最长分结生命期(MSL)的两倍,通常为1分钟~4分钟
这幅图来自《UNIX网络编程,卷1:套接字联网API》2.6.4 TCP状态转换图

这幅图来自《UNIX网络编程,卷1:套接字联网API》2.6.4 TCP连接的分组交换图
此图比较仔细的说明了,客户端与服务端三次握手,数据通信,四次分手的过程以及调用的函数,其中我们比较关注TIME_WAIT状态,可以看出,四次分手时,当服务端准备好断开时,会主动发送一个FIN,这时客户端处于TIME_WAIT状态
TIME_WAIT解决方式可以使用socket opt选项进行复用,并且TIME_WAIT的状态有两个存在的理由

  1. 可靠的实现TCP全双工连接的终止
    当服务端ACK M + 1丢失,那么客户端必须要维护状态信息,当服务端发送FIN N时,要提醒服务端重新发送ACK
  2. 允许老的重复分节在网络中消逝
    当老的连接和端口的链路断开后,过一段时间相同的连接和端口建立另外一个链接,TCP必须保证老连接的数据重复在新链路发送,既然TIME_WAIT的时间是MSL(MSL,即Maximum Segment Lifetime,一个数据分片(报文)在网络中能够生存的最长时间)的两倍,那么足以让老的分组数组只存货MSL时间后就被丢弃

众所周知的端口号范围是0 ~ 1023, 由LANA(the internet assigned numbers authority)
1024 ~ 49154 不受LANA控制,有LANA控制并维护使用状况清单

49152 ~ 65535是动态的或私用的端口,这些端口我们称之临时端口

如果一个服务器的地址是多宿的,我们想让任何一个地址都可以建立连接,那么我们可以指定服务器监听的地址为INADDR_ANY,

ipv4最大的数据报为65535个字节,包括IPV4首部20和tcp首部20,ipv6数据报的最大大小为65575个字节,包括60字节的ipv6首部,
ipv4要求的最小链路MTU是68字节,允许最大的IPV4头部(40字节)拼接最小的片段,ipv6要求的最小MTU为1280字节
ipv4和ipv6都定义了最小重组缓冲区的大小,ipv4的默认缓冲区是576字节,对于ipfv6是1500字节

ipv4首部DF位如果被设置,则代表不分片

quinta@ubuntu:~$ cat /proc/sys/net/ipv4/tcp_rmem
4096    87380   6291456
quinta@ubuntu:~$ cat /proc/sys/net/ipv4/tcp_wmem
4096    16384   4194304
quinta@ubuntu:~$ cat /proc/sys/net/core/rmem_max
212992
quinta@ubuntu:~$ cat /proc/sys/net/core/wmem_max
212992


从上到下,分别是tcp 默认读缓冲区,tcp默认写缓冲区,tcp可设置读最大缓冲区,tcp可设置写最大缓冲区

如果UDP发送的大小大于buffer的长度,那么可能会报错EMSGSIZE,也可能不会报错,但可能有的UDP不返回这种错误。

socketadd_in和 socketaddr_un可以无缝和socketaddr进行转换,都是16字节,那么我们为什么要使用socketaddr_in进行赋值呢,一i那位socketaddr中端口和ip是在一个数组中,对开发者不友好

在这里插入图片描述

套接字编程简介

IPV6的地址族是AF_INET6,
IPV6 新定义了通用字段 sockaddr_storage, sockaddr_storage足够大,可以容纳系统支持的最苛刻的对齐要求。

include <netinet/in.h>
struct sockaddr {
    unsigned short    sa_family;    // 2 bytes address family, AF_xxx
    char              sa_data[14];     // 14 bytes of protocol address
};
 sockaddr_in 占用20字节
// IPv4 AF_INET sockets:
struct sockaddr_in {
    short            sin_family;       // 2 bytes e.g. AF_INET, AF_INET6
    unsigned short   sin_port;    // 2 bytes e.g. htons(3490)
    struct in_addr   sin_addr;     // 4 bytes see struct in_addr, below
    char             sin_zero[8];     // 8 bytes zero this if you want to
};

struct in_addr {
    unsigned long s_addr;          // 4 bytes load with inet_pton()
};

sockaddr_in6 占用28字节

 struct sockaddr_in6 {
     sa_family_t sin6_family;    /* AF_INET6 */
     in_port_t sin6_port;        /* Transport layer port # */
     uint32_t sin6_flowinfo;     /* IPv6 flow information */
     struct in6_addr sin6_addr;  /* IPv6 address */
     uint32_t sin6_scope_id;     /* IPv6 scope-id */
 };
 struct in6_addr {
     union {
         uint8_t u6_addr8[16];
         uint16_t u6_addr16[8];
         uint32_t u6_addr32[4];
     } in6_u;
 
     #define s6_addr                 in6_u.u6_addr8
     #define s6_addr16               in6_u.u6_addr16
     #define s6_addr32               in6_u.u6_addr32
 };

sockaddr_storage占用128字节

#define _SS_MAXSIZE    128  /* Implementation specific max size */
#define _SS_ALIGNSIZE  (sizeof (int64_t))
                         /* Implementation specific desired alignment */
/*
 * DeFinitions used for sockaddr_storage structure paddings design.
 */
#define _SS_PAD1SIZE   (_SS_ALIGNSIZE - sizeof (sa_family_t))
#define _SS_PAD2SIZE   (_SS_MAXSIZE - (sizeof (sa_family_t)+
                              _SS_PAD1SIZE + _SS_ALIGNSIZE))
struct sockaddr_storage {
    sa_family_t  __ss_family;     /* address family */
    /* Following fields are implementation specific */
    char      __ss_pad1[_SS_PAD1SIZE];
              /* 6 byte pad,this is to make implementation
              /* specific pad up to alignment field that */
              /* follows explicit in the data structure */
    int64_t   __ss_align;     /* field to force desired structure */
               /* storage alignment */
    char      __ss_pad2[_SS_PAD2SIZE];
              /* 112 byte pad to achieve desired size,*/
              /* _SS_MAXSIZE value minus size of ss_family */
              /* __ss_pad1,__ss_align fields is 112 */
};

sockaddr_un是UNIX域,

在这里插入图片描述

从进程到内核传递套接字地址结构的函数有三个,bind connect sendto

从内核到进程的函数有四个,accept recvfrom getsockname getpeername,

大端地址 是 高位字节 在 低地址,小端地址则相反

在这里插入图片描述

以下是一段验证大小端的代码和htons 和 ntohs的博客,通过结果可以看出htons和ntohs的转换结果一致,说明了两个函数的作用一致,如果是大端,则转化为小端,如果是小端,则转换为大端

#include<stdio.h>
#include <arpa/inet.h>

int main()
{
    uint16_t num = 4096; // 0x1000

    if(num == htons(num))
    {
        printf("big endian\n");
        printf("address = %X\n", num);
    }
    else
    {
        printf("little endian");
        printf("address = %X\n", num);
    }

    uint16_t big_num = htons(num);
    printf("num = %d\n", big_num);
    printf("address = %X\n", big_num);

    big_num = ntohs(num);
    printf("num = %d\n", big_num);
    printf("address = %X\n", big_num);
    return 0;
}

在这里插入图片描述
inet_aton 用来将ip地址转换为32位的网络序
inet_addr有类似的作用,这个函数现在已经废弃不用
inet_ntoa用来将in_addr结构转换为字符串

随着ipv6的发展,新增了有类似功能的函数
在这里插入图片描述
其中family可以显示的指定网络协议名称,AF_INET或AF_INET6, inet_pton用来将ip地址转换为网络序,inet_ntop转换,可以理解为inet_pton和inet_aton是套了层壳子

基本TCP套接字编程

在这里插入图片描述

在这里插入图片描述
客户端 connect函数前不需要调用bind,这时连接时内核会分配一个临时端口
listen函数把一个未连接的套接字转换为一个被动套接字,指示内核应该接受该套接字的连接请求

int listen(int sockfd, int backlog);其中backlog解释如下,简单可理解为服务器可支持的最大连接数
The backlog argument defines the maximum length to which the queue of pending connections for sockfd may grow

int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);可以看到addr是指对端的ip地址
The argument addr is a pointer to a sockaddr structure. This structure is filled in with the address of the peer socket
fork如果在accept之后,那么文件描述符,父进程与子进程共享,需要将父进程的文件描述符关闭,如果想要彻底关闭文件描述符,那么需要在父进程,子进程都调用close来关闭

在一个没有调用bind的TCP客户端,connect成功返回后,getsocketname返回的是内核赋予该连接的本地ip地址和本地端口号

在以0去bind的时候,getsockname返回的是内核赋予的本地端口号,同样也可以获得某个套接字的地址族,适合于以下情况,未调用bind就connect了

TCP客户/服务端程序示例

SIGCHLD 是内核在任何一个进程停止时,发给它的父进程的一个信号,如果父进程比子进程先结束,而且没有回收子进程,那么子进程将会变成一个僵尸进程

void (*signal(int sig, void (*func)(int)))(int)
图片部分来自于

https://www.runoob.com/cprogramming/c-function-signal.html

在这里插入图片描述
EINTER错误由在非阻塞套接字上不能立即完成的操作返回,例如,当套接字上没有排队数据可读时调用了recv()函数。此错误不是严重错误,相应操作应该稍后重试

wait函数会阻塞直到第一个子进程返回
wait_pid可以按子进程的进程号进行等待,正确的方法是在信号处理函数中循环调用waitpid进行处理

accept需要判断正确错误,如果返回一个非致命的错误,要再次进行accept,出现这种问题的原因是三次握手后,客户端又发送了一次复位的请求,内核队列中将会把此套接字删除,但是业务层调用accept时,不知道曾经有一个已完成的连接被从队列中删除了

当服务器进程停止时,会向客户端发送一个FIN,客户端回送一个ACK,此时服务器的状态时FIN_WAIT2, 客户端的状态是CLOSE_WAIT,但是此时客户端还是可以发送数据的,发送数据不会报错,服务器收到数据时会发送回一个响应,但是业务层不会收到响应,再次发送时会引用SIGPIPE信号,默认导致进程退出

假如客户端和服务器之间通过路由连接,但是服务器异常崩溃,那么tcp会重传数据约12次,直到放弃重传,客户机阻塞在readline中,最后会收到一个ETIMEOUT错误

客户端与服务器通信如果是文本串的格式,相对安全,如果是二进制结构,那么可能存在大小端转换的问题

I/O复用: select 和 poll函数

select和poll和组合的区别是多路复用可以预先告诉进程,使得内核一旦发现进程指定的一个或多个I/O条件就绪,就主动通知进程
异步接收数据时,如果数据没有准备好,那么会返回一个EWOULDBLOCK错误,如果数据复制了一些,那么此时会返回成功

接收数据也可以使用信号驱动模式,通讯过程如下
在这里插入图片描述

异步IO aio_read 和信号驱动模式的主要区别在于,信号驱动式I/O是由内核通知我们何时能启动一个I/O操作,而异步IO模型是由内核通知我们I/O操作何时完成

在这里插入图片描述
select 可以设置低水位,比如我们默认知道小于X字节时,不是有效的数据,则可以设置内核收到X字节时,才通知应用层

close函数把套接字的引用计数减1,当套接字等于0时,才会主动执行关闭操作,但是shutdown函数可以不管引用计数就激发TCP的正常连接终止序列
shutdown 依赖于 howto的值,SHUT_RD 关闭连接的读这一半,SHUT_WR关闭连接写这一半,SHUT_RDWR连接的读半部和写半部都关闭

  for ( ; ; ) {
 36         rset = allset;      /* structure assignment */
 37         nready = Select(maxfd+1, &rset, NULL, NULL, NULL);
 38
 39         if (FD_ISSET(listenfd, &rset)) {    /* new client connection */
 40             clilen = sizeof(cliaddr);
 41             connfd = Accept(listenfd, (SA *) &cliaddr, &clilen);
 42 #ifdef  NOTDEF
 43             printf("new client: %s, port %d\n",
 44                     Inet_ntop(AF_INET, &cliaddr.sin_addr, 4, NULL),
 45                     ntohs(cliaddr.sin_port));
 46 #endif
 47
 48             for (i = 0; i < FD_SETSIZE; i++)
 49                 if (client[i] < 0) {
 50                     client[i] = connfd; /* save descriptor */
 51                     break;
 52                 }
 53             if (i == FD_SETSIZE)
 54                 err_quit("too many clients");
 55
 56             FD_SET(connfd, &allset);    /* add new descriptor to set */
 57             if (connfd > maxfd)
 58                 maxfd = connfd;         /* for select */
 59             if (i > maxi)
 60                 maxi = i;               /* max index in client[] array */
 61
 62             if (--nready <= 0)
 63                 continue;               /* no more readable descriptors */
 64         }

可以看下以上代码块,为什么每次用allset给rset复制,因为每次select时候,如果fd没有变更,那么select 会自动把fd从set中删除

当服务器使用阻塞IO时,可能会有以下问题,客户端只发送了一个字节后就不再发送,这时服务器下次就会在io处阻塞,影响其他用户的发送

如果不在关心某个特定描述符,那么可以把对应pollfd结构中的fd设置为一个负数

套接字选项

主要又四个函数需要着重了解
gesocketopt
setsocketopt
fcntl
ioctl
在这里插入图片描述
在这里插入图片描述
以上是所有的套接字选项

int getsockopt(int sockfd, int level, int optname,
void *optval, socklen_t *optlen);
此时返回的optval是一个整数,如果为0,则代表相应选项被禁止,非0代表被启用
类似的,setsockopt也是如此,

tcp存在流量控制,不允许发送超过窗口大小的数据
TCP套接字缓冲区的长度至少是MSS的四倍

基本UDP套接字编程

UDP发送一个0字节长度是可行的,接受为0也不代表断开连接
在这里插入图片描述

大部分tcp服务器都是并发的,大部分udp服务器都是迭代的

#include	"unp.h"

void
dg_cli(FILE *fp, int sockfd, const SA *pservaddr, socklen_t servlen)
{
	int				n;
	char			sendline[MAXLINE], recvline[MAXLINE + 1];
	socklen_t		len;
	struct sockaddr	*preply_addr;

	preply_addr = Malloc(servlen);

	while (Fgets(sendline, MAXLINE, fp) != NULL) {

		Sendto(sockfd, sendline, strlen(sendline), 0, pservaddr, servlen);

		len = servlen;
		n = Recvfrom(sockfd, recvline, MAXLINE, 0, preply_addr, &len);
		if (len != servlen || memcmp(pservaddr, preply_addr, len) != 0) {
			printf("reply from %s (ignored)\n",
					Sock_ntop(preply_addr, len));
			continue;
		}

		recvline[n] = 0;	/* null terminate */
		Fputs(recvline, stdout);
	}
}

可以看下这段代码有什么问题, 当对端机器只有一个网卡是没问题的,当服务器有多个网卡就会判断错误,遇到这种情况怎么办呢,对端机器要针对每块网卡进行bind操作,用多路转接进行监听

如果服务器没有启动,客户端udp连接发送sendto,可能成功,但是实际上发送失败,因为异步错误不会返回给程序

udp客户发送的临时端口不能改变,但是ip可以改变

在这里插入图片描述

UDP默认缓冲区大小为42080字节
tcp和udp可以绑定同意端口

名字与地址转换

gethostbyname把主机名字映射为IPV4地址,gethostbyaddr执行相反的映射
getservbyname是根据服务名称,协议名称,获取ip地址信息
getservbyport类似

下图是getaddrinfo中hints中ai_family 和 ai_socktype的对应关系
在这里插入图片描述
gai_strerror可以打印出 getaddrinfo错误信息

同样getaddrinfo既可以处理ipv4,也可以处理ipv6
在这里插入图片描述
UDP套接字不需要设置SO_REUSEADDR选项

getnameinfo是getaddrinfo的互补函数

gethostbyname,gethostbyaddr,getservbyname,getservbyport都是不可重入的
inet_pton,inet_atop都是可重入的

高级套接字编程

当服务器既有ipv4网卡和ipv6网卡时,如果客户端是ipv4,那么与服务器通信时会映射为ipv6地址,但是实际上两者的通信方式依然是ipv4通信
当ipv6客户端指向一个ipv4映射的ipv6地址时,那么实际上通信方式依然是ipv4
在这里插入图片描述
在这里插入图片描述

守护进程和inetd超级服务器

int
daemon_init(const char *pname, int facility)
{
	int		i;
	pid_t	pid;

	if ( (pid = Fork()) < 0)
		return (-1);
	else if (pid)
		_exit(0);			/* parent terminates */

	/* child 1 continues... */

	if (setsid() < 0)			/* become session leader */
		return (-1);

	Signal(SIGHUP, SIG_IGN);
	if ( (pid = Fork()) < 0)
		return (-1);
	else if (pid)
		_exit(0);			/* child 1 terminates */

	/* child 2 continues... */

	daemon_proc = 1;			/* for err_XXX() functions */

	chdir("/");				/* change working directory */

	/* close off file descriptors */
	for (i = 0; i < MAXFD; i++)
		close(i);

	/* redirect stdin, stdout, and stderr to /dev/null */
	open("/dev/null", O_RDONLY);
	open("/dev/null", O_RDWR);
	open("/dev/null", O_RDWR);

	openlog(pname, LOG_PID, facility);

	return (0);				/* success */
}

需要充分理解此函数

高级IO函数

connect超时时间一般为75秒
可以使用alarm使的超时时间减短,但是最大超时时间就是75秒

也可以使用SO_RVTTIMEO进行设置

Unit域协议

unit域和普通的区别是只能在本机通信
bind时会bind一个本地地址
socketpair仅仅适用于unix套接字,比较类似pipe函数
在一个未绑定的Unix套接字上发送数据报不会自动给这个套接字捆绑一个路径名
socketpair也可以来传递文件描述符,通过带外数据进行读取

ssize_t
read_fd(int fd, void *ptr, size_t nbytes, int *recvfd)
{
	struct msghdr	msg;
	struct iovec	iov[1];
	ssize_t			n;

#ifdef	HAVE_MSGHDR_MSG_CONTROL
	union {
	  struct cmsghdr	cm;
	  char				control[CMSG_SPACE(sizeof(int))];
	} control_un;
	struct cmsghdr	*cmptr;

	msg.msg_control = control_un.control;
	msg.msg_controllen = sizeof(control_un.control);
#else
	int				newfd;

	msg.msg_accrights = (caddr_t) &newfd;
	msg.msg_accrightslen = sizeof(int);
#endif

	msg.msg_name = NULL;
	msg.msg_namelen = 0;

	iov[0].iov_base = ptr;
	iov[0].iov_len = nbytes;
	msg.msg_iov = iov;
	msg.msg_iovlen = 1;

	if ( (n = recvmsg(fd, &msg, 0)) <= 0)
		return(n);

#ifdef	HAVE_MSGHDR_MSG_CONTROL
	if ( (cmptr = CMSG_FIRSTHDR(&msg)) != NULL &&
	    cmptr->cmsg_len == CMSG_LEN(sizeof(int))) {
		if (cmptr->cmsg_level != SOL_SOCKET)
			err_quit("control level != SOL_SOCKET");
		if (cmptr->cmsg_type != SCM_RIGHTS)
			err_quit("control type != SCM_RIGHTS");
		*recvfd = *((int *) CMSG_DATA(cmptr));
	} else
		*recvfd = -1;		/* descriptor was not passed */
#else
/* *INDENT-OFF* */
	if (msg.msg_accrightslen == sizeof(int))
		*recvfd = newfd;
	else
		*recvfd = -1;		/* descriptor was not passed */
/* *INDENT-ON* */
#endif

	return(n);
}

贴一个关键代码,这块代码主要实现了进程间的fd传递

非阻塞IO

对于一个非阻塞的io,当缓冲空间不够时,会发送一个EWOULDBLOCK的错误,

非阻塞模式下,connect很可能会返回EINPROGRESS错误码

#include	"unp.h"

int
connect_nonb(int sockfd, const SA *saptr, socklen_t salen, int nsec)
{
	int				flags, n, error;
	socklen_t		len;
	fd_set			rset, wset;
	struct timeval	tval;

	flags = Fcntl(sockfd, F_GETFL, 0);
	Fcntl(sockfd, F_SETFL, flags | O_NONBLOCK);

	error = 0;
	if ( (n = connect(sockfd, saptr, salen)) < 0)
		if (errno != EINPROGRESS)
			return(-1);

	/* Do whatever we want while the connect is taking place. */

	if (n == 0)
		goto done;	/* connect completed immediately */

	FD_ZERO(&rset);
	FD_SET(sockfd, &rset);
	wset = rset;
	tval.tv_sec = nsec;
	tval.tv_usec = 0;

	if ( (n = Select(sockfd+1, &rset, &wset, NULL,
					 nsec ? &tval : NULL)) == 0) {
		close(sockfd);		/* timeout */
		errno = ETIMEDOUT;
		return(-1);
	}

	if (FD_ISSET(sockfd, &rset) || FD_ISSET(sockfd, &wset)) {
		len = sizeof(error);
		if (getsockopt(sockfd, SOL_SOCKET, SO_ERROR, &error, &len) < 0)
			return(-1);			/* Solaris pending error */
	} else
		err_quit("select error: sockfd not set");

done:
	Fcntl(sockfd, F_SETFL, flags);	/* restore file status flags */

	if (error) {
		close(sockfd);		/* just in case */
		errno = error;
		return(-1);
	}
	return(0);
}

这段代码首先设置fd非阻塞,如果connect的结果是EINPROGRESS,则代码三次握手还未完成,通过select设置一个超时时间,等到超时时间结束后

服务器accept建议使用非阻塞模式,因为当监听到fd可读时,如果在accept之前客户端就断开,那么accept就会阻塞,所以让accept为非阻塞是很有必要的

ioctl操作

ioctl通常会进行以下6类操作
套接字操作,文件操作,接口操作,ARP高速缓存操作,路由表操作,流系统
在这里插入图片描述

路由套接字

广播

在这里插入图片描述
ipv4支持广播,ipv6不支持
通常广播地址是ip最后是255的地址
内核不允许对广播数据报执行分片

多播

ipv4的D类地址,224.0.0.0 到 239.255.255.255,是ipv4多播地址,
IP_ADD_MEMBERSHIP 和 MCAST_JOIN_GROUP 类似
在这里插入图片描述

高级UDP套接字编程

如果程序想要支持多播和广播,那必须要使用UDP
UDP没有连接建立和删除,只需要两个分组就可以交换一个请求和一个应答,tcp却需要20个分组
tcp有一些udp的优势,如果想要一些特性,必须由应用程序自行提供他们,包括以下
在这里插入图片描述
如果想要请求重传式程序使用UDP,那么必须在客户程序中增加以下两个特性,1) 超时和重传 2)序列号
如果创建一个并发UDP服务器,1 当与客户只发送几个消息,可以fork一个子进程,并让子进程处理
2 与客户端交换多个消息,可以让服务端为每个客户创建一个套接字,在其上bind一个临时端口,然后使用该端口发送对该客户的所有应答

带外数据

带外数据也叫做加速数据,带外数据被认为比普通数据有更高的优先级
在这里插入图片描述
如果新的OOB在旧的OOB被读取之前就到达,那么旧的OOB数据会被丢弃
当紧急数据到达实际的缓冲区,该数据字节可能被拉出带外,也有可能被留在带内,SO_OOBILLINE默认是禁止的
如果进程多次读入同一个带外字节,读入操作将返回EINVAL
内核检测到oob数据发送过来时,需要注册SIGURG, 并且设置文件描述符为F_SETOWN
如果设置了在线数据,读位置总是从带外数据开始的
带外数据可以时任何8位值

SO_KEEPALIVE 在两个小时没有数据交换时,会自动发送一个探针

信号驱动式IO

信号驱动IO时内核有数据时,通过应该注册的回调函数进行通知,信号名称是SIGIO

IP选项

ipv4允许在20个字节首部固定部分之后跟以最多共40个字节的选项
这些选项可以通过IP_OPTIONS套接字选项
可以通过getsockopt或setsockopt进行设置

客户/服务器程序设计样例

在这里插入图片描述

  • 1
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值