网站建设胶州家园,外贸网站怎么注册,网页打不开验证码图片,宿豫网站建设制作报式套接字 1.动态报式套接字2.报式套接字的广播3.报式套接字的多播4.UDP协议分析4.1.丢包原因4.2.停等式流量控制 接linux系统编程 socket part1
1.动态报式套接字
在之前的例子上#xff0c;发送的结构体中的名字由定长改变长。可以用变长结构体。 变长结构体是由gcc扩展的… 报式套接字 1.动态报式套接字2.报式套接字的广播3.报式套接字的多播4.UDP协议分析4.1.丢包原因4.2.停等式流量控制 接linux系统编程 socket part1
1.动态报式套接字
在之前的例子上发送的结构体中的名字由定长改变长。可以用变长结构体。 变长结构体是由gcc扩展的一种技术它是指其最后一个成员的长度不固定flexible array member也叫柔性数组。 使用范围数据长度不固定例如协议对接中有固定的头结构体数据结构体不固定。
struct Var_Len_Struct
{int nsize;char buffer[0];// 或者不指定大小 char buffer[];
};结构体中的最后一个元素一个没有元素的数组柔性数组。我们可以通过动态开辟一个比结构体大的空间然后让buffer去指向那些额外的空间这样就可以实现可变长的结构体了。更为巧妙的是我们甚至可以用nsize存储字符串buffer的长度。
修改代码如下 proto.h 定义NAMEMAX接收名字的最大长度因为接收方不知道大小需要按照最大长度接收 使用变长数组最后一个元素为0或者为空 #ifndef __PROTO_H_
#define __PROTO_H_#define RECVPORT 1986
#define NAMEMAX (512-8-8) // 512为udp包推荐的字节数8为udp的报头大小8为结构体中固定长度的大小即math和chinesestruct msg_t
{uint32_t chinese;uint32_t math;//变长uint8_t name[0];
}__attribute__((packed));# endifsnder.cpp 1.发送的结构体改为结构体指针 2.计算发送的结构体大小 sizeof(struct) strlen(name) 3.动态申请内存malloc 4.sendto函数相应修改 #include stdio.h
#include stdlib.h
#include unistd.h
#include iostream
#include sys/types.h
#include sys/socket.h
#include netinet/in.h
#include netinet/ip.h
#include string.h
#include arpa/inet.h#include proto.hint main(int argc, char **argv) {int sd;//结构体改为结构体指针msg_t* sendmssg;struct sockaddr_in raddr;if (argc 3) {perror(Usage);exit(1);}//strlen()不包括字符串结尾的空字符 \0//sizeof(argv[2]) 返回的是指针的大小而不是字符串的长度if (strlen(argv[2]) NAMEMAX) {std::cout name id too long std::endl;exit(1);}//创建socketsd socket(AF_INET, SOCK_DGRAM, 0);if (sd 0) {perror(socker());exit(1);}//本地绑定可以省略//填写发送消息//变长先计算结构体的长度int size sizeof (msg_t) strlen(argv[2]);//申请内存sendmssg (msg_t*)malloc(size);if (sendmssg NULL) {perror(malloc());exit(1);}memcpy(sendmssg-name, argv[2], strlen(argv[2])1);sendmssg-chinese ntohs(100);sendmssg-math ntohs(100);//对端地址raddr.sin_family AF_INET;raddr.sin_port ntohs(RECVPORT);inet_pton(AF_INET, argv[1], raddr.sin_addr);//发送if (sendto(sd, sendmssg, size, 0, (const sockaddr*)raddr, sizeof(raddr)) -1) {perror(sendto());exit(1);}//关闭close(sd);
}rcver.cpp 1.接收的结构体改结构体指针 2.按最大长度NAMEMAX计算接收结构体的长度 3.动态分配内存 4.recvfrom函数相应修改 #include stdio.h
#include stdlib.h
#include unistd.h
#include iostream
#include sys/types.h
#include sys/socket.h
#include netinet/in.h
#include netinet/ip.h
#include string.h
#include arpa/inet.h#include proto.h
#define IPSTRSIZE 64int main() {// 套接字描述符int sd;// laddr -- local address -- 本机地址// raddr -- remote address -- 对端地址sockaddr_in laddr, raddr;socklen_t raddr_len;// 结构体指针存储接收到的结构体msg_t* mssg;// 存储对端地址点分式char ipstr[IPSTRSIZE];//创建socket创建协议为ipv4的报式套接字0为默认协议即UDPsd socket(AF_INET, SOCK_DGRAM, 0);if (sd 0) {perror(socker());exit(1);}//填写本机的地址信息laddr.sin_family AF_INET;//ip地址和网络端口号是要通过网络发送过去的,所以需要考虑字节序的问题,也就是htonsladdr.sin_port htons(RECVPORT);// 因为本机的ip地址有可能会变化,为了避免ip地址每一次变化,都要进来修改,所以给它匹配一个万能地址0.0.0.0// 对0.0.0.0的定义是any address.就是说在当前绑定阶段,本机的ip地址是多少,这四个0就会自动换成当前的ip地址.inet_pton(AF_INET, 0.0.0.0, laddr.sin_addr.s_addr);//绑定接收的ip地址和端口号if (bind(sd, (const sockaddr*)laddr, sizeof(laddr)) 0) {perror(sendto());exit(1);}// 接收// 这里一定要初始化对端地址的大小raddr_len sizeof(raddr);//不知道对端的地址大小按最大的来接int size sizeof(msg_t) NAMEMAX - 1; mssg (msg_t*)malloc(size);while (1) {if (recvfrom(sd, mssg, size, 0, (sockaddr*)raddr, raddr_len) 0) {perror(recvfrom());exit(1);}inet_ntop(AF_INET, raddr.sin_addr, ipstr, IPSTRSIZE);std::cout ---------recive message from std::string(ipstr) : ntohs(raddr.sin_port) --------- std::endl;// 单字节传输不涉及到大端小端的存储情况std::cout name : mssg-name std::endl;std::cout math : ntohs(mssg-math) std::endl;std::cout chinese : ntohs(mssg-chinese) std::endl;}//关闭close(sd);exit(1);
}vratdrh7771.rsv.ven.veritas.com [60]: ./snder 10.85.171.130 hahhaahha
vratdrh7771.rsv.ven.veritas.com [61]: ./snder 10.85.171.130 44kkkkkvratdrh7771.rsv.ven.veritas.com [71]: ./rcver
---------recive message from 10.85.171.130:33261---------
name:hahhaahha
math:100
chinese:100
---------recive message from 10.85.171.130:36716---------
name:44kkkkkha
math:100
chinese:1002.报式套接字的广播
在使用TCP/IP 协议的网络中主机标识段host ID 为全1 的 IP 地址为广播地址。
广播数据有如下特点
TCP/IP协议栈中传输层只有UDP可以广播TCP没有广播的概念UDP广播不需要经过路由器转发因为路由器不会转发广播数据
套接字机制提供了两个套接字选项接口来控制套接字行为。一个接口用来设置选项另一个接口可以查询选项的状态。
GETSOCKOPT(2) Linux Programmers Manual GETSOCKOPT(2)NAMEgetsockopt, setsockopt - get and set options on socketsSYNOPSIS#include sys/types.h /* See NOTES */#include sys/socket.hint 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);DESCRIPTIONgetsockopt() and setsockopt() manipulate options for the socket referred to by the file descriptor sockfd. Options may existat multiple protocol levels; they are always present at the uppermost socket level.level 标识了选项应用的协议。 如果选项是通用的套接字层次选项则 level 设置成SOL_SOCKET。否则level设置成控制这个选项的协议编号对于TCPlevel是IPPROTO_TCP对于IPlevel是IPPROTO_IP optname 需设置的选项optval根据选项的不同指向一个数据结构或者一个整数。一些选项是on/off开关。如果整数非0则启用选项。如果整数为0则禁止选项。optlen指定了optval指向的对象的大小。 代码示例 snder.cpp设置套接字打开广播选项并向广播地址255.255.255.255发送数据报
#include stdio.h
#include stdlib.h
#include unistd.h
#include iostream
#include sys/types.h
#include sys/socket.h
#include netinet/in.h
#include netinet/ip.h
#include string.h
#include arpa/inet.h#include proto.hint main() {int sd;msg_t sendmssg;struct sockaddr_in raddr;//创建socketsd socket(AF_INET, SOCK_DGRAM, 0);if (sd 0) {perror(socker());exit(1);}//本地绑定可以省略//设置套接字打开广播//不同的层面封装了不同的属性可以用man 7查看//man 7 socket 找 socket option//man 7 udp udp层可以改进的socket option//man 7 ip...//man 7 tcpint val 1;if(setsockopt(sd, SOL_SOCKET, SO_BROADCAST, val, sizeof(val)) 0) {perror(setsockopt());exit(1);}//填写发送消息memset(sendmssg, \0, sizeof(sendmssg));//strcpy(sendmssg.name, tracy);memcpy(sendmssg.name, tracy, sizeof(tracy));sendmssg.chinese ntohs(100);sendmssg.math ntohs(100);//对端地址raddr.sin_family AF_INET;raddr.sin_port ntohs(RECVPORT);inet_pton(AF_INET, 255.255.255.255, raddr.sin_addr);//发送if (sendto(sd, sendmssg, sizeof(sendmssg), 0, (const sockaddr*)raddr, sizeof(raddr)) 0) {perror(sendto());exit(1);}//关闭close(sd);
}
rcver.cpp设置套接字打开广播选项如果不打开可能收到可能收不到
#include stdio.h
#include stdlib.h
#include unistd.h
#include iostream
#include sys/types.h
#include sys/socket.h
#include netinet/in.h
#include netinet/ip.h
#include string.h
#include arpa/inet.h#include proto.h
#define IPSTRSIZE 64int main() {// 套接字描述符int sd;// laddr 本机地址// raddr 对端地址sockaddr_in laddr, raddr;// 存储接收到的结构体msg_t mssg;// 存储对端地址点分式char ipstr[IPSTRSIZE];// 创建socket创建协议为ipv4的报式套接字0为默认协议即UDPsd socket(AF_INET, SOCK_DGRAM, 0);if (sd 0) {perror(socker());exit(1);}int val 1;if(setsockopt(sd, SOL_SOCKET, SO_BROADCAST, val, sizeof(val)) 0) {perror(setsockopt());exit(1);}// 填写本机的地址信息laddr.sin_family AF_INET;// ip地址和网络端口号是要通过网络发送过去的,所以需要考虑字节序的问题,也就是htonsladdr.sin_port htons(RECVPORT);// 因为本机的ip地址有可能会变化,为了避免ip地址每一次变化,都要进来修改,所以给它匹配一个万能地址0.0.0.0// 对0.0.0.0的定义是any address.就是说在当前绑定阶段,本机的ip地址是多少,这四个0就会自动换成当前的ip地址.inet_pton(AF_INET, 0.0.0.0, laddr.sin_addr.s_addr);//绑定接收本机的ip地址和端口号if (bind(sd, (const sockaddr*)laddr, sizeof(laddr)) 0) {perror(sendto());exit(1);}// 接收// 这里一定要初始化对端地址的大小socklen_t addr_len sizeof(raddr);while (1) {if (recvfrom(sd, (void*)mssg, sizeof(mssg), 0, (sockaddr*)raddr, addr_len) 0) {perror(recvfrom());exit(1);}inet_ntop(AF_INET, raddr.sin_addr, ipstr, IPSTRSIZE);std::cout ---------recive message from std::string(ipstr) : ntohs(raddr.sin_port) --------- std::endl;// 单字节传输不涉及到大端小端的存储情况std::cout name : mssg.name std::endl;std::cout math : ntohs(mssg.math) std::endl;std::cout chinese : ntohs(mssg.chinese) std::endl;}//关闭close(sd);exit(1);
}
proto.h
#ifndef __PROTO_H_
#define __PROTO_H_
#define RECVPORT 1986
#define NAMESIZE 11struct msg_t
{//定长不可能有负值uint8_t name[NAMESIZE];uint32_t chinese;uint32_t math;
}__attribute__((packed));# endif注意如果仍然接收不到可以查看防火墙
vratdrh7771.rsv.ven.veritas.com [70]: ./rcver
---------recive message from 10.85.171.130:43497---------
name:tracy
math:100
chinese:1003.报式套接字的多播
多播地址也叫组播地址组播报文的目的地址使用D类IP地址 D类地址不能出现在IP报文的源IP地址字段。组播地址可以分为四类
224.0.0.0224.0.0.255为预留的组播地址永久组地址地址224.0.0.0保留不做分配其它地址供路由协议使用224.0.1.0224.0.1.255是公用组播地址可以用于Internet224.0.2.0238.255.255.255为用户可用的组播地址临时组地址全网范围内有效239.0.0.0239.255.255.255为本地管理组播地址仅在特定的本地范围内有效。 代码示例 proto.h设置一个约定的多播地址
#ifndef __PROTO_H_
#define __PROTO_H_#define MGROUP 224.2.2.2
#define RECVPORT 1986
#define NAMESIZE 11struct msg_t
{//定长不可能有负值uint8_t name[NAMESIZE];uint32_t chinese;uint32_t math;
}__attribute__((packed));# endif
snder.c创建多播组
#include stdio.h
#include stdlib.h
#include unistd.h
#include iostream
#include sys/types.h
#include sys/socket.h
#include netinet/in.h
#include netinet/ip.h
#include string.h
#include arpa/inet.h
#include net/if.h#include proto.hint main(int argc, char **argv) {int sd;msg_t sendmssg;struct sockaddr_in raddr;if (argc 2) {perror(uasge());exit(1);}//创建socketsd socket(AF_INET, SOCK_DGRAM, 0);if (sd 0) {perror(socker());exit(1);}//设置套接字创建多播组//man 7 ipip_mreqn mreq;//multicast group addressinet_pton(AF_INET, MGROUP, mreq.imr_multiaddr);//local本机addressinet_pton(AF_INET, 0.0.0.0, mreq.imr_address);//网络索引号mreq.imr_ifindex if_nametoindex(ens192);//设置套接字创建多播组if(setsockopt(sd, IPPROTO_IP, IP_MULTICAST_IF, mreq, sizeof(mreq)) 0) {perror(setsockopt());exit(1);}//填写发送消息memset(sendmssg, \0, sizeof(sendmssg));//strcpy(sendmssg.name, tracy);memcpy(sendmssg.name, argv[1], sizeof(argv[1]) 1);sendmssg.chinese ntohs(100);sendmssg.math ntohs(100);//对端地址raddr.sin_family AF_INET;raddr.sin_port ntohs(RECVPORT);inet_pton(AF_INET, MGROUP, raddr.sin_addr);//发送if (sendto(sd, sendmssg, sizeof(sendmssg), 0, (const sockaddr*)raddr, sizeof(raddr)) 0) {perror(sendto());exit(1);}//关闭close(sd);
}其中可以使用命令查看网络设备的索引号
ip ad sh如下1、2为索引号lo, ens192为设备名
vratdrh7771.rsv.ven.veritas.com [60]: ip ad sh
1: lo: LOOPBACK,UP,LOWER_UP .....
........
2: ens192: BROADCAST,MULTICAST,UP,LOWER_UP .....
........或者可以通过下列函数来获取网络设备名的索引编号
#include net/if.h
unsigned int if_nametoindex(const char *ifname);rcver.cpp加入多播组
#include stdio.h
#include stdlib.h
#include unistd.h
#include iostream
#include sys/types.h
#include sys/socket.h
#include netinet/in.h
#include netinet/ip.h
#include string.h
#include arpa/inet.h
#include net/if.h#include proto.h
#define IPSTRSIZE 64int main() {// 套接字描述符int sd;// laddr 本机地址// raddr 对端地址sockaddr_in laddr, raddr;// 存储接收到的结构体msg_t mssg;// 存储对端地址点分式char ipstr[IPSTRSIZE];// 创建socket创建协议为ipv4的报式套接字0为默认协议即UDPsd socket(AF_INET, SOCK_DGRAM, 0);if (sd 0) {perror(socker());exit(1);}//加入多播组ip_mreqn mreq;inet_pton(AF_INET, MGROUP, mreq.imr_multiaddr);inet_pton(AF_INET, 0.0.0.0, mreq.imr_address);mreq.imr_ifindex if_nametoindex(ens192);if(setsockopt(sd, IPPROTO_IP, IP_ADD_MEMBERSHIP, mreq, sizeof(mreq)) 0) {perror(setsockopt());exit(1);}// 填写本机的地址信息laddr.sin_family AF_INET;// ip地址和网络端口号是要通过网络发送过去的,所以需要考虑字节序的问题,也就是htonsladdr.sin_port htons(RECVPORT);// 因为本机的ip地址有可能会变化,为了避免ip地址每一次变化,都要进来修改,所以给它匹配一个万能地址0.0.0.0// 对0.0.0.0的定义是any address.就是说在当前绑定阶段,本机的ip地址是多少,这四个0就会自动换成当前的ip地址.inet_pton(AF_INET, 0.0.0.0, laddr.sin_addr.s_addr);//绑定接收本机的ip地址和端口号if (bind(sd, (const sockaddr*)laddr, sizeof(laddr)) 0) {perror(sendto());exit(1);}// 接收// 这里一定要初始化对端地址的大小socklen_t addr_len sizeof(raddr);while (1) {if (recvfrom(sd, (void*)mssg, sizeof(mssg), 0, (sockaddr*)raddr, addr_len) 0) {perror(recvfrom());exit(1);}inet_ntop(AF_INET, raddr.sin_addr, ipstr, IPSTRSIZE);std::cout ---------recive message from std::string(ipstr) : ntohs(raddr.sin_port) --------- std::endl;// 单字节传输不涉及到大端小端的存储情况std::cout name : mssg.name std::endl;std::cout math : ntohs(mssg.math) std::endl;std::cout chinese : ntohs(mssg.chinese) std::endl;}//关闭close(sd);exit(1);
}
运行结果
vratdrh7771.rsv.ven.veritas.com [109]: ./rcver
---------recive message from 10.85.171.130:48514---------
name:mike
math:100
chinese:100
---------recive message from 10.85.171.130:39871---------
name:hahah
math:100
chinese:100多播中有一个特殊的 ip 地址224.0.0.1它表示所有支持多播的地址默认都存在这个组当中并且无法离开。如果 snder 方向这个 ip 地址发送信息就相当于向 255.255.255.255 上发消息。
4.UDP协议分析
4.1.丢包原因
UDP丢包并不是因为TTLTTL是当前包的要跳转的路由的个数linux环境下一般默认为64Windows一般为128一般情况下完全足够。丢包其实是由于阻塞造成的。路由有等待队列并不是我的数据包从本路由到下一个路由是无条件发送的而是有等待队列这个等待队列会有丢包的算法实现。比如当前队列已经排列百分之N的容量时就会随机的丢包等操作。 解决闭环流控(停等式流控)
4.2.停等式流量控制
问题1 发送端发送后接收端返回ACK发送端会在一段时间后才能收到消息 问题2发送端发送消息后丢包了在一定时间RTT后没有收到接收端ACK则重新发送消息 问题3发送端发送消息后接收端收到了数据并且发送了ACK但是ACK数据丢了在一定时间后没有收到接收端ACK则重新发送消息。接收端如何判断收到的包已经收到过了(data包加编号)则直接放弃该包直接回复ACK。 问题4如图 d2 丢包ACK1 延迟回复给了发送端则发送端认为d2发送成功继续发d3实际接受端是没有收到过d2的。所以ACK也要加编号否则就可能出现这类问题。