这里需要指出的一点是,伪首部完全是虚拟的,它并不会和用户数据报一起被发送出去,只是在校验和的计算过程中会被使用到,伪首部主要来自于运载UDP报文的IP数据报首部,将源IP地址和目的IP地址加入到校验和的计算中可以验证用户数据报是否已经到达正确的终点。
所以udp头部大小为8字节。
tcp和udp可以同用一个端口。使用地址复用SO_REUSEADDR即可。
// 接收缓冲区,udp有接收缓冲区,无发送缓冲区
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));
//地址复用
BOOL bReuseaddr = TRUE;
setsockopt( s, SOL_SOCKET, SO_REUSEADDR, ( const char* )&bReuseaddr, sizeof( BOOL ) );
1.创建,socket()函数
2.绑定, bind()函数
3.接收 recvfrom
不用实际读取就能检测到数据的到来,可以调用MSG_PEEK标志的recv活recvfrom,或者调用ioctlsocket或select.
ssize_t recvfrom(int sockfd,void *buf,size_t len,unsigned int flags, struct sockaddr *from,socket_t *fromlen);
sockfd:标识一个已连接套接字的描述字。
buf:接收数据缓冲区。
len:缓冲区长度。
flags:调用操作方式。一般为0, 是一个或者多个标志的组合体,可通过“ | ”操作符连在一起:
from:指针,指向装有源地址的缓冲区。可选,如不关心,则为null
fromlen:指针,指向from缓冲区长度值。可选,如不关心,则为null
返回值
如果正确接收返回接收到的字节数,失败返回-1.返回0也是可行的。tcp的read返回0表示对端已关闭,而udp不是。
udp和tcp都有接收缓冲区,在没有收到数据时,默认是阻塞的,可通过设置超时返回(如select机制,默认最大描述符为1024)
延伸connect
1.没有3路握手,只是检查是否存在立即可知的错误(如网络不可达,但如果是网络可达,但应用程序没启动检查不到的,只有调sendto才返回)
2.如果udp调用connect后,不能使用sendto(或者不能在sendto制定目的地址)
3.如果udp调用connect后,只能接受connect所指定地址的数据报,不能接受别的套接字的数据。
相当于不能进行广播和组播了
4.好处:就是只显性连接一次,可以多次发送数据。传输效率更高。
不然调用sendto就是连接一次,发送一次,断开连接一次。再连接,发送,断开连接
5.多次调用connect,可以指定新的目的地址和端口和断开套接字
关闭closesocket() :
只是将socket的资源归还给协议栈。
1.创建,socket()函数
2.发送 sendto ()函数:
适合在从同一个socket向不同的远程主机发送数据。
或还有一种办法是,调用connect()函数,再调用send()函数。适用于向同一个远程地址发送数据。
int sendto ( socket s , const void * msg, int len, unsigned int flags, const struct sockaddr * to , int tolen ) ;
send 和 sendto 函数在 UDP 层没有输出缓冲区,因此sendto不会阻塞。而是直接返回。
s 套接字
buff 待发送数据的缓冲区
size 缓冲区长度
Flags 调用方式标志位, 一般为0, 改变Flags,将会改变Sendto发送的形式
addr (可选)指针,指向目的套接字的地址
len addr所指地址的长度
成功则返回实际传送出去的字符数,失败返回-1,返回是0也是可以的。
udp并没有真正的发送缓冲区,和tcp还是有区别的。
对客户端的udp而言,进程首次调用sendto时,绑定一个临时端口,不能在修改了。而ip地址可以随客户发送的udp数据报而变动。
udp sendto输出操作成功返回仅仅表示在接口输出队列中具有存放所形成ip数据报的空间。
说明了udp套接字:由它引发的异步错误并不返回给它,除非它也连接(即如调用了connect函数)。这也是udp使用connect的初衷。
主要区别在于:udp获取目标ip地址的方法是recvmsg函数。
在客户端udp中,调用connect并没有和tcp的三路握手,只能是内核检查存放立即可知的错误,记录对端的ip地址和端口,然后立即返回到进程。如果使用了connect函数,就只能使用read(recv)替代recvfrom ,write(send)替代sendto.
udp可以多次调用connect,其目的是指定新的ip地址和端口,或断开套接字。tcp只能一次调用connect。
调用connect并不给服务器发送任何信息,只是保存对端的ip地址和端口号。
tcp和udp可以同用一个端口。
对于已连接的udp套接字可以调用sendto,但不能指定目的地址。
对于已连接的udp套接字只能通过connect断开连接,而不能通过shutdown.
3.关闭closesocket
主要在udp中,可以扩展,从单播到组播,或广播。可以在同一应用程序同时接收和发送。
udp套接字显性地绑定一个本地ip接口,并发送数据。出现什么情况
udp套接字不会真正和网络接口绑定在一起,而是建立一种关联,即被绑定的ip接口地址成为发出去的udp数据报的源ip地址。
一个数据报即udp只要一打开就处于可写的状态,一经命名就处于可读的状态。
所以,应用程序下socket()调用后,马上可以发送数据。
在调用bind()显式命名或调用sendto函数隐式命名后,马上就可以接收数据。
对于udp多播,发送应用进程的套接字可以不必加入到多播组。
一直都UDP层(通过端口号),才能确定是否要丢弃广播数据。
1.创建udp套接字,无须绑定端口和地址。
2.设置udp套接字的广播标志。
int flag = 1;
setsockopt(server_sockfd , SOL_SOCKET , SO_BROADCAST , &flag , sizeof(flag) );
3.调用sendto函数,参数中需要明确广播地址和端口号。
#define BROADCAST_IP "192.168.1.255"
#define CLIENT_PORT 9000bzero(&clientaddr , sizeof(clientaddr));
clientaddr.sin_family = AF_INET;
inet_pton(AF_INET , BROADCAST_IP , &clientaddr.sin_addr.s_addr);clientaddr.sin_port = htons(CLIENT_PORT);
sendto(server_sockfd , buf , strlen(buf) , 0 , (struct sockaddr *)&clientaddr , sizeof(clientaddr));
创建udp套接字,必须绑定端口(该端口是发送端发送的端口)。绑定的IP不可以使用“127.0.0.1”,可以使用真实IP地址或者INADDR_ANY。否则接收失败。
bzero(&localaddr , sizeof(localaddr));localaddr.sin_family = AF_INET;
inet_pton(AF_INET , "0.0.0.0" , &localaddr.sin_addr.s_addr);localaddr.sin_port = htons(CLIENT_PORT);int ret = bind(confd , (struct sockaddr *)&localaddr , sizeof(localaddr));
2.接收方的Socket不需要设置成广播属性。
3.调用recvfrom函数进行接收.
len = recvfrom(confd , buf , sizeof(buf),0 ,(struct sockaddr*)&from,(socklen_t*)&len);
//from为发送端的本地地址。
相关的广播知识点:
BOOL bBroadcast=TRUE;
setsockopt(s,SOL_SOCKET,SO_BROADCAST,(const char*)&bBroadcast,sizeof(BOOL));
特点:
1.广播的数据在子网的所有主机都接收。直到传输层才决定是否丢弃。
2.不能够跨越不同的网络,被路由器所隔离开,即只能在局域网,不能应用到广域网
3.接收端的端口号要与广播端绑定的端口号一样。
组播在网卡处就对接收地址进行判断,从而丢弃数据包。
一个ip地址可以加入到多个组播组。
1.地址范围:D类IP地址。范围:224.0.0.0~239.255.255.255
2.组播组:永久/临时。永久组播组一般由官方分配。
3.224.0.0.0~224.0.0.255为预留的组播地址,即永久组地址。地址224.0.0.0保留不做分配,其它地址供路由协议使用。
4.224.0.1.0~224.0.1.255是公用组播地址,可以用于Internet。
5.224.0.2.0~238.255.255.255为用户可用的组播地址(临时组地址),全网范围内有效。
6.239.0.0.0~239.255.255.255为本地管理组播地址,仅在特定的本地范围内有效。
1.创建udp套接字,无须绑定端口和地址。
2.调用sendto函数,参数中需要明确组播地址和端口号。
#include
#include
#include
#include
#include
#include
#include int main()
{int server = 0;struct sockaddr_in saddr = {0};int client = 0;struct sockaddr_in remote = {0};socklen_t asize = 0;int len = 0;char buf[32] = "Software";int r = 0;//int brd = 1;server = socket(PF_INET, SOCK_DGRAM, 0);if( server == -1 ){printf("server socket error\n");return -1;}saddr.sin_family = AF_INET;saddr.sin_addr.s_addr = htonl(INADDR_ANY); // 本机地址saddr.sin_port = htons(8888);if( bind(server, (struct sockaddr*)&saddr, sizeof(saddr)) == -1 ){printf("udp server bind error\n");return -1;}printf("udp server start success\n");remote.sin_family = AF_INET;remote.sin_addr.s_addr = inet_addr("224.1.1.168"); //设置一个多播地址remote.sin_port = htons(9000);while( 1 ){len = sizeof(remote);r = strlen(buf);sendto(server, buf, r, 0, (struct sockaddr*)&remote, len);sleep(1);}close(server);return 0;
}
1.创建套接字,必须绑定端口(该端口是发送端发送的端口)。绑定的IP不可以使用“127.0.0.1”,可以使用真实IP地址或者INADDR_ANY。否则接收失败。
2.把当前本地的ip地址加人到组播地址。
3.调用recvfrom获取数据,参数为发送端的本地地址。
#include
#include
#include
#include
#include
#include
#include int main()
{int sock = 0;struct sockaddr_in addr = {0};struct sockaddr_in remote = {0};int len = 0;char buf[128] = {0};char input[32] = {0};int r = 0;//多播struct ip_mreq group={0};sock = socket(PF_INET, SOCK_DGRAM, 0);if( sock == -1 ){printf("socket error\n");return -1;}addr.sin_family = AF_INET;addr.sin_addr.s_addr = htonl(INADDR_ANY);addr.sin_port = htons(9000);if( bind(sock, (struct sockaddr*)&addr, sizeof(addr)) == -1 ){printf("udp bind error\n");return -1;}//remote.sin_family = AF_INET;//remote.sin_addr.s_addr = inet_addr("127.0.0.1");//remote.sin_port = htons(8888);group.imr_multiaddr.s_addr=inet_addr("224.1.1.168");group.imr_interface.s_addr=htonl(INADDR_ANY); //local host//这里INADDR_ANY 为0.0.0.0 通过看ipconfig/ifconfig 可以看到有多个//网络ip地址,这个时候让操作系统选择哪一个端口进行多播数据收发。//在实际的工程中需要明确指定需要哪一个网络地址进行多播数据收发,//不能完全依赖操作系统,否者有时候能够收到数据,有时候收不到数据。setsockopt(sock,IPPROTO_IP,IP_ADD_MEMBERSHIP,&group,sizeof(group));while( 1 ){len=sizeof(remote);r = recvfrom(sock, buf, sizeof(buf), 0, (struct sockaddr*)&remote, &len);if( r > 0 ){buf[r] = 0;printf("Receive: %s\n", buf);}else{break;}}close(sock);return 0;
}
ip多播:采用的是“无根”的通讯方式,组内的成员可以相互之间发送和接收数据。IPv4 多播是一个D类的IP地址,范围:224.0.0.0 ----239.255.255.255 之间。
如果多播需要在多个网络断传输,需要考虑使用IGMP协议,该协议运行在主机和组播路由器之间。此外一个套接字注意TTL的值,其作用主要显示数据能传输多远。
1.多播服务端针对特定多播地址只发送一次数据,但是组内的所有客户端都能收到数据
也就是说如果自身不想接受数据,就不要把自己加入到组播地址去。
2.加入特定的多播组即可接收发往该多播组的数据。如果自己想接收数据,就把自己加入组播地址
3.与单播一样,多播是允许在广域网即Internet上进行传输的,而广播仅仅在同一局域网上才能进行;即可以跨网段,跨路由器.所以多播时,路由器能够复制数据并进行转发
常用函数
IPPROTO_IP
当接收者加入到一个多播组以后,再向这个多播组发送数据,这个字段的设置是否允许再返回到本身。
int loop=1; //1:on 0:off
setsockopt(sock,IPPROTO_IP,IP_MULTICAST_LOOP,&loop,sizeof(loop));
IP_MULTICAST_TTL
默认情况下,多播报文的TTL被设置成了1,也就是说到这个报文在网络传送的时候,它只能在自己所在的网络传送,当要向外发送的时候,路由器把TTL减1以后变成了0,这个报文就已经被Discard了。
IP_MULTICAST_IF设置多播接口地址
struct in_addr addr;
setsockopt(s,IPPROTO_IP,IP_MULTICAST_IF,&addr,sizeof(addr))
IP_ADD_MEMBERSHIP 加入一个组播组
struct ip_mreq ipmr;
ipmr.imr_interface.s_addr = inet_addr("192.168.101.1");
ipmr.imr_multiaddr.s_addr = inet_addr("234.5.6.7");
setsockopt(s, IPPROTO_IP, IP_ADDR_MEMBERSHIP, (char*)&ipmr, sizeof(ipmr));
IP_DROP_MEMBERSHIP 离开一个多播组
下一篇:《MySql必知必会》读书笔记