(udp)网络编程套接字Linux(整理)
源IP地址和目的IP地址
唐僧例子1
- 在IP数据包头部中, 有两个IP地址, 分别叫做源IP地址, 和目的IP地址.
- 思考: 我们光有IP地址就可以完成通信了嘛? 想象一下发qq消息的例子, 有了IP地址能够把消息发送到对方的机器上,但是还需要有一个其他的标识来区分出, 这个数据要给哪个程序进行解析.
认识端口号
理解 "端口号" 和 "进程ID"
我们之前在学习系统编程的时候, 学习了 pid 表示唯一一个进程; 此处我们的端口号也是唯一表示一个进程. 那么这 两者之间是怎样的关系?
10086例子
另外, 一个进程可以绑定多个端口号; 但是一个端口号不能被多个进程绑定;
理解源端口号和目的端口号
唐僧例子2
送快递例子
传输层协议(TCP和UDP)的数据段中有两个端口号, 分别叫做源端口号和目的端口号. 就是在描述 "数据是谁发的, 要 发给谁";
IP:Port()
ip地址4字节 端口号两个字节
跨主机
看到的公共部分 是网络
在公网上:
IP地址能表示唯一的一台主机,端口号port,用来标识该主机上的唯一的一个进程
IP:Port=标识全网唯一的一个进程
认识TCP协议
认识UDP协议
网络字节序
ip地址4字节 端口号两个字节
我们已经知道,内存中的多字节数据相对于内存地址有大端和小端之分, 磁盘文件中的多字节数据相对于文件中的偏移地址也有大端小端之分, 网络数据流同样有大端小端之分. 那么如何定义网络数据流的地址呢?
- 发送主机通常将发送缓冲区中的数据按内存地址从低到高的顺序发出;
- 接收主机把从网络上接到的字节依次保存在接收缓冲区中,也是按内存地址从低到高的顺序保存;
- 因此,网络数据流的地址应这样规定:先发出的数据是低地址,后发出的数据是高地址. TCP/IP协议规定,网络数据流应采用大端字节序,即低地址高字节.
- 不管这台主机是大端机还是小端机, 都会按照这个TCP/IP规定的网络字节序来发送/接收数据;
- 如果当前发送主机是小端, 就需要先将数据转成大端; 否则就忽略, 直接发送即可;
为使网络程序具有可移植性,使同样的C代码在大端和小端计算机上编译后都能正常运行,可以调用以下库函数做网络字节序和主机字节序的转换。
网络字节序和主机字节序的转换
主机转网络
服务端
客户端
网络转主机(后面ntoa就不建议使用了)
在 C 或 C++ 中,网络字节序和主机字节序的转换非常重要,特别是在进行网络编程时,因为不同的平台可能有不同的字节序(Endianness)。网络字节序通常是大端字节序(Big Endian),而主机字节序可能是大端或小端(Little Endian)。
网络字节序和主机字节序的转换函数
- **
htons()
**:将主机字节序(Host)转换为网络字节序(Network),适用于 16 位数据(short)。 - **
htonl()
**:将主机字节序(Host)转换为网络字节序(Network),适用于 32 位数据(long)。 - **
ntohs()
**:将网络字节序(Network)转换为主机字节序(Host),适用于 16 位数据(short)。 - **
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.网络套接字编程
udp代码示例
udp服务器
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是面向字节流的,第三个参数不用填,协议类型,创建一个套接字的本质就是打开一个文件,必须要告诉服务器,端口号,然后再绑定套接字端口号
不同类型(第一个参数的)
第二个:定义的套接字的类型
第三个写0就可以
#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;//网络文件描述符
};
把日志功能拷贝进来
创建一个Log对象
std::unique_ptr
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 // 原理:简单粗暴 -- 防拷贝
测试:
绑定bind
绑定端口号
sockaddr结构
- 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 结构
sockaddr_in 结构
虽然socket api的接口是sockaddr, 但是我们真正在基于IPv4编程时, 使用的数据结构是sockaddr_in; 这个结构里主 要有三部分信息: 地址类型, 端口号, IP地址.
in_addr结构
in_addr用来表示一个IPv4的IP地址. 其实就是一个32位的整数;
bzero把上面的结构体清空
INET
addr,ip地址(32位的),port端口号,family所用的域或者家族(AF_INET)
int,uint16_t,uint32_t的区别及其各自的用法
int
、uint16_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 位无符号整数的场景,常用于表示较大范围的正整数,如文件大小、内存地址、网络协议等。
总结对比:
数据类型 | 大小 | 范围 | 用途 |
---|---|---|---|
| 32 位(常见) |
| 通用整数类型,平台相关 |
| 16 位 |
| 用于小范围无符号整数,如协议、传感器数据 |
| 32 位 |
| 用于大范围无符号整数,如文件大小、网络地址 |
选择依据:
-
如果你知道需要存储的数值范围,并且希望节省内存,可以选择
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
可以提高协议的跨平台兼容性,保证数据的准确传输,并且更高效地使用带宽和存储。
##的作用
主机序列转成网络序列
端口号只有两个字节
如果机器本来是大端机那么就什么都不做,如果是小端机,会转换成大端
main中这样传ip
如何快速的将整数IP<->字符串IP
自己写的话如果实现
uint8_t一个字节
这样就把4字节的ip直接转换成了字符串风格的ip
uint32_t是4个字节
把字符串风格的ip地址转化为四字节(网络风格的四字节)
local在用户栈上,这样赋值只是把变量给赋值了,并没有把local变量和内核的网络套接字相关联,还没有进行绑定
再进行bind绑定,绑定的本质,把这个参数设置进内核指定的套接字内部
要强转
以上udp服务器核心的启动代码基本已经完成
思路精华
测试:
#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;
}
服务器应该周而复始的运行
isrunning是是否在运行
从udp中读取数据
read,write是面向字节流的,而udp面向数据报
recv
收消息
分别是客户端结构体和这个结构体的大小socklen_t
客户发的数据,客户是谁
失败了继续重收
整理一下
发送回给客户端
查看端口号和IP地址(netstat -naup)
不带n
测试,ip地址是云服务器的,8080端口号
云服务器最好就直接不写ip地址就好了,直接默认的0
绑定成功
关于port的问题(端口号)
sudo就可以了
0-1023不让绑定,最好绑定1024的端口,有的3306也不可以
写成命令行./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;
}
写一个客户端
makefile里要同时执行两个程序
.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网络文件
客户端要bind吗
一个端口号只能被一个进程绑定,一个程序可以绑定多个端口号,所以要OS自由随机选择,防止定义重复造成冲突,如果两个进程同一个端口号,那么只能启动一个程序。。。客户端的端口号保持唯一性就可以了
服务器的端口号要确定是因为,客户端是要访问服务端的,需要知道确定的端口号和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)
sendto有可能向不同的服务器发消息
整理一下。主机转网络(服务器信息)
端口号主机转网络
接收一下(recvfrom)
收到的信息放进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;
}
测试(客户端向服务器发信息)
客户端怎么知道服务器ip地址和端口号呢,日常生活中是不会知道的,端口号是固定的,约定好的,服务器也要遵守这样的规则,把服务器ip地址拿过来,也就是云服务器的ip(101.34.66.193)(需要开放端口)
(无法使用要用127.0.0.1)
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
多台机器网络信息传递测试
先编译一下程序,再进行如图所示操作
然后把这个文件在另一台电脑打开
再在另一个电脑上打开云服务器创建一个目录,rz一个把文件加入目录
默认是不可执行的
+x就可以了
然后
./udpClient IP(服务器IP) port(服务器启动时的端口号)
此时,在自己的机器上启动服务器,在另一台机器启动客户端,并且发送信息,在服务器就可以看到了
网络处理数据和接收数据耦合度太高了
把网络通信功能和处理数据的功能进行解耦
两个同理(返回类型是string,参数类型是string&)
服务器外面给接收到的数据做处理
修改一下,放在外部处理,这样的意思就是希望服务器这边给服务端做什么处理
服务器收到了消息,收到了消息然后回调是的去调用传进来的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
#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
。
以下是本地环回地址在网络中的含义和用途:
-
自我测试:
- 环回地址允许网络应用程序在不涉及物理网络接口的情况下发送和接收数据包。这对于测试网络软件非常有用,因为它不需要真实的网络连接。
-
服务本地访问:
- 服务器应用程序可以在本地机器上运行并通过环回地址进行访问,这允许开发者在服务部署到生产环境之前在本地进行开发和测试。
-
不占用网络带宽:
- 通过环回地址发送的数据不会离开主机,因此不会占用任何网络带宽。
-
系统内部通信:
- 操作系统内部的不同服务和应用程序可以通过环回地址进行通信,而无需通过网络接口。
-
配置和诊断工具:
- 网络配置和诊断工具经常使用环回地址来检查网络堆栈是否正常工作。
-
安全:
- 因为数据不会离开主机,所以使用环回地址进行通信被认为是安全的,不会暴露给外部网络。
-
标准化:
- 环回地址是网络协议标准的一部分,几乎所有的TCP/IP实现都支持环回地址。
当数据包被发送到环回地址时,操作系统网络堆栈会立即将该数据包返回给发送者,而不进行任何网络传输。这个过程不涉及物理网络接口,因此不会产生任何网络流量。尽管环回地址看起来像是网络中的一个“虚拟”接口,但在网络协议栈中,它与任何其他网络接口一样被处理。
客户端怎么知道服务器ip地址和端口号呢,日常生活中是不会知道的,端口号是固定的,约定好的,服务器也要遵守这样的规则,把服务器ip地址拿过来,也就是云服务器的ip(101.34.66.193)(需要开放端口)
(无法使用要用127.0.0.1)
在服务器查看发信的客户端的端口号和IP
网络转主机拿到端口号和IP
uint16_t port = ntohs(client.sin_port);
网络转主机就可以打印出来了
改变一下处理数据方式,打印出IP和port
代码示例:
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;
}
测试:
只允许一个客户端连接/判断是不是一个新用户(kv)
检查用户是不是新用户
代码
#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阻塞了
客户端改成多线程,一个线程输入一个线程显示
udp的sockfd是可以同时被读写的
是线程安全的
#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;
}
思路和测试结果
制作简单的聊天室类似群聊
首先查看自己打开的几个终端
正好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应用安全入门:架构搭建、漏洞分析与HTTP数据包处理 引言 在当今数字化时代,Web应用已成为企业和个人在线交互的核心。然而,随着技术的发展,Web应用面临的安全挑战也日益增加。本文旨在为初学者提供一个关于Web应用架构搭建、…...
[JAVA]MyBatis框架—获取SqlSession对象
SqlSessionFactory作为MyBatis框架的核心接口有三大特性 SqlSessionFactory是MyBatis的核心对象 用于初始化MyBatis,创建SqlSession对象 保证SqlSessionFactory在应用中全局唯一 1.SqlSessionFactory是MyBatis的核心对象 假设我们要查询数据库的用户信息&#x…...
Perl 简介
Perl 简介 Perl 是一种高级、通用、解释型、动态编程语言。由 Larry Wall 于 1987 年首次发布,它结合了 C、sed、awk 和 shell 脚本语言的特性。Perl 最初被设计用于文本处理,如报告生成和文件转换,但随着时间的推移,它已经发展成…...
spring-bean的销毁流程
1 引入 在 Spring 框架中,Bean 的生命周期管理是其核心功能之一,而 Bean 的注销(Destruction)是生命周期的最后一步。无论是关闭数据库连接、释放线程资源,还是执行缓存持久化操作,合适的销毁策略都至关重…...
问:Spring MVC DispatcherServlet流程步骤梳理
DispatcherServlet是Spring MVC框架中的核心组件,负责接收客户端请求并将其分发到相应的控制器进行处理。作为前端控制器(Front Controller)的实现,DispatcherServlet在整个请求处理流程中扮演着至关重要的角色。本文将探讨Dispat…...
用源码编译虚幻引擎,并打包到安卓平台
用源码编译虚幻引擎,并打包到安卓平台 前往我的博客,获取更优的阅读体验 作业内容: 源码编译UE5.4构建C项目,简单设置打包到安卓平台 编译虚幻 5 前置内容 这里需要将 Epic 账号和 Github 账号绑定,然后加入 Epic 邀请的组织,…...
快速搭建Android开发环境:Docker部署docker-android并实现远程连接
目录 前言 1. 虚拟化环境检查 2. Android 模拟器部署 3. Ubuntu安装Cpolar 4. 配置公网地址 5. 远程访问 小结 6. 固定Cpolar公网地址 7. 固定地址访问 作者简介: 懒大王敲代码,计算机专业应届生 今天给大家聊聊快速搭建Android开发环境&#x…...
「Mac玩转仓颉内测版21」基础篇1 - 仓颉程序的基本组成
本篇将系统介绍Cangjie编程语言中程序的基本组成部分,涵盖 main 函数的定义、包与模块的使用、变量类型、作用域和代码结构原则,帮助开发者理解Cangjie程序的整体结构。 关键词 程序入口点main函数包与模块变量类型与作用域值类型与引用类型代码结构与规…...
【Linux网络编程】简单的UDP套接字
目录 一,socket编程的相关说明 1-1,sockaddr结构体 1-2,Socket API 二,基于Udp协议的简单通信 三,UDP套接字的应用 3-1,实现英译汉字典 一,socket编程的相关说明 Socket编程是一种网络通信…...
在Vue中使用Excalidraw实现在线画板
概述 Excalidraw是一个非常好用的画图板工具,但是是用React写的,本文分享一种在Vue项目中使用的方法。 效果 实现 Excalidraw简介 这篇文章(Excalidraw 完美的绘图工具:https://zhuanlan.zhihu.com/p/684940131)介绍的很全面,…...
游戏+AI的发展历程,AI技术在游戏行业的应用有哪些?
人工智能(AI)与游戏的结合,不仅是技术进步的体现,更是人类智慧的延伸。从最初的简单规则到如今的复杂决策系统,AI在游戏领域的发展历史可谓波澜壮阔。 早在2001年,就有研究指出游戏人工智能领域࿰…...
Methode Electronics EDI 需求分析
Methode Electronics 是一家总部位于美国的全球性技术公司,专注于设计和制造用于多个行业的电子和电气组件,产品涵盖汽车、工业、电信、医疗设备以及消费电子等多个领域,提供创新的解决方案。 填写Methode_EDI_Parameters_Template Methode_…...
2023AE软件、Adobe After Effects安装步骤分享教程
2023AE软件是一款由Adobe公司开发的视频编辑软件,也被称为Adobe After Effects。它在广告、电影、电视和网络视频等领域广泛应用,用于制作动态图形、特效、合成和其他视觉效果。该软件支持多种视频和音频文件格式,具有丰富的插件和预设&#…...
【前端】JavaScript 变量引用、内存与数组赋值:深入解析三种情景
博客主页: [小ᶻZ࿆] 本文专栏: 前端 文章目录 💯前言💯场景一:直接赋值与重新引用为什么结果不是 [3, 4, 5]?1. 引用与赋值的基本概念2. 图示分析 关键总结 💯场景二:引用指向的变化为什么…...
本地项目运行提示跨域问题
项目背景:我使用phpwebstudy在本地搭建了一个项目,然后前端是http://localhost:8080/ 后端我直接创建了一个本地域名,例如www.abc.com 然后vue.config.js配置如下,这个配置在我所有线上环境是没有任何问题的 devServer: {proxy…...
C++ —— string类(上)
目录 string的介绍 string类功能的使用介绍 constructor —— 构造 介绍使用(1)(2)(4) :构造、拷贝构造、带参构造 介绍(3):拷贝string类对象的一部分字符…...
React Native Mac 环境搭建
下载 Mac 版Android Studio 下载 安装 JDK 环境 Flutter 项目实战-环境变量配置一 安装 Node.js 方式一 通过Node.js 官网下载 下载完成后点击安装包进行安装 安装完成...
Python Web 开发的路径管理艺术:FastAPI 项目中的最佳实践与问题解析20241119
Python Web 开发的路径管理艺术:FastAPI 项目中的最佳实践与问题解析 引言:从路径错误到模块化管理的技术旅程 在现代 Python Web 开发中,路径管理是一个常常被忽视却非常重要的问题。尤其是在使用像 FastAPI 和 Tortoise ORM 这样的框架时…...
Rust derive macro(Rust #[derive])Rust派生宏
参考文章:附录 D:派生特征 trait 文章目录 Rust 中的派生宏 #[derive]基础使用示例:派生 Debug 派生其他常用特征示例:派生 Clone 和 Copy 派生宏的限制和自定义派生自定义派生宏上面代码运行时报错了,以下是解释 结论…...
springboot嗨玩旅游网站
摘 要 嗨玩旅游网站是一个专为旅行爱好者打造的在线平台。我们提供丰富多样的旅游目的地信息,包括景点信息、旅游线路、商品信息、社区信息、活动推广等,帮助用户轻松规划行程。嗨玩旅游网站致力于为用户提供便捷、实用的旅行服务,让每一次旅…...
杰发科技AC7840——EEP中RAM的配置
sample和手册中示例代码的sram区地址定义不一样 这个在RAM中使用没有限制,根据这个表格留下足够空间即可 比如需要4096字节的eep空间,可以把RAM的地址改成E000,即E000-EFFF,共4096bytes即可。...
从零开始的c++之旅——map_set的使用
1.序列式容器和关联式容器 序列式容器:逻辑结构为线性序列的数据结构,两个位置之间没有紧密的关系,比如两者交换一下还是序列式的容器,例如string,vector,deque,array等。 关联式容器࿱…...
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 查询的就是两个表相乘,结果为两个表的笛卡尔积 相这样 这种并不是我们想要的结果 通常会添加一些查询条件 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() # 方法 : 添加命令self.args, _ self.parser.par…...
【HCIP]——OSPF综合实验
题目 实验需求 根据上图可得,实验需求为: 1.R5作为ISP:其上只能配置IP地址;R4作为企业边界路由器,出口公网地址需要通过PPP协议获取,并进行CHAP认证。(PS:因PPP协议尚未学习&#…...
PW系列工控电脑复制机:效率与精度双重提升
工控电脑复制应用:效率与精度的双重提升 随着现代企业对大数据、数据备份、和跨平台兼容性需求的快速增长,工控电脑已成为数据密集型产业的核心设备。针对工控环境中大量数据复制的特殊需求,PW系列NVMe/SATA PCIe SSD复制机(如PW…...
学习QT第二天
QT6示例运行 运行一个Widgets程序运行一个QT Quick示例 工作太忙了,难得抽空学点东西。-_-||| 博客中有错误的地方,请各位道友及时指正,感谢! 运行一个Widgets程序 在QT Creator的欢迎界面中,点击左侧的示例…...
11.20作业
题目一: 题目: // 数组的行列转置 代码: // 数组的行列转置 #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…...
平台类网站建设公司/百度推广登陆
发现很多教程很乱,有的是浅尝则止比如说就是一个demo就完事了,有的是比较乱,可能是之前使用过别的工作流,很多东西就直接带过,看的云里雾里的。比如说一些名词,都不清楚怎么回事。 这里发现还是官方的文档…...
wordpress导航栏目/软文范例100字以内
音箱由哪几部分组成? 市面上的音箱形形色色,但无论哪一种,都是由喇叭单元(术语叫扬声器单元)和箱体这两大最基本的部分组成,另外,绝大多数音箱至少使用了两只或两只以上的喇叭单元实行所谓的多…...
asp net4.0网站开发/深圳企业网站制作公司
1、安装VMware Workstation。 2、下载MAC OS镜像(.cdr文件) 3、下载unlocker206 注:以上资源可百度、谷歌搜索进行下载。(如果未找到资源,请下面评论,谢谢合作) 4、确保VMware虚拟机完全关闭的情况下,以管理…...
网站开发与网页后台开发/怎么建立公司网站
1)事件:用户对程序的某一种功能性操作。 Java中的事件主要有两种: 1.组件类事件 componentEvent、ContainerEvent、WindowEvent、FocusEvent、PaintEvent、MouseEvent共六大类, 它们均是当组件的状态发生变化时…...
seo在线网站诊断推推蛙/百度外包公司有哪些
2019独角兽企业重金招聘Python工程师标准>>> Bean的作用域Spring 3中为Bean定义了5中作用域,分别为singleton(单例)、prototype(原型)、request、session和global session,5种作用域说明如下&am…...
武汉网站建设价格低/百度快速排名提升
【实例简介】基于jspmvcmysql的个人博客网站,包括前段页面,后台Java代码,界面友好,可以实现登录注册功能,浏览博客,发表博客,个人信息主页等功能。【实例截图】【核心代码】746d4044-ff62-4387-9c6c-aa36de…...