Linux_网络编程_3. socket 编程

Linux_网络编程

3. socket 编程

3.1. 使用 TCP 协议的流程
服务端:socket → bind → listen → while(1){ → accept → recv → send → close → } → close
客户端:socket→ → → → → → → → → → → →connect → send → recv → → → → → close

1. TCP 通信的基本步骤
服务器:
2. socket 函数:生成一个套接口描述符 3. bind 函数:用来绑定一个端口号和 IP 地址 4. listen 函数: 使服务器的这个端口和 IP 处于监听状态 实例1 : 使本地ip处于LISTEN(监听)状态
5. accept 函数:接受远程连接请求, 建立起通信连接 6. recv 函数: 接收储存传来的数据 7. send 函数:用新的套接字发送数据给指定的远端主机 8. close 函数:关闭文件
客户端:
1. connect 函数:请求连接远程服务器 实例2 : 实现tcp服务器端,客户端各发一条数据给对方 实例3 : 使用tcp实现即时聊天 实例4:使用tcp实现即时聊天,客户端支持断开重连(使用select)

3.2. 使用 UDP 协议的流程
服务端:socket → bind → recvfrom →→→→→ sendto → close
客户端:socket →→→→→→→→→ sendto → recvfrom → close

1. sendto()函数向服务器发送数据 2. recvfrom()函数接收服务器的数据 实例5:udp实现客户端和服务器端传话 实例6:udp实现客户端和服务器端传话

3.3. 设置套接口的选项 setsockopt 的用法
函数 setsockopt 设置选项值 实例7:使用SO_REUSEADDR实现重用本地地址和端口

3.4. 单播、广播、组播(多播)(不常用)

3.5. 什么是 DDos(SYN Flooding)攻击,如何防护

下面仅作了解

3.6. 描述符属性修改及文件描述符的传递

1. fcntl 函数 2. Socketpair 函数简介 3. Sendmsg 函数简介 4. Recvmsg
5. Writev 6.Readv 7. Cmsg 用来设定我们的*msg_control 指针

3.1. 使用 TCP 协议的流程

**1. TCP 通信的基本步骤如下: 服务端:socket → bind → listen → while(1){ → accept → recv → send → close → } → close 客户端:socket→ → → → → → → → → → → →connect → send → recv → → → → → close** Linux_网络编程_3. socket 编程_第1张图片
netstat -an | grep ^tcp   -查看TCP协议的监听

客户端一对一 服务器, 服务器一对多客户端

服务器端:

头文件:
#include
#include
#include
#include


2. socket 函数:生成一个套接口描述符

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

作用:生成一个套接口描述符。

参数:

  • domain :
    AF_INET:Ipv4 网络协议
    AF_INET6:IPv6 网络协议
  • type :
    SOCK_STREAM ( tcp )
    SOCK_DGRAM ( udp )
  • protocol :
    指定 socket 所使用的传输协议编号。通常为 0

**返回值:**成功则返回套接口描述符,失败返回-1。

常用实例:

int sfd = socket(AF_INET, SOCK_STREAM, 0);  -打开之前, sfd = 3,打开一个 sfd 为 4, 即第一个没被占用的描述符
if(sfd == -1)
{
	perror("socket");
	exit(-1);
} 

3. bind 函数:用来绑定一个端口号和 IP 地址

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

作用:用来绑定一个端口号和 IP 地址,使套接口与指定的端口号和 IP 地址相关联。
(只能绑定本地的IP地址, 不能绑定其他机器)

参数:

  • sockfd: 为前面 socket 的返回值。
  • my_addr : 为结构体指针变量

对于不同的 socket domain 定义了一个通用的数据结构

struct sockaddr  -此结构体不常用 
{
	unsigned short int sa_family;  -调用 socket()时的 domain 参数,即 AF_INET 值。
	char sa_data[14];  -最多使用 14 个字符长度 , 这个地方自己拼接信息, 不友好
}; 

sockaddr 结构会因使用不同的 socket domain 而有不同结构定义,
例如使用 AF_INET domain ( Ipv4 ),其 socketaddr 结构定义便为

struct sockaddr_in  -常用的结构体 
{
	unsigned short int sin_family;  -即为 sa_family ➔AF_INET 
	uint16_t sin_port;  -为使用的 port 编号 
	struct in_addr sin_addr;  -为 IP 地址 
	unsigned char sin_zero[8];  -未使用 
}; 
struct in_addr 
{ 
	uint32_t s_addr; 
}; 
  • addrlen : sockaddr结构体长度。通常是计算 sizeof(struct sockaddr);

返回值:成功则返回 0,失败返回-1

常用实例:

struct sockaddr_in my_addr;  -定义结构体变量
memset(&my_addr, 0, sizeof(struct sockaddr)); -将结构体清空
---bzero(&my_addr, sizeof(struct sockaddr));
my_addr.sin_family = AF_INET;  -表示采用 Ipv4 网络协议 
my_addr.sin_port = htons(8888);  -表示端口号为 8888,通常是大于 1024 的一个值。
---htons()用来将参数指定的 16 位 hostshort 转换成网络字符顺序
my_addr.sin_addr.s_addr = inet_addr("192.168.0.101"); 
---inet_addr()用来将IP地址字符串转换成网络所使用的二进制数字,如果为 INADDR_ANY,这表示服务器自动填充本机 IP 地址。
if(bind(sfd, (struct sockaddr*)&my_str, sizeof(struct socketaddr)) == -1) {
	perror("bind");
	close(sfd);
	exit(-1);
} 

(注:通过将 my_addr.sin_port 置为 0,函数会自动为你选择一个未占用的端口来使用。同样,通过将 my_addr.sin_addr.s_addr 置为 INADDR_ANY,系统会自动填入本机 IP 地址。)


4. listen 函数: 使服务器的这个端口和 IP 处于监听状态

int listen(int sockfd,int backlog); 

作用 : 使服务器的这个端口和 IP 处于监听状态等待网络中某一客户机的连接请求。如果客户端有连接请求,端口就会接受这个连接。

参数:

  • sockfd : 为前面 socket 的返回值.即 sfd
  • backlog : 指定 同时 能处理的最大连接要求 ( 并发 ),通常为 10 或者 5。
    (最大值可设至 128)

返回值: 成功则返回 0,失败返回-1

常用实例:

if(listen(sfd, 10) == -1) 
{
	perror("listen");
	close(sfd);
	exit(-1);
} 

本地给本地发包测试 , 为了避免源地址和目的地址一样, 目的地址将被改为环回地址 , 环回地址是主机用于向自身发送通信的一个特殊地址。

实例1 : 使本地ip处于LISTEN(监听)状态

int main(int argc, char *argv[])
{
	ARGS_CHECK(argc, 3);
	int socketFd = socket(AF_INET, SOCK_STREAM, 0);
	ERROR_CHECK(socketFd, -1, "socket"); -类似open
	struct sockaddr_in serAddr;
	bzero(&serAddr, sizeof(serAddr));
	serAddr.sin_family = AF_INET;
	serAddr.sin_port = htons(atoi(argv[2])); -htonl不可以,接到的数不对导致出错
	serAddr.sin_addr.s_addr = inet_addr(argv[1]);
	int ret;
	ret = bind(socketFd, (struct sockaddr*)&serAddr, sizeof(serAddr));
	ERROR_CHECK(ret, -1, "bind");
	listen(socketFd, 10);
	while(1); -为了进行测试,让程序卡住
	return 0;
}

5. accept 函数:接受远程连接请求, 建立起通信连接

作用 :接受远程计算机的连接请求,建立起与客户机之间的通信连接。

( 服务器处于监听状态时, 如果某时刻获得客户机的连接请求,此时并不是立即处理这个请求,而是将这个请求放在等待队列中,当系统空闲时再处理客户机的连接请求。)

(当 accept 函数接受一个连接时,会返回一个新的 socket 标识符, 以后的数据传输和读取就要通过这个新的 socket 编号来处理,原来参数中的 socket 也可以继续使用,继续监听其它客户机的连接请求。)
(也就是说,类似于移动营业厅,如果有客户打电话给 10086,此时服务器就会请求连接,处理一些事务之后,就通知一个话务员接听客户的电话,也就是说,后面的所有操作,此时已经于服务器没有关系,而是话务员跟客户的交流。)
(对应过来,客户请求连接我们的服务器, 我们服务器先做了一些绑定和监听等等操作之后,如果允许连接,则调用 accept 函数产生一个新的套接字,然后用这个新的套接字跟我们的客户进行收发数据。)
(也就是说,服务器跟一个客户端连接成功,会有两个套接字。)

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

参数:

  • s : 为前面 socket 的返回值 , 即 sfd
  • addr :结构体指针变量,和 bind 的结构体是同种类型的,系统会把远程主机的信息(远程 主机的地址和端口号信息)保存到这个指针所指的结构体中。
  • addrlen : 表示结构体的长度,为整型指针, 数据大小

返回值:成功则返回新的 socket 处理代码 new_fd,失败返回-1

常用实例:

struct sockaddr_in clientaddr;
memset(&clientaddr, 0, sizeof(struct sockaddr)); 
int addrlen = sizeof(struct sockaddr);
int new_fd = accept(sfd, (struct sockaddr*)&clientaddr, &addrlen);
if(new_fd == -1)
{
	perror("accept");
	close(sfd);
	exit(-1);
} 
printf("%s %d success connect\n",inet_ntoa(clientaddr.sin_addr),ntohs(clientaddr.sin_port)); 

6. recv 函数: 接收储存传来的数据

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

作用:用新的套接字来接收远端主机传来的数据,并把数据存到由参数 buf 指向的内存空间

参数:

  • sockfd : 为前面 accept 的返回值, 即 new_fd,也就是新的套接字
  • buf ; 表示缓冲区
  • len : 表示缓冲区的长度
  • flags : 调用操作方式,通常为 0

返回值:成功则返回实际接收到的字符数,可能会少于你所指定的接收长度。失败返回-1

常用实例:
char buf[512] = {0};

if(recv(new_fd, buf, sizeof(buf), 0) == -1)
{
 	perror("recv");
 	close(new_fd);
 	close(sfd);
 	exit(-1);
 	} 
 	puts(buf); 
}

7. send 函数:用新的套接字发送数据给指定的远端主机

int send(int s,const void * msg,int len,unsigned int flags);  

作用 : 用新的套接字发送数据给指定的远端主机

参数:

  • s : 为前面 accept 的返回值.即 new_fd
  • msg : 一般为常量字符串
  • len : 表示长度
  • flags : 调用操作方式,通常为 0

返回值:成功则返回实际传送出去的字符数,可能会少于你所指定的发送长度。失败返回 -1

常用实例:

if(send(new_fd, "hello", 6, 0) == -1)
{
	perror("send");
	close(new_fd);
	close(sfd);
	exit(-1);
} 

8. close 函数:关闭文件

int close(int fd);  

作用 : 当使用完文件后若已不再需要则可使用 close()关闭该文件,并且 close()会让数据写回磁盘, 并释放该文件所占用的资源

参数:
fd : 为前面的 sfd 或者 new_fd

返回值: 若文件顺利关闭则返回 0,发生错误时返回-1

常用实例:

close(new_fd);
close(sfd);

可以调用 shutdown 实现半关闭

int shutdown(int sockfd, int how); 

函数行为由how值决定。
SHUT_RD值为0,关闭连接的读这一半,套接字中不再有数据接收,且套接字接收缓冲区中的现有数据全都被丢弃,该套接字描述符不能再被进程调用,对端发送的数据会被确认,然后丢弃。

SHUT_WR值为1,关闭连接的写这一半。这称为半关闭,当前在套接字发送缓冲区数据被发送,然后连接终止序列。不论套接字描述符引用技术是否等于0,写半部都会被关闭。

SHUT_RDWR值为2,连接的读和写都关闭。相当于先调用SHUT_RD,再调用SHUT_WR


客户端:

1. connect 函数:请求连接远程服务器

int connect (int sockfd,struct sockaddr * serv_addr,int addrlen);  

作用 : 用来请求连接远程服务器,将参数 sockfd 的 socket 连至参数 serv_addr 指定的服务器 IP 和端口号上去。

参数:

  • sockfd : 为前面 socket 的返回值,即 sfd
  • serv_addr : 为结构体指针变量,存储着远程服务器的 IP 与端口号信息。
  • addrlen: 表示结构体变量的长度

返回值:成功则返回 0,失败返回-1

常用实例:

struct sockaddr_in seraddr;  -请求连接服务器
memset(&seraddr, 0, sizeof(struct sockaddr));
seraddr.sin_family = AF_INET;
seraddr.sin_port = htons(8888); -服务器的端口号
seraddr.sin_addr.s_addr = inet_addr("192.168.0.101");  -服务器的 ip
if(connect(sfd, (struct sockaddr*)&seraddr, sizeof(struct sockaddr)) == -1)
{
	perror("connect");
	close(sfd);
	exit(-1);
} 

客户端一对一 , 服务器一对多, accept 多个客户端, 每个 newFd 对应一个客户端


实例2 : 实现tcp服务器端,客户端各发一条数据给对方

服务器:

int main(int argc, char *argv[])
{
	ARGS_CHECK(argc, 3);
	int socketFd = socket(AF_INET, SOCK_STREAM, 0);
	ERROR_CHECK(socketFd, -1, "socket"); -类似open
	struct sockaddr_in serAddr;
	bzero(&serAddr, sizeof(serAddr));
	serAddr.sin_family = AF_INET;
	serAddr.sin_port = htons(atoi(argv[2])); -htonl不可以,接到的数不对导致出错
	serAddr.sin_addr.s_addr = inet_addr(argv[1]);
	int ret;
	ret = bind(socketFd, (struct sockaddr*)&serAddr, sizeof(serAddr)); 
	ERROR_CHECK(ret, -1, "bind");
	listen(socketFd, 10);
	struct sockaddr_in client; -拿到对端的信息
	bzero(&client, sizeof(client));
	socklen_t clientLen = sizeof(client);
	int newFd = accept(socketFd, (struct sockaddr*)&client, &clientLen);
	printf("client ip = %s, client port = %d\n", inet_ntoa(client.sin_addr), ntohs(client.sin_port));
	char buf[1024] = {0};
	recv(newFd, buf, sizeof(buf),0);
	printf("server gets %s\n", buf);
	send(newFd, "I am Jocker", 11, 0);
	//while(1); -用来测试查看tcp状态
	close(newFd);
	close(socketFd);
	return 0;
}

客户端:

int main(int argc, char *argv[])
{
	ARGS_CHECK(argc, 3);
	int socketFd = socket(AF_INET, SOCK_STREAM, 0);
	ERROR_CHECK(socketFd, -1, "socket"); -类似open
	struct sockaddr_in serAddr;
	bzero(&serAddr, sizeof(serAddr));
	serAddr.sin_family = AF_INET;
	serAddr.sin_port = htons(atoi(argv[2])); -htonl不可以,接到的数不对导致出错
	serAddr.sin_addr.s_addr = inet_addr(argv[1]);
	int ret;
	ret = connect(socketFd, (struct sockaddr*)&serAddr, sizeof(serAddr));
	ERROR_CHECK(ret, -1, "connect");
	send(socketFd, "I am Queen", 10, 0);
	char buf[1024] = {0};
	recv(socketFd, buf, sizeof(buf), 0);
	printf("client gets %s\n", buf);
	// while(1); -用作测试
	close(socketFd);
	return 0;
}

bind 中强制类型转换是因为serAddrstruct sockaddr_in 类型 , 而 bind 接口参数类型是 struct sockaddr *

recv send用法类似read write用法

使用两个窗口分别启动 服务器 和 客户端
服务器输入: ./server + 本地IP地址
客户端输入: ./client + 本地IP地址

运行结果 :

服务器:

client ip = 192.168.220.128, client port = 35930
server gets I am Queen

客户端:

client gets I am Jocker

实例3 : 使用tcp实现即时聊天

首先封装一个接口

int tcpInit(int *sfd, char *ip, char *port)
{
	
	int socketFd = socket(AF_INET, SOCK_STREAM, 0);
	ERROR_CHECK(socketFd, -1, "socket");
	struct sockaddr_in serAddr;
	bzero(&serAddr, sizeof(serAddr));
	serAddr.sin_family = AF_INET;
	serAddr.sin_port = htons(atoi(port));
	serAddr.sin_addr.s_addr = inet_addr(ip);
	int ret;
	ret = bind(socketFd, (struct sockaddr*)&serAddr, sizeof(serAddr));
	ERROR_CHECK(ret, -1, "bind");
	listen(socketFd, 10);
	*sfd = socketFd;
	return 0;
}

服务器:

int tcpInit(int*, char*, char*);
int main(int argc, char* argv[])
{
	ARGS_CHECK(argc, 3);
	int socketFd;
	int ret;
	ret = tcpInit(&socketFd,argv[1],argv[2]);
	if(-1 == ret)
	{
		return -1;
	}
	int newFd = accept(socketFd, NULL, NULL);
	fd_set rdset;
	char buf[10];
	while(1)
	{
		if(strlen(buf) == 0)
		{
			printf("connect success\n");
		}
		FD_ZERO(&rdset);
		FD_SET(STDIN_FILENO, &rdset);
		FD_SET(newFd, &rdset);
		ret = select(newFd + 1, &rdset, NULL, NULL, NULL);
		if(ret > 0)
		{ 
			if(FD_ISSET(STDIN_FILENO, &rdset))
			{
				bzero(buf, sizeof(buf));
				ret = read(STDIN_FILENO, buf, sizeof(buf));
				if(0 == ret)
				{
					printf("You exit. chat end...\n");
					break;
				}
				send(newFd, buf, strlen(buf) - 1, 0);	
			}
			if(FD_ISSET(newFd, &rdset))
			{
				bzero(buf,sizeof(buf));
				ret = recv(newFd, buf, sizeof(buf), 0);
				if(0 == ret) -判断对方断开
				{
					printf("Peer exit. chat end...\n");	
					break;
				}
				printf("%s\n", buf);
			}
		}
	}
	return 0;
}

客户端:

int main(int argc, char *argv[])
{
	ARGS_CHECK(argc, 3);
	int socketFd = socket(AF_INET, SOCK_STREAM, 0);
	ERROR_CHECK(socketFd, -1, "socket");
	struct sockaddr_in serAddr;
	bzero(&serAddr, sizeof(serAddr));
	serAddr.sin_family = AF_INET;
	serAddr.sin_port = htons(atoi(argv[2]));
	serAddr.sin_addr.s_addr = inet_addr(argv[1]);
	int ret;
	ret = connect(socketFd, (struct sockaddr*)&serAddr, sizeof(serAddr));
	ERROR_CHECK(ret, -1, "connect");
	fd_set rdset;
	char buf[1024];
	int time = 0;
	while(1)
	{	
		if(time == 0)
		{
			printf("connect success\n");
		}
		++time;
		FD_ZERO(&rdset);
		FD_SET(STDIN_FILENO, &rdset);
		FD_SET(socketFd, &rdset);
		ret = select(socketFd + 1, &rdset, NULL, NULL, NULL);
		if(ret > 0)
		{ 
			if(FD_ISSET(STDIN_FILENO, &rdset))
			{
				bzero(buf, sizeof(buf));
				ret = read(STDIN_FILENO, buf, sizeof(buf));
				if(0 == ret)
				{
					printf("You exit. chat end...\n");
					break;
				}
				send(socketFd, buf, strlen(buf) - 1, 0);	
			}
			if(FD_ISSET(socketFd, &rdset))
			{
				bzero(buf,sizeof(buf));
				ret = recv(socketFd, buf, sizeof(buf), 0);
				if(0 == ret)
				{
					printf("Peer exit. chat end...\n");
					break;
				}
				printf("%s\n", buf);
			}
		}
	}
	close(socketFd);
	return 0;
}

使用两个窗口分别启动 服务器 和 客户端
服务器输入: ./server + 本地IP地址
客户端输入: ./client + 本地IP地址

运行结果 :

服务器:

connect success
hi
can you hear me
yes
I went offline first
bye

客户端:

connect success
hi
can you hear me
yes
I went offline first
bye
Peer exit. chat end…

实例4:使用tcp实现即时聊天,客户端支持断开重连(使用select)

还是使用之前的封装函数 int tcpInit(int *sfd, char *ip, char *port)

服务器:

int tcpInit(int*, char*, char*);
int main(int argc, char* argv[])
{
	ARGS_CHECK(argc, 3);
	int socketFd;
	int ret;
	ret = tcpInit(&socketFd,argv[1],argv[2]);
	if(-1 == ret)
	{
		return -1;
	}
	int newFd;
	fd_set rdset;
	fd_set needMoniterFd; -始终记录要监控的描述符
	FD_ZERO(&needMoniterFd);
	FD_SET(STDIN_FILENO, &needMoniterFd);
	FD_SET(socketFd, &needMoniterFd);
	char buf[1024];
	while(1)
	{
		memcpy(&rdset, &needMoniterFd, sizeof(fd_set));
		ret = select(14, &rdset, NULL, NULL, NULL);
		if(ret > 0)
		{ 
			if(FD_ISSET(socketFd, &rdset))
			{
				newFd = accept(socketFd, NULL, NULL);
				ERROR_CHECK(newFd, -1, "accept");
				FD_SET(newFd, &needMoniterFd);
				printf("connect success\n");
			}
			if(FD_ISSET(STDIN_FILENO, &rdset))
			{
				bzero(buf, sizeof(buf));
				ret = read(STDIN_FILENO, buf, sizeof(buf));
				if(0 == ret)
				{
					printf("You exit. chat end...\n");
					break;
				}
				send(newFd, buf, strlen(buf) - 1, 0);	
			}
			if(FD_ISSET(newFd, &rdset))
			{
				bzero(buf,sizeof(buf));
				ret = recv(newFd, buf, sizeof(buf), 0);
				if(0 == ret) -判断对方断开
				{
					printf("Peer exit....\n");	
					FD_CLR(newFd, &needMoniterFd);
					close(newFd);
				}else{
					printf("%s\n", buf);
				}
				printf("%s\n", buf);
			}
		}
	}
	return 0;
}

客户端:

int main(int argc, char *argv[])
{
	ARGS_CHECK(argc, 3);
	int socketFd = socket(AF_INET, SOCK_STREAM, 0);
	ERROR_CHECK(socketFd, -1, "socket"); //类似open
	struct sockaddr_in serAddr;
	bzero(&serAddr, sizeof(serAddr));
	serAddr.sin_family = AF_INET;
	serAddr.sin_port = htons(atoi(argv[2])); //htonl不可以,接到的数不对导致出错
	serAddr.sin_addr.s_addr = inet_addr(argv[1]);
	int ret;
	ret = connect(socketFd, (struct sockaddr*)&serAddr, sizeof(serAddr));
	ERROR_CHECK(ret, -1, "connect");
	fd_set rdset;
	char buf[1024];
	int time = 0;
	while(1)
	{	
		if(time == 0)
		{
			printf("connect success\n");
		}
		++time;
		FD_ZERO(&rdset);
		FD_SET(STDIN_FILENO, &rdset);
		FD_SET(socketFd, &rdset);
		ret = select(socketFd + 1, &rdset, NULL, NULL, NULL);
		if(ret > 0)
		{ 
			if(FD_ISSET(STDIN_FILENO, &rdset))
			{
				bzero(buf, sizeof(buf));
				ret = read(STDIN_FILENO, buf, sizeof(buf));
				if(0 == ret)
				{
					printf("You exit. chat end...\n");
					break;
				}
				send(socketFd, buf, strlen(buf) - 1, 0);	
			}
			if(FD_ISSET(socketFd, &rdset))
			{
				bzero(buf,sizeof(buf));
				ret = recv(socketFd, buf, sizeof(buf), 0);
				if(0 == ret)
				{
					printf("Peer exit. chat end...\n");
					break;
				}
				printf("%s\n", buf);
			}
		}
	}
	close(socketFd);
	return 0;
}

使用两个窗口分别启动 服务器 和 客户端
服务器输入: ./server + 本地IP地址
客户端输入: ./client + 本地IP地址

运行结果 :

服务器:

connect success
Are ya ready kids?
Aye Aye Captain.
I can’t hear you.
Aye Aye Captain!
OHHHHHHH, Who lives in a pineapple under the sea?
SpongeBob SquarePants!
Peer exit…

connect success
Absorbent and yellow and porous is he!
SpongeBob SquarePants!

客户端:

connect success
Are ya ready kids?
Aye Aye Captain.
I can’t hear you.
Aye Aye Captain!
OHHHHHHH, Who lives in a pineapple under the sea?
SpongeBob SquarePants
^C

中途^C 退出客户端,然后重新输入: ./client + 本地IP地址 运行

connect success
Absorbent and yellow and porous is he!
SpongeBob SquarePants!
Peer exit. chat end…

就完成了客户端断开重连


3.2. 使用 UDP 协议的流程图

UDP 通信流程图如下:
服务端:socket → bind → recvfrom →→→→→ sendto → close
客户端:socket →→→→→→→→→ sendto → recvfrom → close

Linux_网络编程_3. socket 编程_第2张图片

服务器一对多客户端,客户端描述符一对多服务器

1. sendto()函数

int sendto(int sockfd, const void *msg,int len,unsigned int flags,const struct sockaddr *to, socklen_t tolen); 

作用:向服务器发送数据

参数:

  • sockfd : socket函数的返回值
  • msg : 一般为常量字符串
  • len : 表示长度
  • flags : 调用操作方式,通常为 0
  • (该函数比 send()函数多了两个参数)
  • to :指向接收数据的主机地址信息的结构体(sockaddr_in需类型转换),一般表示目地机的 IP 地址和端口号信息
  • tolen :to所指结构体的长度,常被赋值为 sizeof (struct sockaddr)

返回值:sendto 函数也返回实际发送的数据字节长度,在出现发送错误时返回-1。


2. recvfrom()函数

int recvfrom(int sockfd,void *buf,int len,unsigned int flags,struct sockaddr *from,int *fromlen);

作用:接收服务器的数据

参数:

  • sockfd : 为前面 accept 的返回值, 即 new_fd,也就是新的套接字
  • buf ; 表示缓冲区
  • len : 表示缓冲区的长度
  • flags : 调用操作方式,通常为 0
  • from指向发送数据的客户端地址信息的结构体(sockaddr_in需类型转换),是一个 struct sockaddr 类型的变量,该变量保存连接机的IP地址及端口号
  • fromlen :指针,指向from结构体长度值,常置为 sizeof (struct sockaddr)

返回值:

  • recvfrom()返回时,fromlen 包含实际存入 from 中的数据字节数。
  • recvfrom() 函数返回接收到的字节数或当出现错误时返回-1,并置相应的 errno。

实例5:udp实现客户端和服务器端传话

服务器:

int main(int argc, char* argv[])
{
	ARGS_CHECK(argc, 3);
	int socketFd=socket(AF_INET, SOCK_DGRAM, 0);
	ERROR_CHECK(socketFd, -1, "socket");
	struct sockaddr_in serAddr;
	bzero(&serAddr, sizeof(serAddr));
	serAddr.sin_family = AF_INET;
	serAddr.sin_port = htons(atoi(argv[2]));
	serAddr.sin_addr.s_addr = inet_addr(argv[1]);
	int ret;
	ret = bind(socketFd, (struct sockaddr*)&serAddr, sizeof(serAddr)); -端口激活
	ERROR_CHECK(ret, -1, "bind");
	struct sockaddr_in client;
	bzero(&client, sizeof(client));
	socklen_t fromLen = sizeof(struct sockaddr);
	char buf[128] = {0};
	recvfrom(socketFd, buf, sizeof(buf), 0, (struct sockaddr*)&client, &fromLen);
	printf("gets [%s]\nclient ip = %s, port = %d\n", buf,inet_ntoa(client.sin_addr), ntohs(client.sin_port));
	sendto(socketFd, "I am Joker", 10, 0, (struct sockaddr*)&client, sizeof(client));
	close(socketFd);
	return 0;
}

客户端:

int main(int argc, char* argv[])
{
	ARGS_CHECK(argc, 3);
	int socketFd=socket(AF_INET, SOCK_DGRAM, 0);
	ERROR_CHECK(socketFd, -1, "socket");
	struct sockaddr_in serAddr;
	bzero(&serAddr, sizeof(serAddr));
	serAddr.sin_family = AF_INET;
	serAddr.sin_port = htons(atoi(argv[2]));
	serAddr.sin_addr.s_addr = inet_addr(argv[1]);
	char buf[128] = {0};
	sendto(socketFd, "I am client", 11, 0, (struct sockaddr*)&serAddr, sizeof(struct sockaddr));
	recvfrom(socketFd, buf, sizeof(buf), 0, NULL, NULL);
	printf("client gets [%s]\n", buf);
	close(socketFd);
	return 0;
}

使用两个窗口分别启动 服务器 和 客户端
服务器输入: ./server + 本地IP地址
客户端输入: ./client + 本地IP地址

运行结果 :

服务器:

gets [I am client]
client ip = 192.168.1.1, port = 49999

客户端:

client gets I am Jocker


实例6:udp实现客户端和服务器端传话

服务器:

int main(int argc, char* argv[])
{
	ARGS_CHECK(argc, 3);
	int socketFd = socket(AF_INET, SOCK_DGRAM, 0);
	ERROR_CHECK(socketFd, -1, "socket");
	struct sockaddr_in serAddr;
	bzero(&serAddr, sizeof(serAddr));
	serAddr.sin_family = AF_INET;
	serAddr.sin_port = htons(atoi(argv[2]));
	serAddr.sin_addr.s_addr = inet_addr(argv[1]);
	int ret;
	ret = bind(socketFd, (struct sockaddr*)&serAddr, sizeof(serAddr)); -端口激活
	ERROR_CHECK(ret, -1, "bind");
	struct sockaddr_in client;
	bzero(&client, sizeof(client));
	socklen_t fromLen = sizeof(struct sockaddr);
	char buf[128] = {0};
	recvfrom(socketFd, buf, sizeof(buf), 0, (struct sockaddr*)&client, &fromLen);
	printf("gets [%s]\nclient ip = %s, port = %d\n", buf,inet_ntoa(client.sin_addr), ntohs(client.sin_port));
	fd_set rdset;
	while(1)
	{
		FD_ZERO(&rdset);
		FD_SET(STDIN_FILENO, &rdset);
		FD_SET(socketFd, &rdset);
		ret = select(socketFd + 1, &rdset, NULL, NULL, NULL);
		if(FD_ISSET(STDIN_FILENO, &rdset))
		{
			bzero(buf, sizeof(buf));
			read(STDIN_FILENO, buf, sizeof(buf) - 1);
			sendto(socketFd, buf, strlen(buf) - 1, 0, (struct sockaddr*)&client, sizeof(client));
		}
		if(FD_ISSET(socketFd, &rdset))
		{
			bzero(buf, sizeof(buf));
			recvfrom(socketFd, buf, sizeof(buf) - 1, 0, (struct sockaddr*)&client, &fromLen);
			printf("%s\n", buf);
		}
	}
	close(socketFd);
	return 0;
}

客户端:

int main(int argc, char* argv[])
{
	ARGS_CHECK(argc, 3);
	int socketFd = socket(AF_INET, SOCK_DGRAM, 0);
	ERROR_CHECK(socketFd, -1, "socket");
	struct sockaddr_in serAddr;
	bzero(&serAddr, sizeof(serAddr));
	serAddr.sin_family = AF_INET;
	serAddr.sin_port = htons(atoi(argv[2]));
	serAddr.sin_addr.s_addr = inet_addr(argv[1]);
	char buf[128] = {0};
	sendto(socketFd, "1", 1, 0, (struct sockaddr*)&serAddr, sizeof(struct sockaddr));
	fd_set rdset;
	while(1)
	{
		FD_ZERO(&rdset);
		FD_SET(STDIN_FILENO, &rdset);
		FD_SET(socketFd, &rdset);
		select(socketFd + 1, &rdset, NULL, NULL, NULL);
		if(FD_ISSET(STDIN_FILENO, &rdset))
		{
			bzero(buf, sizeof(buf));
			read(STDIN_FILENO, buf, sizeof(buf));
			sendto(socketFd, buf, strlen(buf)-1, 0, (struct sockaddr*)&serAddr, sizeof(serAddr));
		}
		if(FD_ISSET(socketFd, &rdset))
		{
			bzero(buf, sizeof(buf));
			recvfrom(socketFd, buf, sizeof(buf), 0, NULL, NULL );
			printf("%s\n", buf);
		}
	}
	close(socketFd);
	return 0;
}

使用两个窗口分别启动 服务器 和 客户端
服务器输入: ./server + 本地IP地址
客户端输入: ./client + 本地IP地址

运行结果 :

服务器:

128 2000
gets [1]
client ip = 192.168.1.1, port = 36666
can you hear me
yes
bye

客户端:

can you hear me
yes
bye

其中某一断断开对另一端没有影响


注意:
操作系统的 UDP 接收流程如下:
收到一个 UDP 包后,验证没有错误后,放入一个包队列中,队列中的每一个元素就是一个完整的 UDP 包。当应用程序通过 recvfrom()读取时, OS 把相应的一个完整 UDP 包取出,然后拷贝到用户提供的内存中,物理用户提供的内存大小是多少,OS 都会完整取出一个 UDP 包。如果用户提供的内存小于这个 UDP 包的大小,那么在填充慢内存后,UDP 包剩余的部分就会被丢弃,以后再也无法取回。
(TCP像管道,UDP像消息队列)
这与 TCP 接收完全不同,TCP 没有完整包的概念,也没有边界,OS 只会取出用户要求的大小,剩余的仍然保留在 OS 中,下次还可以继续取出。

思考题: 机器 A 向机器 B 发送数据,以 TCP 方式发送 3 个包,对方可能会收到几个包,以 UDP 方式发送 3 个包,对方可能会收到几个包
(TCP至少3个, 3,6,9,…个包,UDP 0-3 个包)


3.3. 设置套接口的选项 setsockopt 的用法

头文件:
#include
#include

**1. 函数 `setsockopt` 设置选项值**
int setsockopt(int sockfd, int level, int optname, const void *optval, socklen_t optlen); 

作用:用于任意类型、任意状态套接口的设置选项值

参数:

  • sockfd:标识一个套接口的描述符
  • level:选项定义的层次;支持 SOL_SOCKETIPPROTO_TCPIPPROTO_IPIPPROTO_IPV6
  • optname:需设置的选项
  • optval:指针,指向存放选项值的缓冲区
  • optlen:optval 缓冲区长度

optname可选项

选项名称 说明 数据类型
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_REUSEADDR 允许重用本地地址和端口 int
SO_TYPE 获得套接字类型 int
SO_BSDCOMPAT 与 BSD 系统兼容 int
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

实例7:使用SO_REUSEADDR实现重用本地地址和端口

解决了程序再次使用IP是被使用 Address already in use 的问题,就是 setsockopt 加上 SO_REUSEADDR

使用之前的服务器和客户端(实例3)

将封装的文件修改

int tcpInit(int *sfd, char *ip, char *port)
{
	
	int socketFd = socket(AF_INET, SOCK_STREAM, 0);
	ERROR_CHECK(socketFd, -1, "socket");
	struct sockaddr_in serAddr;
	bzero(&serAddr, sizeof(serAddr));
	serAddr.sin_family = AF_INET;
	serAddr.sin_port = htons(atoi(port));
	serAddr.sin_addr.s_addr = inet_addr(ip);
	int ret;
	int reuse = 1;
	ret = setsockopt(socketFd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(int)); -设置属性
	ERROR_CHECK(ret, -1, "setsockopt");
	ret = bind(socketFd, (struct sockaddr*)&serAddr, sizeof(serAddr));
	ERROR_CHECK(ret, -1, "bind");
	listen(socketFd, 10);
	*sfd = socketFd;
	return 0;
}

就可以实现退出后重用同地址的效果

全部都必须要放在 bind 之前,另外通常是用于 UDP 的。

  • 如果在已经处于 ESTABLISHED 状态下的 socket(一般由端口号和标志符区分)调用 closesocket(一 般不会立即关闭而经历 TIME_WAIT 的过程)后想继续重用该 socket:
    int reuse=1;
	setsockopt(s,SOL_SOCKET ,SO_REUSEADDR,(const char*)&reuse,sizeof(int)); 
  • send()recv()过程中有时由于网络状况等原因,发收不能预期进行,而设置收发时限:
	int nNetTimeout=1000; -1- 发送时限 
	setsockopt(socket,SOL_S0CKET,SO_SNDTIMEO,(char *)&nNetTimeout,sizeof(int));
	- 接收时限 
	setsockopt(socket,SOL_S0CKET,SO_RCVTIMEO,(char *)&nNetTimeout,sizeof(int)); 
  • send() 的时候,返回的是实际发送出去的字节(同步)或发送到 socket 缓冲区的字节(异步),系统默认的 状态发送和接收一次为 8688 字节(约为 8.5K);在实际的过程中发送数据和接收数据量比较大,可以设置 socket 缓冲区,而避免了 send()recv()不断的循环收发:
	- 接收缓冲区 
	int nRecvBuf=32*1024;   - 设置为 32K 
	setsockopt(s,SOL_SOCKET,SO_RCVBUF,(const char*)&nRecvBuf,sizeof(int)); 
	- 发送缓冲区 
	int nSendBuf=32*1024;    - 设置为 32K 
	setsockopt(s,SOL_SOCKET,SO_SNDBUF,(const char*)&nSendBuf,sizeof(int)); 
  • 如果在发送数据时,希望不经历由系统缓冲区到 socket 缓冲区的拷贝而影响程序的性能:
	int nZero=0;  
	setsockopt(socket,SOL_SOCKET,SO_SNDBUF,(char *)&nZero,sizeof(int)); 
  • 同上在 recv()完成上述功能(默认情况是将 socket 缓冲区的内容拷贝到系统缓冲区):
	 int nZero=0; 
	 setsockopt(socket,SOL_SOCKET,SO_RCVBUF,(char *)&nZero,sizeof(int)); 
  • 一般在发送 UDP 数据报的时候,希望该 socket 发送的数据具有广播特性:
	int bBroadcast= 1; 
	setsockopt(s,SOL_SOCKET,SO_BROADCAST,(const char*)&bBroadcast,sizeof(int));

3.4. 单播、广播、组播(多播)(不常用)

多播广播是用于建立分步式系统:例如网络游戏、ICQ 聊天构建、远程视频会议系统的重要工具。 使用多播广播的程序和 UDP 的单播程序相似。区别在于多播广播程序使用特殊的 IP 地址。

对于单播而言,单播用于两个主机之间的端对端通信

对于广播而言,广播用于一个主机对整个局域网上所有主机上的数据通信
广播只能用于客户机向 服务器广播,因为客户机要指明广播的 IP 地址“192.168.0.255”和广播的端口号。服务器端 bind 的时候, 绑定的端口号要跟广播的端口号是同一个。这样才能收到广播消息。

对于多播而言,也称为 “组播” ,将网络中同一业务类型主机进行了逻辑上的分组,进行数据收发的时候其数据仅仅在同一分组中进行,其他的主机没有加入此分组不能收发对应的数据。单播和广播是两个极端,要么对一个主机进行通信,要么对整个局域网上的主机进行通信。实际情况下,经常需要对一组特定的主机进行通信,而不是整个局域网上的所有主机,这就是多播的用途。例如,我们通常所说的 讨论组。IPv4 多播地址采用 D 类 IP 地址确定多播的组。在 Internet 中,多播地址范围是从 224.0.0.0 到 234.255.255.255。(可穿透局域网)

多播的程序设计也要使用 setsockopt()函数和 getsockopt() 函数来实现。其中对于 setsockopt 的第二个 参数 level 不再是 SOL_SOCKET,而是 IPPROTO_IP;而且第三个参数 optname 常见的选项有:

Optname 含义
IP_ADD_MEMBERSHIP 在指定接口上加入组播组
IP_DROP_MEMBERSHIP 退出组播组

选项 IP_ADD_MEMBERSHIP 和 IP_DROP_MEMBERSHIP 加入或者退出一个组播组

通过选项 IP_ADD_MEMBERSHIP 和 IP_DROP_MEMBERSHIP,对一个结构 struct ip_mreq 类型的变量进行控制。struct ip_mreq 原型如下:

struct ip_mreq 
{ 
	struct in_addr    imr_multiaddr;  -加入或者退出的多播组 IP 地址
	struct in_addr    imr_interface;  -加入或者退出的网络接口 IP 地址,本机 IP
}; 

选项 IP_ADD_MEMBERSHIP 用于加入某个多播组,之后就可以向这个多播组发送数据或者从多播 组接收数据。此选项的值为 mreq 结构,成员 imr_multiaddr 是需要加入的多播组IP地址,成员 imr_interface 是本机需要加入多播组的网络接口 IP 地址。例如:

3.5. 什么是 DDos(SYN Flooding)攻击,如何防护

在探讨 SYN 攻击之前,我们先看看 linux 内核对 SYN 是怎么处理的:

  • Server 接收到 SYN 连接请 求。 内部维护一个队列(我们暂称之半连接队列,半连接并不准确), 发送 ack 及 syn 给 Client 端, 等待 Client 端的 ack 应答,接收到则完成三次握手建立连接。 如果接收不到 ack 应答,则根据延时重传 规则继续发送 ack 及 syn 给客户端。

利用上述特点。我们构造网络包,源地址随机构建,意味着当 Server 接到 SYN 包时,应答 ack 和 syn 时不会得到回应。在这种情况下, Server 端,内核就会维持一个很大的队列来管理这些半连接当半连接足够多的时候,就会导致新来的正常连接请求得不到响应, 也就是所谓的 DOS 攻击。 详细见下图所示:
Linux_网络编程_3. socket 编程_第3张图片

SYN Flood 攻击防护手段

  • tcp_max_syn_backlog:半连接队列长度
  • tcp_synack_retries: syn+ack 的重传次数
  • tcp_syncookies : syn cookie

一般的防御措施就是就是减小 SYN+ACK 重传次数,增加半连接队列长度,启用 syn cookie
不过在高强度攻击面前,调优 tcp_syn_retries 和 tcp_max_syn_backlog 并不能解决根本问题,更有效的防御手段
激活 tcp_syncookies,在连接真正创建起来之前,它并不会立刻给请求分配数据区存储连接状态,而
是通过构建一个带签名的序号来屏蔽伪造请求。
(DDOS拒绝服务攻击)


下面的仅作了解


3.6. 描述符属性修改及文件描述符的传递

1. fcntl 函数

头文件:
#include
#include
#include

int fcntl(int fd, int cmd);
int fcntl(int fd, int cmd, int arg);
int fcntl(int fd, int cmd, struct flock *lock); 

作用:可以改变已打开的文件描述符性质,针对(文件)描述符提供控制

参数:

① 参数fd
是被参数 cmd 操作(如下面的描述)的描述符,代表要设置的文件描述符.
(针对 cmd 的值,fcntl 能够接受第三个参数 int arg )

② 参数cmd
代表打算操作的指令
有以下几种情况:

  • F_DUPFD用来查找大于或等于参数arg的最小且仍未使用的文件描述符,并且复制参数fd的文件描述符。执行成功则返回新复制的文件描述符。新描述符与fd共享同一文件表项,但是新描述符有它自己的一套文件描述符标志,其中FD_CLOEXEC文件描述符标志被清除。如dup2()。
  • F_GETFD 取得close-on-exec旗标。若此旗标的FD_CLOEXEC位为0,代表在调用exec()相关函数时文件将不会关闭。
  • F_SETFD 设置close-on-exec 旗标。该旗标以参数argFD_CLOEXEC位决定。
  • F_GETFL 取得文件描述符状态旗标,此旗标为 open() 的参数 flags
  • F_SETFL 设置文件描述符状态旗标,参数arg为新旗标,但只允许O_APPEND、O_NONBLOCK和O_ASYNC位的改变,其他位的改变将不受影响。
  • F_GETLK 取得文件锁定的状态。

③ 参数lock
参数lock指针为flock 结构指针
定义如下:

struct flock
{
short int l_type;
short int l_whence;
off_t l_start;
off_t l_len;
pid_t l_pid;
};
.
l_type 有三种状态:F_RDLCK 建立一个供读取用的锁定 F_WRLCK 建立一个供写入用的锁定 F_UNLCK 删除之前建立的锁定
.
l_whence 也有三种方式: SEEK_SET 以文件开头为锁定的起始位置。 SEEK_CUR 以目前文件读写位置为锁定的起始位置 SEEK_END 以文件结尾为锁定的起始位置。
.
l_start 表示相对l_whence位置的偏移量,两者一起确定锁定区域的开始位置。
.
**l_len**表示锁定区域的长度,如果为0表示从起点(由l_whencel_start决定的开始位置)开始直到最大可能偏移量为止。即不管在后面增加多少数据都在锁的范围内。
.
返回值: 成功返回依赖于cmd的值,若有错误则返回-1,错误原因存于errno.


2. Socketpair 函数简介

int socketpair(int domain, int type, int protocol, int sv[2]); 

作用 : 建立一对匿名的已经连接的套接字
前面 3 个参数参照 socket,domain 变为 AF_LOCAL,sv[2] , 放我们 fd[2]


3. Sendmsg 函数简介

ssize_t sendmsg (int s, const struct msghdr *msg, int flags); 

作用 sendmsg 系统调用用于发送消息到另一个套接字

参数:
要在其上发送消息的套接口 s
信息头结构指针 msg,这会控制函数调用的功能
可选的标记位参数 flags。这与 send 或是 sendto 函数调用的标记参数相同。

返回值 : 函数的返回值为实际发送的字节数。否则,返回-1 表明发生了错误,而 errno 表明错误原因。


struct msghdr 结构体

结构定义如下:

struct msghdr {
	void *msg_name;
	socklen_t msg_namelen;
	struct iovec *msg_iov;
	size_t msg_iovlen;
	void *msg_control;
	size_t msg_controllen;
	int  msg_flags;
}; 

结构成员可以分为四组。
他们是:

  • 套接口地址成员 msg_name 与 msg_namelen。
  • I/O 向量引用 msg_iov 与 msg_iovlen。
  • 附属数据缓冲区成员 msg_control 与 msg_controllen。
  • 接收信息标记位 msg_flags。

成员 msg_name 与 msg_namelen
这些成员只有当我们的套接口是一个数据报套接口时才需要。
msg_name 成员指向我们要发送或是接收信息的套接口地址
成员 msg_namelen 指明了这个套接口地址的长度
( 当调用 recvmsg 时,msg_name 会指向一个将要接收的地址的接收区域。当调用 sendmsg 时,这会指向一个数据报将要发送到的目的地址。 )
( 注意,msg_name 定义为一个(void *)数据类型。我们并不需要将我们的套接口地址转换为(struct sockaddr *)。 )

成员 msg_iov 与 msg_iovlen
这些成员指定了我们的 I/O 向量数组的位置以及他包含多少项。
msg_iov 成员指向一个 struct iovec 数组。我们将会回忆起 I/O 向量指向我们的缓冲区。
成员 msg_iov 指明了在我们的 I/O 向量数组中 有多少元素。

成员 msg_control 与 msg_controllen
这些成员指向了我们附属数据缓冲区并且表明了缓冲区大小。
msg_control 指向附属数据缓冲区
msg_controllen 指明了缓冲区大小。
这个通过 man cmsg 了解,或者通过下面的 cmsg 熟悉


4. Recvmsg

int recvmsg(int s, struct msghdr *msg, unsigned int flags); 

参数:
要在其上接收信息的套接口 s 信息头结构指针 msg,这会控制函数调用的操作。
可选标记位参数 flags
与 recv 或是 recvfrom 函数调用的标记参数相同。
这个函数的返回值为实际接收的字节数。否则,返回-1 表明发生了错误,而 errno 表明错误原因。


5. Writev

ssize_t writev(int fd, const struct iovec *iov, int iovcnt) 

一次写入多个 buf 内容
struct iovec {
void iov_base; / Starting address /
size_t iov_len; /
Number of bytes to transfer */
};


6.Readv

ssize_t readv(int fd, const struct iovec *iov, int iovcnt); 

一次读取多个 buf 内容


7. Cmsg 用来设定我们的*msg_control 指针

size_t CMSG_LEN(size_t length);

返回结构体 cmsghdr 的大小,length 填入的是 cmsg_data[]的大小,我们填入的是描述符 fd,所以是 sizeof(int)

unsigned char *CMSG_DATA(struct cmsghdr *cmsg); 
struct cmsghdr {
	socklen_t cmsg_len;    /* data byte count, including he ader */ 
	int cmsg_level;  /* originating protocol */
	int  cmsg_type; /* protocol-specific type */
	/* followed by unsigned char cmsg_data[]; */
}; 

CMSG_DATA 返回 cmsg_data 的起始地址,也就是通过它放入我们的文件描述符

*(int*)CMSG_DATA(cmsg)=fd; 
cmsg->cmsg_level = SOL_SOCKET; 
cmsg->cmsg_type = SCM_RIGHTS; 

你可能感兴趣的:(LInux,网络编程)