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;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/84566.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Java高级-动态代理

动态代理 1.介绍2.案例 1.介绍 public interface Star {String sing(String name);void dance(); }public class BigStar implements Star{private String name;public BigStar(String name) {this.name name;}public String sing(String name) {System.out.println(this.name…

主打低功耗物联网国产替代,纵行科技ZT1826芯片以速率和灵敏度出圈

在低功耗物联网领域&#xff0c;国产替代的趋势越演越烈。 9月20日&#xff0c;纵行科技在“IOTE 2023深圳物联网通信技术与应用高峰论坛”发表了“自主原创Advanced M-FSK调制技术助力国产替代和泛在物联”的演讲&#xff0c;并推出了ZT1826芯片&#xff0c;以“更低功耗、更…

会C++还需要再去学Python吗?

提到的C、数据结构与算法、操作系统、计算机网络和数据库技术等确实是计算机科学中非常重要的基础知识领域&#xff0c;对于软件开发和计算机工程师来说&#xff0c;它们是必备的核心知识。掌握这些知识对于开发高性能、可靠和安全的应用程序非常重要。Python作为一种脚本语言&…

GLTF编辑器:在线模型材质编辑工具

GLTF 编辑器 是一个功能强大、易于使用的在线3D模型编辑和查看工具&#xff0c;它支持多种格式的3D模型导入并将模型导出为GLB格式&#xff0c;除了可以对3D模型进行基本属性的修改之外&#xff0c;还支持对模型原点重置以及模型材质纹理修改。对于3D开发者和设计师来说&#x…

机器学习入门教学——损失函数(交叉熵法)

1、前言 我们在训练神经网络时&#xff0c;最常用到的方法就是梯度下降法。在了解梯度下降法前&#xff0c;我们需要了解什么是损失(代价)函数。所谓求的梯度&#xff0c;就是损失函数的梯度。如果不知道什么是梯度下降的&#xff0c;可以看一下这篇文章&#xff1a;机器学习入…

百度APP iOS端包体积50M优化实践(六)无用方法清理

一、前言 百度APP包体积经过一期优化&#xff0c;如无用资源清理&#xff0c;无用类下线&#xff0c;Xcode编译相关优化&#xff0c;体积已经有了明显的减少。但是优化后APP包体积在iPhone11上仍有350M的空间占用。与此同时百度APP作为百度的旗舰APP&#xff0c;业务迭代非常多…

mysq 主从同步错误之 Error_code 1032 handler error HA_ERR_KEY_NOT_FOUND

错误说明&#xff1a; MySQL主从同步的1032错误&#xff0c;一般是指要更改的数据不存在&#xff0c;SQL_THREAD提取的日志无法应用故报错&#xff0c;造成同步失败 &#xff08;Update、Delete、Insert一条已经delete的数据&#xff09;。 1032的错误本身对数据一致性没什么影…

基于SSM的四六级报名与成绩查询系统的设计与实现

末尾获取源码 开发语言&#xff1a;Java Java开发工具&#xff1a;JDK1.8 后端框架&#xff1a;SSM 前端&#xff1a;采用JSP技术开发 数据库&#xff1a;MySQL5.7和Navicat管理工具结合 服务器&#xff1a;Tomcat8.5 开发软件&#xff1a;IDEA / Eclipse 是否Maven项目&#x…

【2】贪心算法-综述

前言 从前&#xff0c;有一个很穷的人救了一条蛇的命&#xff0c;蛇为了报答他的救命之 恩&#xff0c;于是就让这个人提出要求&#xff0c;满足他的愿望。这个人一开始只要求简 单的衣食&#xff0c;蛇都满足了他的愿望&#xff0c;后来慢慢地贪欲升起&#xff0c;要求做官&am…

selenium+python实现基本自动化测试

安装selenium 打开命令控制符输入&#xff1a;pip install -U selenium 火狐浏览器安装firebug&#xff1a;www.firebug.com&#xff0c;调试所有网站语言&#xff0c;调试功能 Selenium IDE 是嵌入到Firefox 浏览器中的一个插件&#xff0c;实现简单的浏览器操 作的录制与回…

屏幕分辨率dpi解析(adb 调试查看)

authordaisy.skye的博客_CSDN博客-嵌入式,Qt,Linux领域博主 ro.sf.lcd_density属性指定了这个机型使用的dpi是多少&#xff0c;dpi全称是dots per inch&#xff0c;对角线每英寸的像素点的个数。 密度 ldpi mdpi hdpi xhdpi xxhdpi 分辨率 240x320 320x480 480x800 7…

Cannot find module ‘core-js/modules/es6.regexp.constructor‘

npm run dev 之后报如下错误 解决方法&#xff1a;npm install core-js2 如果超时或者下载时间慢可以尝试 用cnpm install core-js2

软件工程知识总结梳理

&#x1f525;&#x1f525;宏夏Coding网站&#xff0c;致力于为编程学习者、互联网求职者提供最需要的内容&#xff01;网站内容包括求职秘籍&#xff0c;葵花宝典&#xff08;学习笔记&#xff09;&#xff0c;资源推荐等内容。在线阅读&#xff1a;https://hongxiac.com&…

Nodejs 第十六章(ffmpeg)

FFmpeg 是一个开源的跨平台多媒体处理工具&#xff0c;可以用于处理音频、视频和多媒体流。它提供了一组强大的命令行工具和库&#xff0c;可以进行视频转码、视频剪辑、音频提取、音视频合并、流媒体传输等操作。 FFmpeg 的主要功能和特性&#xff1a; 格式转换&#xff1a;…

Network: use `--host` to expose

vite 启动项目提示 Network: use --host to expose 同事不能通过本地IP地址访问项目 解决方案&#xff1a;package.json中启动命令配置本地IP地址 vite --host 192.168.200.252

Twitter图片数据优化的细节

Twitter个人数据优化&#xff1a;吸引更多关注和互动 头像照片在Twitter上&#xff0c;头像照片是最快识别一个账号的方法之一。因此&#xff0c;请务必使用公司的标志或与品牌相关的图片。建议尺寸为400x400像素。 为了建立强大的品牌形象和一致性&#xff0c;强烈建议在所有…

虹科教您 | 可实现带宽计量和延迟计算的时间敏感网络测试工具RELY-TSN-LAB操作指南与基本功能测试

1. RELY-TSN-LAB产品概述 时间敏感网络(TSN)能够合并OT和IT世界&#xff0c;这将是真正确保互操作性和标准化的创新性技术。这项技术的有效开发将显著降低设备成本、维护、先进分析服务的无缝集成以及减少对单个供应商的依赖。为了在这些网络中实现确定性&#xff0c;需要控制…

Kotlin Android中错误及异常处理最佳实践

Kotlin Android中错误及异常处理最佳实践 Kotlin在Android开发中的错误处理机制以及其优势 Kotlin具有强大的错误处理功能&#xff1a;Kotlin提供了强大的错误处理功能&#xff0c;使处理错误变得简洁而直接。这个特性帮助开发人员快速识别和解决错误&#xff0c;减少了调试代…

12:STM32---RTC实时时钟

目录 一:时间相关 1:Unix时间戳 2: UTC/GMT 3:时间戳转化 二:BKP 1:简历 2:基本结构 三: RTC 1:简历 2: 框图 3:RTC基本结构 4:RTC操作注意 四:案例 A:读写备份寄存器 1:连接图 2: 步骤 3: 代码 B:实时时钟 1:连接图 2:函数介绍 3:代码 一:时间相关 1:Un…

Linux -- 使用多张gpu卡进行深度学习任务(以tensorflow为例)

在linux系统上进行多gpu卡的深度学习任务 确保已安装最新的 TensorFlow GPU 版本。 import tensorflow as tf print("Num GPUs Available: ", len(tf.config.list_physical_devices(GPU)))1、确保你已经正确安装了tensorflow和相关的GPU驱动&#xff0c;这里可以通…