【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 用户数据报协议
  • 传输层协议:
    • 在网络通信中负责提供端到端的数据传输服务的协议。
  • 无连接:
    • 无连接指的是数据传输时不需要先建立连接再进行通信的方式,比如所有人都能给你的电子邮箱发送邮件。
  • 不可靠传输:
    • 在不可靠传输中,数据传输过程中不进行可靠性保证的方式,发送方将数据发送给接收方,但不对数据的正确性和完整性进行确认和修复。
  • 面向数据报:
    • 数据在传输过程中被划分为独立的数据报进行传输,每个数据报(也称为包、帧等)都包含了完整的源地址、目标地址和其他必要的信息,使得每个数据报都能够独立地进行路由和处理。

可靠与不可靠传输,更多的标明的是一种通信特征。不能说tcpudp哪个更好,只能说哪个更合适。


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_in6sockaddr结构体的几个具体实现,用于在网络编程中表示不同类型的套接字地址:

  • 其中,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绑定:

在这里插入图片描述
该接口是指定socketsockaddr进行绑定,第三个参数是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存储着套接字的信息,bindsockaddrsocket绑定起来,然后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下的服务了。

在这里插入图片描述

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

如若内容造成侵权/违法违规/事实不符,请联系一条长河网进行投诉反馈,一经查实,立即删除!

相关文章

【面试经典150 | 矩阵】旋转图像

文章目录 写在前面Tag题目来源题目解读解题思路方法一&#xff1a;原地旋转方法二&#xff1a;翻转代替旋转 写在最后 写在前面 本专栏专注于分析与讲解【面试经典150】算法&#xff0c;两到三天更新一篇文章&#xff0c;欢迎催更…… 专栏内容以分析题目为主&#xff0c;并附带…

线性表的链式存储结构——链表

一、顺序表优缺点 优点&#xff1a;我们知道顺序表结构简单&#xff0c;便于随机访问表中任一元素&#xff1b; 缺点&#xff1a;顺序存储结构不利于插入和删除&#xff0c;不利于扩充&#xff0c;也容易造成空间浪费。 二、链表的定义 ①&#xff1a;概念&#xff1a; 用一组任…

Springboot+vue的在线试题题库管理系统(有报告),Javaee项目,springboot vue前后端分离项目。

演示视频&#xff1a; Springbootvue的在线试题题库管理系统&#xff08;有报告&#xff09;&#xff0c;Javaee项目&#xff0c;springboot vue前后端分离项目。 项目介绍&#xff1a; 本文设计了一个基于Springbootvue的前后端分离的在线试题题库管理系统&#xff0c;采用M&…

PHP 数码公司运营管理系统mysql数据库web结构apache计算机软件工程网页wamp

一、源码特点 PHP 数码公司运营管理系统系统是一套完善的web设计系统&#xff0c;对理解php编程开发语言有帮助&#xff0c;系统具有完整的源代码和数据库&#xff0c;系统主要采用B/S模式开发。 php 数码公司运营管理系统 代码 https://download.csdn.net/download/qq_41…

基于 jasypt 实现spring boot 配置文件脱敏

前言 在项目构建过程中&#xff0c;保护敏感信息的安全性至关重要&#xff0c;为了提高系统的安全性能&#xff0c;我们采用了Jasypt来对配置文件中的敏感信息进行加密处理&#xff0c;以确保系统的机密信息不被轻易泄露。 步骤 添加Maven依赖 首先&#xff0c;我们需要添加…

【kubernetes】kubernetes中的StatefulSet使用

TOC 1 为什么需要StatefulSet 常规的应用通常使用Deployment&#xff0c;如果需要在所有机器上部署则使用DaemonSet&#xff0c;但是有这样一类应用&#xff0c;它们在运行时需要存储一些数据&#xff0c;并且当Pod在其它节点上重建时也希望这些数据能够在重建后的Pod上获取&…

buuctf-[Zer0pts2020]Can you guess it?

点击source&#xff0c;进入源代码 <?php include config.php; // FLAG is defined in config.phpif (preg_match(/config\.php\/*$/i, $_SERVER[PHP_SELF])) {exit("I dont know what you are thinking, but I wont let you read it :)"); }if (isset($_GET[so…

【算法学习】-【双指针】-【复写零】

LeetCode原题链接&#xff1a;1089. 复写零 下面是题目描述&#xff1a; 给你一个长度固定的整数数组 arr &#xff0c;请你将该数组中出现的每个零都复写一遍&#xff0c;并将其余的元素向右平移。 注意&#xff1a;请不要在超过该数组长度的位置写入元素。请对输入的数组 …

【浅记】分而治之

归并排序 算法流程&#xff1a; 将数组A[1,n]排序问题分解为A[1,n/2]和A[n/21,n]排序问题递归解决子问题得到两个有序的子数组将两个子数组合并为一个有序数组 符合分而治之的思想&#xff1a; 分解原问题解决子问题合并问题解 递归式求解 递归树法 用树的形式表示抽象递…

7.网络原理之TCP_IP(下)

文章目录 4.传输层重点协议4.1TCP协议4.1.1TCP协议段格式4.1.2TCP原理4.1.2.1确认应答机制 ACK&#xff08;安全机制&#xff09;4.1.2.2超时重传机制&#xff08;安全机制&#xff09;4.1.2.3连接管理机制&#xff08;安全机制&#xff09;4.1.2.4滑动窗口&#xff08;效率机制…

uni-app:实现元素在屏幕中的居中(绝对定位absolute)

一、实现水平居中 效果 代码 <template><view><view class"center">我需要居中</view></view> </template><style>.center {position: absolute;left:50%;transform: translateX(-50%);border:1px solid black;} </s…

freertos简介与移植

freertos是一个可裁剪的小型rtos系统&#xff0c;特点&#xff1a; 支持抢占式&#xff0c;合作式和时间片调度saferos衍生自freertos&#xff0c;更完整提供了一个用于低功耗的tickless模式系统的组件在创建时可以选择动态或者静态的ram&#xff0c;例如任务&#xff0c;消息…

快排三种递归及其优化,非递归和三路划分

个人主页&#xff1a;Lei宝啊 愿所有美好如期而遇 目录 快排简介&#xff1a; 快排的三种递归实现&#xff1a; Hoare&#xff1a; 挖坑&#xff1a; 双指针&#xff1a; 小区间优化&#xff1a; 三数取中优化&#xff1a; 快排非递归实现&#xff1a; 快排的三路划…

计算机网络(一):概述

参考引用 计算机网络微课堂-湖科大教书匠计算机网络&#xff08;第7版&#xff09;-谢希仁 1. 计算机网络在信息时代的作用 计算机网络已由一种通信基础设施发展成为一种重要的信息服务基础设施计算机网络已经像水、电、煤气这些基础设施一样&#xff0c;成为我们生活中不可或…

《CTFshow-Web入门》10. Web 91~110

Web 入门 索引web91题解总结 web92题解总结 web93题解 web94题解 web95题解 web96题解 web97题解 web98题解 web99题解总结 web100题解 web101题解 web102题解 web103题解 web104题解 web105题解总结 web106题解 web107题解 web108题解 web109题解 web110题解 ctf - web入门 索…

红外遥控器 数据格式,按下及松开判断

红外遥控是一种无线、非接触控制技术&#xff0c;具有抗干扰能力强&#xff0c;信息传输可靠&#xff0c;功耗低&#xff0c;成本低&#xff0c;易实现等显著优点&#xff0c;被诸多电子设备特别是家用电器广泛采用&#xff0c;并越来越多的应用到计算机系统中。 同类产品的红…

iOS 视频压缩 mov转mp4 码率

最近还是因为IM模块的功能&#xff0c;IOS录制MOV视频发送后&#xff0c;安卓端无法播放&#xff0c;迫不得已兼容将MOV视频转为MP4发送。 其中mov视频包括4K/24FPS、4K/30FPS、4K/60FPS、720p HD/30FPS、1080p HD/30FPS、1080p HD/60FPS&#xff01; 使用AVAssetExportSessi…

使用sqlmap获取数据步骤

文章目录 1.使用sqlmap获取所有数据库2.使用sqlmap获取当前连接数据库3.使用sqlmap获取当前数据库下所有表名4.使用sqlmap获取当前数据库下某个表下所有列名5.使用sqlmap获取当前数据库下某个表下指定字段的数据6.测试当前用户是否是管理员7.使用burpsqlmap批量检测8.脱库命令9…

zkLogin构建者的最佳实践和业务思考

随着zkLogin在Sui主网上线&#xff0c;构建者可以开始为其应用程序提供丝滑的帐户创建服务。与任何新技术集成一样&#xff0c;构建者需要考虑许多重要的问题&#xff0c;以降低风险并成功优化。 本文概述了其中一些考虑因素&#xff0c;并突出了zkLogin文档中提供的明确指导。…

二、机器学习基础知识:Python数据处理基础

文章目录 1、基本数据类型1.1 数字类型&#xff08;Number&#xff09;1.2 字符串类型&#xff08;String&#xff09;1.3 列表类型&#xff08;List&#xff09;1.4 元组类型&#xff08;Tuple&#xff09;1.5 字典类型&#xff08;Dictionary&#xff09;1.6 集合类型&#x…