当前位置: 首页 > news >正文

Socket编程基础(1)

目录

预备知识

 socket通信的本质

认识TCP协议和UDP协议

网络字节序

socket编程流程

socket编程时常见的函数

服务端绑定

整数IP和字符串IP

客户端套接字的创建和绑定


预备知识

理解源IP和目的IP

        源IP指的是发送数据包的主机的IP地址,目的IP指的是接收数据包的主机的IP地址。在网络通信中,当一台主机需要向另一台主机发送数据包时,它需要将数据包的目的IP地址设置为接收主机的IP地址,同时在数据包的头部中加入源IP地址,以便接收主机能够知道数据包来自哪个主机。

理解源MAC地址和目的MAC地址

        源MAC地址和目的MAC地址是数据链路层中的两个重要概念,MAC地址是每个网卡独有的物理地址,用于在局域网中标识设备。

源MAC地址是指发送数据包的主机的网卡的MAC地址,目的MAC地址是指接收数据包的主机的网卡的MAC地址。在数据包传输过程中,数据包的源MAC地址和目的MAC地址会随着数据包一起传输,并被中间的路由器或交换机使用来判断数据包的转发。

当数据包从源主机发送时,它首先需要找到目的主机所在的网卡,这时候就需要目的MAC地址。发送主机会将目的MAC地址填入数据包中,以便路由器或交换机能够将数据包准确地发送给目的主机。而源MAC地址则用于标识数据包的来源,这样目的主机就能够知道数据包是从哪个主机发送过来的

理解源端口号和目的端口号

        在TCP/IP协议中,源端口号和目的端口号是定义在传输层的概念,用于在不同主机之间的通信中唯一标识一个虚拟连接。源端口号和目的端口号组成了一个socket,可以理解为一个网络应用程序的地址。

源端口号是发送方的端口号,目的端口号是接收方的端口号。在发送数据时,网络应用程序会将数据发送给目标主机的特定端口号,而接收方则会通过监听特定端口号来接收发送方传来的数据。

端口号的范围是0~65535,其中0~1023是系统端口号,已经被分配给一些知名的服务或应用程序,比如80号端口是HTTP服务默认端口、 21号端口是FTP服务默认端口、25号端口是SMTP服务默认端口。而1024~65535是动态端口号,可以由应用程序自行分配使用。

通过源端口号和目的端口号,TCP/IP协议可以建立并维护一条虚拟连接,保证数据包的可靠传输,并可以区分不同的应用程序之间的通信。

 socket通信的本质

        Socket通信的本质是建立在TCP/IP协议之上的一种应用层协议,它用于在网络上实现进程之间的通信。在Socket通信中,进程可以作为客户端或服务器端,通过IP地址和端口号建立连接并进行数据传输。

在建立Socket连接时,客户端会向特定IP地址和端口号发送连接请求,服务器端会接受连接请求并建立连接。一旦连接建立成功,客户端和服务器端就可以通过Socket实例进行数据的收发。

Socket通信的本质是通过TCP/IP协议在网络上传输数据,确保数据的可靠性和稳定性。TCP协议提供可靠的数据传输和流量控制,IP协议提供数据包的路由和分发。通过Socket通信,可以建立一对一、一对多、多对多的通信方式,实现进程之间的高效通信,广泛应用于互联网、局域网和各种分布式系统中。

总结:通过IP地址和MAC地址可以实现数据有一台主机传输到另一台主机了,但在实际生活中我们希望的是能将数据传输到指定的应用中(在机器上称进程),例如我们在自己主机上的京东APP上点赞一个店家时我们希望这个数据会传输到其它主机上的京东APP上,而不是淘宝APP或其它APP。这时就需要使用端口号来标识特定的进程APP。

在不同的两台主机上,可能会同时存在多个正在进行跨网络通信的进程,当数据到达目的主机时就需要通过端口号找到该主机上对应的通信进程,然后将数据交给该进程进行处理。而该主机也需要记住发送端的信息,当数据处理完后返还处理结果。

端口号的理解

端口号(port)是传输层协议的内容

端口号是一个2字节16位的整数;

端口号用来标识一个进程, 告诉操作系统, 当前的这个数据要交给哪一个进程来处理;

IP地址 + 端口号能够标识网络上的某一台主机的某一个进程; 一个端口号只能被一个进程占用。

理解 "端口号" 和 "进程ID"

        到这里有人会问当数据到达目的主机时,为何不以进程PID为根据将数据发送给目的进程?端口号(prot)的作用和进程ID具有同样的功能,都是唯一标识一台主机上的某一个进程。为何还要创建出端口号?

首先进程ID是用于唯一标识系统内所有进程的,属于系统级概念,端口号用于唯一标识系统内要进行跨网络通信的进程,属于网络概念。并不是所有的进程都需要进行跨网络通信。

当数据到达目的主机时如何找到要接收数据的端口号?

底层采用哈希表方式建立端口号和进程IPD或PCB之间的映射,当底层拿到端口号时通过算法找到对应的进程。

认识TCP协议和UDP协议

TCP协议

TCP协议(Transmission Control Protocol,传输控制协议)是一种可靠的、面向连接的协议。它主要用于在计算机网络上提供可靠的数据传输服务。

TCP协议是一种面向字节流的协议,它通过将数据分成一系列的数据包进行传输,从而保证了数据的可靠性。在TCP连接建立后,通信双方将通过三次握手建立连接。数据传输过程中,TCP协议会对每一个数据包进行确认和校验,确保数据的完整性和正确性。当接收方接收到一个数据包时,会向发送方发送一个确认消息,确保发送方已经正确地发送了该数据包。如果发送方在一定时间内没有收到确认消息,它会重新发送相同的数据包,以确保数据的可靠传输。

总结:

传输层协议

有连接

可靠传输

面向字节流

UDP协议

UDP协议(User Datagram Protocol,用户数据报协议)是一种简单的、无连接的网络传输协议。UDP协议的特点是速度快,但可靠性比较低。

UDP协议是一种面向数据报的协议,它将数据分割成一个个独立的数据包进行传输,这些数据包被称为用户数据报(UDP Datagram)。在传输数据的过程中,UDP协议不会进行数据的确认和校验,也不会保证数据的顺序性。因此,UDP协议比TCP协议要快很多,但会存在数据丢失的风险。

 总结:

传输层协议

无连接

不可靠传输

面向数据报

网络字节序

网络字节序,也叫大端字节序,是一种字节序(byte order)规范,它规定了数据在网络传输时的排序方式。

计算机数据存储模式

大端模式:数据的高字节存储在低地址,低字节存储在高地址。

小端模式:数据的高字节存储在高地址,低字节存储在低地址。

        在网络传输中,不同计算机上的处理器可能采用不同的字节序,为了保证数据在网络中传输时的正确性,就需要使用统一的字节序,即网络字节序。在网络字节序中,整数类型的数据的高字节存储在低地址,低字节存储在高地址,与大端字节序相同。而在小端字节序中,整数类型的数据的高字节存储在高地址,低字节存储在低地址。因此,在网络传输中,数据需要转换为网络字节序,才能确保不同计算机之间的数据传输的正确性。

网络字节序是一种字节序规范,它规定了数据在网络传输时的排序方式,能够保证不同计算机之间的数据传输的正确性。

socket编程流程

sockaddr结构

        套接字属于进程间通信中的一种,它支持本地通信和网络通信。在使用套接字进行跨网络通信是我们需要指明IP地址和端口号,而使用于本地通信则不需要,因此套接字提供了sockaddr_in结构体和sockaddr_un结构体,其中sockaddr_in结构体用于跨网络通信,而sockaddr_un结构体用于本地通信,网络让套接字的本地通信和跨网络通信都能使用一套相同的接口,系统还提供了sockaddr结构体。这三个结构体的头部的16个比特位都是一样的,这个字段叫协议家族。

所以我们在传参是就不用传入sockaddr_in或sockaddr_un这样的结构体,而使用统一的sockaddr,我们通过设置协议家族参数来表明我们要进行本地通信还是网络通信,这样套接字的本地通信和网络通信就得到了统一。

注意:在编写程序时,还是使用相应得结构体,如要进行网络通信时,定义时仍使用sockaddr_in,但在传参时强转为sockaddr*。

socket编程时常见的函数

socket( )

socket( )是一个系统调用,用于在应用程序中创建一个新的套接字(socket)。

该函数的原型如下:

int socket(int domain, int type, int protocol);

函数参数说明:

  • domain:套接字通信的协议族,相当于struct sockaddr结构体的前16位,如果使用于本地通信就设置为AF_UNIX,如果使用于网络通信就设置AF_INET(IPv4协议)和AF_INET6(IPv6协议)等。
  • type:套接字的类型,常见的有SOCK_STREAM(面向连接的TCP协议)和SOCK_DGRAM(无连接的UDP协议)等。
  • protocol:指定协议,通常为0,表示使用默认协议。

函数返回值:

  • 成功:返回一个socket的文件描述符,可以用于后续网络通信中。
  • 失败:返回-1,并设置errno全局变量表示具体错误原因。

当我们在程序中执行了socket函数时,底层会进行以下几个步骤:

  1. 内核创建一个新的socket对象,并为它分配唯一的文件描述符。

  2. 内核为此socket分配相关的资源,包括缓冲区、状态信息等。

  3. 内核为此socket分配本地端口号(如果是TCP协议的话),并对本地端口进行绑定(bind)操作,使得其它进程可以通过这个端口号来与此socket进行通信。

  4. 内核返回新socket对象的文件描述符,程序可以通过这个文件描述符来访问此socket。

 
        当进程调用socket函数时,实际就相当于打开了一个“网络文件”,这时进程就要对这个"网络文件"进行管理,就要为这个"网络文件"分配文件描述符,这个文件描述符会作为socket函数的返回值返回用户,与普通的磁盘文件不同的是,磁盘文件是将数据刷新到磁盘上就完成了数据的操作,而网络文件则是将文件缓冲区的数据将会刷新到网卡里而卡是负责数据发送的,最终会将数据发送到网络中。

创建套接字

        进行创建套接字时,要根据使用场景而填入相应的参数,这里我们使用UDP进行网络通信,所以协议族填AF_INET,SOCK_DGRAM,第三个参数填0为默认。

class udpserver
{public:
bool InitServer()
{//创建套接字_sockfd=socket(AF_INET,SOCK_DGRAM,0);//AF_INET//表示要进行网络通信,SOCK_DGRAM  UDP协议if(_sockfd<0){//表示创建套接字失败std::cout<<" socket error";return false;}std::cout<<" socket create success,socfd:"<<_sockfd<< std::endl;return true;}
~udpserver()
{if(_sockfd>0){close(_sockfd);//关闭文件描述符}
}
private:
int _sockfd;//网络文件描述符};
#include"server.hpp"
int main()
{udpserver* ser=new udpserver();ser->InitServer();return 0;
}

运行结果: 

服务端绑定


        套接字创建好后,就相当于打开了一个文件,这个文件和其他文件并没有什么不同;操作系统并不知道要将文件的内容写到磁盘还网卡,所以我们需要将这个文件与一个本地地址进行绑( 通常指IP地址和端口号) , 这就不得不介绍bind函数。

bind函数是一个系统调用,它在应用程序的套接字(socket)和本地地址(IP地址和端口号)之间建立一个关联关系,使得其他应用程序可以通过该本地地址与该套接字进行通信。bind函数的主要作用是将一个特定的套接字绑定到一个特定的本地地址上,使得该套接字在网络上唯一地标识一个通信节点,从而实现网络通信。bind函数的函数原型如下:

int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

其中,sockfd是由socket函数返回的套接字描述符,addr是一个指向本地地址结构体的指针,addrlen是该结构体的长度。bind函数的调用方法如下:

struct sockaddr_in addr;
int sockfd = socket(AF_INET, SOCK_STREAM, 0); // 创建一个TCP套接字
memset(&addr, 0, sizeof(addr)); // 清空地址结构体
addr.sin_family = AF_INET; // 设置地址族为IPv4
addr.sin_addr.s_addr = htonl(INADDR_ANY); // 设置本地地址为任意IP地址
addr.sin_port = htons(8080); // 设置本地端口号为8080
if (bind(sockfd, (struct sockaddr *)&addr, sizeof(addr)) < 0) { // 绑定套接字和本地地址perror("bind error");exit(EXIT_FAILURE);
}

在上面的例子中,我们首先通过socket函数创建了一个TCP套接字,然后创建了一个本地地址结构体addr,该结构体包含了本地IP地址和端口号等信息,然后将该结构体和套接字描述符作为参数传入bind函数中,调用bind函数将套接字和本地地址绑定起来。在绑定成功后,应用程序就可以通过该本地地址来访问该套接字,实现网络通信的功能。

#pragma once#include <iostream>
#include <cerrno>
#include <cstring>
#include <cstdlib>
#include <functional>
#include <strings.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <pthread.h>
#include<unistd.h>
#include <unordered_map>
using namespace std;class udpserver
{public:
udpserver(std::string ip,int port):_sockfd(-1),_port(port),_ip(ip)
{}
bool InitServer()
{//创建套接字_sockfd=socket(AF_INET,SOCK_DGRAM,0);//AF_INET//表示要进行网络通信,SOCK_DGRAM  UDP协议if(_sockfd<0){//表示创建套接字失败std::cout<<" socket error";return false;}std::cout<<" socket create success,socfd:"<<_sockfd<< std::endl;struct sockaddr_in local;memset(&local,'\0',sizeof(local));//清空结构体local.sin_family=AF_INET;local.sin_port=htons(_port);local.sin_addr.s_addr=inet_addr(_ip.c_str());//绑定if(bind(_sockfd,(struct sockaddr*)&local,sizeof(sockaddr))<0){std::cout<<"bind error"<<std::endl;return false;}std::cout<<"bind success"<<std::endl;return true;}~udpserver()
{if(_sockfd>0){close(_sockfd);//关闭文件描述符}
}
private:
int _sockfd;//网络文件描述符
int _port;//端口号
std::string _ip;//IP地址};
#include"server.hpp"
int main(int argc,char*argv[])
{if(argc!=2){std:cout<<"usage:"<<argv[0]<<" port"<<std::endl;return 1;}std::string ip="127.0.0.1";int port=atoi(argv[1]);udpserver* ser=new udpserver(ip,port);ser->InitServer();return 0;
}

整数IP和字符串IP

IP地址是Internet Protocol的缩写,是一个32位二进制数,通常表示为四个8位二进制数,即四个数字,每个数字范围在0到255之间,用点分十进制形式表示。例如:192.168.1.1

IP地址的表示形式有两种:

整数IP:将点分十进制形式表示的IP地址转换为一个32位整数,例如:192.168.1.1可以转换为十进制数3232235777。

字符串IP:即点分十进制形式表示的IP地址,例如:192.168.1.1。

整数IP转为字符串IP

        将整数IP转换为字符串IP的主要原因是方便人们阅读和理解IP地址。字符串IP的形式更符合人们的阅读习惯,同时也更容易记忆。在实际应用中,比如在配置网络设备和编写网络应用程序时,我们通常使用字符串IP来表示IP地址,因为这更符合人们的直觉和需要。

inet_ntoa()函数是一个用于将32位无符号整数表示的IP地址转换为点分十进制字符串表示的网络函数。它的头文件为"arpa/inet.h",使用时需要包含该头文件。

该函数的原型如下:

char *inet_ntoa(struct in_addr in);

参数in是一个struct in_addr类型的结构体,表示待转换的32位无符号整数表示的IP地址。函数返回值是转换后的点分十进制字符串表示的IP地址,如果转换失败则返回NULL。

例如,以下代码将32位无符号整数表示的IP地址192.168.0.1转换为点分十进制字符串,并打印出转换后的结果:

inet_ntoa()函数是一个用于将32位无符号整数表示的IP地址转换为点分十进制字符串表示的网络函数。它的头文件为"arpa/inet.h",使用时需要包含该头文件。

该函数的原型如下:

char *inet_ntoa(struct in_addr in);

参数in是一个struct in_addr类型的结构体,表示待转换的32位无符号整数表示的IP地址。

函数返回值是转换后的点分十进制字符串表示的IP地址,如果转换失败则返回NULL。

例如,以下代码将32位无符号整数表示的IP地址192.168.0.1转换为点分十进制字符串,并打印出转换后的结果:

#include <stdio.h>
#include <arpa/inet.h>int main() {in_addr_t ip_int = 3232235521; // 192.168.0.1的32位无符号整数表示struct in_addr in;in.s_addr = ip_int;char* ip_str = inet_ntoa(in);if (ip_str == NULL) {printf("IP address conversion error!\n");return -1;} else {printf("IP address in string format: %s\n", ip_str);return 0;}
}

需要注意的是,由于inet_ntoa()函数返回的是指向静态缓冲区的指针,因此每次调用该函数时返回的字符串都会被覆盖。如果需要保存转换后的IP地址字符串,可以使用strdup()函数对其进行复制。

recvfrom()函数是一个用于在UDP协议中接收数据的网络函数。它的头文件为<sys/socket.h>,使用时需要包含该头文件。

该函数的原型如下:

ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr *src_addr, socklen_t *addrlen);

参数说明:

参数sockfd为已经创建好的socket文件描述符,用于接收数据;

参数buf为接收数据的缓冲区;参数len为缓冲区的长度,即最多接收的字节数;

参数flags一般为0,表示没有特殊要求;

参数src_addr为指向存放发送者IP地址和端口号的sockaddr结构体指针;

参数addrlen为指向sockaddr结构体长度的指针,接收到的发送者地址长度会写入其中。

返回值:函数返回值为接收到的字节数,如果出错则返回-1。

注意:udp是不面向连接的,所以在使用udp协议进行通信时,我们在获取数据信息时,还要获取数据发送方的有关属性,包括IP地址和端口号,要将struct sockadd_in* 转为更适合传输的struct sockadd*类型。

例如,以下代码从创建好的socket文件描述符sock_fd中接收数据,并输出接收到的信息:

#include <stdio.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>int main() {int sock_fd = socket(AF_INET, SOCK_DGRAM, 0);if (sock_fd == -1) {printf("Failed to create socket!\n");return -1;}struct sockaddr_in serv_addr;memset(&serv_addr, 0, sizeof(serv_addr));serv_addr.sin_family = AF_INET;serv_addr.sin_addr.s_addr = inet_addr("127.0.0.1");serv_addr.sin_port = htons(8888);if (bind(sock_fd, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) == -1) {printf("bind error!\n");return -1;}char buf[1024] = {0};struct sockaddr_in cli_addr;socklen_t cli_len = sizeof(cli_addr);ssize_t recv_len = recvfrom(sock_fd, buf, sizeof(buf), 0, (struct sockaddr *)&cli_addr, &cli_len);if (recv_len == -1) {printf("recvfrom error!\n");return -1;}printf("Received message from %s:%d, message: %s\n", inet_ntoa(cli_addr.sin_addr), ntohs(cli_addr.sin_port), buf);close(sock_fd);return 0;
}

此处为UDP服务器代码,创建套接字,进行bind()操作后等待客户端发送数据,使用recvfrom()函数接收数据,然后输出接收到的信息。

#pragma once#include <iostream>
#include <cerrno>
#include <cstring>
#include <cstdlib>
#include <functional>
#include <strings.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <pthread.h>
#include<unistd.h>
#include <unordered_map>
using namespace std;class udpserver
{public:
udpserver(std::string ip,int port):_sockfd(-1),_port(port),_ip(ip)
{}
bool InitServer()
{//创建套接字_sockfd=socket(AF_INET,SOCK_DGRAM,0);//AF_INET//表示要进行网络通信,SOCK_DGRAM  UDP协议if(_sockfd<0){//表示创建套接字失败std::cout<<" socket error";return false;}std::cout<<" socket create success,socfd:"<<_sockfd<< std::endl;struct sockaddr_in local;memset(&local,'\0',sizeof(local));//清空结构体local.sin_family=AF_INET;local.sin_port=htons(_port);local.sin_addr.s_addr=inet_addr(_ip.c_str());//绑定if(bind(_sockfd,(struct sockaddr*)&local,sizeof(sockaddr))<0){std::cout<<"bond error"<<std::endl;return false;}std::cout<<"bond success"<<std::endl;return true;}void Start()
{#define SIZE 1024 char buffer[SIZE];while(true){struct sockaddr_in perr;socklen_t len=sizeof(perr);//读取数据ssize_t size=recvfrom(_sockfd,buffer,sizeof(buffer)-1,0,(struct sockaddr*)&perr,&len);if(size<0)//读取失败{std::cout<<"recvfrom cerror"<<std::endl;}else{buffer[size]='\0';int port=ntohs(perr.sin_port);std::string ip=inet_ntoa(perr.sin_addr);std::cout<<ip<<": "<<port<<"# "<<buffer<<std::endl;}}
}
~udpserver()
{if(_sockfd>0){close(_sockfd);//关闭文件描述符}
}
private:
int _sockfd;//网络文件描述符
int _port;//端口号
std::string _ip;//IP地址};
#include"server.hpp"
int main(int argc,char*argv[])
{if(argc!=2){std:cout<<"usage:"<<argv[0]<<" port"<<std::endl;return 1;}std::string ip="127.0.0.1";int port=atoi(argv[0]);udpserver* ser=new udpserver(ip,port);ser->InitServer();ser->Start();return 0;
}

客户端套接字的创建和绑定

客户端在创建套接字时参数的传递与服务端创建套接字时一样都是AF_INET, SOCK_DGRAM, 0,不同的是服务端需要进行端口号绑定,而客户端不需要。

但在网络通信,通信双方都需要找到对方,因此服务端和客户端都需要有各自的IP地址和端口号,只不过服务端需要进行端口号的绑定,而客户端不需要。
因为服务器就是为了给别人提供服务的,因此服务器必须要让别人知道自己的IP地址和端口号,IP地址一般对应的就是域名,而端口号一般没有显示指明过,因此服务端的端口号一定要是一个众所周知的端口号,并且选定后不能轻易改变,否则客户端是无法知道服务端的端口号的,这就是服务端要进行绑定的原因,只有绑定之后这个端口号才真正属于自己,因为一个端口只能被一个进程所绑定,服务器绑定一个端口就是为了独占这个端口。
而客户端在通信时虽然也需要端口号,但客户端一般是不进行绑定的,客户端访问服务端的时候,端口号只要是唯一的就行了,不需要和特定客户端进程强相关。

客户端不需要绑定端口号是因为客户端的套接字在创建时会自动由操作系统分配一个随机端口号,并且这个随机端口号会在套接字连接到服务器端时被发送给服务器端,因此服务器端就可以使用该端口号来与客户端进行数据传输。客户端套接字的随机端口号一般是在1024到65535之间的一个空闲端口号,这样可以避免与其他已知端口号冲突。
如果客户端绑定了某个端口号,那么以后这个端口号就只能给这一个客户端使用,就是这个客户端没有启动,这个端口号也无法分配给别人,并且如果这个端口号被别人使用了,那么这个客户端就无法启动了。所以客户端的端口只要保证唯一性就行了,因此客户端端口可以动态的进行设置,并且客户端的端口号不需要我们来设置,当我们调用类似于sendto这样的接口时,操作系统会自动给当前客户端获取一个唯一的端口号。
也就是说,客户端每次启动时使用的端口号可能是变化的,此时只要我们的端口号没有被耗尽,客户端就永远可以启动。

#pragma once
#include <iostream>
#include <string>
#include <cstring>
#include<unistd.h>
#include <sys/types.h> 
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>class udpclient
{
public:udpclient(std::string server_ip,int server_port):_sockfd(-1),_server_port(server_port),_server_ip(server_ip){}bool Initclient(){//创建套接字_sockfd=socket(AF_INET,SOCK_DGRAM,0);//AF_INET//表示要进行网络通信,SOCK_DGRAM  UDP协议if(_sockfd<0){//表示创建套接字失败std::cout<<" socket error";return false;}return true;}udpclient(){if(_sockfd>0){close(_sockfd);//关闭文件描述符}}private:int _sockfd;//文件描述符int _server_port;//服务端端口号std::string _server_ip;//服务端的IP地址
};

当客户端与服务端进行绑定后接下来就是要向对方发送消息进行通信了。

UDP通信中的sendto函数用于向指定的IP地址和端口号发送数据报。该函数的调用格式如下:

int sendto(int sockfd, const void *buf, size_t len, int flags, const struct sockaddr *dest_addr, socklen_t addrlen);

参数说明:

  • sockfd:要发送数据的套接字文件描述符。
  • buf:待发送的数据指针。
  • len:待发送的数据长度。
  • flags:发送标志,一般为0。
  • dest_addr:指向目的地址结构体的指针,需要将IP地址和端口号分别填入该结构体的sin_addr和sin_port成员中。
  • addrlen:目的地址结构体的长度。

调用sendto函数时需要填写目的地址结构体,例如:

struct sockaddr_in dest_addr;
dest_addr.sin_family = AF_INET;
dest_addr.sin_port = htons(port);
dest_addr.sin_addr.s_addr = inet_addr(ip);

其中,port和ip分别为目的端口号和IP地址字符串。

sendto函数会将指定的数据报发送到指定的目的地址,如果发送成功返回发送的字节数,如果失败返回-1,并设置全局errno变量的值。由于UDP是无连接的,因此在发送数据时不需要建立连接。

UDP通信中的sendto函数用于向指定的IP地址和端口号发送数据报。该函数的调用格式如下:

int sendto(int sockfd, const void *buf, size_t len, int flags, const struct sockaddr *dest_addr, socklen_t addrlen);

参数说明:

  • sockfd:要发送数据的套接字文件描述符。
  • buf:待发送的数据指针。
  • len:待发送的数据长度。
  • flags:发送标志,一般为0。
  • dest_addr:指向目的地址结构体的指针,需要将IP地址和端口号分别填入该结构体的sin_addr和sin_port成员中。
  • addrlen:目的地址结构体的长度。

调用sendto函数时需要填写目的地址结构体,例如:

struct sockaddr_in dest_addr;
dest_addr.sin_family = AF_INET;
dest_addr.sin_port = htons(port);
dest_addr.sin_addr.s_addr = inet_addr(ip);

其中,port和ip分别为目的端口号和IP地址字符串。

sendto函数会将指定的数据报发送到指定的目的地址,如果发送成功返回发送的字节数,如果失败返回-1,并设置全局errno变量的值。由于UDP是无连接的,因此在发送数据时不需要建立连接。

#pragma once
#include <iostream>
#include <string>
#include <cstring>
#include<unistd.h>
#include <sys/types.h> 
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>class udpclient
{
public:udpclient(std::string server_ip,int server_port):_sockfd(-1),_server_port(server_port),_server_ip(server_ip){}bool Initclient(){//创建套接字_sockfd=socket(AF_INET,SOCK_DGRAM,0);//AF_INET//表示要进行网络通信,SOCK_DGRAM  UDP协议if(_sockfd<0){//表示创建套接字失败std::cout<<" socket error";return false;}return true;}void Start(){std::string msg;struct sockaddr_in perr;memset(&perr,'\0',sizeof(perr));perr.sin_family=AF_INET;perr.sin_port=htons(_server_port);perr.sin_addr.s_addr=inet_addr(_server_ip.c_str());while(true){std::cout<<"Please Enter#";getline(std::cin,msg);sendto(_sockfd,msg.c_str(),msg.size(),0,(struct sockaddr*)&perr,sizeof(perr));//std::cout<<"sendto()  success"<<std::endl;}}udpclient(){if(_sockfd>0){close(_sockfd);//关闭文件描述符}}private:int _sockfd;//文件描述符int _server_port;//服务端端口号std::string _server_ip;//服务端的IP地址
};
#include"client.hpp"
int main(int argc,char*argv[])
{if(argc!=3){std::cout<<"usage:"<<argv[0]<<" port"<<std::endl;return 1;}std::string server_ip=argv[1];int server_port=atoi(argv[2]);udpclient* cli=new udpclient(server_ip,server_port);cli->Initclient();cli->Start();return 0;
}

相关文章:

Socket编程基础(1)

目录 预备知识 socket通信的本质 认识TCP协议和UDP协议 网络字节序 socket编程流程 socket编程时常见的函数 服务端绑定 整数IP和字符串IP 客户端套接字的创建和绑定 预备知识 理解源IP和目的IP 源IP指的是发送数据包的主机的IP地址&#xff0c;目的IP指的是接收数据包…...

无线通信——Mesh自组网的由来

阴差阳错找到了一个工作&#xff0c;是做无线通信的&#xff0c;因为无线设备采用Mesh&#xff0c;还没怎么接触过&#xff0c;网上搜索下发现Mesh的使用场景不多&#xff0c;大部分都是用在家里路由器上面。所以写了片关于Mesh网的文档。Mesh网可应用在无网络区域的地方&#…...

LRU、LFU 内存淘汰算法的设计与实现

1、背景介绍 LRU、LFU都是内存管理淘汰算法&#xff0c;内存管理是计算机技术中重要的一环&#xff0c;也是多数操作系统中必备的模块。应用场景&#xff1a;假设 给定你一定内存空间&#xff0c;需要你维护一些缓存数据&#xff0c;LRU、LFU就是在内存已经满了的情况下&#…...

常用工具使用

ubuntu 1.1 ubuntu与windows 互相复制问题 方法一、 打开虚拟机 &#xff0c;点击上方导航栏 ‘虚拟机’ 查看VMware Tools是否安装&#xff0c;安装即可 方法二、 apt-get autoremove open-vm-tools apt-get install open-vm-tools apt-get install open-vm-tools-desktop…...

HashMap源码解析_jdk1.8(一)

HashMap解析 HashMap源码解析_jdk1.8&#xff08;一&#xff09;哈希常用数据结构查找/插入/删除性能比较。哈希冲突 HashMap的数据结构HashMap相关变量size和capacity HashMap源码解析_jdk1.8&#xff08;一&#xff09; 哈希 Hash&#xff0c;一般翻译做“散列”&#xff0…...

Android最好用的日志打印库(自动追踪日志代码位置)

给大家推荐一个自己写的日志打印的库&#xff0c;我愿称之为最强日志打印库&#xff1a;BytUtilLog Byt是Big一统的缩写&#xff0c;大一统日志打印库&#xff0c;哈哈&#xff01;搞个笑&#xff0c;很早就写好了&#xff0c;但后面忙起来就忘了写一篇文章推一下它了&#xff…...

面试官的哪些举动,暗示你通过了面试?

其实在求职过程中都会发现&#xff0c;求职者面试时间一般在20分钟以上&#xff0c;如果求职者较多&#xff0c;可能会在10分钟左右。(会在介绍以往工作上减少时间&#xff0c;内容主要以简单介绍&#xff0c;薪资要求&#xff0c;能力评价&#xff0c;到岗时间等等) 拿面试时…...

​旅行季《乡村振兴战略下传统村落文化旅游设计》许少辉八一新著想象和世界一样宽广

​旅行季《乡村振兴战略下传统村落文化旅游设计》许少辉八一新著想象和世界一样宽广...

Linux学习第19天:Linux并发与竞争实例: 没有规矩不成方圆

Linux版本号4.1.15 芯片I.MX6ULL 大叔学Linux 品人间百味 思文短情长 先说点题外话&#xff0c;上周参加行业年会&#xff0c;停更了一周。接下来的周五就要开启国庆中秋双节模式&#xff0c;所以有的时候&#xff0c;尤其是工作以后…...

Unity添加自定义菜单按钮

如果你想在Unity编辑器中添加自定义菜单按钮&#xff0c;你可以使用Unity的MenuSystem API。这是一个简单的示例&#xff1a; 首先需要引用using UnityEditor; using UnityEngine; using UnityEditor; 两个命名空间 然后在方法前添加 [MenuItem("原菜单名/自定义名…...

PHP8的类与对象的基本操作之类的实例化-PHP8知识详解

定义完类和方法后&#xff0c;并不是真正创建一个对象。类和对象可以描述为如下关系。类用来描述具有相同数据结构和特征的“一组对象”&#xff0c;“类”是“对象”的抽象&#xff0c;而“对象”是“类”的具体实例&#xff0c;即一个类中的对象具有相同的“型”&#xff0c;…...

C/S架构学习之TCP服务器

TCP服务器的实现流程&#xff1a;一、创建套接字&#xff08;socket函数&#xff09;&#xff1a;通信域选择IPV4网络协议、套接字类型选择流式&#xff1b; int sockfd socket(AF_INET,SOCK_STREAM,0); //通信域选择IPV4、套接字类型选择流式二、填充服务器的网络信息结构体&…...

基于微信小程序的线上教育课程付费商城(源码+lw+部署文档+讲解等)

文章目录 前言具体实现截图论文参考详细视频演示为什么选择我自己的网站自己的小程序&#xff08;小蔡coding&#xff09;有保障的售后福利 代码参考源码获取 前言 &#x1f497;博主介绍&#xff1a;✌全网粉丝10W,CSDN特邀作者、博客专家、CSDN新星计划导师、全栈领域优质创作…...

Linux基础指令(五)

目录 前言1. 打包和压缩1.1 是什么1.2 为什么1.3 怎么办&#xff1f; 2. zip & unzip3. tar 指令结语&#xff1a; 前言 欢迎各位伙伴来到学习 Linux 指令的 第五天&#xff01;&#xff01;&#xff01; 在上一篇文章 Linux基本指令(四) 当中&#xff0c;我们学习了 fin…...

C语言结构体的一些鲜为人知的小秘密

目录 一、结构体内存对齐规则&#xff1a; 1.1范例 1.2结构体内存对齐规则 1.3自定义默认对齐数 二、位段 2.1什么是位段 2.2位段的内存分配 2.3位段的不足 三、枚举和联合体 3.1枚举 3.1.1枚举类型的定义 3.1.2枚举类型的使用 3.2联合体 3.2.1联合体的定义 3.…...

kubernetes问题(一)-探究Pod被驱逐的原因及解决方法

1 k8s evicted是什么 k8s evicted是Kubernetes中的一个组件&#xff0c;主要用于处理Pod驱逐的情况。在Kubernetes中&#xff0c;当Node节点资源不够用时&#xff0c;为了保证整个集群的运行稳定&#xff0c;会按照一定的优先级和策略将其中的Pod驱逐出去。这时就需要一个组件…...

论文速览【序列模型 seq2seq】—— 【Ptr-Net】Pointer Networks

标题&#xff1a;Pointer Networks文章链接&#xff1a;Pointer Networks参考代码&#xff08;非官方&#xff09;&#xff1a;keon/pointer-networks发表&#xff1a;NIPS 2015领域&#xff1a;序列模型&#xff08;RNN seq2seq&#xff09;改进 / 深度学习解决组合优化问题【…...

Denoising diffusion implicit models 阅读笔记

Denoising diffusion probabilistic models (DDPMs)从马尔科夫链中采样生成样本&#xff0c;需要迭代多次&#xff0c;速度较慢。Denoising diffusion implicit models (DDIMs)的提出是为了加速采样过程&#xff0c;减少迭代的次数&#xff0c;并且要求DDIM可以复用DDPM训练的网…...

【Java 基础篇】Executors工厂类详解

在多线程编程中&#xff0c;线程池是一项重要的工具&#xff0c;它可以有效地管理和控制线程的生命周期&#xff0c;提高程序的性能和可维护性。Java提供了java.util.concurrent包来支持线程池的创建和管理&#xff0c;而Executors工厂类是其中的一部分&#xff0c;它提供了一些…...

SpringBoot MongoDB操作封装

1.引入Jar包 <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-mongodb</artifactId></dependency> 2.MongoDbHelper操作 /*** MongoDB Operation class* author Mr.Li* date 2022-12-05*…...

PyTorch 模型性能分析和优化 — 第 1 部分

一、说明 这篇文章的重点将是GPU上的PyTorch培训。更具体地说&#xff0c;我们将专注于 PyTorch 的内置性能分析器 PyTorch Profiler&#xff0c;以及查看其结果的方法之一&#xff0c;即 PyTorch Profiler TensorBoard 插件。 二、深度框架 训练深度学习模型&#xff0c;尤其是…...

Unity3D 简易音频管理器

依赖于Addressable 依赖于单例模板&#xff1a;传送门 using System.Collections.Generic; using System.Security.Cryptography; using System; using UnityEngine; using UnityEngine.AddressableAssets;namespace EasyAVG {public class AudioManager : MonoSingleton<…...

【李沐深度学习笔记】线性回归

课程地址和说明 线性回归p1 本系列文章是我学习李沐老师深度学习系列课程的学习笔记&#xff0c;可能会对李沐老师上课没讲到的进行补充。 线性回归 如何在美国买房&#xff08;经典买房预测问题&#xff09; 一个简化的模型 线性模型 其中&#xff0c; x → [ x 1 , x 2 ,…...

微信收款码费率0.38太坑了

作为一个有多年运营经验的商家&#xff0c;我本人在申请收款功能时曾经走过了不少弯路。我找遍了市面上的知名的支付公司&#xff0c;但了解到的收款手续费率通常都在0.6左右&#xff0c;最低也只能降到0.38。这个过程吃过不少苦头。毕竟&#xff0c;收款功能是我们商家的命脉&…...

【学习笔记】CF1103D Professional layer

首先分析不出啥性质&#xff0c;所以肯定是暴力优化&#x1f605; 常见的暴力优化手段有均摊&#xff0c;剪枝&#xff0c;数据范围分治&#xff08;points&#xff09;&#xff0c;答案值域分析之类的。 比较经典的题目是 CF1870E Another MEX Problem&#xff0c;可以用剪枝…...

vue之Pinia

定义 Store | Pinia 开发文档 1.什么是Pinaia Pinia 是 Vue 的专属状态管理库&#xff0c;它允许你跨组件或页面共享状态。 2.理解Pinaia核心概念 定义Store 在深入研究核心概念之前&#xff0c;我们得知道 Store 是用 defineStore() 定义的&#xff0c;它的第一个参数要求是一…...

antd-vue 级联选择器默认值不生效解决方案

一、业务场景&#xff1a; 最近在使用Vue框架和antd-vue组件库的时候&#xff0c;发现在做编辑回显时** 级联选择器** 组件的默认值不生效。为了大家后面遇到和我一样的问题&#xff0c;给大家分享一下 二、bug信息&#xff1a; 三、问题原因&#xff1a; 确定不了唯一的值&a…...

分享53个Python源码源代码总有一个是你想要的

分享53个Python源码源代码总有一个是你想要的 链接&#xff1a;https://pan.baidu.com/s/1ew3w2_DXlSBrK7Mybx3Ttg?pwd8888 提取码&#xff1a;8888 项目名称 100-Python ControlXiaomiDevices DRF-ADMIN 后台管理系统 FishC-Python3小甲鱼 Flask框架的api项目脚手架 …...

【每日一题】658. 找到 K 个最接近的元素

658. 找到 K 个最接近的元素 - 力扣&#xff08;LeetCode&#xff09; 给定一个 排序好 的数组 arr &#xff0c;两个整数 k 和 x &#xff0c;从数组中找到最靠近 x&#xff08;两数之差最小&#xff09;的 k 个数。返回的结果必须要是按升序排好的。 整数 a 比整数 b 更接近 …...

并发任务队列(字节青训测试题)

需求描述 封装一个并发任务队列类&#xff0c;用于对一些异步任务按指定的并发数量进行并发执行。 /*** 延迟函数* param {number} time - 延迟时间* return {Promise} delayFn - 延迟函数(异步封装)*/ function timeout(time) {return new Promise((resolve) > {setTimeo…...