【Linux】UDP的服务端 + 客户端
文章目录
- 📖 前言
- 1. TCP和UDP
- 2. 网络字节序
- 2.1 大小端字节序:
- 2.2 转换接口:
- 3. socket接口
- 3.1 sockaddr结构:
- 3.2 配置sockaddr_in:
- 3.3 inet_addr:
- 3.4 inet_ntoa:
- 3.5 bind绑定:
- 4. 服务端start
- 4.1 recvfrom:
- 4.2 sendto:
- 5. 客户端
- 6. 测试
- 6.1 本地回环地址:
- 7. Windows客户端
📖 前言
从上一章开始我们正式进入Linux网络编程的学习,上回中我们对网络有了大概的认识,宏观上了解了网络的传输过程,对局域网广域网以及Mac地址和IP地址有了初步的认识。
本章我们正式进入网络编程,用代码来实现网络间的通信,学习认识相关的接口……
1. TCP和UDP
为了完成通信,传输层有两个重要的协议。
- TCP:Transmission Control Protocol 传输控制协议
- 传输层协议:
-
- 在网络通信中负责提供端到端的数据传输服务的协议。
- 有链接:
-
- 在数据传输之前,发送方和接收方需要建立一个可靠的连接。
- 可靠传输:
-
- 通过一系列的机制和算法来确保数据能够完整、准确地传输到目标主机,并且按照正确的顺序进行重组和接收。
- 面向字节流:
-
- 是一种数据传输的方式,其中数据被视为一连串的字节序列。
- UDP:User Datagram Protocol 用户数据报协议
- 传输层协议:
-
- 在网络通信中负责提供端到端的数据传输服务的协议。
- 无连接:
-
- 无连接指的是数据传输时不需要先建立连接再进行通信的方式,比如所有人都能给你的电子邮箱发送邮件。
- 不可靠传输:
-
- 在不可靠传输中,数据传输过程中不进行可靠性保证的方式,发送方将数据发送给接收方,但不对数据的正确性和完整性进行确认和修复。
- 面向数据报:
-
- 数据在传输过程中被划分为独立的数据报进行传输,每个数据报(也称为包、帧等)都包含了完整的源地址、目标地址和其他必要的信息,使得每个数据报都能够独立地进行路由和处理。
可靠与不可靠传输,更多的标明的是一种通信特征。不能说tcp
和udp
哪个更好,只能说哪个更合适。
2. 网络字节序
我们之前学过C语言都知道,有大端机和小端机,那么不同的计算机的字节序,要向网上发,其他计算结接收时,不知道发过来的数据是按照大端还是小端字节序来读,所有必须要有统一的规定。
规定网络字节序列是一种大端序列。
- 要保证发到网络中的序列必须是大端的。
- 无论是发送方还是接收方,都要直接或者间接的将自己的数据由主机序列转成网络序列,或者由网络序列转成主机序列。
2.1 大小端字节序:
- 大端字节序(Big-endian):
- 是指将高位字节存储在低地址,低位字节存储在高地址的方式。
- 例如,十六进制数
0x12345678
在大端字节序下的内存存储方式为0x12 0x34 0x56 0x78
。
- 小端字节序(Little-endian):
- 是指将低位字节存储在低地址,高位字节存储在高地址的方式。
- 例如,十六进制数
0x12345678
在小端字节序下的内存存储方式为0x78 0x56 0x34 0x12
。
2.2 转换接口:
如果主机就是大端机,这些函数什么都不会做。主机是小端机,则会将主机字节序转换成网络字节序(大端字节序),网络字节序转主机字节序也是同样的道理。
3. socket接口
- Socket(套接字)是一种用于网络通信的编程接口和抽象概念,它使得应用程序可以通过
标准的流式或数据报
式方式发送和接收数据包,实现不同计算机之间的通信。 - 在计算机网络中,
一个 Socket 由 IP 地址和端口号两部分组成
。 -
- IP 地址用于标识网络上的一个主机,而端口号则用于标识该主机上的一个进程。
-
- Socket 通过 IP 地址和端口号来唯一确定一个网络上的进程,并且可以通过多种协议(如 TCP、UDP)进行数据传输。
- 使用 Socket 接口,应用程序可以直接访问网络协议栈,与其他主机建立连接并进行数据交互。因此,Socket 是实现各种协议和服务的基础,如 HTTP、SMTP、FTP、Telnet 等。
返回值:
Linux下的一切皆文件包括了socket接口。每个打开的文件(包括socket)都会被分配一个文件描述符(file descriptor),它是一个非负整数。
- 使用socket API 创建一个socket时,会返回一个socket文件描述符(socket fd),我们可以通过这个socket fd来进行网络的发送和接收操作。
实际上,发送和接收数据就像对文件写入和读取数据一样操作。
- 发送数据时,我们可以使用类似于写入文件的操作,使用write()函数将数据写入到socket fd中。
- 接收数据时,我们可以使用类似于读取文件的操作,使用read()函数从socket fd中读取数据。
- 此外,还可以使用其他文件操作函数,如open()、close()、select()等,对socket进行更复杂的操作。
API:
API 是
Application Programming Interface
的缩写,翻译为应用程序编程接口。是一组定义了不同软件组件之间,相互通信和交互的规范和工具集合。它允许不同的软件系统应用程序或服务之间进行数据传递、功能调用和交互操作。
那么socket是打开了一个文件吗:
- 在Linux中,socket并不是打开一个文件,而是提供了一种抽象的接口,用于进行网络通信。
- 尽管在编程上可以将socket看作是一个文件描述符,但实际上并没有打开一个物理文件。
- 当我们调用socket()函数创建一个socket时,操作系统会为该socket分配资源,并返回一个文件描述符(socket fd)。
- 这个文件描述符是一个整数值,用于标识该socket。
3.1 sockaddr结构:
Socket 是一种抽象层,提供了一种通用的应用程序编程接口(API),允许应用程序通过网络或本地主机之间进行通信。它可以用于不同协议的网络通信,包括 TCP、UDP 等。
除了在网络通信中使用外,它还可以用于同一台计算机上的应用程序之间的通信,例如进程间通信、线程间通信等。
那么一个接口干两件事,如何区分呢?
- sockaddr,用来接收目标信息,这个值的参数可以是
sockaddr_in/scokaddr_un/sockadd_in6
之中的任意一个(需要强转指针)。
sockaddr
是一个通用的地址结构体,它主要用于在网络编程中传递和表示套接字地址。在实际使用中,我们通常会使用sockaddr
的具体派生结构体,例如sockaddr_in(IPv4)或sockaddr_in6(IPv6)或scokaddr_un,它们在sockaddr的基础上添加了特定的字段,以方便使用不同类型的套接字地址。
sockaddr_in、sockaddr_un和sockaddr_in6
是sockaddr
结构体的几个具体实现,用于在网络编程中表示不同类型的套接字地址:
- 其中,
sockaddr_in
结构体用于表示IPv4地址,包括一个16位端口号和一个32位IP地址。该结构体 “继承” 自sockaddr
结构体,并且增加了专门存储端口号和IP地址的字段。 sockaddr_un
结构体用于表示UNIX域套接字的地址,包括UNIX域套接字的路径名。该结构体同样 “继承” 自sockaddr
结构体,并且增加了存储路径名的字段。sockaddr_in6
结构体用于表示IPv6地址,该结构体也同样 “继承” 自sockaddr
结构体,而其增加了专门存储IPv6地址和端口号的字段。- 在使用这些结构体时,可以根据需要将它们强制转换为
sockaddr
结构体使用,以便在函数调用中进行传递。
补充:
- 相同起始成员: sockaddr结构体和这些特定结构体都有名为 “sa_family” 的成员变量,用于指示地址家族。这个成员在不同的特定结构体中具有相同的位置和作用。
- 强制类型转换: 因为这些特定结构体的首部成员与sockaddr结构体的首部成员相同,并且只有首部成员是重要的,所以可以通过将特定结构体的指针强制转换为sockaddr结构体的指针,并传递给需要sockaddr结构体参数的函数。
3.2 配置sockaddr_in:
因为用的是ipv4的网络通信,所以这里需要初始化一个sockaddr_in
类型的结构体:
// 绑定网络信息,指明ip + port
// 先填充基本信息到 struct sockaddr_in
struct sockaddr_in local;
bzero(&local, sizeof(local));// 清空操作
首先是把协议家族设置为IPV4,端口配置为代码所在函数参数中传的端口号:
// 填充协议家族,域
local.sin_family = AF_INET;
// 填充服务器对应的端口号信息,一定是会发给对方的,port_一定会到网络中
local.sin_port = htons(port_);
这个
local.sin_family
就是前16位,确定是本地通信还是网络通信,也可以用PF_INET
是一样的。
然后配置IP:
local.sin_addr.s_addr = ip_.empty() ? htonl(INADDR_ANY) : inet_addr(ip_.c_str());
- 服务器都必须具有IP地址,
"xx.yy.zz.aaa"
,字符串风格点分十进制,4字节IP,uint32_t ip
。 INADDR_ANY(0)
:程序员不关心会bind到哪一个ip, 任意地址bind,强烈推荐的做法,所有服务器一般的做法。
3.3 inet_addr:
inet_addr()
函数可以将一个点分十进制的IPv4地址转换为网络字节序,下的32位二进制整数,即4个字节的IP地址。
in_addr_t inet_addr(const char *cp);
因为对于网络来说并不认识字符串类型的ip,只认识网络字节流规定的ip。
IPV4地址是由四个十进制数组成(每个十进制数的数值是8位二进制数的数值),每个数组表示一个字节,范围从0~255
,用点分十进制表示。
- 例如,
123.123.0.1
是一个IPV4地址。 - 每个区域都是8个比特位一字节的数据。
in_addr_t
转到定义就是uint32_t
。
ip第四个字节,用的是位段来存储的:
// 示例
struct ip
{uint32_t part1:8;uint32_t part2:8;uint32_t part3:8;uint32_t part4:8;
}
3.4 inet_ntoa:
inet_ntoa()
函数将网络请求中的IP地址转换为字符串类型,接受一个struct in_addr
类型的参数,该类型表示一个IPv4地址。
从网络请求中获取到的IP地址转换为struct in_addr
类型,然后再使用inet_ntoa
函数将其转换为字符串类型。
char *inet_ntoa(struct in_addr in);
很多同学不知道struct in_addr
是什么类型,我们不妨在vscode中点开struct sockaddr_in
类型定义来看看:
所以我们在传参时,只需要将struct sockaddr_in
类的对象的成员传过去就好了。
返回值是个char*类型的,那么字符串在哪呢?
inet_ ntoa
这个函数返回了一个char*
,很显然是这个函数自己在内部为我们申请了一块内存来保存ip的结果。- 那么这块内存需要我们手动释放吗?答案是不需要!
- 它会返回一个静态申请的
buffer
。 - man手册上说,
inet_ntoa
函数,是把这个返回结果放到了静态存储区,这个时候不需要我们手动进行释放。
#include <iostream>
#include <netinet/in.h>
#include <arpa/inet.h>int main()
{struct sockaddr_in addr1;struct sockaddr_in addr2;addr1.sin_addr.s_addr = 0;addr2.sin_addr.s_addr = 0xffffffff;char* ptr1 = inet_ntoa(addr1.sin_addr);char* ptr2 = inet_ntoa(addr2.sin_addr);std::cout << "ptr1: " << ptr1 << " " << "ptr2: " << ptr2 << std::endl;return 0;
}
在APUE中, 明确提出inet_ntoa不是线程安全的函数。但是在Centos7上测试, 并没有出现问题, 可能内部的实现加了互斥锁。
3.5 bind绑定:
该接口是指定socket
和sockaddr
进行绑定,第三个参数是addr参数的大小。
// bind 网络信息 -- 将数据填入到操作系统里
if (bind(sockfd_, (const struct sockaddr *)&local, sizeof(local)) == -1)
{logMessage(FATAL, "bind: %s:%d", strerror(errno), sockfd_);exit(2);
}
在之前的初始化struct sockaddr_in
时,我们提到过INADDR_ANY
:
INADDR_ANY(0)
:程序员不关心会bind到哪一个ip, 任意地址bind,强烈推荐的做法,所有服务器一般的做法。
INADDR_ANY
:转到定义上去看,我们发现它就是0。
关于端口号,不要绑定,0到1023以前的端口号,是服务器或者特定服务用的,一绑定可能就出错了:
绑定完之后,我们的服务器就配置成功了!
小结一下:(个人理解)
sockaddr
存储着套接字的信息,bind
将sockaddr
和socket
绑定起来,然后socket
函数去处理套接字。
具体代码如下:
// udp服务器,只需要,1. 创建套接字 2. 填充信息之后做绑定,绑定完成之后就算完成
void init()
{// 1. 创建socket套接字sockfd_ = socket(AF_INET, SOCK_DGRAM, 0); // 就是打开了一个文件if (sockfd_ < 0){logMessage(FATAL, "socket:%s:%d", strerror(errno), sockfd_);exit(1);}// 日志logMessage(DEBUG, "socket create success: %d", sockfd_);// 2. 绑定网络信息,指明ip + port// 2.1 先填充基本信息到 struct sockaddr_instruct sockaddr_in local; // local在哪里开辟的空间? 用户栈,就是临时变量,我们要将其写入内核中bzero(&local, sizeof(local)); // 也可以用memset// 填充协议家族,域local.sin_family = AF_INET; // 这个family就是前16位,确定是本地通信还是网络通信,也可以用PF_INET是一样的// 填充服务器对应的端口号信息,一定是会发给对方的,port_一定会到网络中local.sin_port = htons(port_);// 服务器都必须具有IP地址,"xx.yy.zz.aaa",字符串风格点分十进制 -> 4字节IP -> uint32_t ip// INADDR_ANY(0): 程序员不关心会bind到哪一个ip, 任意地址bind,强烈推荐的做法,所有服务器一般的做法// inet_addr: 指定填充确定的IP,特殊用途,或者测试时使用,除了做转化,还会自动给我们进行 h—>n (主机转网络)local.sin_addr.s_addr = ip_.empty() ? htonl(INADDR_ANY) : inet_addr(ip_.c_str());// 2.2 bind 网络信息 -- 将数据填入到操作系统里if (bind(sockfd_, (const struct sockaddr *)&local, sizeof(local)) == -1){logMessage(FATAL, "bind: %s:%d", strerror(errno), sockfd_);exit(2);}logMessage(DEBUG, "socket bind success: %d", sockfd_);
}
4. 服务端start
4.1 recvfrom:
recvfrom
函数用于接收UDP协议的数据报,它从指定的文件描述符处读取数据,并将数据保存在指定的缓冲区buf中,同时将发送方的地址信息存储在addr参数所指向的结构体中。
返回值:
- 如果接收成功,并且收到了数据,则返回接收到的数据的字节数。
- 如果连接关闭,即对方套接字(socket)关闭连接,则返回0。
- 如果发生错误,返回-1,并且可以使用errno变量获取具体的错误码。
start具体实现:
void start()
{char inbuffer[1024]; // 将来读取到的数据,都放在这里char outbuffer[1024]; // 将来发送的数据,都放在这里// 服务器设计的时候,服务器都是死循环while (true){// 远端struct sockaddr_in peer; // 输出型参数socklen_t len = sizeof(peer); // 输入输出型参数// demo2// UDP是无连接的// 对方给你发了消息,你想不想给对方回消息?// 要的!后面的两个参数是输出型参数,发消息的一方会将属性写到对应的peer和len当中// 不断地从网络当中进行数据读取:ssize_t s = recvfrom(sockfd_, inbuffer, sizeof(inbuffer) - 1, 0,(struct sockaddr *)&peer, &len);// 数据已经读到了吧if (s > 0){inbuffer[s] = 0; // 当做字符串}else if (s == -1){logMessage(WARINING, "recvfrom: %s:%d", strerror(errno), sockfd_);continue;}// 谁发的消息,将对方的信息提取出来:// 读取成功的,除了读取到对方的数据,你还要读取到对方的网络地址[ip:port]std::string peerIp = inet_ntoa(peer.sin_addr); // 拿到了对方的IPuint32_t peerPort = ntohs(peer.sin_port); // 拿到了对方的port// 打印出来客户端给服务器发送过来的消息logMessage(NOTICE, "[%s:%d]# %s", peerIp.c_str(), peerPort, inbuffer);for (int i = 0; i < strlen(inbuffer); i++){if(isalpha(inbuffer[i]) && islower(inbuffer[i]))outbuffer[i] = toupper(inbuffer[i]);elseoutbuffer[i] = toupper(inbuffer[i]);}// 谁给我发消息,立马转回去sendto(sockfd_, outbuffer, strlen(outbuffer), 0, (struct sockaddr*)&peer, len);}
}
4.2 sendto:
sendto
函数用于通过UDP协议发送数据报。它可以将指定的缓冲区中的数据发送到目标地址。
在我们上述start函数中,我们还实现了一个功能就是将收到的信息处理之后,再发回出去。客户端可以再用recvfrom接到消息,再显示出来。
服务类的成员变量和main函数:
// 使用手册
static void Usage(const std::string porc)
{std::cout << "Usage:\n\t" << porc << " port [ip]" << std::endl;
}/// @brief 我们想写一个简单的udpSever
/// 云服务器有一些特殊情况:
/// 1. 禁止你bind云服务器上的任何确定IP, 只能使用INADDR_ANY,如果你是虚拟机,随意
class UdpServer
{
public:UdpServer(int port, std::string ip = "") : port_((uint16_t)port), ip_(ip), sockfd_(-1){}~UdpServer(){}// .........private:// 服务器必须得有端口号信息uint16_t port_;// 服务器必须得有ip地址std::string ip_;// 服务器的socket fd信息int sockfd_;// onlineuserstd::unordered_map<std::string, struct sockaddr_in> users;
};// ./udpServer port [ip]
int main(int argc, char *argv[])
{if (argc != 2 && argc != 3) // 反面:argc == 2 || argc == 3{Usage(argv[0]);exit(3);}uint16_t port = atoi(argv[1]);std::string ip;if (argc == 3){ip = argv[2];}UdpServer svr(port, ip);svr.init();svr.start();return 0;
}
5. 客户端
有了上述知识,客户端的实现就一马平川了。
struct sockaddr_in server;static void Usage(std::string name)
{std::cout << "Usage:\n\t" << name << " server_ip server_port" << std::endl;
}void *recverAndPrint(void *args)
{while (true){int sockfd = *(int *)args;char buffer[1024];struct sockaddr_in temp;socklen_t len = sizeof(temp);ssize_t s = recvfrom(sockfd, buffer, sizeof(buffer), 0, (struct sockaddr *)&temp, &len);if (s > 0){buffer[s] = 0;std::cout << "server echo# " << buffer << std::endl;}}
}// ./udpClient server_ip server_port
// 如果一个客户端要连接server必须知道server对应的ip和port
int main(int argc, char *argv[])
{if (argc != 3){Usage(argv[0]);exit(1);}// 1. 根据命令行,设置要访问的服务器IPstd::string server_ip = argv[1];uint16_t server_port = atoi(argv[2]);// 2. 创建客户端// 2.1 创建socket,服务器是udp的已经跑起来了,客户端也要想办法去连接服务器// 所以客户端也必须得有套接字信息int sockfd = socket(AF_INET, SOCK_DGRAM, 0);assert(sockfd > 0);// 2.2 填写服务器对应的信息bzero(&server, sizeof(server));server.sin_family = AF_INET;server.sin_port = htons(server_port);server.sin_addr.s_addr = inet_addr(server_ip.c_str());// 创建一个线程就可以了// pthread_t t;// pthread_create(&t, nullptr, recverAndPrint, (void *)&sockfd);// 3. 通讯过程std::string buffer;while (true){std::cerr << "Please Enter# ";std::getline(std::cin, buffer);// 发送消息给server:// 客户端首次调用sendto函数的时候,我们的client会自动bind自己的ip和portsendto(sockfd, buffer.c_str(), buffer.size(), 0,\(const struct sockaddr *)&server, sizeof(server)); // 发完消息之后再转发回去char buffer[1024];struct sockaddr_in temp;socklen_t len = sizeof(temp);ssize_t s = recvfrom(sockfd, buffer, sizeof(buffer), 0, (struct sockaddr*)&temp, &len);if (s > 0){buffer[s] = 0;std::cout << "server echo# " << buffer << std::endl; }}close(sockfd);return 0;
}
客户端需不需要bind???是需要bind的,但是不需要用户自己bind,而是OS自动bind的!!!
- 所谓的 “不需要” ,指的是:不需要用户自己bind端口信息!因为OS会自动给你绑定,你也最好这么做!
- 如果我非要自己bind呢???可以!严重不推荐!!!
- 所有的客户端软件,服务器在进行通信的时候,必须得有 client[ip:port] <-> server[ip:port]为什么呢??
-
- 因为client很多,不能给客户端bind指定的port,port可能已经被别的client使用了。
-
- 一旦被别的client使用了你的client就无法启动了,因为一个客户端只能被一个进程绑定。
-
- 只能让操作系统随机生成端口号,用的时候拿去用,不用了就回收掉,下次客户端再来再把端口号给别的客户端。
那么server凭什么要bind呢??
- server提供的服务,必须被所有人知道!server不能随便改变!
- client端口号是多少一点都不重要,只需要保证唯一性就可以,因为没人连它。
在填写好服务端主机的信息之后,客户端直接就可以向服务端发送消息,main函数:
// ./udpServer port [ip]
int main(int argc, char *argv[])
{if (argc != 2 && argc != 3) // 反面:argc == 2 || argc == 3{Usage(argv[0]);exit(3);}uint16_t port = atoi(argv[1]);std::string ip;if (argc == 3){ip = argv[2];}UdpServer svr(port, ip);svr.init();svr.start();return 0;
}
6. 测试
在测试之前,我们先把日志实现一下:
#pragma once#include <cstdio>
#include <ctime>
#include <cstdarg>
#include <cassert>
#include <cstring>
#include <cerrno>
#include <stdlib.h>#define DEBUG 0
#define NOTICE 1
#define WARINING 2
#define FATAL 3const char *log_level[]={"DEBUG", "NOTICE", "WARINING", "FATAL"};// logMessage(DEBUG, "%d", 10);
void logMessage(int level, const char *format, ...)
{assert(level >= DEBUG);assert(level <= FATAL);char *name = getenv("USER");char logInfo[1024];va_list ap; // ap就是一个char*类型va_start(ap, format);vsnprintf(logInfo, sizeof(logInfo) - 1, format, ap);va_end(ap); // ap = NULLFILE *out = (level == FATAL) ? stderr:stdout;fprintf(out, "%s | %u | %s | %s\n", \log_level[level], \(unsigned int)time(nullptr),\name == nullptr ? "unknow":name,\logInfo);
}
6.1 本地回环地址:
127.0.0.1
是IPv4地址中的本地回环地址。它通常被称为“localhost”,可用于测试计算机或网络设备的网络功能,以及运行并测试网络应用程序等。当计算机尝试连接到127.0.0.1
时,它实际上是在尝试与自己本身通信,因此这个地址非常有用。
客户端发的消息,经过网络协议栈,不往网络里发,到了网络协议栈的最底部,再由最底部向上交付。再交付给另一个进程对应的缓冲区里面,让那个进程读到消息。
加上服务端收到信息后,再将字符窜改为大写再转发回去:
我们可以创建一个多人聊天室,将所有人的信息(ip + port)都保存在unordered_map
中,只要有用户连到主机上,就将其添加到哈希表中。
void checkOnlineUser(std::string &ip, uint32_t port, struct sockaddr_in &peer)
{std::string key = ip;key += ":";key += std::to_string(port);auto iter = users.find(key);if(iter == users.end()){users.insert({key, peer});}else{// iter->first, iter->second->// do nothing}
}
并且将收到的信息群发给所有的用户:
void messageRoute(std::string ip, uint32_t port, std::string info)
{std::string message = "[";message += ip;message += ":";message += std::to_string(port);message += "]# ";message += info;// 给每个在线用户都发回去for(auto &user : users){sendto(sockfd_, message.c_str(), message.size(), 0, (struct sockaddr*)&(user.second), sizeof(user.second));}
}
UDP的sendto和recvfrom是阻塞式的,sendto会一直阻塞直到数据成功发送或者发生错误,所以我们要加多线程:
void *recverAndPrint(void *args)
{while (true){int sockfd = *(int *)args;char buffer[1024];struct sockaddr_in temp;socklen_t len = sizeof(temp);ssize_t s = recvfrom(sockfd, buffer, sizeof(buffer), 0, (struct sockaddr *)&temp, &len);if (s > 0){buffer[s] = 0;std::cout << "server echo# " << buffer << std::endl;}}
}
加了多线程之后,客户端有两个线程,主个线程在发消息, 获取用户输入,发消息;新线程在不断地收消息,并且打印到显示器上去。
为了让输出的内容更直观的显示出来,我们将客户端输出的内容写入到命名管道fifo中(注意:命名管道要现将读端打开,所以先要cat < fifo
然后再在服务端发送消息)
客户端的cout 本来是向显示器打印的,结果被重定向到了fifo命名管道中,重定向只是改变了输出流的目标,将输出内容发送到指定的管道文件中,但不会影响管道中已有的内容,所以才会出现上述情况(原有的信息依旧显示的情况)。
备注:
- 在 shell 命令中,将输出重定向到 FIFO 命名管道时,先前存在于管道中的数据不会被删除或清空,而是会被保留。
- 与重定向到普通文件不同,FIFO 命名管道是一种特殊的文件类型,用于进程间通信。
- 当写入进程写入数据时,读取进程可以从管道中读取数据。
当将命令的输出重定向到一个 FIFO 命名管道时,它会将输出写入到管道中,并且不会影响管道中现有的任何数据。
7. Windows客户端
#pragma warning(disable:4996)#include <iostream>
#include <string>
#include <cstdio>
#include <cassert>
#include <WinSock2.h>#pragma comment(lib, "Ws2_32.lib")int server_port = 8080;
std::string server_ip = "xxx.yyy.zz.mm";int main()
{WSADATA data;(void)WSAStartup(MAKEWORD(2, 2), &data);(void)data;SOCKET sockfd = socket(AF_INET, SOCK_DGRAM, 0);assert(sockfd > 0);// 2.2 填写服务器对应的信息struct sockaddr_in server;memset(&server, 0, sizeof server);server.sin_family = AF_INET;server.sin_port = htons(server_port);server.sin_addr.s_addr = inet_addr(server_ip.c_str());// 3. 通讯过程std::string buffer;while (true){std::cerr << "Please Enter# ";std::getline(std::cin, buffer);// 发送消息给server:// 客户端首次调用sendto函数的时候,我们的client会自动bind自己的ip和portsendto(sockfd, buffer.c_str(), buffer.size(), 0, (const struct sockaddr*)&server, sizeof(server));char buffer[1024];struct sockaddr_in temp;int len = sizeof(temp);int s = recvfrom(sockfd, buffer, sizeof(buffer), 0, (struct sockaddr*)&temp, &len);if (s > 0){buffer[s] = 0;std::cout << "server echo# " << buffer << std::endl;}}closesocket(sockfd);WSACleanup();system("pause");return 0;
}
除了开头一些Windows需要的东西外,其他的与Linux下的代码一模一样,这样我们就可以在Windows端来访问部署在Linux下的服务了。
相关文章:

【Linux】UDP的服务端 + 客户端
文章目录 📖 前言1. TCP和UDP2. 网络字节序2.1 大小端字节序:2.2 转换接口: 3. socket接口3.1 sockaddr结构:3.2 配置sockaddr_in:3.3 inet_addr:3.4 inet_ntoa:3.5 bind绑定: 4. 服…...

德国自动驾驶卡车公司【Fernride】完成1900万美元A轮融资
来源:猛兽财经 作者:猛兽财经 猛兽财经获悉,总部位于德国沃尔夫斯堡的自动驾驶卡车公司【Fernride】今日宣布已完成1900万美元A轮融资,本轮融资完成后Fernride的融资金额已经达到了达到5000万美元。 本轮融资由Deep Tech and Cli…...

实现水平垂直居中的十种方式
本文节选自我的博客:实现水平垂直居中的十种方式 💖 作者简介:大家好,我是MilesChen,偏前端的全栈开发者。📝 CSDN主页:爱吃糖的猫🔥📣 我的博客:爱吃糖的猫&…...

头条号热点采集工具-头条号热文采集软件
有一种魔法,能让信息传遍大地,让新闻在互联网上迅速传播,引发关注和讨论,那就是头条热点。无论你是一名自媒体创作者,还是一个信息追踪者,头条热点都是你不能忽视的宝贵资源。然而,如何获取这些…...

了解”变分下界“
“变分下界”:在变分推断中,我们试图找到一个近似概率分布q(x)来逼近真实的概率分布p(x)。变分下界是一种用于评估近似概率分布质量的指标,通常用来求解最优的近似分布。它的计算涉及到对概率分布的积分或期望的估计...

Andriod 简单控件
目录 一、文本显示1.1 设置文本内容1.2 设置文本大小1.3 设置文本颜色 二、视图基础2.1 设置视图宽高2.2 设置视图间距2.3 设置视图对齐方式 三、常用布局3.1 线性布局LinearLayout3.2 相对布局RelativeLayout3.3 网格布局GridLayout3.4 滚动视图ScrollView 四、按钮触控4.1 按…...
Substructure‑aware subgraph reasoning for inductive relation prediction
摘要 关系预测的目的是推断知识图中实体之间缺失的关系,其中归纳关系预测因其适用于新兴实体的有效性而广受欢迎。大多数现有方法学习逻辑组合规则或利用子图来预测缺失关系。尽管在性能方面已经取得了很大的进展,但目前的模型仍然不是最优的,因为它们捕获拓扑信息的能力有…...

古诗词学习鉴赏APP设计与实现(源码+lw+部署文档+讲解等)
文章目录 前言具体实现截图论文参考详细视频演示为什么选择我自己的网站自己的小程序(小蔡coding)有保障的售后福利 代码参考源码获取 前言 💗博主介绍:✌全网粉丝10W,CSDN特邀作者、博客专家、CSDN新星计划导师、全栈领域优质创作…...
深度学习与python theano
文章目录 前言1.人工神经网络2.计算机神经网络3.反向传播4.梯度下降-cost 函数1.一维2.二维3.局部最优4.迁移学习 5. theano-GPU-CPU theano介绍1.安装2.基本用法1.回归2.分类 3.function用法4.shared 变量5.activation function6.Layer层7.regression 回归例子8.classificatio…...

【算法优选】双指针专题——贰
文章目录 😎前言🌲[快乐数](https://leetcode.cn/problems/happy-number/)🚩题目描述🚩题⽬分析:🚩算法思路:🚩代码实现: 🎋[盛水最多的容器](https://leetco…...
AI智能电话机器人实用吗
近几年,人工智能得到很大的发展,同时语音识别技术的不断完善,很多以语音识别为基础的应用涌现出来,尤其是最近3年,出现了很多智能电话机器人。百度开发者大会上展示了百度智能客服也吸引了很多人对智能电话机器人的兴趣…...

网络爬虫--伪装浏览器
从用户请求的Headers反反爬 在访问某些网站的时候,网站通常会用判断访问是否带有头文件来鉴别该访问是否为爬虫,用来作为反爬取的一种策略。很多网站都会对Headers的User-Agent进行检测,还有一部分网站会对Referer进行检测(一些资…...

C/C++程序的内存开辟
前面我们说过,计算机中内存分为三个区域:栈区,堆区,静态区 但是这只是个简化的版本,接下来我们仔细看看内存区域的划分 C/C程序内存分配的几个区域: 栈区(stack):在执行…...

【Java 进阶篇】JDBC DriverManager 详解
JDBC(Java Database Connectivity)是 Java 标准库中用于与数据库进行交互的 API。它允许 Java 应用程序连接到各种不同的数据库管理系统(DBMS),执行 SQL 查询和更新操作,以及处理数据库事务。在 JDBC 中&am…...

2023年Linux总结常用命令
1.常用命令 1.1创建文件夹 mkdir -p forever/my 1.2当前目录 pwd 1.3创建文件 touch 1.txt 1.4查看文件 cat 1.txt 1.5复制文件 说明:-r是复制文件夹 cp -r my myCopy 1.6删除文件 说明:-r带包删除文件夹,-f表示强制删除(保存问题) rm -r…...

Mybatis3详解 之 全局配置文件详解
1、全局配置文件 前面我们看到的Mybatis全局文件并没有全部列举出来,所以这一章我们来详细的介绍一遍,Mybatis的全局配置文件并不是很复杂,它的所有元素和代码如下所示: <?xml version"1.0" encoding"UTF-8&…...

力扣-345.反转字符串中的元音字母
Idea 将s中的元音字母存在字符串sv中,并且使用一个数组依次存储元音字母的下标。 然后将字符串sv进行反转,并遍历元音下标数组,将反转后的字符串sv依次插入到源字符串s中 AC Code class Solution { public:string reverseVowels(string s) {…...

643. 子数组最大平均数I(滑动窗口)
目录 一、题目 二、代码 一、题目 643. 子数组最大平均数 I - 力扣(LeetCode) 二、代码 class Solution { public:double findMaxAverage(vector<int>& nums, int k) {double Average INT_MIN;double sum nums[0];int left 0, right 0…...

Java 21 新特性:虚拟线程(Virtual Threads)
I often take exercise. Why only yesterday I had breakfast in bed. 在Java 21中,引入了虚拟线程(Virtual Threads)来简化和增强并发性,这使得在Java中编程并发程序更容易、更高效。 虚拟线程,也称为“用户模式线程…...

18scala笔记
Scala2.12 视频地址 1 入门 1.1 发展历史 … 1.2 Scala 和 Java Scala Java 编写代码使用scalac编译成.class字节码文件scala .class文件 执行代码 1.3 特点 1.4 安装 视频地址 注意配置好环境变量 简单代码 1.5 编译文件 编译scala文件会产生两个.class文件 使用java…...

idea大量爆红问题解决
问题描述 在学习和工作中,idea是程序员不可缺少的一个工具,但是突然在有些时候就会出现大量爆红的问题,发现无法跳转,无论是关机重启或者是替换root都无法解决 就是如上所展示的问题,但是程序依然可以启动。 问题解决…...
【根据当天日期输出明天的日期(需对闰年做判定)。】2022-5-15
缘由根据当天日期输出明天的日期(需对闰年做判定)。日期类型结构体如下: struct data{ int year; int month; int day;};-编程语言-CSDN问答 struct mdata{ int year; int month; int day; }mdata; int 天数(int year, int month) {switch (month){case 1: case 3:…...

(十)学生端搭建
本次旨在将之前的已完成的部分功能进行拼装到学生端,同时完善学生端的构建。本次工作主要包括: 1.学生端整体界面布局 2.模拟考场与部分个人画像流程的串联 3.整体学生端逻辑 一、学生端 在主界面可以选择自己的用户角色 选择学生则进入学生登录界面…...

[ICLR 2022]How Much Can CLIP Benefit Vision-and-Language Tasks?
论文网址:pdf 英文是纯手打的!论文原文的summarizing and paraphrasing。可能会出现难以避免的拼写错误和语法错误,若有发现欢迎评论指正!文章偏向于笔记,谨慎食用 目录 1. 心得 2. 论文逐段精读 2.1. Abstract 2…...
Qwen3-Embedding-0.6B深度解析:多语言语义检索的轻量级利器
第一章 引言:语义表示的新时代挑战与Qwen3的破局之路 1.1 文本嵌入的核心价值与技术演进 在人工智能领域,文本嵌入技术如同连接自然语言与机器理解的“神经突触”——它将人类语言转化为计算机可计算的语义向量,支撑着搜索引擎、推荐系统、…...
安卓基础(aar)
重新设置java21的环境,临时设置 $env:JAVA_HOME "D:\Android Studio\jbr" 查看当前环境变量 JAVA_HOME 的值 echo $env:JAVA_HOME 构建ARR文件 ./gradlew :private-lib:assembleRelease 目录是这样的: MyApp/ ├── app/ …...

Python基于历史模拟方法实现投资组合风险管理的VaR与ES模型项目实战
说明:这是一个机器学习实战项目(附带数据代码文档),如需数据代码文档可以直接到文章最后关注获取。 1.项目背景 在金融市场日益复杂和波动加剧的背景下,风险管理成为金融机构和个人投资者关注的核心议题之一。VaR&…...

9-Oracle 23 ai Vector Search 特性 知识准备
很多小伙伴是不是参加了 免费认证课程(限时至2025/5/15) Oracle AI Vector Search 1Z0-184-25考试,都顺利拿到certified了没。 各行各业的AI 大模型的到来,传统的数据库中的SQL还能不能打,结构化和非结构的话数据如何和…...

【Linux】Linux安装并配置RabbitMQ
目录 1. 安装 Erlang 2. 安装 RabbitMQ 2.1.添加 RabbitMQ 仓库 2.2.安装 RabbitMQ 3.配置 3.1.启动和管理服务 4. 访问管理界面 5.安装问题 6.修改密码 7.修改端口 7.1.找到文件 7.2.修改文件 1. 安装 Erlang 由于 RabbitMQ 是用 Erlang 编写的,需要先安…...
绕过 Xcode?使用 Appuploader和主流工具实现 iOS 上架自动化
iOS 应用的发布流程一直是开发链路中最“苹果味”的环节:强依赖 Xcode、必须使用 macOS、各种证书和描述文件配置……对很多跨平台开发者来说,这一套流程并不友好。 特别是当你的项目主要在 Windows 或 Linux 下开发(例如 Flutter、React Na…...