UDP网络编程

发布时间:2023年12月29日

一、TCP和UDP对比以及应用场景分析

  1. TCP面向连接
  • TCP需要建立连接,UDP无连接。 所以UDP快,适用于实时性要求高的场景,而TCP安全。
  • 并且由于UDP无连接,所以支持多对多通信,而TCP只能一对一通信。
  1. TCP可靠性传输,UDP不可靠
  • TCP有三次握手和四次挥手、序号、确认应答、超时重传、滑动窗口以及拥塞控制等机制来保证可靠性传输,UDP没有这些机制。所以UDP在传输量小的情况下快,TCP可靠性传输并适用于大量数据传输的情况。
  • TCP这些机制需要在报文首部中设置SYN、ACK等数据,增大了首部开销(首部20字节),UDP只需要8字节
  1. TCP面向字节流传输,UDP面向报文传输
  • 面向字节流是以字节为单位发送数据,并且一个数据包可以以字节大小来拆分成多个数据包,以方便发送。TCP可以先将数据存放在发送缓冲区中,可以等待数据到一定大小发送,也可以直接发送,没有固定大小可言。UDP需要每次发送都需要发送固定长度的数据包,如果长度过长需要应用层协议主动将其裁剪到适合长度。

所以TCP一般用在需要传输大量数据且对可靠性要求高的情况下,如HTTP;而UDP一般适用于对实时性要求高的场景,如直播、游戏、语音通话等

二、TCP和UDP编程过程

TCP

  1. TCP 服务器端–主要步骤
1.建立监听socket 			//socket()
2.监听socket绑定ip地址和端口	//bind()
3.进行监听					//listen()
//接收到syn报文后协议栈负责三次握手
4.三次握手结束后为全连接中的tcp控制块分配fd,即建立与客户端对话的socket	//accept()
5.传输数据				//send(), recv()
6.断开连接				//close()
//协议栈负责四次挥手
  1. TCP 客户端–主要步骤
1.建立与服务器对话的客户端socket			//socket()
2.客户端socket绑定ip地址和端口(可选)		//bind()
3.根据服务器的地址和端口进行连接			//connect()
//协议栈发送syn报文,进行三次握手
4.传输数据				//send(), recv()
5.断开连接				//close()
//协议栈负责四次挥手

UDP

  1. UDP --主要步骤
1.创建一个监听socket				//socket()
2.设置监听socket属性(可选)		//setsockopt()
3.监听socket绑定ip地址和端口		//bind()
4.接收和发送数据				//recvfrom(), sendto()
5.关闭网络连接				//close()

三、函数详解

1.setsockopt()
端口复用允许在一个应用程序可以把 n 个套接字绑在一个端口上而不出错

extern int setsockopt (int __fd, int __level, int __optname,
		       const void *__optval, socklen_t __optlen) __THROW;
int __fd : 被设置的socket
int __level : 选项定义的层次;支持SOL_SOCKET、IPPROTO_TCP、IPPROTO_IP和IPPROTO_IPV6
				SOL_SOCKET : 通用套接字选项.
				IPPROTO_IP : IP选项.IPv4套接口
				IPPROTO_TCP : TCP选项.
				IPPROTO_IPV6 : IPv6套接口
int __optname : 欲设置的选项,有下列几种数值

当level为SOL_SOCKET时,optname可以有以下选项(一部分)
选项名称                  说明                           数据类型
SO_BROADCAST     允许发送广播数据            int
SO_DEBUG        允许调试                int
SO_LINGER        延迟关闭连接          struct linger
SO_OOBINLINE      带外数据放入正常数据流         int
SO_RCVBUF        接收缓冲区大小             int
SO_SNDBUF        发送缓冲区大小             int
SO_RCVLOWAT      接收缓冲区下限             int
SO_SNDLOWAT      发送缓冲区下限             int
SO_RCVTIMEO       接收超时           struct timeval
SO_SNDTIMEO       发送超时           struct timeval
SOREUSEADDR          允许重用本地地址                 	int
SOREUSEPORT			  允许重用端口						int

const void *__optval : 	指针,指向存放选项待设置的新值的缓冲区,可以指向普通变量或结构体
						获得或者是设置套接字选项.根据选项名称的数据类型进行转换
socklen_t __optlen :    optval缓冲区长度
  1. sendto()
extern ssize_t sendto (int __fd, const void *__buf, size_t __n,
		       int __flags, __CONST_SOCKADDR_ARG __addr,
		       socklen_t __addr_len);
int __fd : socket文件描述符
const void *__buf : 发送数据的首地址
size_t __n : 发送数据的长度
int __flags : 	0, 默认方式发送数据,fags还可以设为以下标志的组合
				MSG OOB, 发送带外数据
				MSG_DONTROUTE, 告诉IP协议,目的主机在本地网络,没有必要查找路由表
				MSGDONTWAI, 设置为非阳塞操作
				MSGNOSIGNAL, 表示发送动作不愿被SIGPIPE信号中断
__CONST_SOCKADDR_ARG __addr : 目的主机的地址和端口
socklen_t __addr_len : __addr的长度
  1. recvfrom()
extern ssize_t recvfrom (int __fd, void *__restrict __buf, size_t __n,
			 int __flags, __SOCKADDR_ARG __addr,
			 socklen_t *__restrict __addr_len);
int __fd : socket文件描述符
void *__restrict__ __buf : 接收数据的首地址
size_t __n : 可接受数据的最大长度
int __flags : 	0, 默认方式接收数据,flags还可以设为以下标志的组合
				MSG OOB, 接收带外数据
				MSG PEEK, 查看数据标志,返回的数据并不在系统中删除,如果再次调用recv通数会返回相同的数据内容
				MsG DONTAT, 设置为非阴塞操作
				MSG WAITALL, 强迫接收到en大小的数据后才返回,除非有错误或有信号产生
__SOCKADDR_ARG __addr : 发送方的IP地址和端口
socklen_t *__restrict __addr_len : __addr的长度

四、完整代码

服务器端

#include <stdio.h>
#include <string.h>

#include <sys/socket.h>
#include <unistd.h>
#include <netinet/in.h>
#include <arpa/inet.h>



#include <errno.h>
#include <pthread.h>

int main(){
    //创建socket
    int socketfd = socket(AF_INET, SOCK_DGRAM, 0);
    
    //定义地址
    struct sockaddr_in server_addr;
    memset(&server_addr, 0, sizeof(sockaddr_in));

    server_addr.sin_family = AF_INET;
    server_addr.sin_port = htons(6666);
    server_addr.sin_addr.s_addr = htonl(INADDR_ANY);

    //绑定
    if(bind(socketfd, (struct sockaddr*)&server_addr, sizeof(struct sockaddr))==-1){
        perror("bind error");
        return -1;
    }

    //接受数据
    char buffer[1024] = {0};

    struct sockaddr_in recv_addr;
    socklen_t recvlen = sizeof(recv_addr);
    int recv_size = recvfrom(socketfd, buffer, 1024, 0, (struct sockaddr *)&recv_addr, &recvlen);
    if(recv_size == -1){
        printf("recv erro\n");
    }
    printf("recv from client : %s\n", buffer);
    
    //发送数据
    char message[1024] = {"this is server!"};
    sendto(socketfd, message, 1024, 0, (struct sockaddr*)&recv_addr, sizeof(recv_addr));
    close(socketfd);

    
}

客户端

#include <stdio.h>
#include <string.h>

#include <sys/socket.h>
#include <unistd.h>
#include <netinet/in.h>
#include <arpa/inet.h>

#include <errno.h>
#include <pthread.h>

int main(){
    //创建socket,返回文件描述符
    int socketfd = socket(AF_INET, SOCK_DGRAM, 0);
    
    //定义地址
    struct sockaddr_in server_addr;
    memset(&server_addr, 0, sizeof(sockaddr_in));

    server_addr.sin_family = AF_INET;
    server_addr.sin_port = htons(8888);
    server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
    
    //绑定地址
    if(bind(socketfd, (struct sockaddr*)&server_addr, sizeof(struct sockaddr))==-1){
        perror("bind error");
        return -1;
    }

    //定义目标地址
    struct sockaddr_in target_addr;
    memset(&target_addr, 0, sizeof(sockaddr_in));
    target_addr.sin_family = AF_INET;
    target_addr.sin_port = htons(6666);
    server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
    
    //发送数据
    char buffer[1024] = {"this is client!"};
    sendto(socketfd, buffer, 1024, 0, (struct sockaddr*)&target_addr, sizeof(target_addr));

    //接受数据
    struct sockaddr_in recv_addr;
    socklen_t recvlen = sizeof(recv_addr);
    int recv_size = recvfrom(socketfd, buffer, 1024, 0, (struct sockaddr *)&recv_addr, &recvlen);
    if(recv_size == -1){
        printf("recv erro\n");
    }

    printf("recv from server : %s\n", buffer);
    close(socketfd);
    
}

备注

这么写需要服务器端先启动,不然客户端发送的时候服务端还没启动就会丢失数据然后阻塞在recvfrom

参考资源:https://blog.csdn.net/JMW1407/article/details/107321853

文章来源:https://blog.csdn.net/Fiyanna/article/details/135258805
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。