UDP/TCP基础
只有UDP才有广播、组播的传递方式;而TCP是一对一连接通信。多播的重点是高效的把同一个包尽可能多的发送到不同的,甚至可能是未知的设备。但是TCP连接是一对一明确的,只能单播。
概念
- 单播
常见于多个client向服务端上报各自消息,用于信息收集,逻辑上是一对一的,因为上报的消息是不同的。此时,客户端的sockaddr_in中的IP地址信息需要设置为具体的服务器地址。 - 多播
主机被逻辑上归类为两组,后续消息收发都在各自分组上进行,互不影响。此时,服务端需要设置加入组播组和退出组播组的设置即可。 - 广播
常见于一个客户端向多个服务端广播消息,执行同一个动作或者初始化。此时,需要对客户端的socket设置广播标志以及sockaddr_in中的IP地址设置"255.255.255.255"或者使用htonl(INADDR_BROADCAST)
在TCP连接中,recv等函数默认为阻塞模式(block),即直到有数据到来之前函数不会返回,而我们有时则需要一种超时机制使其在一定时间后返回而不管是否有数据到来,这里我们就会用到setsockopt()函数:
C++
单播
函数 | 返回值 | 备注 |
---|
int socket() | 成功返回文件描述符0,1,2,失败返回-1 | | int bind() | 成功返回0,失败返回-1 | 参数socklen_t addrlen | int listen() | 成功返回0,失败返回-1 | | ssize_t sendto() | 成功返回发送的字节数,失败返回-1 | | ssize_t recvfrom() | 成功返回接收的字节数,失败返回-1 | | int connect() | 成功返回0,失败返回-1 | | int accept() | 成功返回0,失败返回-1 | | ssize_t send() | 成功返回发送的字节数,失败返回-1 | | ssize_t recv() | 成功返回接收的字节数,失败返回-1 | |
socket()
int socket(int domain, int type, int protocol);
失败返回-1,成功则返回该文件描述符
关于sock_stream与sock_dgram:
- sock_stream 是有保障的(即能保证数据正确传送到对方)面向连接的SOCKET,多用于资料(如文件)传送。
- sock_dgram 是无保障的面向消息的socket , 主要用于在网络上发广播信息。
SOCK_STREAM是基于TCP的,数据传输比较有保障。SOCK_DGRAM是基于UDP的,专门用于局域网,基于广播SOCK_STREAM 是数据流,一般是tcp/ip协议的编程,SOCK_DGRAM分是数据抱,是udp协议网络编程
struct sockaddr_in
#include <netinet.h>
#include <arpa/inet.h>
struct sockaddr_in{
short sin_family;
u_short sin_port;
struct in_addr sin_addr;
char sin_zero[8];
};
设置IP和端口号时使用,sin_port和sin_addr()必须是网络字节序(Network Byte Order)。
struct in_addr
用来存放32bit IP地址
typedef struct in_addr {
union {
struct { UCHAR s_b1,s_b2,s_b3,s_b4; } S_un_b;
struct { USHORT s_w1,s_w2; } S_un_w;
ULONG S_addr;
} S_un;
#define s_addr S_un.S_addr
#define s_host S_un.S_un_b.s_b2
#define s_net S_un.S_un_b.s_b1
#define s_imp S_un.S_un_w.s_w2
#define s_impno S_un.S_un_b.s_b4
#define s_lh S_un.S_un_b.s_b3
} IN_ADDR, *PIN_ADDR, FAR *LPIN_ADDR;
struct sockaddr
struct sockaddr {
u_short sa_family;
char sa_data[14];
};
作为sendto()和recvfrom()参数时使用
bind()
int bind(int sockfd, const struct sockaddr *addr,
socklen_t addrlen);
一般用于服务器,绑定套接字对象与服务器IP端口号,成功则返回0,失败返回-1
listen()
int listen(int sockfd, int backlog);
backlog指定能同时处理的最大连接要求,成功则返回0,失败则返回-1。
sendto()
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,
const struct sockaddr *dest_addr, socklen_t addrlen);
将UDP数据报发给指定地址,返回发送的数据报的字节(bytes)数 \sockfd: socket描述符。 \buf: UDP数据报缓存地址。 \len: UDP数据报长度。 \flags: 该参数一般为0。 \dest_addr: struct sockaddr_in类型,指明UDP数据发往哪里报。 \addrlen: 对方地址长度,一般为:sizeof(struct sockaddr_in)。
recvfrom()
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,
struct sockaddr *src_addr, socklen_t *addrlen);
从指定地址接收UDP数据报,返回接收到的数据报的字节(bytes)数。一般用于UDP。 \src_addr:struct sockaddr_in类型,指明从哪里接收UDP数据报。
connect()
int connect(int sockfd, const struct sockaddr *addr,
socklen_t addrlen);
客户端请求连接函数,向服务器发送连接请求,这也是从客户端发起TCP三次握手请求的开始。服务器端的协议族,网络地址以及端口都会存放在该函数的addr地址当中。 返回0时说明已经connect成功,返回值是-1时,表示connect失败。
accpet()
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
接收函数accept并不是真正的接收,而是客户端向服务器端监听端口发起的连接。对于TCP来说,accept从阻塞状态返回的时候,已经完成了三次握手的操作。Accept其实是取了一个已经处于connected状态的连接,然后把对方的协议族,网络地址以及端口都存在了client_addr中,返回一个用于操作的新的文件描述符,该文件描述符表示客户端与服务器端的连接,通过对该文件描述符操作,可以向client端发送和接收数据。同时之前socket创建的sockfd,则继续监听有没有新的连接到达本地端口。返回大于0的文件描述符则表示accept成功,否则失败。
send()
ssize_t send(int sockfd, const void *buf, size_t len, int flags);
无论客户端和服务器都需要用到send()函数向TCP连接的另一端发送数据,客户端用该函数向服务器发送请求,服务器用该函数向客户端发送应答。 成功则返回发送的字节数,失败则返回-1。
recv()
ssize_t recv(int sockfd, void *buf, size_t len, int flags);
无论服务器和客户端都需要从TCP连接的另一端接收数据,sockfd指定接收端套接字描述符。 recv函数返回其实际copy的字节数。如果recv在copy时出错,那么它返回SOCKET_ERROR;
memset()
void *memset(void *s, int c, size_t n);
htons(), htonl(), ntohs(), ntohl()
在host与网络字节序(n)之间转换
inet_pton()
int inet_pton(int af, const char *src, void *dst);
将IPv4或IPv6地址从text转换为binary,成功则返回1。#include <arpa/inet.h>, 如
inet_pton(AF_INET, SERVER_IP, &servaddr.sin_addr);
inet_ntop()
const char *inet_ntop(int af, const void *src,
char *dst, socklen_t size);
将IPv4或IPv6地址从binary转换为text,成功则返回dst指针。#include <arpa/inet.h>, 如
组播
把消息同时传递给一组目的地址,其策略高效,因为消息在每条网络链路上只需传递一次,只有在链路分叉的时候才需要把消息进行复制。 特点:
- 需要相同数据流的客户端加入相同的组共享一条数据流,节省了服务器负载
- 直接向组播组内的其中一个成员发送数据即可,那么该成员所在的组播组的其它成员也会接收到这个数据
Linux查找结构定义
$ grep “struct ip_mreqn” -r /usr/include/ $ grep “struct ip_mreqn” -r /usr/include/ -n //指定行号
struct ip_mreqn()
struct ip_mreqn {
struct in_addr imr_multiaddr;
struct in_addr imr_address;
int imr_ifindex;
};
组播地址结构。 imr_ifindex
用例:
inet_pton(AF_INET, GROUP, &group.imr_multiaddr);
inet_pton(AF_INET, "0.0.0.0", &group.imr_address);
group.imr_ifindex = if_nametoindex("eth0");
setsockopt()
int setsockopt(int sockfd, int level, int optname,
const void *optval, socklen_t optlen);
sockfd: 指向一个打开的套接口描述字 level: 指定选项代码的类型。包括
- SOL_SOCKET: 基本套接口
- IPPROTO_IP: IPv4套接口
- IPPROTO_IPV6: IPv6套接口
- IPPROTO_TCP: TCP套接口
optname: 选项名称 optval: 是一个指向变量的指针 类型:整形,套接口结构, 其他结构类型:linger{}, timeval{} optlen: optval 的大小
optname详细参数搬运
IP_HDRINCL 在数据包中包含IP首部,这个选项常用于黑客技术中,隐藏自己的IP地址 IP_OPTINOS IP首部选项 IP_TOS 服务类型 IP_TTL 生存时间
以下IPV4选项用于组播: IPv4 选项数据类型描述 IP_ADD_MEMBERSHIP struct ip_mreq 加入到组播组中 IP_ROP_MEMBERSHIP struct ip_mreq 从组播组中退出 IP_MULTICAST_IF struct ip_mreq 指定提交组播报文的接口 IP_MULTICAST_TTL u_char 指定提交组播报文的TTL IP_MULTICAST_LOOP u_char 使组播报文环路有效或无效 IP_DROP_MEMBERSHIP 该选项用来从某个组播组中退出。数据结构ip_mreq的使用方法与上面相同。 IP_MULTICAST_IF 该选项可以修改网络接口,在结构ip_mreq中定义新的接口。 IP_MULTICAST_TTL 设置组播报文的数据包的TTL(生存时间)。默认值是1,表示数据包只能在本地的子网中传送。 IP_MULTICAST_LOOP 组播组中的成员自己也会收到它向本组发送的报文。这个选项用于选择是否激活这种状态。
NOTE:
- 在多播组中,默认情况下一个发出多播信息的节点也会收到自己发送的信息,这称为多播回环,可以关闭多播回环:
bool val=false;
setsocket(s,IPPROTO_IP,IP_MULTICAST_LOOP,(char*)val,sizeof(val));
- 在多播时,通常要设置适当的TTL(TTL的值是多少,那么多播信息就可以经过多少路由器,每经过一个路由器,TTL的值自动减1):
int val=3;
setsocket(s, IPPROTO_IP, IP_MULTICAST_TTL, (char*)val, sizeof(int));
getsockopt()
int getsockopt(int sockfd, int level, int optname,
void *optval, socklen_t *optlen);
Windows
#include <winsock2.h>
#include <WS2tcpip.h>
#pragma comment(lib, "Ws2_32.lib")
WSADATA wsaData;
WSAStartup(MAKEWORD(2,2), &wsaData);
SOCKET sockM=WSAJoinLeaf(s,(SOCKADDR*)&multiCastGroup,sizeof(multiCastGroup),NULL,NULL,NULL,NULL,JL_BOTH);
Qt
Client
Server
|