TCP/IP网络编程-C++(上)

TCP/IP网络编程-C++ (上)

  • 一、基于TCP的服务端/客户端
    • 1、server端代码
    • 2、client端代码
    • 3、`socket()` 函数
      • 3.1、函数原型
      • 3.2、参数解析
        • 3.2.1、协议族(domain参数)
        • 3.2.2、套接字类型(type参数)
        • 3.2.3、最终使用的协议(protocol参数)
    • 4、`struct sockaddr_in` IPv4地址结构体
      • 4.1、结构体原型
      • 4.2、结构体成员分析
      • 4.3、`struct sockaddr` 结构体
    • 5、字节序转换
    • 6、`bind()` 函数
      • 6.1、字符串IP与网络字节序互相转换
        • 6.1.1、`inet_addr()`
        • 6.1.2、 `inet_aton()`
        • 6.1.3、`inet_ntoa()`
      • 6.2 向套接字分配网络地址bind()函数
        • 6.2.1、函数原型:
        • 6.2.2、参数解析:
    • 7、`listen()` 函数 - 进入等待连接请求状态
      • 7.1 函数原型:
      • 7.2 参数解析:
    • 8、`accept()` 函数 - 受理客户端连接请求
      • 8.1 函数原型:
      • 8.2 参数解析
    • 9、`connect()`函数 - 向服务端发送连接请求
      • 9.1 函数原型:
      • 9.2 参数解析
      • 9.3 客户端地址信息在哪里
    • 10、基于TCP的服务端、客户端实现字符串转换
      • 10.1 客户端代码实现:
      • 10.2 服务端代码实现:
  • 二、基于UDP的服务端/客户端
    • 1、server端代码实现
    • 2、client端代码实现
    • 3、`sendto()` 函数 - 填写地址并传输数据的I/O函数
      • 3.1 函数原型
      • 3.2 参数解析
      • 3.3 UDP客户端地址分配
    • 4、`recvfrom()` 函数 - 接收数据
      • 4.1 函数原型
      • 4.2 参数解析
    • 5、存在数据边界的UDP套接字
    • 6、创建已连接UDP套接字
      • 6.1 已连接UDP client端代码实现

一、基于TCP的服务端/客户端

  • 先给出server端和client端代码,client向server发送请求,server接受请求并回复client消息,这里简单回复"hello world!",后面详细说明函数作用

1、server端代码

#include <iostream>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <string>
#include <unistd.h>
#include <string.h>int main(void){int serv_sock = socket(PF_INET, SOCK_STREAM, 0);  if(serv_sock == -1)std::cout<<"socket error\n";struct sockaddr_in serv_addr;memset(&serv_addr, 0, sizeof(serv_addr));serv_addr.sin_family = AF_INET;serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);serv_addr.sin_port = htons(8080);if(bind(serv_sock, (struct sockaddr*) &serv_addr, sizeof(serv_addr)) == -1)std::cout<<"bind error\n";if(listen(serv_sock, 3) == -1)std::cout<<"listen error\n";struct sockaddr_in clie_addr;memset(&clie_addr, 0, sizeof(clie_addr));socklen_t clie_addr_size = 0;int clie_sock = accept(serv_sock, (struct sockaddr*) &clie_addr, &clie_addr_size);if(clie_sock == -1)std::cout<<"accept error\n";std::string message = "hello world!";send(clie_sock, message.c_str(), message.size(), 0);close(clie_sock);close(serv_sock);return 0;
}

2、client端代码

#include <iostream>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <string>
#include <unistd.h>
#include <string.h>int main(void){int clie_sock = socket(PF_INET, SOCK_STREAM, 0);if(clie_sock == -1)std::cout<<"socket error\n";std::string server_ip = "127.0.0.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(server_ip.c_str());serv_addr.sin_port = htons(8080);if(connect(clie_sock, (struct sockaddr*)&serv_addr, sizeof(serv_addr)) == -1)std::cout<<"connect error\n";char message[30] = {0};if(recv(clie_sock, message,30, 0) == -1)std::cout<<"read error\n";std::string str_message(message);std::cout<<"message : "<<str_message<<"\n";close(clie_sock);return 0; 
}
  • 可以大概看看,不理解没关系,接下来会解释每一个函数的作用

3、socket() 函数

3.1、函数原型

	socket(int domain, int type, int protocol); // 成功会返回文件描述符,失败返回-1

3.2、参数解析

  • domain-协议族:套接字中使用的协议族信息。
  • type-套接字类型:套接字数据传输类型信息。
  • protocol-最终使用的协议:计算机间通信中使用的协议信息。
3.2.1、协议族(domain参数)
  • 协议族就是协议的分类信息,在<sys/socket.h>中声明的协议分类信息如下表:
名称协议族
PF_INETIPv4互联网协议族
PF_INET6IPv6互联网协议族
PF_LOCAL本地通信的UNIX协议族
PF_PACKET底层套接字的协议族
PF_IPXIPX Novell协议族
3.2.2、套接字类型(type参数)
  • 套接字类型指的是套接字数据传输方式,这里介绍两种类型,面向连接的套接字(TCP)面向消息的套接字(UDP)
  • 1、SOCK_STREAM:表示使用面向连接的套接字,提供可靠的、按序传递的、基于自己的服务。
  • 2、SOCK_DGRAM:表示使用面向消息的套接字,提供不可靠的、不按序传递的、以数据高速传输为目的的服务。
3.2.3、最终使用的协议(protocol参数)
  • 最终使用的协议指的是同一协议族中存在多个数据传输方式相同的协议,在同一协议族中数据传输方式相同,但使用的协议不同,一般该参数传递0即可。

4、struct sockaddr_in IPv4地址结构体

4.1、结构体原型

struct sockaddr_in
{sa_family_t		sin_family;		// 地址族uint16_t		sin_port;		// 16位TCP/UDP端口号struct in_addr	sin_addr;		// 32位IP地址char			sin_zero[8];	// 不使用
}
// struct in_addr原型
struct in_addr
{In_addr_t	s_addr;		// 32位IP地址
}

4.2、结构体成员分析

  • sin_family:每种协议使用的地址族均不同,比如IPv4使用4字节地址族,IPv6使用16字节地址族。常用的地址族如下表:
地址族含义
AF_INETIPv4网络中使用的地址族
AF_INET6IPv6网络中使用的地址族
AF_LOCAL本地通信中采用的UNIX协议的地址族
  • sin_port:保存16位端口号
  • sin_addr:保存32位IP地址信息
  • sin_zero:没有特殊含义,为了使得sockaddr_in与sockaddr大小保持一致而插入的成员,(sockaddr后面介绍),填充位0即可

4.3、struct sockaddr 结构体

  • 在调用bind()函数时是将sockaddr_in转换为sockaddr进行传入的,因为bind()函数要求的参数类型是sockaddr,但为什么不直接定义sockaddr进行传入呢,而是定义sockaddr_in转换为sockaddr呢?如下sockaddr结构体原型:
struct sockaddr
{sa_family_t		sin_family;		// 地址族char 			sa_data[14];	//地址信息
}
  • 因为最终IP地址信息和端口号会保存到sa_data成员中,直接想sa_data中填写信息比较麻烦,所以就有了sockaddr_in,使用起了更方便。

5、字节序转换

  • 由于CPU存储数据分为大端序和小端序,但是网络数据传输中同一使用大端序,所以下面介绍字节序转换函数
  • (1)uint32_t ntohl (uint32_t __netlong):将uint32_t类型数据从网络字节序转换为主机字节序
  • (2)uint16_t ntohs (uint16_t __netshort):将uint16_t类型数据从网络字节序转换为主机字节序
  • (3)uint32_t htonl (uint32_t __hostlong):将uint32_t类型数据从主机字节序转换为网络字节序
  • (4)uint16_t htons (uint16_t __hostshort):将uint16_t类型数据从主机字节序转换为网络字节序
  • 只需要在想sockaddr_in结构体填充数据时需要进行转换,其它情况不需要转换字节序,这都是自动完成的。当然sockaddr_in中的sin_family也不需要进行字节序转换,因为sin_family 并不会发送到网络上(详细信息自行了解)。

6、bind() 函数

6.1、字符串IP与网络字节序互相转换

6.1.1、inet_addr()
  • 作用:将字符串类型IP转换为转换为整数并返回,示例如下:
#include <arpa/inet.h>
#include <string>
int main(void)
{std::string str_ip = "127.0.0.1";uint32_t u_ip = inet_addr(str_ip.c_str());	// 返回的结果u_ip可以直接赋值给sockaddr_in中的sin_addr.s_addrif(u_ip == INADDR_NONE){std::cout<<"无效ip"<<std::endl;}return 0;
}
6.1.2、 inet_aton()
  • 作用:将字符串类型IP转换为转换为整数同时将结果直接写入sockaddr_in中的sin_addr,调用成功返回true(1),失败返回false(0),示例如下:
#include <arpa/inet.h>
#include <string>
int main(void)
{std::string str_ip = "127.0.0.1";struct sockaddr_in addr;int ret = inet_aton(str_ip.c_str(), &addr.sin_addr);	if(ret == 0){std::cout<<"无效ip"<<std::endl;}return 0;
}
6.1.3、inet_ntoa()
  • 作用:将网络字节序的IP转换为字符串,成功是返回字符串IP,失败是返回-1,示例如下:
#include <arpa/inet.h>
#include <string>
int main(void)
{struct sockaddr_in addr;addr.sin_addr.s_addr = htonl(0x1020304);std::string str_ip = inet_ntoa(addr.sin_addr);	std::cout<<str_ip<<std::endl;return 0;
}

6.2 向套接字分配网络地址bind()函数

6.2.1、函数原型:
int bind (int __fd, const struct sockaddr * __addr, socklen_t __len);
//	成功返回0,失败返回-1
6.2.2、参数解析:
参数参数说明
__fd要分配地址信息的套接字文件描述符,socket()函数返回的文件描述符
__addrstruct sockaddr_in IPv4地址结构信息
__len参数2的长度

7、listen() 函数 - 进入等待连接请求状态

7.1 函数原型:

	int listen(int sock, int backlog);//	成功时返回0, 失败时返回-1

7.2 参数解析:

参数参数说明
sock套接字文件描述符
backlog连接请求等待队列的长度,若为5,则队列长度为5,表示最多使5个连接请求进入队列
  • 所谓的等待连接请求状态是指客户端请求连接时,在受理连接前一直使请求处于等待连接状态

8、accept() 函数 - 受理客户端连接请求

8.1 函数原型:

	int accept(int sock, struct sockaddr* addr, socklen_t* addrlen);// 成功时返回套接字文件描述符,失败时返回-1

8.2 参数解析

参数参数说明
sock套接字文件描述符
addr保存发起连接请求的客户端地址信息
addrlen保存第二个参数的地址长度

9、connect()函数 - 向服务端发送连接请求

9.1 函数原型:

	int connect(int sock, struct sockaddr* servaddr, socklen_t* addrlen);//	成功时返回0,失败时返回-1

9.2 参数解析

参数参数说明
sock客户端套接字文件描述符
servaddr目标服务器端地址信息
addrlen第二个参数的长度

9.3 客户端地址信息在哪里

  • 客户端调用 connect() 函数函数后,发生一下两种情况才会完成函数调用:
    (1)、服务端接收到连接请求
    (2)、发生断网等异常情况而中断连接请求
  • 需要注意的是,连接请求并不意味着服务端调用 accept() 函数,其实是服务端把连接请求信息记录在等待队列中,因此 connect() 函数返回后并不立即进行数据交换。
  • 实现服务端必经过程之一就是给套接字分配IP和端口号,但在客户端实现过程中并没有为套接字分配IP和端口号,而是在创建套接字后就立即调用了connect()函数。难道客户端套接字不需要IP和端口号吗?当然不是,学过计算机网络都知道网络中数据交换必须要IP和端口号,既然如此,那客户端套接字何时?何地?如何分配地址呢?
    (1)、何时:在调用connect()函数时
    (2)、何地:操作系统内核中自动完成
    (3)、如何:IP使用主机的IP,端口随机分配
  • 因此,客户端IP和端口号是在调用connect()函数时自动分配,无需使用bind()函数进行分配。

10、基于TCP的服务端、客户端实现字符串转换

  • 我们已经学完了基础函数,前面我们给出的示例是很简单的调用流程,即客户端发送请求,服务端接受请求并发送了“hello world”的信息,然后各自关闭了连接。那服务端如何循环的接收并处理客户端的请求呢?客户端如何不断的向服务端发送请求呢?接下里我们实现一个简单的字符串转换功能,即客户端向服务端发送字符串,服务端接收到字符串后将字符串中大写字符转换为小写并返回给客户端。

10.1 客户端代码实现:

#include <iostream>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <string>
#include <unistd.h>
#include <string.h>
const int BUFFER_SIZE = 128;int main(void){int clie_sock = socket(PF_INET, SOCK_STREAM, 0);if(clie_sock == -1)std::cout<<"socket error\n";std::string server_ip = "127.0.0.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(server_ip.c_str());serv_addr.sin_port = htons(8080);if(connect(clie_sock, (struct sockaddr*)&serv_addr, sizeof(serv_addr)) == -1)std::cout<<"connect error\n";while(true){std::string str_input = "";std::cout<<"please input:";std::cin>>str_input;if(str_input == "q")break;send(clie_sock, str_input.c_str(), str_input.size(), 0);char message[BUFFER_SIZE] = {0};int recv_size = 0;if((recv_size = recv(clie_sock, message, BUFFER_SIZE - 1, 0)) == -1)std::cout<<"read error\n";message[recv_size] = '\0';std::string str_message(message, recv_size);std::cout<<"before:"<<str_input<<"\n";std::cout<<"later:"<<str_message<<"\n";}close(clie_sock);return 0; 
}

10.2 服务端代码实现:

#include <iostream>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <string>
#include <unistd.h>
#include <string.h>
#include <regex>
#include <cctype>
const int BUFFER_SIZE = 128;void to_lower(const std::string& str_input, std::string& str_output){std::regex pattern("^[a-zA-Z]+$");bool is_letters = std::regex_match(str_input, pattern);if(is_letters){str_output.resize(str_input.size());std::transform(str_input.begin(), str_input.end(), str_output.begin(), ::tolower);}else{str_output = "包含其它字符,转换失败!";}return;
}int main(void){int serv_sock = socket(PF_INET, SOCK_STREAM, 0);  if(serv_sock == -1)std::cout<<"socket error\n";struct sockaddr_in serv_addr;memset(&serv_addr, 0, sizeof(serv_addr));serv_addr.sin_family = AF_INET;serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);serv_addr.sin_port = htons(8080);if(bind(serv_sock, (struct sockaddr*) &serv_addr, sizeof(serv_addr)) == -1)std::cout<<"bind error\n";if(listen(serv_sock, 3) == -1)std::cout<<"listen error\n";struct sockaddr_in clie_addr;memset(&clie_addr, 0, sizeof(clie_addr));socklen_t clie_addr_size = 0;int clie_sock = 0;clie_sock = accept(serv_sock, (struct sockaddr*) &clie_addr, &clie_addr_size);if(clie_sock == -1)std::cout<<"accept error\n";while(true){char mess[BUFFER_SIZE] = {0};int recv_size = 0;if((recv_size = recv(clie_sock, mess, BUFFER_SIZE - 1, 0)) == -1) {std::cout<<"recv error\n";}mess[recv_size] = '\0';std::string str_message(mess, recv_size);std::string str_result = "";to_lower(str_message, str_result);send(clie_sock, str_result.c_str(), str_result.size(), 0);}close(clie_sock);close(serv_sock);return 0;
}

二、基于UDP的服务端/客户端

  • UDP服务端/客户端不像TCP那样在连接状态下交换数据,因此与TCP不同,无需经过连接过程。也就是说不必调用TCP连接过程中的listen()函数和accept()函数,UPD只有 创建套接字 的过程和 数据交换 的过程
  • UPD不同于TCP,不存在请求连接和受理过程,因此在某种意义上无法明确区分服务端和客户端,只能因提供服务的一方称为服务端。
  • 下面使用UDP方式实现 字符串转换 的例子

1、server端代码实现

#include <iostream>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <string>
#include <unistd.h>
#include <string.h>
#include <regex>
#include <cctype>
const int BUFFER_SIZE = 128;void to_lower(const std::string& str_input, std::string& str_output){std::regex pattern("^[a-zA-Z]+$");bool is_letters = std::regex_match(str_input, pattern);if(is_letters){str_output.resize(str_input.size());std::transform(str_input.begin(), str_input.end(), str_output.begin(), ::tolower);}else{str_output = "包含其它字符,转换失败!";}return;
}int main(void){int serv_sock = socket(PF_INET, SOCK_DGRAM, 0);  if(serv_sock == -1)std::cout<<"socket error\n";struct sockaddr_in serv_addr;memset(&serv_addr, 0, sizeof(serv_addr));serv_addr.sin_family = AF_INET;serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);serv_addr.sin_port = htons(8080);if(bind(serv_sock, (struct sockaddr*) &serv_addr, sizeof(serv_addr)) == -1)std::cout<<"bind error\n";struct sockaddr_in clie_addr;memset(&clie_addr, 0, sizeof(clie_addr));socklen_t clie_addr_size = 0;int clie_sock = 0;while(true){char message[BUFFER_SIZE] = {0};int mess_len = recvfrom(serv_sock, message, BUFFER_SIZE, 0, (struct sockaddr*)&clie_addr, &clie_addr_size);message[mess_len] = '\0';std::string str_input(message, mess_len);std::string str_output = "";to_lower(str_input, str_output);sendto(serv_sock, str_output.c_str(), str_output.size(), 0, (struct sockaddr*)&clie_addr, clie_addr_size);}close(serv_sock);return 0;
}

2、client端代码实现

#include <iostream>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <string>
#include <unistd.h>
#include <string.h>
const int BUFFER_SIZE = 128;int main(void){int clie_sock = socket(PF_INET, SOCK_DGRAM, 0);if(clie_sock == -1)std::cout<<"socket error\n";std::string server_ip = "127.0.0.1";struct sockaddr_in serv_addr, from_addr;socklen_t from_addr_size = 0;memset(&serv_addr, 0, sizeof(serv_addr));serv_addr.sin_family = AF_INET;serv_addr.sin_addr.s_addr = inet_addr(server_ip.c_str());serv_addr.sin_port = htons(8080);while(true){std::string str_input = "";std::cout<<"please input:";std::cin>>str_input;if(str_input == "q")break;sendto(clie_sock, str_input.c_str(), str_input.size(), 0, (struct sockaddr*)&serv_addr, sizeof(serv_addr));char message[BUFFER_SIZE] = {0};int mess_len = recvfrom(clie_sock, message, BUFFER_SIZE, 0, (struct sockaddr*)&from_addr, &from_addr_size);message[mess_len] = '\0';std::string str_message(message, mess_len);std::cout<<"before:"<<str_input<<"\n";std::cout<<"later:"<<str_message<<"\n";}close(clie_sock);return 0; 
}

3、sendto() 函数 - 填写地址并传输数据的I/O函数

3.1 函数原型

 	ssize_t sendto(int sock, void* buff, size_t nbytes, int flags, struct sockaddr* to, socklen_t addrlen);// 成功返回传输的字节数,失败返回-1

3.2 参数解析

参数参数解析
sock用于传输数据的UDP套接字文件描述符
buff将要传输数据的地址
nbytes将要传输数据的长度,以字节为单位
flags可选项,没有传递0即可
to存有目标地址信息的sockaddr结构体变量
addrlento参数的长度

3.3 UDP客户端地址分配

  • 在TCP中客户端是通过connect()函数字符为客户端分配地址的,那UDP中如何分配地址呢?是在 sendto() 函数中进行地址分配的,如果调用sendto()函数时发现没有分配地址信息,则在首次调用时给相应套接字自动分配IP和端口号,IP是主机IP,端口号随机分配。而且此时分配的地址信息回一直保留到程序结束,因此也可以与其它UDP进行数据交换。

4、recvfrom() 函数 - 接收数据

4.1 函数原型

	ssize_t recvfrom(int sock, void* buff, size_t nbytes, int flags, sockaddr* from, socklen_t* addrlen);// 成功时返回接收的字节数,失败返回-1

4.2 参数解析

参数参数解析
sock用于接收数据的UDP套接字文件描述符
buff接受到数据的缓冲地址
nbytes可接收的最大字节数,不能超过buff所指的缓冲大小
flags可选项,没有传递0即可
from保存发送端的地址信息
addrlen保存from参数长度的变量的地址值

5、存在数据边界的UDP套接字

  • UDP是具有数据边界的协议,在传输过程中调用I/O函数的次数非常重要。因此,输入函数和输出函数的调用次数应该完全一致,这样才能保证接收全部已发送数据。比如客户端调用了3次sendto()函数,那么服务端也必须调用3次recvfrom()函数才可以接收客户端3次发送的信息。这和TCP不同,TCP客户端调用3次send()函数,那么服务端可以只调用1次recv()函数就可以接收3次发送的信息。

6、创建已连接UDP套接字

  • TCP套接字中需要注册待传输数据的目标IP和端口号,而UDP中则无需注册,因此,通过sendto()函数传输数据的过程大致分为3个阶段:
    (1)、第一阶段:向UDP套接字注册目标IP和端口号
    (2)、第二阶段:传输数据
    (3)、第三阶段:删除UDP套接字中注册的目标地址信息
  • 每次调用sendto函数时重复上述过程。每次都变更目标地址,因此可以重复利用同一UDP套接字向不同目标传输数据。这种未注册目标地址信息的套接字称为未连接套接字,反之,注册了目标地址的套接字称为连接connected套接字。显然,UDP套接字默认属于未连接套接字。但UDP套接字在下述情况下显得不太合理: “IP为211.210.147.82的主机82号端口共准备了3个数据,调用3次sendto函数进行传输。”此时需重复3次上述三阶段。因此,要与同一主机进行长时间通信时,将UDP套接字变成已连接套接字会提高效率。上述三个阶段中,第一个和第三个阶段占整个通信过程近1/3的时间,缩短这部分时间将大大提高整体性能。
  • 创建已连接UDP套接字的过程格外简单,只需针对UDP套接字调用connect()函数即可,当然,针对UDP套接字调用connect()函数并不意味着要与对方套接字连接,这只是向UDP套接字注册目标IP和端口号。之后每次调用sendto()函数只需要传输数据,因为已经指定了收发对象,所以此时不仅可以使用sendto()、recvfrom()函数,还可以使用send()和recv()函数。
  • 下面给出已连接UDP套接字client端代码实现,服务端没有变化。

6.1 已连接UDP client端代码实现

#include <iostream>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <string>
#include <unistd.h>
#include <string.h>
const int BUFFER_SIZE = 128;int main(void){int clie_sock = socket(PF_INET, SOCK_DGRAM, 0);if(clie_sock == -1)std::cout<<"socket error\n";std::string server_ip = "127.0.0.1";struct sockaddr_in serv_addr, from_addr;socklen_t from_addr_size = 0;memset(&serv_addr, 0, sizeof(serv_addr));serv_addr.sin_family = AF_INET;serv_addr.sin_addr.s_addr = inet_addr(server_ip.c_str());serv_addr.sin_port = htons(8080);if(connect(clie_sock, (struct sockaddr*)&serv_addr, sizeof(serv_addr)) == -1)std::cout<<"connect error\n";while(true){std::string str_input = "";std::cout<<"please input:";std::cin>>str_input;if(str_input == "q")break;// sendto(clie_sock, str_input.c_str(), str_input.size(), 0, (struct sockaddr*)&serv_addr, sizeof(serv_addr));send(clie_sock, str_input.c_str(), str_input.size(), 0);char message[BUFFER_SIZE] = {0};// int mess_len = recvfrom(clie_sock, message, BUFFER_SIZE, 0, (struct sockaddr*)&from_addr, &from_addr_size);int mess_len = recv(clie_sock, message, BUFFER_SIZE - 1, 0);message[mess_len] = '\0';std::string str_message(message, mess_len);std::cout<<"before:"<<str_input<<"\n";std::cout<<"later:"<<str_message<<"\n";}close(clie_sock);return 0; 
}
  • 已连接UDP客户端比未连接UDP客户端只多了connect()函数调用,而且已连接UDP客户端中还可以使用send()和recv()函数进行数据交换,其它部分并没有什么不同。

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

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

相关文章

【AI绘画】Midjourney进阶:色调详解(下)

博客主页&#xff1a; [小ᶻ☡꙳ᵃⁱᵍᶜ꙳] 本文专栏: AI绘画 | Midjourney 文章目录 &#x1f4af;前言&#x1f4af;Midjourney中的色彩控制为什么要控制色彩&#xff1f;为什么要在Midjourney中控制色彩&#xff1f; &#x1f4af;色调纯色调灰色调暗色调 &#x1f4af…

【MySQL篇】持久化和非持久化统计信息的深度剖析(第一篇,总共六篇)

&#x1f4ab;《博主介绍》&#xff1a;✨又是一天没白过&#xff0c;我是奈斯&#xff0c;DBA一名✨ &#x1f4ab;《擅长领域》&#xff1a;✌️擅长Oracle、MySQL、SQLserver、阿里云AnalyticDB for MySQL(分布式数据仓库)、Linux&#xff0c;也在扩展大数据方向的知识面✌️…

PH热榜 | 2024-11-27

DevNow 是一个精简的开源技术博客项目模版&#xff0c;支持 Vercel 一键部署&#xff0c;支持评论、搜索等功能&#xff0c;欢迎大家体验。 在线预览 1. Agentplace 标语&#xff1a;这是一个能创建互动式AI网站和应用的平台。 介绍&#xff1a;Agentplace是一个平台&#xf…

ffmpeg 增亮 docker 使用

使用最新的 docker pull jrottenberg/ffmpeg docker run -it --rm -v /path/to/input:/input -v /path/to/output:/output jrottenberg/ffmpeg <ffmpeg command>比如我想增亮 在 /home 目录下 有一个 video.mp4 docker run --rm -v /home:/home jrottenberg/ffmpeg:7…

单片机学习笔记 11. 外部中断

更多单片机学习笔记&#xff1a;单片机学习笔记 1. 点亮一个LED灯单片机学习笔记 2. LED灯闪烁单片机学习笔记 3. LED灯流水灯单片机学习笔记 4. 蜂鸣器滴~滴~滴~单片机学习笔记 5. 数码管静态显示单片机学习笔记 6. 数码管动态显示单片机学习笔记 7. 独立键盘单片机学习笔记 8…

【PyTorch】(基础一)----pytorch环境搭建

PyTorch环境搭建 该系列笔记主要参考了小土堆的视频教程&#xff0c;传送门&#xff1a;P1. PyTorch环境的配置及安装&#xff08;Configuration and Installation of PyTorch)【PyTorch教程】_哔哩哔哩_bilibili PyTorch 是一个开源的机器学习库&#xff0c;主要用 Python 编…

uniapp开发支付宝小程序自定义tabbar样式异常

解决方案&#xff1a; 这个问题应该是支付宝基础库的问题&#xff0c;除了依赖于官方更新之外&#xff0c;开发者可以利用《自定义 tabBar》曲线救国 也就是创建一个空内容的自定义tabBar&#xff0c;这样即使 tabBar 被渲染出来&#xff0c;但从视觉上也不会有问题 1.官方文…

YOLOv11融合PIDNet中的PagFM模块及相关改进思路

YOLOv11v10v8使用教程&#xff1a; YOLOv11入门到入土使用教程 YOLOv11改进汇总贴&#xff1a;YOLOv11及自研模型更新汇总 《PIDNet: A Real-time Semantic Segmentation Network Inspired by PID Controllers》 一、 模块介绍 论文链接&#xff1a;https://arxiv.org/pdf/2…

NSCTF 做题笔记

[GWCTF 2019]pyre 下载附件&#xff0c;是一个pyc文件。 转换为py文件。 在用vscode打开。 分析源码。源码就是进行了异或和数值转换。 有一点很坑&#xff0c;凑得中的值要转换为ASCII值否则就是一串乱码。 编写脚本&#xff1a; #include<iostream> #include<s…

java——spring容器启动流程

Spring容器的启动流程是一个复杂但有序的过程&#xff0c;它涉及多个步骤来确保应用程序的组件被正确加载、配置和初始化。以下是Spring容器启动的主要步骤&#xff1a; 一、加载配置文件 Spring容器首先会加载配置文件&#xff0c;这些配置文件通常包含了应用程序的组件、依…

九、Ubuntu Linux操作系统

一、Ubuntu简介 Ubuntu Linux是由南非人马克沙特尔沃思(Mark Shutteworth)创办的基于Debian Linux的操作系统&#xff0c;于2004年10月公布Ubuntu是一个以桌面应用为主的Linux发行版操作系统Ubuntu拥有庞大的社区力量&#xff0c;用户可以方便地从社区获得帮助其官方网站:http…

python excel接口自动化测试框架!

今天采用Excel继续写一个接口自动化测试框架。 设计流程图 这张图是我的excel接口测试框架的一些设计思路。 首先读取excel文件&#xff0c;得到测试信息&#xff0c;然后通过封装的requests方法&#xff0c;用unittest进行测试。 其中&#xff0c;接口关联的参数通过正则进…

基本功能实现

目录 1、环境搭建 2、按键控制灯&电机 LED 电机 垂直按键(机械按键) 3、串口调试功能 4、定时器延时和定时器中断 5、振动强弱调节 6、万年历 7、五方向按键 1、原理及分析 2、程序设计 1、环境搭建 需求: 搭建一个STM32F411CEU6工程 分析: C / C 宏定义栏…

Android 13 Aosp 默认允许应用动态权限

图库 frameworks/base/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java 修改 public void grantDefaultPermissions(int userId) {DelayingPackageManagerCache pm new DelayingPackageManagerCache();grantPermissionsToSysCompon…

操作系统 内存管理——针对实习面试

目录 操作系统 内存管理什么是虚拟内存&#xff1f;什么是物理内存&#xff1f;解释虚拟内存和物理内存的区别什么是分页式存储&#xff1f;什么是分段式存储&#xff1f;解释分页式存储和分段式存储的区别什么是内存碎片&#xff1f;描述几种常见的内存分配算法描述几种常见的…

开源免费的 分布式配置中心 介绍 与 选型 建议

分布式配置中心的应用场景介绍 在微服务架构中&#xff0c;配置管理变得尤为复杂。首先&#xff0c;我们可以想象下&#xff0c;如果没有配置中心&#xff0c;我们的项目可能是这样的&#xff1a;不同环境的配置文件都放在项目里面&#xff0c;部署时可以通过启动参数来指定使…

Linux入门攻坚——39、Nginx入门

Nginx&#xff1a;engine X Tengine&#xff1a;淘宝改进维护的版本 Registry&#xff1a; 使用了libevent库&#xff1a;高性能的网络库 epoll()函数 Nginx特性&#xff1a; 模块化设计、较好的扩展性&#xff1b;&#xff08;但不支持动态加载模块功能&#…

Asp.net core Autofac 案例 注入、AOP 启用接口代理拦截 启用 类代理拦截=== 只会拦截虚方法

资料 core 实现autofac 》》》 安装 如下工具包 安装之后 如出现 这种 》》》编写 AOP类 using Castle.DynamicProxy; using System.Diagnostics;namespace Web01.AOP {/// <summary>/// 日志记录/// </summary>public class LoggingInterceptor : IInterc…

网络安全事件管理

一、背景 信息化技术的迅速发展已经极大地改变了人们的生活&#xff0c;网络安全威胁也日益多元化和复杂化。传统的网络安全防护手段难以应对当前繁杂的网络安全问题&#xff0c;构建主动防御的安全整体解决方案将更有利于防范未知的网络安全威胁。 国内外的安全事件在不断增…

详谈面试题:Vue、React为什么使用虚拟DOM

虚拟DOM是一种在前端框架中广泛使用的技术&#xff0c;它可以提升开发效率。那么国外流行的框架svelte没有使用虚拟DOM&#xff0c;而是直接操作真实DOM&#xff0c;效率依然很高。为什么Vue和React不采用这种方式呢&#xff1f; 目录 一、框架设计 二、解耦运行环境 三、总…