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

(udp)网络编程套接字Linux(整理)

 源IP地址和目的IP地址

唐僧例子1

  • 在IP数据包头部中, 有两个IP地址, 分别叫做源IP地址, 和目的IP地址.
  • 思考: 我们光有IP地址就可以完成通信了嘛? 想象一下发qq消息的例子, 有了IP地址能够把消息发送到对方的机器上,但是还需要有一个其他的标识来区分出, 这个数据要给哪个程序进行解析.

认识端口号

5aa466f7741c43a6a19bacf33aabd297.png

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

我们之前在学习系统编程的时候, 学习了 pid 表示唯一一个进程; 此处我们的端口号也是唯一表示一个进程. 那么这 两者之间是怎样的关系?

10086例子

另外, 一个进程可以绑定多个端口号; 但是一个端口号不能被多个进程绑定;fe650b7d258a451db453b5bfa3ed4483.png216566e140c14ef1a5b81f7c0b77b650.png

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

唐僧例子2

送快递例子

传输层协议(TCP和UDP)的数据段中有两个端口号, 分别叫做源端口号和目的端口号. 就是在描述 "数据是谁发的, 要 发给谁";

IP:Port()

ip地址4字节  端口号两个字节

跨主机 

7f46fb8563ff448bbee7b5773dd222f6.png

看到的公共部分       是网络

e63b13bff86441209aa88a58a35e44a9.png

在公网上:
IP地址能表示唯一的一台主机,端口号port,用来标识该主机上的唯一的一个进程
IP:Port=标识全网唯一的一个进程

9ed020d12d3c41fea1c69616d95c77b8.pngfe650b7d258a451db453b5bfa3ed4483.png216566e140c14ef1a5b81f7c0b77b650.png

认识TCP协议

68e77c3741a24c249575602bfb9c54e6.png

认识UDP协议

cb6e6a1c88d24b698fd2049578f21457.png

网络字节序

ip地址4字节  端口号两个字节

我们已经知道,内存中的多字节数据相对于内存地址有大端和小端之分, 磁盘文件中的多字节数据相对于文件中的偏移地址也有大端小端之分, 网络数据流同样有大端小端之分. 那么如何定义网络数据流的地址呢?

  • 发送主机通常将发送缓冲区中的数据按内存地址从低到高的顺序发出;
  • 接收主机把从网络上接到的字节依次保存在接收缓冲区中,也是按内存地址从低到高的顺序保存;
  • 因此,网络数据流的地址应这样规定:先发出的数据是低地址,后发出的数据是高地址. TCP/IP协议规定,网络数据流应采用大端字节序,即低地址高字节.
  • 不管这台主机是大端机还是小端机, 都会按照这个TCP/IP规定的网络字节序来发送/接收数据;
  • 如果当前发送主机是小端, 就需要先将数据转成大端; 否则就忽略, 直接发送即可;

a8346459c5d84253b9f3e6010f9f7c41.png

为使网络程序具有可移植性,使同样的C代码在大端和小端计算机上编译后都能正常运行,可以调用以下库函数做网络字节序和主机字节序的转换。

2c926d5138e3446ba4c12cb6e023c15c.png

480c8a69229b485982c3aef641c8087d.png

网络字节序和主机字节序的转换

主机转网络

服务端

客户端 

 网络转主机(后面ntoa就不建议使用了)

在 C 或 C++ 中,网络字节序和主机字节序的转换非常重要,特别是在进行网络编程时,因为不同的平台可能有不同的字节序(Endianness)。网络字节序通常是大端字节序(Big Endian),而主机字节序可能是大端或小端(Little Endian)。

网络字节序和主机字节序的转换函数

  1. **htons()**:将主机字节序(Host)转换为网络字节序(Network),适用于 16 位数据(short)。
  2. **htonl()**:将主机字节序(Host)转换为网络字节序(Network),适用于 32 位数据(long)。
  3. **ntohs()**:将网络字节序(Network)转换为主机字节序(Host),适用于 16 位数据(short)。
  4. **ntohl()**:将网络字节序(Network)转换为主机字节序(Host),适用于 32 位数据(long)。

示例代码

#include <stdio.h>
#include <arpa/inet.h>  // 包含字节序转换函数(适用于 Linux 和 UNIX)int main() {uint16_t host_short = 0x1234;  // 主机字节序的 16 位数据uint32_t host_long = 0x12345678;  // 主机字节序的 32 位数据// 主机字节序转网络字节序uint16_t network_short = htons(host_short);uint32_t network_long = htonl(host_long);// 网络字节序转主机字节序uint16_t converted_short = ntohs(network_short);uint32_t converted_long = ntohl(network_long);// 输出结果printf("Host short: 0x%04X, Network short: 0x%04X, Converted back: 0x%04X\n",host_short, network_short, converted_short);printf("Host long: 0x%08X, Network long: 0x%08X, Converted back: 0x%08X\n",host_long, network_long, converted_long);return 0;
}

解释

  • **htons()**:将主机字节序的 16 位数据(host_short)转换为网络字节序。
  • **htonl()**:将主机字节序的 32 位数据(host_long)转换为网络字节序。
  • **ntohs()**:将网络字节序的 16 位数据(network_short)转换为主机字节序。
  • **ntohl()**:将网络字节序的 32 位数据(network_long)转换为主机字节序。

输出示例

Host short: 0x1234, Network short: 0x3412, Converted back: 0x1234
Host long: 0x12345678, Network long: 0x78563412, Converted back: 0x12345678

适用平台

这些字节序转换函数通常用于 Linux 和 UNIX 系统中,通过包含 arpa/inet.h 头文件来访问。对于 Windows 系统,提供了类似的转换函数,但位于 winsock2.h 中,使用方法相同。

总结

  • htons() 和 **htonl()**:用于将主机字节序转换为网络字节序(适用于 16 位和 32 位数据)。
  • ntohs() 和 **ntohl()**:用于将网络字节序转换为主机字节序。

这些转换函数在进行网络编程时非常重要,可以确保不同平台和字节序的主机之间进行正确的数据传输和处理。

网络转主机序列,主机序列转网络序列

发送到网络里的要转网络序列,从网络里拿的要转主机序列

地址转换函数(字符串传4字节IP)

本节只介绍基于IPv4的socket网络编程,sockaddr_in中的成员struct in_addr sin_addr表示32位 的IP 地址

但是我们通常用点分十进制的字符串表示IP 地址,以下函数可以在字符串表示 和in_addr表示之间转换;

字符串转in_addr的函数:

in_addr转字符串的函数:

其中inet_pton和inet_ntop不仅可以转换IPv4的in_addr,还可以转换IPv6的in6_addr,因此函数接口是void *addrptr。 代码示例:

关于inet_ntoa

inet_ntoa这个函数返回了一个char*, 很显然是这个函数自己在内部为我们申请了一块内存来保存ip的结果. 那么是 否需要调用者手动释放呢

man手册上说, inet_ntoa函数, 是把这个返回结果放到了静态存储区. 这个时候不需要我们手动进行释放.

那么问题来了, 如果我们调用多次这个函数, 会有什么样的效果呢? 参见如下代码:

运行结果如下:

因为inet_ntoa把结果放到自己内部的一个静态存储区, 这样第二次调用时的结果会覆盖掉上一次的结果.

  • 思考: 如果有多个线程调用 inet_ntoa, 是否会出现异常情况呢?
  • 在APUE中, 明确提出inet_ntoa不是线程安全的函数;
  • 但是在centos7上测试, 并没有出现问题, 可能内部的实现加了互斥锁;
  • 在多线程环境下, 推荐使用inet_ntop, 这个函数由调用者提供一个缓冲区保存结果, 可以规避线程安全问题;

套接字编程的种类:

1.域间套接字编程2.原始套接字编程3.网络套接字编程

d64c03e49b54459a82f8462e3fa411a3.png

udp代码示例

udp服务器

ca4091b5be82448daecdf81c49d6f1dd.png

socket(创建一个套接字)

socket创建套接字

第一个参数是一个域

socket 常见API

// 创建 socket 文件描述符 (TCP/UDP, 客户端 + 服务器)
int socket(int domain, int type, int protocol);// 绑定端口号 (TCP/UDP, 服务器) 
int bind(int socket, const struct sockaddr *address,socklen_t address_len);// 开始监听socket (TCP, 服务器)
int listen(int socket, int backlog);// 接收请求 (TCP, 服务器)
int accept(int socket, struct sockaddr* address,socklen_t* address_len);// 建立连接 (TCP, 客户端)
int connect(int sockfd, const struct sockaddr *addr,socklen_t addrlen);

第一个参数:创建一个套接字的域,什么叫做域呢

可以理解为我们所要的那个套接字他是属于什么AF_INET,将来是使用IPv4还是IPv6网络通信的,还是有叫本地通信也叫作域间通信,第二个参数是socket对应的类型,udp是面向用户数据报(SOCK_DGRAM)tcp是面向字节流的第三个参数不用填,协议类型,创建一个套接字的本质就是打开一个文件,必须要告诉服务器,端口号,然后再绑定套接字端口号

8d653c031bf046d5aa810558107d2b7f.png

不同类型(第一个参数的) 

8716260b5598498d938bbcbd53f4783a.png

第二个:定义的套接字的类型

30f91d16c7da430a8bff696543150763.png

第三个写0就可以

71ccbde87cf1426295bbaf65c70b00e8.png

#pragma once
#include<iostream>
#include <sys/types.h>          
#include <sys/socket.h>
using namespace std;
class UdpSever
{
public:UdpSever(){}void Init(){sockfd = socket(AF_INET, SOCK_DGRAM, 0);}void Run(){}~UdpSever(){}
private:int sockfd;//网络文件描述符
};

把日志功能拷贝进来

99144e4d1376478fa96baba7ecefd7b8.png

创建一个Log对象

8b5540f7b349445d89640824c509a900.png

740be8267ae442d49fd83f34c6ec5278.png

 std::unique_ptr3d9a64f6500d4915b22a6f79297d3eb5.png

c04034a05b5349a4b2afd45c2866be76.png

C++11中开始提供更靠谱的unique_ptr

文档:https://cplusplus.com/reference/memory/unique_ptr/

unique_ptr的实现原理:简单粗暴的防拷贝,下面简化模拟实现了一份UniquePtr来了解它的原 理

/ C++11库才更新智能指针实现

// C++11出来之前,boost搞除了更好用的scoped_ptr/shared_ptr/weak_ptr // C++11将boost库中智能指针精华部分吸收了过来

// C++11->unique_ptr/shared_ptr/weak_ptr // unique_ptr/scoped_ptr // 原理:简单粗暴 -- 防拷贝

测试:

3b54eddb23834907af8c46e9fd808acc.png

ada8892b420b4ebc89d44d8d5485ce29.png

绑定bindc4c721283d374ddf95eddc091e2fabe0.png

ba21e2d038d646c2ad598624e655f350.png

绑定端口号

sockaddr结构

3355b591bfbc4a36ae4e65a4883999d1.png9f566ed4b506466d93ba183f949e7e0a.png

  • IPv4和IPv6的地址格式定义在netinet/in.h中,IPv4地址用sockaddr_in结构体表示,包括16位地址类型, 16 位端口号和32位IP地址.
  • IPv4、IPv6地址类型分别定义为常数AF_INET、AF_INET6. 这样,只要取得某种sockaddr结构体的首地址, 不需要知道具体是哪种类型的sockaddr结构体,就可以根据地址类型字段确定结构体中的内容.
  • socket API可以都用struct sockaddr *类型表示, 在使用的时候需要强制转化成sockaddr_in; 这样的好 处是程序的通用性, 可以接收IPv4, IPv6, 以及UNIX Domain Socket各种类型的sockaddr结构体指针做为参数;

sockaddr 结构 de0c98c98fdf4063a5893be71629af69.png

sockaddr_in 结构 

e610f6c82eff42288335ea95c0ed089f.png 虽然socket api的接口是sockaddr, 但是我们真正在基于IPv4编程时, 使用的数据结构是sockaddr_in; 这个结构里主 要有三部分信息: 地址类型, 端口号, IP地址.

in_addr结构 

012fc56492ba47a8845ceffec8cb05ff.png in_addr用来表示一个IPv4的IP地址. 其实就是一个32位的整数;

bzero把上面的结构体清空

5c80c4518d9f4ade955e831260500fbf.png

f051463c366842ca9deedd4019d2984b.png

INET

6b5b0f5524774deb843fc63d04ab5c15.png

addr,ip地址(32位的),port端口号,family所用的域或者家族(AF_INET)

c0525808bd5142f8855f07c36eb7734a.png

int,uint16_t,uint32_t的区别及其各自的用法

intuint16_t 和 uint32_t 都是表示整数类型的数据类型,它们在表示范围和用途上有所不同。下面是它们的区别和常见的用法:

1. int

  • 定义int 是C/C++中的标准整数类型,通常用于表示整数。它的大小和范围依赖于平台(操作系统和硬件)。

  • 大小和范围

    • 在大多数现代平台上,int 通常是 32 位的(4 字节),但是也有一些平台会使用 16 位(2 字节)或 64 位(8 字节)来表示。

    • 在 32 位系统上,int 的范围通常是:-2,147,483,648 到 2,147,483,647

    • 在 64 位系统上,int 的范围一般相同,但可能会有所不同,具体取决于编译器和操作系统。

  • 用途:适用于一般整数的存储,特别是当你不需要指定特定位数的整数时。通常是默认的整数类型。

2. uint16_t

  • 定义uint16_t 是一个固定宽度的无符号整数类型,表示一个 16 位(2 字节)的无符号整数。它是通过 C99 标准中的 <stdint.h> 头文件定义的。

  • 大小和范围

    • 它的大小是固定的 16 位(2 字节)。

    • 范围是 0 到 65535(即 2^16 - 1)。

  • 用途:用于需要 16 位无符号整数的场景,适用于存储比较小的正整数值,节省内存空间,常用于协议数据、传感器数据、颜色值等场景。

3. uint32_t

  • 定义uint32_t 是一个固定宽度的无符号整数类型,表示一个 32 位(4 字节)的无符号整数。它同样是通过 <stdint.h> 头文件定义的。

  • 大小和范围

    • 它的大小是固定的 32 位(4 字节)。

    • 范围是 0 到 4,294,967,295(即 2^32 - 1)。

  • 用途:用于需要 32 位无符号整数的场景,常用于表示较大范围的正整数,如文件大小、内存地址、网络协议等。

总结对比:

数据类型

大小

范围

用途

int

32 位(常见)

-2,147,483,648 到 2,147,483,647

通用整数类型,平台相关

uint16_t

16 位

0 到 65535

用于小范围无符号整数,如协议、传感器数据

uint32_t

32 位

0 到 4,294,967,295

用于大范围无符号整数,如文件大小、网络地址

选择依据:

  • 如果你知道需要存储的数值范围,并且希望节省内存,可以选择 uint16_t 或 uint32_t

  • 如果不关心存储大小且需要处理负数,int 是默认选择。

  • 需要无符号整数时,uint16_t 和 uint32_t 是更为合适的选择。

在网络中为什么用uint16_t,不用int

在网络通信中,通常使用 uint16_t 而不是 int 主要有以下几个原因:

1. 一致性和标准化

  • uint16_t 是一个 无符号 16 位整数,大小和范围是固定的,明确且可移植。无论在哪个平台或操作系统上,uint16_t 都始终表示一个 16 位的正整数,范围为 0 到 65535,没有任何平台差异。

  • 相反,int 的大小和范围依赖于编译器、操作系统以及硬件架构(通常是 32 位或 64 位),这使得其在不同平台间可能有不同的表现。例如,32 位平台上的 int 范围是 -2,147,483,648 到 2,147,483,647,而 64 位平台可能会有所不同。

在网络协议中,数据交换需要确保不同系统间能正确地理解数据。如果每个平台的 int 范围不同,可能会导致解析数据时出错。因此,使用 uint16_t 等固定宽度的数据类型可以避免这种问题。

2. 明确的数值表示

  • 网络通信中传输的很多数据(例如端口号、长度、标识符等)都是 非负整数,并且其数值范围通常是有限的。uint16_t 正好满足这些需求,它能够表示 0 到 65535 范围的正整数,这对于大多数网络协议中的字段来说已经足够。

  • 如果使用 int,会引入不必要的负数范围。例如,某些协议字段如长度或计数信息是不需要负值的,因此使用 uint16_t 可以更好地表达数据的本意。

3. 节省带宽和存储空间

  • 网络传输中的数据结构通常是非常紧凑的,节省带宽和存储空间是设计网络协议时的重要考虑因素。uint16_t 是一个固定的 16 位类型,比 int(通常是 32 位)节省了一半的空间。

  • 如果某个字段本来只需要表示 0 到 65535 的值,使用 int 会浪费空间(特别是在带宽受限的网络环境中,数据包大小需要尽量精简),而 uint16_t 刚好满足需求。

4. 与协议格式一致

  • 网络协议(如 IP、TCP、UDP 等)中通常定义了固定宽度的字段,例如端口号、数据长度等。协议规范中往往会指定使用 16 位(uint16_t)或 32 位(uint32_t)等类型来表示数据。

    • 例如,TCP/IP 协议中,端口号使用的是 16 位的无符号整数(uint16_t),而 数据包长度等字段也通常是无符号整数。

  • 使用 uint16_t 可以确保与协议规范一致,使得协议解析更加标准化,避免不同平台或编译器之间的数据解释差异。

总结:

  • uint16_t 在网络协议中比 int 更加适用,因为它的大小和范围是固定的,明确表示非负整数,且可以确保不同平台之间的一致性。

  • int 的范围和大小是依赖于平台的,可能导致不必要的负数值,并且会占用更多的内存空间,因此不适合在需要精确控制数据结构大小的网络通信中使用。

因此,使用 uint16_t 可以提高协议的跨平台兼容性,保证数据的准确传输,并且更高效地使用带宽和存储。

##的作用

68b6861ef85849c191d84188843e7b5c.png

主机序列转成网络序列

端口号只有两个字节

2e1123cc704443a89ac8020a108d16dd.png

如果机器本来是大端机那么就什么都不做,如果是小端机,会转换成大端

c0013a9e5f304bd0a67d911c6e4700b2.png

main中这样传ip

031c972e5a004cceba22a99899d0867d.png

如何快速的将整数IP<->字符串IP

自己写的话如果实现

8bce586b9dc94543a33d6f6f738bf6ac.png

uint8_t一个字节

0866c11933ab45bd920e7f66f37ebca7.png

这样就把4字节的ip直接转换成了字符串风格的ip

30c12eb315a04f03be782e49ac883f8f.png

uint32_t是4个字节

6a7aef0bf67942d4b7348a1c709ac482.png

b4c24c62eb58476fa417510dbef10f76.png

把字符串风格的ip地址转化为四字节(网络风格的四字节)

d85398646f664206b6a2ff827f3687e8.png

c48fdcff3a2742518d1cf5fa61676d16.png

61cfae9004764b0cb3dc622db1682eff.png

f0bb917ae08545628eca39d895680b63.png

local在用户栈上,这样赋值只是把变量给赋值了,并没有把local变量和内核的网络套接字相关联,还没有进行绑定

7c694700f12042a4a0ccd7e494bd661d.png

再进行bind绑定,绑定的本质,把这个参数设置进内核指定的套接字内部

35591e052180455dae3a6b5d96c5da19.png

要强转

fb3ad359bdab4c69a0afc0efbbc7d94e.png

ac04fbe9a19146bda9ecee5d9a7d6168.png

以上udp服务器核心的启动代码基本已经完成

思路精华

111911d2542b40fd9bfd284fbc7258d6.png

ed4bef38062240b2b8a11e6330b5263e.png

d67907c3a0984066af87965d37ad6549.png

测试:

#pragma once
#include<iostream>
#include <sys/types.h>          
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <cstring>
#include <stdio.h>
#include "log.hpp"
using namespace std;extern Log1 log1;//日志器声明enum{SOCKET_ERR = 1,BIND_ERR
};uint16_t defaultport = 8080;
string defaultip = "0.0.0.0";
const int Size = 1024;
class UdpSever
{
public:UdpSever(const uint16_t& port = defaultport, const string& ip = defaultip):sockfd_(0), port_(port), ip_(defaultip), isrunning_(false){}void Init(){//创建udp socketsockfd_ = socket(AF_INET, SOCK_DGRAM, 0);if(sockfd_ < 0){log1(Fatal, "socket create error, sockfd:%d", sockfd_);exit(SOCKET_ERR);}log1(Info, "socket create success, sockfd:%d", sockfd_);//2.bind socket(绑定端口号)struct sockaddr_in local;bzero(&local, sizeof(local));local.sin_family = AF_INET;local.sin_port = htons(port_);//需要保证我的端口号是网络字节序列,因为该端口号是要给对方发送的local.sin_addr.s_addr = inet_addr(ip_.c_str());//1.string -> uint32_t  uint32_t必须是网络序列if(bind(sockfd_, (const struct sockaddr*)&local, sizeof(local))< 0){log1(Fatal, "bind error, error:%d, err string:%s", errno, strerror(errno));   exit(BIND_ERR);}log1(Info, "bind success, error:%d, err string:%s", errno, strerror(errno));   }void Run(){isrunning_ = true;//const int size = 1024;char inbuffer[Size];while(isrunning_){struct sockaddr_in client;socklen_t len = sizeof(client);ssize_t n = recvfrom(sockfd_, inbuffer, sizeof(inbuffer) - 1, 0, (struct sockaddr*)&client, &len);if(n < 0){log1(Warning, "recvfrom error, error:%d, err string:%s", errno, strerror(errno));   continue;}inbuffer[0] = 0;//充当了一次数据的处理string info = inbuffer;string echo_string = "sever echo#" + info;//发送回给客户端sendto(sockfd_, echo_string.c_str(), echo_string.size(), 0, (struct sockaddr*)&client, len);}}~UdpSever(){if(sockfd_ > 0)close(sockfd_);}
private:int sockfd_;//网络文件描述符uint16_t port_;//表明服务器进程端口号string ip_;bool isrunning_;
};
#include "udpServer.hpp"
#include<memory>
using namespace std;
Log1 log1;//"120.78.126.148"点分十进制字符串风格的IP地址
int main()
{unique_ptr<UdpSever> svr(new UdpSever());//云服务器IPsvr->Init();svr->Run();return 0;
}

 6bcd49d626e34e37a4f6dfa0cfb4342b.png

服务器应该周而复始的运行

isrunning是是否在运行

150800fad9c94a69a315ee9192635a1f.png

8a83c9c5d62548dd8dc2cac8698ed6a3.png

从udp中读取数据

read,write是面向字节流的,而udp面向数据报

recv

d1a78aee60254924b3ac3601c6d929f7.png

收消息

分别是客户端结构体和这个结构体的大小socklen_t

客户发的数据,客户是谁153620d9ea404721a99033d7c27f7f79.png

失败了继续重收 

ea4486a754e14257ae3442347e98fab9.png

整理一下 

e29881e551024a14bbd5f7e1e82e84d9.png

发送回给客户端

8f97adcdc495448b845dd4cce2d8fa18.pngdda9202234de4fbb993d2d2aa256a70f.png

查看端口号和IP地址(netstat -naup)c9e1d760820c41d991b813024c5b6a47.png

d372d6f6b5c2493aa678d4d213888329.png

05306a773a6b4561b7fac031b2167244.png

不带n 

1f56c163b5a04742824cfce48e59b8f4.png 07afb60366fa463896b4c667d7470b5d.png

测试,ip地址是云服务器的,8080端口号

云服务器最好就直接不写ip地址就好了,直接默认的0

绑定成功

e3acc093e21e4e76afa8edf00b8151f1.png

关于port的问题(端口号)

eaa3d49e3acf4cb384e85605338afb66.png

sudo就可以了        

41242b5685484a40bf686c471ba0e522.png

0b444476a1834d979ba3e2ff713e43a8.png

0-1023不让绑定,最好绑定1024的端口,有的3306也不可以 

74ec78ea3b9345c595cb1024f10ba246.png

写成命令行./udpServer+port

IP默认0

#include "udpServer.hpp"
#include<memory>
using namespace std;
Log1 log1;//"120.78.126.148"点分十进制字符串风格的IP地址
void Usage(string proc)
{cout<< "\n\rUsage" << proc << "port[1024+]\n" << endl;
}//./udpServer+port
int main(int argc, char* argv[])
{if(argc != 2){Usage(argv[0]);exit(0);}uint16_t port = stoi(argv[1]);unique_ptr<UdpSever> svr(new UdpSever(port));//云服务器IPsvr->Init();svr->Run();return 0;
}

349b0712e5bc4549b88b71809e32b67c.png

写一个客户端

makefile里要同时执行两个程序

39bfe10913934f53b0f80b9c571f3bda.png

.PHONY:all
udpServer:main.ccg++ -o $@ $^ -std=c++11
udpClient:udpClient.ccg++ -o $@ $^ -std=c++11
.PHONY:clean
clean:rm -f udpServer udpClient

创建套接字

#include<iostream>
#include <sys/types.h>          
#include <sys/socket.h>
using namespace std;int main()
{//                  IPv4      用户数据报int sockfd = socket(AF_INET, SOCK_DGRAM, 0);if(sockfd < 0){cout<< "socker error" << endl;return 1;}return 0;
}

记得close网络文件

6e4b021ef1cb4fbc888a5fc6b5e3dd61.pngf433e03ec6594cdea8f457e928e194b9.png

客户端要bind吗

一个端口号只能被一个进程绑定,一个程序可以绑定多个端口号,所以要OS自由随机选择,防止定义重复造成冲突,如果两个进程同一个端口号,那么只能启动一个程序。。。客户端的端口号保持唯一性就可以了

cfb91714b41b43a8aa309a35dd9aef4e.png

服务器的端口号要确定是因为,客户端是要访问服务端的,需要知道确定的端口号和ip

客户端写成命令行./udpServer+ip+port

void Usage(string proc)
{cout<< "\n\rUsage:" << proc << "severip severport\n" << endl;
}int main(int argc, char* argv[])
{if(argc != 3){Usage(argv[0]);exit(0);}string severip = argv[1];uint16_t severport = stoi(argv[2]);//                  IPv4      用户数据报int sockfd = socket(AF_INET, SOCK_DGRAM, 0);if(sockfd < 0){cout<< "socker error" << endl;return 1;}close(sockfd);return 0;
}

1.数据  2.发给谁(sendto)

89095d2b795e45889c16f4a61f2ab46e.png

sendto有可能向不同的服务器发消息 

整理一下。主机转网络(服务器信息)

端口号主机转网络 

edee9a1c029a4dfabf26675b1e7878cd.png

32fd402a42ad4f05ab9430baefffcfc9.png

接收一下(recvfrom)

4f570cad89da4618886b1e7a20735943.png

收到的信息放进buffer

代码示例

#include <iostream>
#include <sys/types.h>          
#include <sys/socket.h>
#include <unistd.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <strings.h>
using namespace std;
void Usage(string proc)
{cout<< "\n\rUsage:" << proc << "severip severport\n" << endl;
}int main(int argc, char* argv[])
{if(argc != 3){Usage(argv[0]);exit(0);}string severip = argv[1];uint16_t severport = stoi(argv[2]);struct sockaddr_in server;server.sin_family = AF_INET;server.sin_port = htons(severport);server.sin_addr.s_addr = inet_addr(severip.c_str());bzero(&server, sizeof(server));socklen_t len = sizeof(server);//长度//                  IPv4      用户数据报int sockfd = socket(AF_INET, SOCK_DGRAM, 0);if(sockfd < 0){cout<< "socker error" << endl;return 1;}string message;char buffer[1024];while(true){cout<< "Please Enter@ "; getline(cin, message);//空格不会作为结束标识符//1.数据  2.发给谁sendto(sockfd, message.c_str(), message.size(), 0, (struct sockaddr*)&server, len);struct sockaddr_in temp;socklen_t len = sizeof(temp);ssize_t s = recvfrom(sockfd, buffer, 1023, 0, (struct sockaddr*)&temp, &len);if(s > 0){buffer[s] = 0;cout<< buffer << endl;}}close(sockfd);return 0;
}

测试(客户端向服务器发信息)

035c24ea907a433c8ecc9fe6e90b521f.png

客户端怎么知道服务器ip地址和端口号呢,日常生活中是不会知道的,端口号是固定的,约定好的,服务器也要遵守这样的规则,把服务器ip地址拿过来,也就是云服务器的ip(101.34.66.193)(需要开放端口)5dc7ea4203ca4b529dd2ba0faeeb5179.png

6ab8d3ca4a604fe0bd999e350c8dfdfa.png

(无法使用要用127.0.0.1)

37df3e5955794755909f9b17bac768ba.png

84b2360d3ba142ac920ad7aee14774b7.png

udpServer.hpp

#pragma once
#include <iostream>
#include <string>
#include <strings.h>
#include <cstring>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <functional>
#include "log.hpp"
using namespace std;using fun_t = function<string(const string&)>;
//typedef function<string(const string&)> fun_t;extern Log1 log1;//日志器声明enum{SOCKET_ERR = 1,BIND_ERR
};uint16_t defaultport = 8080;
string defaultip = "0.0.0.0";
const int Size = 1024;class UdpServer
{
public:UdpServer(const uint16_t& port = defaultport, const string& ip = defaultip):sockfd_(0), port_(port), ip_(ip), isrunning_(false){}void Init(){//创建udp socketsockfd_ = socket(AF_INET, SOCK_DGRAM, 0);if(sockfd_ < 0){log1(Fatal, "socket create error, sockfd:%d", sockfd_);exit(SOCKET_ERR);}log1(Info, "socket create success, sockfd:%d", sockfd_);//2.bind socket(绑定端口号)struct sockaddr_in local;bzero(&local, sizeof(local));local.sin_family = AF_INET;local.sin_port = htons(port_);//需要保证我的端口号是网络字节序列,因为该端口号是要给对方发送的local.sin_addr.s_addr = inet_addr(ip_.c_str());//1.string -> uint32_t  uint32_t必须是网络序列if(bind(sockfd_, (const struct sockaddr*)&local, sizeof(local))< 0){log1(Fatal, "bind error, error:%d, err string:%s", errno, strerror(errno));   exit(BIND_ERR);}log1(Info, "bind success, error:%d, err string:%s", errno, strerror(errno));   }void Run(fun_t func)//加了fun_t func本质是对代码进行分层{isrunning_ = true;//const int size = 1024;char inbuffer[Size];while(isrunning_){struct sockaddr_in client;socklen_t len = sizeof(client);//收到数据ssize_t n = recvfrom(sockfd_, inbuffer, sizeof(inbuffer) - 1, 0, (struct sockaddr*)&client, &len);if(n < 0){log1(Warning, "recvfrom error, error:%d, err string:%s", errno, strerror(errno));   continue;}//收到了一个数据inbuffer[n] = 0;//充当了一次数据的处理//处理数据放在外面去处理string info = inbuffer;//string echo_string = "sever echo#" + info;string echo_string = func(info);//在外面去处理  cout<< echo_string <<endl;  // 这个你刚刚注释了//发送回给客户端sendto(sockfd_, echo_string.c_str(), echo_string.size(), 0, (struct sockaddr*)&client, len);}}~UdpServer(){if(sockfd_ > 0)close(sockfd_);}
private:int sockfd_;//网络文件描述符uint16_t port_;//表明服务器进程端口号string ip_;bool isrunning_;
};

udpClient.cc

#include <iostream>
#include <sys/types.h>          
#include <sys/socket.h>
#include <unistd.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <strings.h>
using namespace std;
void Usage(string proc)
{cout<< "\n\rUsage:" << proc << "severip severport\n" << endl;
}int main(int argc, char* argv[])
{if(argc != 3){Usage(argv[0]);exit(0);}string serverip = argv[1];uint16_t serverport = stoi(argv[2]);struct sockaddr_in server;bzero(&server, sizeof(server));server.sin_family = AF_INET;server.sin_port = htons(serverport);server.sin_addr.s_addr = inet_addr(serverip.c_str());socklen_t len = sizeof(server);//长度//                  IPv4      用户数据报int sockfd = socket(AF_INET, SOCK_DGRAM, 0);if(sockfd < 0){cout<< "socker error" << endl;return 1;}string message;char buffer[1024];while(true){cout<< "Please Enter@ "; getline(cin, message);//空格不会作为结束标识符//cout<< message <<endl;//1.数据  2.发给谁sendto(sockfd, message.c_str(), message.size(), 0, (struct sockaddr*)&server, len);struct sockaddr_in temp;socklen_t len = sizeof(temp);ssize_t s = recvfrom(sockfd, buffer, 1023, 0, (struct sockaddr*)&temp, &len);if(s > 0){buffer[s] = 0;cout<< buffer << endl;}}close(sockfd);return 0;
}

main.cc 

#include "udpServer.hpp"
#include<memory>
using namespace std;
Log1 log1;//"120.78.126.148"点分十进制字符串风格的IP地址
void Usage(string proc)
{cout<< "\n\rUsage" << proc << "port[1024+]\n" << endl;
}//处理字符串----udpServer.hpp里的fun_t
string Handler(const string& str)
{//这里收到客户发来的请求string res = "Server get a message: ";res += str;return res;
}// bool SafeCheck(const std::string &cmd)
// {
//     int safe = false;
//     std::vector<std::string> key_word = {
//         "rm",
//         "mv",
//         "cp",
//         "kill",
//         "sudo",
//         "unlink",
//         "uninstall",
//         "yum",
//         "top",
//         "while"
//     };
//     for(auto &word : key_word)
//     {
//         auto pos = cmd.find(word);
//         if(pos != std::string::npos) return false;
//     }//     return true;
// }// std::string ExcuteCommand(const std::string &cmd)
// {
//     std::cout << "get a request cmd: " << cmd << std::endl;
//     if(!SafeCheck(cmd)) return "Bad man";//     FILE *fp = popen(cmd.c_str(), "r");
//     if(nullptr == fp)
//     {
//         perror("popen");
//         return "error";
//     }
//     std::string result;
//     char buffer[4096];
//     while(true)
//     {
//         char *ok = fgets(buffer, sizeof(buffer), fp);
//         if(ok == nullptr) break;
//         result += buffer;
//     }
//     pclose(fp);//     return result;
// }//./udpServer+port
int main(int argc, char* argv[])
{if(argc != 2){Usage(argv[0]);exit(0);}uint16_t port = stoi(argv[1]);unique_ptr<UdpServer> svr(new UdpServer(port));//云服务器IPsvr->Init();svr->Run(Handler);return 0;
}

makefile

.PHONY:all
all:udpServer udpClient
udpServer:main.ccg++ -o $@ $^ -std=c++11
udpClient:udpClient.ccg++ -o $@ $^ -std=c++11
.PHONY:clean
clean:rm -f udpServer udpClient

 多台机器网络信息传递测试

先编译一下程序,再进行如图所示操作

919f897b73df429aa3453fbb3fe7f17f.png

然后把这个文件在另一台电脑打开

0a236f6da2494c86894920838bce5b2c.png

再在另一个电脑上打开云服务器创建一个目录,rz一个把文件加入目录

0f217bcc7ce44eb19aa79a4c3505e630.png

默认是不可执行的

ee4ad5565c2e4f41adf91390090a347b.png

+x就可以了

7bbf7a29e09549819d160f41ba26e9f1.png

然后

./udpClient IP(服务器IP) port(服务器启动时的端口号)

62cad6d655b446a7a07b2c4fd70160fd.png

此时,在自己的机器上启动服务器,在另一台机器启动客户端,并且发送信息,在服务器就可以看到了

网络处理数据和接收数据耦合度太高了

把网络通信功能和处理数据的功能进行解耦

两个同理(返回类型是string,参数类型是string&)

705345a312a6414b90ac60a8a275eceb.png

服务器外面给接收到的数据做处理 

05a0baa0feba41ec8e816cc09d367230.png

修改一下,放在外部处理,这样的意思就是希望服务器这边给服务端做什么处理

3883840adcab42c1ac78f0645f1e8da3.png

bedcfffd806f4aa9a645bee0e9d02d23.png

服务器收到了消息,收到了消息然后回调是的去调用传进来的func方法,把这个数据做加工,处理完之后把结果返回

#include "udpServer.hpp"
#include<memory>
using namespace std;
Log1 log1;//"120.78.126.148"点分十进制字符串风格的IP地址
void Usage(string proc)
{cout<< "\n\rUsage" << proc << "port[1024+]\n" << endl;
}//处理字符串----udpServer.hpp里的fun_t
string Handler(const string& str)
{//这里收到客户发来的请求string res = "Server get a message: ";res += str;return res;
}//./udpServer+port
int main(int argc, char* argv[])
{if(argc != 2){Usage(argv[0]);exit(0);}uint16_t port = stoi(argv[1]);unique_ptr<UdpServer> svr(new UdpServer(port));//云服务器IPsvr->Init();svr->Run(Handler);return 0;
}
#pragma once
#include <iostream>
#include <string>
#include <strings.h>
#include <cstring>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <functional>
#include "log.hpp"
using namespace std;using fun_t = function<string(const string&)>;
//typedef function<string(const string&)> fun_t;extern Log1 log1;//日志器声明enum{SOCKET_ERR = 1,BIND_ERR
};uint16_t defaultport = 8080;
string defaultip = "0.0.0.0";
const int Size = 1024;class UdpServer
{
public:UdpServer(const uint16_t& port = defaultport, const string& ip = defaultip):sockfd_(0), port_(port), ip_(ip), isrunning_(false){}void Init(){//创建udp socketsockfd_ = socket(AF_INET, SOCK_DGRAM, 0);if(sockfd_ < 0){log1(Fatal, "socket create error, sockfd:%d", sockfd_);exit(SOCKET_ERR);}log1(Info, "socket create success, sockfd:%d", sockfd_);//2.bind socket(绑定端口号)struct sockaddr_in local;bzero(&local, sizeof(local));local.sin_family = AF_INET;local.sin_port = htons(port_);//需要保证我的端口号是网络字节序列,因为该端口号是要给对方发送的local.sin_addr.s_addr = inet_addr(ip_.c_str());//1.string -> uint32_t  uint32_t必须是网络序列if(bind(sockfd_, (const struct sockaddr*)&local, sizeof(local))< 0){log1(Fatal, "bind error, error:%d, err string:%s", errno, strerror(errno));   exit(BIND_ERR);}log1(Info, "bind success, error:%d, err string:%s", errno, strerror(errno));   }void Run(fun_t func)//加了fun_t func本质是对代码进行分层{isrunning_ = true;//const int size = 1024;char inbuffer[Size];while(isrunning_){struct sockaddr_in client;socklen_t len = sizeof(client);//收到数据ssize_t n = recvfrom(sockfd_, inbuffer, sizeof(inbuffer) - 1, 0, (struct sockaddr*)&client, &len);if(n < 0){log1(Warning, "recvfrom error, error:%d, err string:%s", errno, strerror(errno));   continue;}//收到了一个数据inbuffer[n] = 0;//充当了一次数据的处理//处理数据放在外面去处理string info = inbuffer;//string echo_string = "sever echo#" + info;string echo_string = func(info);//在外面去处理//cout<< echo_string <<endl;//发送回给客户端sendto(sockfd_, echo_string.c_str(), echo_string.size(), 0, (struct sockaddr*)&client, len);}}~UdpServer(){if(sockfd_ > 0)close(sockfd_);}
private:int sockfd_;//网络文件描述符uint16_t port_;//表明服务器进程端口号string ip_;bool isrunning_;
};

如果发来的是命令呢

跟我们平时链接xshellf发命令类似

POPEN

13de6b82158f4a4abfb17810b21724d4.png

#include "udpServer.hpp"
#include<memory>
using namespace std;
Log1 log1;//"120.78.126.148"点分十进制字符串风格的IP地址
void Usage(string proc)
{cout<< "\n\rUsage" << proc << "port[1024+]\n" << endl;
}//处理字符串----udpServer.hpp里的fun_t
string Handler(const string& str)
{//这里收到客户发来的请求string res = "Server get a message: ";res += str;return res;
}// bool SafeCheck(const std::string &cmd)
// {
//     int safe = false;
//     std::vector<std::string> key_word = {
//         "rm",
//         "mv",
//         "cp",
//         "kill",
//         "sudo",
//         "unlink",
//         "uninstall",
//         "yum",
//         "top",
//         "while"
//     };
//     for(auto &word : key_word)
//     {
//         auto pos = cmd.find(word);
//         if(pos != std::string::npos) return false;
//     }//     return true;
// }// std::string ExcuteCommand(const std::string &cmd)
// {
//     std::cout << "get a request cmd: " << cmd << std::endl;
//     if(!SafeCheck(cmd)) return "Bad man";//     FILE *fp = popen(cmd.c_str(), "r");
//     if(nullptr == fp)
//     {
//         perror("popen");
//         return "error";
//     }
//     std::string result;
//     char buffer[4096];
//     while(true)
//     {
//         char *ok = fgets(buffer, sizeof(buffer), fp);
//         if(ok == nullptr) break;
//         result += buffer;
//     }
//     pclose(fp);//     return result;
// }//./udpServer+port
int main(int argc, char* argv[])
{if(argc != 2){Usage(argv[0]);exit(0);}uint16_t port = stoi(argv[1]);unique_ptr<UdpServer> svr(new UdpServer(port));//云服务器IPsvr->Init();svr->Run(Handler);return 0;
}

本地环回127.0.0.1

本地环回主要用于客户端和服务端的测试

测试,并绑定127.0.0.1的ip地址

本地环回地址(Loopback Address)在网络技术中指的是一种特殊的IP地址,用于网络软件测试以及本地机器上的服务访问,而不会将数据包发送到本地机器之外的任何地方。最常用的本地环回地址是 127.0.0.1,它对应于IPv4协议,而在IPv6中,环回地址通常表示为 ::1

以下是本地环回地址在网络中的含义和用途:

  1. 自我测试

    • 环回地址允许网络应用程序在不涉及物理网络接口的情况下发送和接收数据包。这对于测试网络软件非常有用,因为它不需要真实的网络连接。
  2. 服务本地访问

    • 服务器应用程序可以在本地机器上运行并通过环回地址进行访问,这允许开发者在服务部署到生产环境之前在本地进行开发和测试。
  3. 不占用网络带宽

    • 通过环回地址发送的数据不会离开主机,因此不会占用任何网络带宽。
  4. 系统内部通信

    • 操作系统内部的不同服务和应用程序可以通过环回地址进行通信,而无需通过网络接口。
  5. 配置和诊断工具

    • 网络配置和诊断工具经常使用环回地址来检查网络堆栈是否正常工作。
  6. 安全

    • 因为数据不会离开主机,所以使用环回地址进行通信被认为是安全的,不会暴露给外部网络。
  7. 标准化

    • 环回地址是网络协议标准的一部分,几乎所有的TCP/IP实现都支持环回地址。

当数据包被发送到环回地址时,操作系统网络堆栈会立即将该数据包返回给发送者,而不进行任何网络传输。这个过程不涉及物理网络接口,因此不会产生任何网络流量。尽管环回地址看起来像是网络中的一个“虚拟”接口,但在网络协议栈中,它与任何其他网络接口一样被处理。

 客户端怎么知道服务器ip地址和端口号呢,日常生活中是不会知道的,端口号是固定的,约定好的,服务器也要遵守这样的规则,把服务器ip地址拿过来,也就是云服务器的ip(101.34.66.193)(需要开放端口)5dc7ea4203ca4b529dd2ba0faeeb5179.png

6ab8d3ca4a604fe0bd999e350c8dfdfa.png

(无法使用要用127.0.0.1)

37df3e5955794755909f9b17bac768ba.png

84b2360d3ba142ac920ad7aee14774b7.png

在服务器查看发信的客户端的端口号和IP

网络转主机拿到端口号和IP

            uint16_t port = ntohs(client.sin_port);

网络转主机就可以打印出来了d55c0255addb4b5a96e9f51fe9a67ac6.png

13da7e54168d49908fc96d7661c6eaeb.png

95c05c683e614a5094a913a891808961.png

改变一下处理数据方式,打印出IP和port

83366a71a18245a28105445d70b4c731.png

6151b051ba774cd293414d6edd48b1b7.png

代码示例:

server

#pragma once
#include <iostream>
#include <string>
#include <strings.h>
#include <cstring>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <functional>
#include <unordered_map>
#include "log.hpp"
using namespace std;using fun_t = function<string(const string&, string &, uint16_t)>;
//typedef function<string(const string&)> fun_t;extern Log1 log1;//日志器声明enum{SOCKET_ERR = 1,BIND_ERR
};uint16_t defaultport = 8080;
string defaultip = "0.0.0.0";
const int Size = 1024;class UdpServer
{
public:UdpServer(const uint16_t& port = defaultport, const string& ip = defaultip):sockfd_(0), port_(port), ip_(ip), isrunning_(false){}void Init(){//创建udp socketsockfd_ = socket(AF_INET, SOCK_DGRAM, 0);if(sockfd_ < 0){log1(Fatal, "socket create error, sockfd:%d", sockfd_);exit(SOCKET_ERR);}log1(Info, "socket create success, sockfd:%d", sockfd_);//2.bind socket(绑定端口号)struct sockaddr_in local;bzero(&local, sizeof(local));local.sin_family = AF_INET;local.sin_port = htons(port_);//需要保证我的端口号是网络字节序列,因为该端口号是要给对方发送的local.sin_addr.s_addr = inet_addr(ip_.c_str());//1.string -> uint32_t  uint32_t必须是网络序列if(bind(sockfd_, (const struct sockaddr*)&local, sizeof(local))< 0){log1(Fatal, "bind error, error:%d, err string:%s", errno, strerror(errno));   exit(BIND_ERR);}log1(Info, "bind success, error:%d, err string:%s", errno, strerror(errno));   }//检查用户是不是新用户void CheckUser(const struct sockaddr_in& client){}void Run(fun_t func)//加了fun_t func本质是对代码进行分层{isrunning_ = true;//const int size = 1024;char inbuffer[Size];while(isrunning_){struct sockaddr_in client;socklen_t len = sizeof(client);//收到数据ssize_t n = recvfrom(sockfd_, inbuffer, sizeof(inbuffer) - 1, 0, (struct sockaddr*)&client, &len);if(n < 0){log1(Warning, "recvfrom error, error:%d, err string:%s", errno, strerror(errno));   continue;}//接收端口号和ipuint16_t clientport = ntohs(client.sin_port);string clientip = inet_ntoa(client.sin_addr);CheckUser(client);//收到了一个数据inbuffer[n] = 0;//充当了一次数据的处理//处理数据放在外面去处理string info = inbuffer;//string echo_string = "sever echo#" + info;string echo_string = func(info, clientip, clientport);//在外面去处理  cout<< echo_string <<endl;  //发送回给客户端sendto(sockfd_, echo_string.c_str(), echo_string.size(), 0, (struct sockaddr*)&client, len);}}~UdpServer(){if(sockfd_ > 0)close(sockfd_);}
private:int sockfd_;//网络文件描述符uint16_t port_;//表明服务器进程端口号string ip_; //任意地址 0bool isrunning_;unordered_map<string, struct sockaddr_t> online_user;
};

main.cc

#include "udpServer.hpp"
#include<memory>
using namespace std;
Log1 log1;//"120.78.126.148"点分十进制字符串风格的IP地址
void Usage(string proc)
{cout<< "\n\rUsage" << proc << "port[1024+]\n" << endl;
}//处理字符串----udpServer.hpp里的fun_t
string Handler(const string& info, string &clientip, uint16_t clientport)
{cout<< "[clientip: " << clientip << " " << clientport << "]#" << endl;//这里收到客户发来的请求string res = "Server get a message: ";res += info;return res;
}//./udpServer+port
int main(int argc, char* argv[])
{if(argc != 2){Usage(argv[0]);exit(0);}uint16_t port = stoi(argv[1]);unique_ptr<UdpServer> svr(new UdpServer(port));//云服务器IPsvr->Init();svr->Run(Handler);return 0;
}

测试:

3013153250d94bf1b4a19a4bbf5bece2.png

只允许一个客户端连接/判断是不是一个新用户(kv)

检查用户是不是新用户

bc4f321c7fcc47a7a19e5e722297e2e3.png

dff1e337f7a045b2aa2f15d7bdbe9e30.png

6c4c089958f84f618e590fc8f5901c5d.png

4fd111e7084f427ba5ac96171960cdf7.png

代码

#pragma once
#include <iostream>
#include <string>
#include <strings.h>
#include <cstring>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <functional>
#include <unordered_map>
#include "log.hpp"
using namespace std;using fun_t = function<string(const string&, string &, uint16_t)>;
//typedef function<string(const string&)> fun_t;extern Log1 log1;//日志器声明enum{SOCKET_ERR = 1,BIND_ERR
};uint16_t defaultport = 8080;
string defaultip = "0.0.0.0";
const int Size = 1024;class UdpServer
{
public:UdpServer(const uint16_t& port = defaultport, const string& ip = defaultip):sockfd_(0), port_(port), ip_(ip), isrunning_(false){}void Init(){//创建udp socketsockfd_ = socket(AF_INET, SOCK_DGRAM, 0);if(sockfd_ < 0){log1(Fatal, "socket create error, sockfd:%d", sockfd_);exit(SOCKET_ERR);}log1(Info, "socket create success, sockfd:%d", sockfd_);//2.bind socket(绑定端口号)struct sockaddr_in local;bzero(&local, sizeof(local));local.sin_family = AF_INET;local.sin_port = htons(port_);//需要保证我的端口号是网络字节序列,因为该端口号是要给对方发送的local.sin_addr.s_addr = inet_addr(ip_.c_str());//1.string -> uint32_t  uint32_t必须是网络序列if(bind(sockfd_, (const struct sockaddr*)&local, sizeof(local))< 0){log1(Fatal, "bind error, error:%d, err string:%s", errno, strerror(errno));   exit(BIND_ERR);}log1(Info, "bind success, error:%d, err string:%s", errno, strerror(errno));   }//检查用户是不是新用户void CheckUser(const struct sockaddr_in& client, string &clientip, uint16_t clientport){auto iter = online_user_.find(clientip);//kv结构检查在不在if(iter == online_user_.end())//如果不在,那就添加{online_user_.insert({clientip, client});}cout<< "[clientip: " << clientip << " " << clientport << "] add to online user" << endl;}//广播给所有人,所有人都在unordered_map里void Broadcast(const string &info, string &clientip, uint16_t clientport){for(auto& user : online_user_){string message = "[clientip:";message += clientip;message += " ";message += clientport;message += "]#";message += info;socklen_t len = sizeof(user.second); sendto(sockfd_, message.c_str(), message.size(), 0, (struct sockaddr*)(&user.second), len);}}void Run(/*fun_t func*/)//加了fun_t func本质是对代码进行分层{isrunning_ = true;//const int size = 1024;char inbuffer[Size];while(isrunning_){struct sockaddr_in client;socklen_t len = sizeof(client);//收到数据ssize_t n = recvfrom(sockfd_, inbuffer, sizeof(inbuffer) - 1, 0, (struct sockaddr*)&client, &len);if(n < 0){log1(Warning, "recvfrom error, error:%d, err string:%s", errno, strerror(errno));   continue;}//接收端口号和ip// uint16_t clientport = ntohs(client.sin_port);// string clientip = inet_ntoa(client.sin_addr);uint16_t clientport = ntohs(client.sin_port);string clientip = inet_ntoa(client.sin_addr);CheckUser(client, clientip, clientport);//把消息广播给所有人string info = inbuffer;Broadcast(info, clientip, clientport);// //收到了一个数据// inbuffer[n] = 0;// //充当了一次数据的处理// //处理数据放在外面去处理// string info = inbuffer;// //string echo_string = "sever echo#" + info;// string echo_string = func(info, clientip, clientport);//在外面去处理  // cout<< echo_string <<endl;  // //发送回给客户端// sendto(sockfd_, echo_string.c_str(), echo_string.size(), 0, (struct sockaddr*)&client, len);}}~UdpServer(){if(sockfd_ > 0)close(sockfd_);}
private:int sockfd_;//网络文件描述符uint16_t port_;//表明服务器进程端口号string ip_; //任意地址 0bool isrunning_;unordered_map<string, struct sockaddr_in> online_user_;
};

客户端整改

#include <iostream>
#include <sys/types.h>          
#include <sys/socket.h>
#include <unistd.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <strings.h>
using namespace std;
void Usage(string proc)
{cout<< "\n\rUsage:" << proc << "severip severport\n" << endl;
}int main(int argc, char* argv[])
{if(argc != 3){Usage(argv[0]);exit(0);}//                  IPv4      用户数据报int sockfd = socket(AF_INET, SOCK_DGRAM, 0);string serverip = argv[1];uint16_t serverport = stoi(argv[2]);struct sockaddr_in server;bzero(&server, sizeof(server));server.sin_family = AF_INET;server.sin_port = htons(serverport);server.sin_addr.s_addr = inet_addr(serverip.c_str());socklen_t len = sizeof(server);//长度if(sockfd < 0){cout<< "socker error" << endl;return 1;}string message;char buffer[1024];while(true){cout<< "Please Enter@ "; getline(cin, message);//空格不会作为结束标识符//cout<< message <<endl;//1.数据  2.发给谁sendto(sockfd, message.c_str(), message.size(), 0, (struct sockaddr*)&server, len);struct sockaddr_in temp;socklen_t len = sizeof(temp);ssize_t s = recvfrom(sockfd, buffer, 1023, 0, (struct sockaddr*)&temp, &len);if(s > 0){buffer[s] = 0;cout<< buffer << endl;}}close(sockfd);return 0;
}

登录qq时,不发送信息也能收到信息,但目前的客户端没法做到

会在getline阻塞住

发送完再回收到,就又在getline阻塞了

cdb648adb1074feb8c0ec5bdd8829905.png

客户端改成多线程,一个线程输入一个线程显示

udp的sockfd是可以同时被读写的

是线程安全的

 983f9c94299e496b9d17ac15e6a5bebf.png

8690bddac3ec495f808eb5955ab10b35.png

bb888ba0845240fc8f1fa9fa16411dff.png

abd5fccd76294417aeb34d94e0ef1fc1.png

a4f075b63ef74c32ad0b1b2427fbd293.png

#include <iostream>
#include <sys/types.h>
#include <sys/socket.h>
#include <unistd.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <strings.h>
#include <pthread.h>
using namespace std;
void Usage(string proc)
{cout << "\n\rUsage:" << proc << "severip severport\n"<< endl;
}//线程的数据
struct ThreadData
{struct sockaddr_in server;int sockfd;
};// 收
void *recv_message(void *args)
{ThreadData* td = static_cast<ThreadData*>(args);//安全的类型转换char buffer[1024];    while (true){struct sockaddr_in temp;socklen_t len = sizeof(temp);ssize_t s = recvfrom(td->sockfd, buffer, 1023, 0, (struct sockaddr *)&temp, &len);if (s > 0){buffer[s] = 0;cout << buffer << endl;}}
}// 发 就要知道客户端的套接字信息 
void *send_message(void *args)
{ThreadData* td = static_cast<ThreadData*>(args);//安全的类型转换string message;socklen_t len = sizeof(td->server); // 长度while (true){cout << "Please Enter@ ";getline(cin, message); // 空格不会作为结束标识符// cout<< message <<endl;// 1.数据  2.发给谁sendto(td->sockfd, message.c_str(), message.size(), 0, (struct sockaddr *)&(td->server), len);}
}int main(int argc, char *argv[])
{if (argc != 3){Usage(argv[0]);exit(0);}//创建线程的数据 对象struct ThreadData td;//                  IPv4      用户数据报td.sockfd = socket(AF_INET, SOCK_DGRAM, 0);string serverip = argv[1];uint16_t serverport = stoi(argv[2]);bzero(&td.server, sizeof(td.server));td.server.sin_family = AF_INET;td.server.sin_port = htons(serverport);td.server.sin_addr.s_addr = inet_addr(serverip.c_str());// 创建一个收线程一个发线程pthread_t recvr, sender;pthread_create(&recvr, nullptr, recv_message, &td);pthread_create(&sender, nullptr, send_message, &td);if (td.sockfd < 0){cout << "socker error" << endl;return 1;}pthread_join(recvr, nullptr);pthread_join(sender,nullptr);close(td.sockfd);return 0;
}

思路和测试结果73db3ddc08824520bf6c6fb12cac1d0e.png

制作简单的聊天室类似群聊

首先查看自己打开的几个终端

正好3个

上面的这个终端是0

这是1

这是2

将客户端的收消息,用标准错误显示出来,文件描述符是2

覆盖问题

这个是因为是在往/dev/pts/4这个里面写入,你可以将其理解为一个文件,本来文件中有一行内容为aaaaaaaa,每次显示,然后现在再从头输入bbbb,这个bbbb覆盖bbbbaaaa,还是会输出bbbbaaaa

相关文章:

(udp)网络编程套接字Linux(整理)

源IP地址和目的IP地址 唐僧例子1 在IP数据包头部中, 有两个IP地址, 分别叫做源IP地址, 和目的IP地址.思考: 我们光有IP地址就可以完成通信了嘛? 想象一下发qq消息的例子, 有了IP地址能够把消息发送到对方的机器上,但是还需要有一个其他的标识来区分出, 这个数据要给哪个程序进…...

Web应用安全入门:架构搭建、漏洞分析与HTTP数据包处理

Web应用安全入门&#xff1a;架构搭建、漏洞分析与HTTP数据包处理 引言 在当今数字化时代&#xff0c;Web应用已成为企业和个人在线交互的核心。然而&#xff0c;随着技术的发展&#xff0c;Web应用面临的安全挑战也日益增加。本文旨在为初学者提供一个关于Web应用架构搭建、…...

[JAVA]MyBatis框架—获取SqlSession对象

SqlSessionFactory作为MyBatis框架的核心接口有三大特性 SqlSessionFactory是MyBatis的核心对象 用于初始化MyBatis&#xff0c;创建SqlSession对象 保证SqlSessionFactory在应用中全局唯一 1.SqlSessionFactory是MyBatis的核心对象 假设我们要查询数据库的用户信息&#x…...

Perl 简介

Perl 简介 Perl 是一种高级、通用、解释型、动态编程语言。由 Larry Wall 于 1987 年首次发布&#xff0c;它结合了 C、sed、awk 和 shell 脚本语言的特性。Perl 最初被设计用于文本处理&#xff0c;如报告生成和文件转换&#xff0c;但随着时间的推移&#xff0c;它已经发展成…...

spring-bean的销毁流程

1 引入 在 Spring 框架中&#xff0c;Bean 的生命周期管理是其核心功能之一&#xff0c;而 Bean 的注销&#xff08;Destruction&#xff09;是生命周期的最后一步。无论是关闭数据库连接、释放线程资源&#xff0c;还是执行缓存持久化操作&#xff0c;合适的销毁策略都至关重…...

问:Spring MVC DispatcherServlet流程步骤梳理

DispatcherServlet是Spring MVC框架中的核心组件&#xff0c;负责接收客户端请求并将其分发到相应的控制器进行处理。作为前端控制器&#xff08;Front Controller&#xff09;的实现&#xff0c;DispatcherServlet在整个请求处理流程中扮演着至关重要的角色。本文将探讨Dispat…...

用源码编译虚幻引擎,并打包到安卓平台

用源码编译虚幻引擎&#xff0c;并打包到安卓平台 前往我的博客,获取更优的阅读体验 作业内容: 源码编译UE5.4构建C项目&#xff0c;简单设置打包到安卓平台 编译虚幻 5 前置内容 这里需要将 Epic 账号和 Github 账号绑定&#xff0c;然后加入 Epic 邀请的组织&#xff0c…...

快速搭建Android开发环境:Docker部署docker-android并实现远程连接

目录 前言 1. 虚拟化环境检查 2. Android 模拟器部署 3. Ubuntu安装Cpolar 4. 配置公网地址 5. 远程访问 小结 6. 固定Cpolar公网地址 7. 固定地址访问 作者简介&#xff1a; 懒大王敲代码&#xff0c;计算机专业应届生 今天给大家聊聊快速搭建Android开发环境&#x…...

「Mac玩转仓颉内测版21」基础篇1 - 仓颉程序的基本组成

本篇将系统介绍Cangjie编程语言中程序的基本组成部分&#xff0c;涵盖 main 函数的定义、包与模块的使用、变量类型、作用域和代码结构原则&#xff0c;帮助开发者理解Cangjie程序的整体结构。 关键词 程序入口点main函数包与模块变量类型与作用域值类型与引用类型代码结构与规…...

【Linux网络编程】简单的UDP套接字

目录 一&#xff0c;socket编程的相关说明 1-1&#xff0c;sockaddr结构体 1-2&#xff0c;Socket API 二&#xff0c;基于Udp协议的简单通信 三&#xff0c;UDP套接字的应用 3-1&#xff0c;实现英译汉字典 一&#xff0c;socket编程的相关说明 Socket编程是一种网络通信…...

在Vue中使用Excalidraw实现在线画板

概述 Excalidraw是一个非常好用的画图板工具&#xff0c;但是是用React写的&#xff0c;本文分享一种在Vue项目中使用的方法。 效果 实现 Excalidraw简介 这篇文章(Excalidraw 完美的绘图工具&#xff1a;https://zhuanlan.zhihu.com/p/684940131)介绍的很全面&#xff0c;…...

游戏+AI的发展历程,AI技术在游戏行业的应用有哪些?

人工智能&#xff08;AI&#xff09;与游戏的结合&#xff0c;不仅是技术进步的体现&#xff0c;更是人类智慧的延伸。从最初的简单规则到如今的复杂决策系统&#xff0c;AI在游戏领域的发展历史可谓波澜壮阔。 早在2001年&#xff0c;就有研究指出游戏人工智能领域&#xff0…...

Methode Electronics EDI 需求分析

Methode Electronics 是一家总部位于美国的全球性技术公司&#xff0c;专注于设计和制造用于多个行业的电子和电气组件&#xff0c;产品涵盖汽车、工业、电信、医疗设备以及消费电子等多个领域&#xff0c;提供创新的解决方案。 填写Methode_EDI_Parameters_Template Methode_…...

2023AE软件、Adobe After Effects安装步骤分享教程

2023AE软件是一款由Adobe公司开发的视频编辑软件&#xff0c;也被称为Adobe After Effects。它在广告、电影、电视和网络视频等领域广泛应用&#xff0c;用于制作动态图形、特效、合成和其他视觉效果。该软件支持多种视频和音频文件格式&#xff0c;具有丰富的插件和预设&#…...

【前端】JavaScript 变量引用、内存与数组赋值:深入解析三种情景

博客主页&#xff1a; [小ᶻZ࿆] 本文专栏: 前端 文章目录 &#x1f4af;前言&#x1f4af;场景一&#xff1a;直接赋值与重新引用为什么结果不是 [3, 4, 5]&#xff1f;1. 引用与赋值的基本概念2. 图示分析 关键总结 &#x1f4af;场景二&#xff1a;引用指向的变化为什么…...

本地项目运行提示跨域问题

项目背景&#xff1a;我使用phpwebstudy在本地搭建了一个项目&#xff0c;然后前端是http://localhost:8080/ 后端我直接创建了一个本地域名&#xff0c;例如www.abc.com 然后vue.config.js配置如下&#xff0c;这个配置在我所有线上环境是没有任何问题的 devServer: {proxy…...

C++ —— string类(上)

目录 string的介绍 string类功能的使用介绍 constructor —— 构造 介绍使用&#xff08;1&#xff09;&#xff08;2&#xff09;&#xff08;4&#xff09; &#xff1a;构造、拷贝构造、带参构造 介绍&#xff08;3&#xff09;&#xff1a;拷贝string类对象的一部分字符…...

React Native Mac 环境搭建

下载 Mac 版Android Studio 下载 安装 JDK 环境 Flutter 项目实战-环境变量配置一 安装 Node.js 方式一 通过Node.js 官网下载 下载完成后点击安装包进行安装 安装完成...

Python Web 开发的路径管理艺术:FastAPI 项目中的最佳实践与问题解析20241119

Python Web 开发的路径管理艺术&#xff1a;FastAPI 项目中的最佳实践与问题解析 引言&#xff1a;从路径错误到模块化管理的技术旅程 在现代 Python Web 开发中&#xff0c;路径管理是一个常常被忽视却非常重要的问题。尤其是在使用像 FastAPI 和 Tortoise ORM 这样的框架时…...

Rust derive macro(Rust #[derive])Rust派生宏

参考文章&#xff1a;附录 D&#xff1a;派生特征 trait 文章目录 Rust 中的派生宏 #[derive]基础使用示例&#xff1a;派生 Debug 派生其他常用特征示例&#xff1a;派生 Clone 和 Copy 派生宏的限制和自定义派生自定义派生宏上面代码运行时报错了&#xff0c;以下是解释 结论…...

springboot嗨玩旅游网站

摘 要 嗨玩旅游网站是一个专为旅行爱好者打造的在线平台。我们提供丰富多样的旅游目的地信息&#xff0c;包括景点信息、旅游线路、商品信息、社区信息、活动推广等&#xff0c;帮助用户轻松规划行程。嗨玩旅游网站致力于为用户提供便捷、实用的旅行服务&#xff0c;让每一次旅…...

杰发科技AC7840——EEP中RAM的配置

sample和手册中示例代码的sram区地址定义不一样 这个在RAM中使用没有限制&#xff0c;根据这个表格留下足够空间即可 比如需要4096字节的eep空间&#xff0c;可以把RAM的地址改成E000&#xff0c;即E000-EFFF&#xff0c;共4096bytes即可。...

从零开始的c++之旅——map_set的使用

1.序列式容器和关联式容器 序列式容器&#xff1a;逻辑结构为线性序列的数据结构&#xff0c;两个位置之间没有紧密的关系&#xff0c;比如两者交换一下还是序列式的容器&#xff0c;例如string&#xff0c;vector&#xff0c;deque&#xff0c;array等。 关联式容器&#xff1…...

Docker中的一些常用命令

find / -type f -name “文件名” 2>/dev/null 寻找所有目录中的这个文件 pwd 查看当前目录的地址 docker pull 镜像名 强制拉镜像 docker run 运行docker systemctl daemon-reload 关闭docker systemctl start docker 启动docker systemctl restart docker 重启docker /…...

自存 sql常见语句和实际应用

关于连表 查询两个表 SELECT * FROM study_article JOIN study_article_review 查询的就是两个表相乘&#xff0c;结果为两个表的笛卡尔积 相这样 这种并不是我们想要的结果 通常会添加一些查询条件 SELECT * FROM study_articleJOIN study_article_review ON study_art…...

python | argparse模块在命令行的使用中的重要作用

import argparseclass TestCases:def __init__(self, nameNone, expect_resultNone):self.name nameself.expect expect_resultself.parser argparse.ArgumentParser() # 创建命令解析器self.add_arguments() # 方法 &#xff1a; 添加命令self.args, _ self.parser.par…...

【HCIP]——OSPF综合实验

题目 实验需求 根据上图可得&#xff0c;实验需求为&#xff1a; 1.R5作为ISP&#xff1a;其上只能配置IP地址&#xff1b;R4作为企业边界路由器&#xff0c;出口公网地址需要通过PPP协议获取&#xff0c;并进行CHAP认证。&#xff08;PS&#xff1a;因PPP协议尚未学习&#…...

PW系列工控电脑复制机:效率与精度双重提升

工控电脑复制应用&#xff1a;效率与精度的双重提升 随着现代企业对大数据、数据备份、和跨平台兼容性需求的快速增长&#xff0c;工控电脑已成为数据密集型产业的核心设备。针对工控环境中大量数据复制的特殊需求&#xff0c;PW系列NVMe/SATA PCIe SSD复制机&#xff08;如PW…...

学习QT第二天

QT6示例运行 运行一个Widgets程序运行一个QT Quick示例 工作太忙了&#xff0c;难得抽空学点东西。-_-||| 博客中有错误的地方&#xff0c;请各位道友及时指正&#xff0c;感谢&#xff01; 运行一个Widgets程序 在QT Creator的欢迎界面中&#xff0c;点击左侧的示例&#xf…...

11.20作业

题目一&#xff1a; 题目&#xff1a; // 数组的行列转置 代码&#xff1a; // 数组的行列转置 #include <stdio.h> int main() {int a[2][3], i, j, b[3][2];printf("输入一个两行三列的数组a:\n");for (i 0; i < 2; i)for (j 0; j < 3; j){scanf…...

平台类网站建设公司/百度推广登陆

发现很多教程很乱&#xff0c;有的是浅尝则止比如说就是一个demo就完事了&#xff0c;有的是比较乱&#xff0c;可能是之前使用过别的工作流&#xff0c;很多东西就直接带过&#xff0c;看的云里雾里的。比如说一些名词&#xff0c;都不清楚怎么回事。 这里发现还是官方的文档…...

wordpress导航栏目/软文范例100字以内

音箱由哪几部分组成&#xff1f; 市面上的音箱形形色色&#xff0c;但无论哪一种&#xff0c;都是由喇叭单元&#xff08;术语叫扬声器单元&#xff09;和箱体这两大最基本的部分组成&#xff0c;另外&#xff0c;绝大多数音箱至少使用了两只或两只以上的喇叭单元实行所谓的多…...

asp net4.0网站开发/深圳企业网站制作公司

1、安装VMware Workstation。 2、下载MAC OS镜像&#xff08;.cdr文件&#xff09; 3、下载unlocker206 注&#xff1a;以上资源可百度、谷歌搜索进行下载。(如果未找到资源&#xff0c;请下面评论&#xff0c;谢谢合作) 4、确保VMware虚拟机完全关闭的情况下&#xff0c;以管理…...

网站开发与网页后台开发/怎么建立公司网站

1&#xff09;事件&#xff1a;用户对程序的某一种功能性操作。 Java中的事件主要有两种&#xff1a; 1&#xff0e;组件类事件 componentEvent、ContainerEvent、WindowEvent、FocusEvent、PaintEvent、MouseEvent共六大类&#xff0c; 它们均是当组件的状态发生变化时…...

seo在线网站诊断推推蛙/百度外包公司有哪些

2019独角兽企业重金招聘Python工程师标准>>> Bean的作用域Spring 3中为Bean定义了5中作用域&#xff0c;分别为singleton&#xff08;单例&#xff09;、prototype&#xff08;原型&#xff09;、request、session和global session&#xff0c;5种作用域说明如下&am…...

武汉网站建设价格低/百度快速排名提升

【实例简介】基于jspmvcmysql的个人博客网站,包括前段页面&#xff0c;后台Java代码&#xff0c;界面友好&#xff0c;可以实现登录注册功能&#xff0c;浏览博客&#xff0c;发表博客&#xff0c;个人信息主页等功能。【实例截图】【核心代码】746d4044-ff62-4387-9c6c-aa36de…...