TCP/UDP基本概念
首先要知道的名词是TCP/IP协议;
TCP/IP协议是从OSI参考模型中简化而来,简化为四层协议;
使用UDP通信协议的场景有哪些?
一般是传输音视频的应用中,因为丢失1帧的视频数据基本无影响,毕竟每秒20帧以上的视频丢失一帧两帧的代价相比于传输数据量增大带来的延迟,完全可以接受使用不可靠的UDP协议传输;
使用TCP的使用场景?
最常见的就是HTTP协议(超文本传输协议)了,HTTP协议是应用层协议,那么它的传输层选择了TCP协议,毕竟HTTP的请求都是需要确保可靠完整性;
HTTP协议并没有规定必须使用TCP或者基于TCP支持的协议层,也可以使用其他输出层协议,所以相对UDP而言,HTTP选择TCP协议而不会选择UDP,并且HTTP更加在意TCP协议提供传输控制,按顺序组织数据,和错误纠正。
Socket与TCP/UDP的基本关系
应用程序一般都是通过socket来使用TCP和UDP服务,TCP和UDP则管理socket的数据传递;
关于socket有三种类型:
1 流式socket(SOCK_STREAM) TCP协议
2 数据包socket(SOCK_DGRAM) UDP协议
3 原始socket,直接对底层协议(IP或者ICMP)操作
关于socket的一些重要数据结构
struct sockaddr
{
unsigned short sa_family; /*地址族*/
char sa_data[14]; /*14字节的协议地址,包含该socket的IP地址和端口号。*/
};
struct sockaddr_in
{
short int sa_family; /*地址族*/
unsigned short int sin_port; /*端口号*/
struct in_addr sin_addr; /*IP地址*/
unsigned char sin_zero[8]; /*填充0 以保持与struct sockaddr同样大小*/
};
点分十进制和网络字节的关系
计算机数据存储有两种字节优先顺序造成的冲突:
计算机数据存储有大端模式和小端模式,而网络上传输使用大端模式;
高位字节优先(称为大端模式)和低位字节优先(称为小端模式), Internet上数据以高位字节优先顺序在网络上传输, 而计算机存储则通常采用小端模式 。因此在有些情况下,需要对这两个字节存储优先顺序进行相互转化。
#include <netinet/in.h>
uint16_t htons(unit16_t host16bit) host16bit:主机字节序的16位数据
uint32_t htonl(unit32_t host32bit) host32bit:主机字节序的32位数据
uint16_t ntohs(unit16_t net16bit) net16bit:网络字节序的16位数据
uint32_t ntohs(unit32_t net32bit) net32bit:网络字节序的32位数据
// 成功:返回要转换的字节数, 出错:1
//地址格式转化
//IPv4常用函数:
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
int inet_aton(const char *string, struct in_addr*addr); //一个字符串IP地址转换为一个32位的网络序列IP地址
in_addr_t inet_addr(const char* strptr); //将点分十进制的IP转换成一个长整数型数
char*inet_ntoa(struct in_addr in); //将十进制网络字节序转换为点分十进制IP格式的字符串
IPv4和IPv6兼容的函数:
#include <arpa/inet.h>
1 int inet_pton(int family, const char *strptr, void *addrptr); // 点分十进制-> 二进制整数
2 int inet_ntop(int family, void *addrptr, char *strptr, size_t len); // 二进制整数 -> 点分十进制
点分十进制和整型之间的转化和联系
// demo.c
#include <stdio.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>
int main(){
int ip = 16777216;
char ip_str[];
printf("%10s : %d\n", "code", ip);
strcpy(ip_str, inet_ntoa(*(struct in_addr *)&ip));
printf("%10s : %s\n", "to string", ip_str);
printf("%10s : %d\n", "to int", inet_addr(ip_str));
return 0;
}
$ gcc demo.c
$ ./a.out
code : 16777216
to string : 0.0.0.1
to int : 16777216
gethostbyname和DNS
#include <netdb.h>
1 struct hostent *gethostbyname(const char *hostname)
2 int getaddrinfo(const char *node, const char *service, const struct addrinfo *hints, struct addrinfo **result)
3 int getaddrinfo(const char *node, const char *service, const struct addrinfo *hints, struct addrinfo **result)
struct hostent
{
char *h_name;/*正式主机名*/
char **h_aliases;/*主机别名*/
int h_addrtype;/*地址类型*/
int h_length;/*地址字节长度*/
char **h_addr_list;/*指向IPv4或IPv6的地址指针数组*/
}
struct addrinfo
{
int ai_flags;/*AI_PASSIVE, AI_CANONNAME;*/
int ai_family;/*地址族*/
int ai_socktype;/*socket类型*/
int ai_protocol;/*协议类型*/
size_t ai_addrlen;/*地址字节长度*/
char *ai_canonname;/*主机名*/
struct sockaddr *ai_addr;/*socket结构体*/
struct addrinfo *ai_next;/*下一个指针链表*/
}
getaddrinfo函数用法的示例
/* getaddrinfo.c */
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <netdb.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <sys/socket.h>
int main()
{
struct addrinfo hints, *res = NULL;
int rc;
memset(&hints, 0, sizeof(hints));
/*设置addrinfo结构体中各参数 */
hints.ai_flags = AI_CANONNAME;
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_DGRAM;
hints.ai_protocol = IPPROTO_UDP;
/*调用getaddinfo函数*/
rc = getaddrinfo("localhost", NULL, &hints, &res);
if (rc != 0) {
perror("getaddrinfo");
exit(1);
}else{
printf("Host name is %s\n", res->ai_canonname);
printf("addrlen is %d\n", res->ai_addrlen);
printf("ai_family is %d\n", res->ai_family);
}
exit(0);
}
// 运行结果
$ gcc getaddrinfo.c
$ ./a.out
Host name is localhost
addrlen is 16
ai_family is 2
利用Socket实现TCP和UDP
使用socket实现发送端(客户端)程序逻辑如下 |
使用socket实现接收端(服务端)程序逻辑如下 |
以上是我们常用到的流程,通常在TCP中使用send()和recv(), 而在UDP中使用sendto()和recvfrom()
但是其实并不是固定用法 只是通俗用法,下面摘自《嵌入式Linux应用程序开发标准教程》的官方流程图
使用TCP协议socket编程流程图如下 |
使用UDP协议socket编程流程图如下 |
int send( SOCKET s,char *buf,int len,int flags ); |
功能:不论是客户还是服务器应用程序都用send函数来向TCP连接的另一端发送数据。客户程序一般用send函数向服务器发送请求,而服务器则通常用send函数来向客户程序发送应答。
参数一:指定发送端套接字描述符;
参数二:存放应用程序要发送数据的缓冲区;
参数三:实际要发送的数据的字节数;
参数四:一般置为0。
同步Socket的send函数的执行流程,当调用该函数时,send先比较待发送数据的长度len和套接字s的发送缓冲的长度(因为待发送数据是要copy到套接字s的发送缓冲区的,注意并不是send把s的发送缓冲中的数据传到连接的另一端的,而是协议传的(即在系统内核中完成),send仅仅是把buf中的数据copy到s的发送缓冲区的剩余空间里):
1.如果len大于s的发送缓冲区的长度,该函数返回SOCKET_ERROR;
2.如果len小于或者等于s的发送缓冲区的长度,那么send先检查协议是否正在发送s的发送缓冲中的数据,如果是就等待协议把数据发送完,如果协议还没有开始发送s的发送缓冲中的数据或者s的发送缓冲中没有数据,那么 send就比较s的发送缓冲区的剩余空间和len:
(i)如果len大于剩余空间大小send就一直等待协议把s的发送缓冲中的数据发送完;
(ii)如果len小于剩余空间大小send就仅仅把buf中的数据copy到剩余空间里。
3.如果send函数copy数据成功,就返回实际copy的字节数,如果send在copy数据时出现错误,那么send就返回SOCKET_ERROR;如果send在等待协议传送数据时网络断开的话,那么send函数也返回SOCKET_ERROR。
注意:send函数把buf中的数据成功copy到s的发送缓冲的剩余空间里后它就返回了,但是此时这些数据并不一定马上被传到连接的另一端。如果协议在后续的传送过程中出现网络错误的话,那么下一个Socket函数就会返回SOCKET_ERROR。(每一个除send外的Socket函数在执行的最开始总要先等待套接字的发送缓冲中的数据被协议传送完毕才能继续,如果在等待时出现网络错误,那么该Socket函数就返回 SOCKET_ERROR)
int recv( SOCKET s, char *buf, int len, int flags) |
功能:不论是客户还是服务器应用程序都用recv函数从TCP连接的另一端接收数据。
参数一:指定接收端套接字描述符;
参数二:指明一个缓冲区,该缓冲区用来存放recv函数接收到的数据;
参数三:指明buf的长度;
参数四 :一般置为0。
同步Socket的recv函数的执行流程:当应用程序调用recv函数时,recv先等待s的发送缓冲中的数据被协议传送完毕,
如果协议在传送s的发送缓冲中的数据时出现网络错误,那么recv函数返回SOCKET_ERROR;
如果s的发送缓冲中没有数据或者数据被协议成功发送完毕后,recv先检查套接字s的接收缓冲区,如果s接收缓冲区中没有数据或者协议正在接收数据,那么recv就一直
等待,直到协议把数据接收完毕;
当协议把数据接收完毕,recv函数就把s的接收缓冲中的数据copy到buf中(注意协议接收到的数据可能大于buf的长度,所以在这种情况下要调用几次recv函数才能把s的接收缓冲中的数据copy完。recv函数仅仅是copy数据,真正的接收数据是协议来完成的),recv函数返回其实际copy的字节数;
如果recv在copy时出错,那么它返回SOCKET_ERROR;如果recv函数在等待协议接收数据时网络中断了,那么它返回0。
引用于
谈一谈不可靠的UDP连接
一个例程 , 这次例程是使用到了windows系统和wince嵌入式系统进行测试(wince程序为服务端)
// client.cpp 发送端(运行在PC端 192.168.8.170)
SOCKET sock_Client = socket(AF_INET,SOCK_DGRAM,IPPROTO_UDP);
SOCKADDR_IN addr_server;
addr_server.sin_family=AF_INET;
addr_server.sin_port=htons(4567);
addr_server.sin_addr.S_un.S_addr=inet_addr("192.168.8.41");
while(1){
sendto(sock_Client,sendBuf,strlen(sendBuf),0,(SOCKADDR*)&addr_server,sizeof(SOCKADDR));
recvfrom(sock_Client,receBuf,BUFFER_SIZE,0,(SOCKADDR*)&sock,&len);
}
// 接收端server.cpp(运行到开发板中 192.168.8.41)
SOCKET sockServer = socket(AF_INET,SOCK_DGRAM,IPPROTO_UDP);
SOCKADDR_IN addr_Server;
addr_Server.sin_family=AF_INET;
addr_Server.sin_port=htons(4567);
addr_Server.sin_addr.S_un.S_addr=INADDR_ANY;
bind(sockServer,(SOCKADDR*)&addr_Server,sizeof(addr_Server));
while(1){
recvfrom(sockServer, receBuf, 1024, 0, (SOCKADDR*) &addr_Clt, &fromlen);
sendto(sockServer,Response, strlen(Response), 0, (SOCKADDR*)&addr_Clt, sizeof(SOCKADDR));
}
看抓包情况可以看到 UDP的不可靠传输在传输数据时是没有任何前置的握手确认对方存在等操作 就直接的包往外发送 这是由UDP协议的原理决定 UDP不需要确认对方是否在等待接收 只需要由下层协议(网际层)检测到IP的存在 就会把数据发送出去 至于对方是否处于接收状态 不在UDP的发送方进行验证 而TCP协议才会有握手过程
并且在调用sendto函数后 由协议直接返回发送的数据长度 无法通过返回的数据长度确认数据是否发送成功
只能在服务器端进行逻辑处理 接收到客户端数据 根据相对应的通信协议返回数据进行协商等
UDP组播接收端和发送端的需要用到的配置:
关于socket编程基本知识
关于fcntl和select的区别后续追加
因篇幅问题不能全部显示,请点此查看更多更全内容
Copyright © 2019- stra.cn 版权所有 赣ICP备2024042791号-4
违法及侵权请联系:TEL:199 1889 7713 E-MAIL:2724546146@qq.com
本站由北京市万商天勤律师事务所王兴未律师提供法律服务