TCP与UDP
传输层主要应用的协议模型有两种,一种是TCP协议,另外一种则是UDP协议。TCP协议在网络通信中占主导地位,绝大多数的网络通信借助TCP协议完成数据传输。但UDP也是网络通信中不可或缺的重要通信手段。
相较于TCP而言,UDP通信的形式更像是发短信。不需要在数据传输之前建立、维护连接。只专心获取数据就好。省去了三次握手的过程,通信速度可以大大提高,但与之伴随的通信的稳定性和正确率便得不到保证。因此,我们称UDP为“无连接的不可靠报文传递”。
那么与我们熟知的TCP相比,UDP有哪些优点和不足呢?由于无需创建连接,所以UDP开销较小,数据传输速度快,实时性强。多用于对实时性要求较高的通信场合,如视频会议、电话会议等。但随之也伴随着数据传输不可靠,传输数据的正确率、传输顺序和流量都得不到控制和保证。所以,通常情况下,使用UDP协议进行数据传输,为保证数据的正确性,我们需要在应用层添加辅助校验协议来弥补UDP的不足,以达到数据可靠传输的目的。
与TCP类似,UDP也有可能出现缓冲区被填满后,再接收数据是丢包的现象。由于它没有TCP滑动窗口的机制,通常采用如下两种方法:
1)服务器应用层设计流量控制,控制发送数据速度
2)借助setsockopt函数改变接收缓冲区大小
#include <sys/socket.h> int setsockopt(int sockfd,int level,int optname,const void *optval,socklen_t optlen); int n = 220x1024; setsockopt(sockfd,SOL_SOCKET,SO_RCVBUF,&n,sizeof(n));
C/S模型UDP
由于UDP不需要维护连接,程序逻辑简单,但是UDP协议是不可靠的,保证通讯可靠性的机制需要在应用层实现。
代码案例:
#include <string.h> #include <netinet/in.h> #include <stdio.h> #include <unistd.h> #include <string.h> #include <arpa/inet.h> #include <ctype.h> #define MAXLINE 80 #define SERV_PORT 8080 int main(void) { int n,i; struct sockaddr_in servaddr,cliaddr; socklen_t cliaddr_len; int sockfd; char str[INET_ADDRSTRLEN]; char buf[MAXLINE]; sockfd = socket(AF_INET,SOCK_DGRAM,0); bzero(&servaddr,sizeof(servaddr)); servaddr.sin_family = AF_INET; servaddr.sin_addr.s_addr = htonl(INADDR_ANY); servaddr.sin_port = htons(SERV_PORT); bind(sockfd,(struct sockaddr *)&servaddr,sizeof(servaddr)); while(1) { cliaddr_len = sizeof(cliaddr); n = recvfrom(sockfd,buf,MAXLINE,0,(struct sockaddr *)&cliaddr,&cliaddr_len); if(n == -1) { perror("recvfrom error"); } printf("连接来自 %s 在端口 %d\n", inet_ntop(AF_INET,&cliaddr.sin_addr,str,sizeof(str)),ntohs(cliaddr.sin_port)); for(i=0;i<n;++i) { buf[i] = toupper(buf[i]); } n = sendto(sockfd,buf,n,0,(struct sockaddr *)&cliaddr,sizeof(cliaddr)); if(n == -1) { perror("sendto error"); } } close(sockfd); return 0; }
#include <stdio.h> #include <string.h> #include <unistd.h> #include <netinet/in.h> #include <string.h> #include <ctype.h> #define MAXLINE 80 #define SERV_PORT 8080 int main(void) { int n; struct sockaddr_in servaddr; char str[INET_ADDRSTRLEN]; char buf[MAXLINE]; int sockfd; sockfd = socket(AF_INET,SOCK_DGRAM,0); bzero(&servaddr,sizeof(servaddr)); servaddr.sin_family = AF_INET; servaddr.sin_port = htons(SERV_PORT); inet_pton(AF_INET,"127.0.0.1",&servaddr.sin_addr); while(fgets(buf,MAXLINE,stdin) != NULL) { n = sendto(sockfd,buf,strlen(buf),0,(struct sockaddr*)&servaddr,sizeof(servaddr)); if(n == -1) { perror("sendto error"); } n = recvfrom(sockfd,buf,MAXLINE,0,NULL,0); if(n == -1) { perror("recvfrom error"); } write(STDOUT_FILENO,buf,n); } close(sockfd); return 0; }
广播
int setsockopt(sockfd,SOL_SOCKET,SO_BROADCAST,&flag,sizeof(flag)); //修改sock属性
给sockfd开放广播权限
组播
组播可以是永久的也可以是临时的。组播地址中,有一部分由官方分配,称为永久组播组。永久组播组保持不变的是它的ip地址,组中的成员构成可以发生变化。永久组播组中成员可以是任意的,甚至可以为0。那些没有保留下来供永久组播使用的ip组播地址,可以被临时组播组利用。
224.0.0.0 ~ 224.0.0.255 为预留的组播地址(永久地址),地址224.0.0.0保留不做分配,其它供路由协议使用
224.0.1.0 ~ 224.0.1.255 为公用组播地址,可以用于Internet,欲使用需申请
224.0.2.0 ~ 238.255.255.255 为用户可用的组播地址(临时地址,全网范围内有效)
239.0.0.0 ~ 239.255.255.255 为本地管理组播地址,仅在特定的本地范围内有效
可使用 ip ad命令查看网卡编号 ip adress
使用命令启动/关闭网卡 sudo ifconfig etho up/down
使用 if_nametoindex 函数命令可以根据网卡名,获取网卡序号
#include <net/if.h>
unsigned int if_nametoindex(const char *ifname);
代码案例:
#include <stdio.h> #include <stdlib.h> #include <sys/types.h> #include <sys/socket.h> #include <net/if.h> #include <unistd.h> #include <arpa/inet.h> #define SERVER_PORT 8080 #define CLIENT_PORT 9000 #define MAXLINE 1500 #define GROUP "239.0.0.2" //本地组播地址 /* struct ip_mreqn { struct in_addr imr_multiaddr; 组播组的IP struct in_addr imr_address; 本地IP int imr_ifindex; 网卡编号 }; */ int main(void) { int sockfd,i; struct sockaddr_in serveraddr,clientaddr; struct ip_mreqn group; char buf[MAXLINE] = "itcast\n"; sockfd = socket(AF_INET,SOCK_DGRAM,0); bzero(&serveraddr,sizeof(serveraddr)); serveraddr.sin_family = AF_INET; serveraddr.sin_addr.s_addr = htonl(INADDR_ANY); serveraddr.sin_port = htons(SERVER_PORT); bind(sockfd,(struct sockaddr *)&serveraddr,sizeof(serveraddr)); //设置组地址 inet_pton(AF_INET,GROUP,&group.imr_multiaddr); //本地任意 IP inet_pton(AF_INET,"0.0.0.0",&group.imr_address); //eth0 group.imr_ifindex = if_nametoindex("eth0"); setsockopt(sockfd,IPPROTO_IP,IP_MULTICAST_IF,&group,sizeof(group)); //构造 client 地址和端口 bzero(&clientaddr,sizeof(clientaddr)); clientaddr.sin_family = AF_INET; inet_pton(AF_INET,GROUP,&clientaddr.sin_addr.s_addr); clientaddr.sin_port = htons(CLIENT_PORT); while(1) { sendto(sockfd,buf,strlen(buf),0,(struct sockaddr *)&clientaddr,sizeof(clientaddr)); sleep(1); } close(sockfd); return 0; }
#include <stdio.h> #include <stdlib.h> #include <sys/types.h> #include <sys/socket.h> #include <net/if.h> #include <unistd.h> #include <arpa/inet.h> #include <sys/stat.h> #define SERVER_PORT 8080 #define MAXLINE 4096 #define CLIENT_PORT 9000 #define GROUP "239.0.0.2" //本地组播地址 int main() { struct sockaddr_in serveraddr,localaddr; int confd; ssize_t len; char buf[MAXLINE]; //定义组播结构体 struct ip_mreqn group; confd = socket(AF_INET,SOCK_DGRAM,0); //初始化本地端地址 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); bind(confd,(struct sockaddr *)&localaddr,sizeof(localaddr)); //设置组地址 inet_pton(AF_INET,GROUP,&group.imr_multiaddr); //本地任意 inet_pton(AF_INET,"0.0.0.0",&group.imr_address); //eth0 group.imr_ifindex = if_nametoindex("eth0"); setsockopt(confd,IPPROTO_IP,IP_ADD_MEMBERSHIP,&group,sizeof(group)); while(1) { len = recvfrom(confd,buf,sizeof(buf),0,NULL,0); write(STDERR_FILENO,buf,len); } close(confd); return 0; }
setsockopt作用
1.端口复用
2.设置缓冲区大小
3.开放广播权限
4.开放组播权限