Linux---网络套接字

端口号

端口号

端口号是一个2字节16位的整数;
端口号用来标识一个进程, 告诉操作系统, 当前的这个数据要交给哪一个进程来处理;
IP地址 + 端口号能够标识网络上的某一台主机的某一个进程;
一个端口号只能被一个进程占用

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

现在用户要刷抖音,假如抖音的端口号为4321,获取一个抖音短视频,抖音应用通过协议连接到抖音服务器的IP地址,然后建立连接,抖音服务器传送短视频的数据到达用户的设备上。在通信的过程中,一定要知道端口号,不知道的话无法通信。

一个进程可以绑定多个端口号,但是一个端口号不能被多个进程绑定。

认识TCP协议

此处我们先对TCP(Transmission Control Protocol 传输控制协议)有一个直观的认识; 后面我们再详细讨论TCP的一些细节问题.
传输层协议
有连接
可靠传输
面向字节流

认识UDP协议

此处我们也是对UDP(User Datagram Protocol 用户数据报协议)有一个直观的认识; 后面再详细讨论.
传输层协议
无连接
不可靠传输
面向数据报

网络字节序

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

大端:低位地址存放高位数据,高位地址存放低位数据
小端:低位地址存放低位数据,高位地址存放高位数据

大端和小端只是对数据类型长度是两个及以上的,如int short,对于单字节没限制,在网络中经常需要考虑大端和小端的是ip和端口。

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

在网络通信中,为了确保数据在不同系统之间能正确解释,网络字节序被定义为大端序。
 


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

这些函数名很好记,h表示host,n表示network,l表示32位长整数,s表示16位短整数。
例如htonl表示将32位的长整数从主机字节序转换为网络字节序,例如将IP地址转换后准备发送。
如果主机是小端字节序,这些函数将参数做相应的大小端转换然后返回 ;
如果主机是大端字节序,这些 函数不做转换,将参数原封不动地返回。


IP地址转换函数

inet_pton 函数是一个用于将点分十进制的IPv4或IPv6地址转换为二进制形式的函数,
即将地址从文本表示形式转换为网络字节序的二进制形式。这个函数的声明如下:
#include <arpa/inet.h>
int inet_pton(int af, const char *src, void *dst);
af 参数表示地址族,可以是 AF_INET 表示IPv4,也可以是 AF_INET6 表示IPv6。
src 参数是包含要转换的IP地址的点分十进制字符串。
dst 参数是指向存储转换后二进制形式的地址的指针。
函数的返回值是整数,如果转换成功,则返回1(表示成功),
如果地址格式无效或发生错误,则返回nullptr(表示无效的地址格式)
或-1(表示发生了错误)。
  1. IP 地址转换函数:
  2. p->表示点分十进制的字符串形式
  3. to->到
  4. n->表示 network 网络

地址转换函数

inet_ntop 函数是用于将网络字节序的地址转换为字符串表示的函数。
它是IPv4和IPv6通用的函数,可以用于将网络地址转换为点分十进制字符串或IPv6的十六进制字符串。
const char *inet_ntop(int af, const void *src, char *dst, socklen_t size);af:地址族(Address Family),通常是 AF_INET 表示IPv4,AF_INET6 表示IPv6。
src:指向存储网络地址的结构体的指针。
dst:用于存储转换后的字符串的缓冲区指针。
size:缓冲区的大小。
该函数成功时返回指向转换后的字符串的指针,失败时返回 NULL。

还有另一个函数

#include <arpa/inet.h>in_addr_t inet_addr(const char *cp);
这个函数接受一个表示IP地址的字符串(cp参数),
并返回一个in_addr_t类型的二进制格式表示的IP地址。
如果转换失败,函数返回INADDR_NONE。这个函数仅支持IPV4

socket编程常见接口

// 创建 socket 文件描述符 (TCP/UDP, 客户端 + 服务器
int socket(int domain, int type, int protocol)
函数描述:创建socket
参数说明:
domain:协议版本AF_INET IPV4AF_INET6 IPV6AF_UNIX_LOACL 本地套接字使用
type:协议类型
SOCK_STREAM 流式,默认使用的协议是tcp协议
SOCK_DGRAM报式,默认使用的是udp协议
protocal:
一般填0,表示使用对应类型的默认协议

// 绑定端口号 (TCP/UDP, 服务器)
int bind(int socket, const struct sockaddr *address,
socklen_t address_len);
函数描述:将socket文件描述符和ip,port绑定
参数说明:
socket: 调用 socket 孟数返回的文件描述符addr: 本地服务器的 IP 地址和 PORT,struct sockaddr_in serv;serv.sin_family = AF_INET:serv.sin_port = htons(8888)://serv.sin_addr.s_addr = htonl(INADDR_ANY)://INADDR_ANY: 表示使用本机任意有效的可用IP
inet_pton(AF_INET, "127.0.0.1", &serv.sin_addr.s_addr)
addrlen: addr 变量的占用的内存大小
返回值:失败返回-1,并设置errno

// 开始监听socket (TCP, 服务器)
int listen(int socket, int backlog)
函数描述: 将套接字由主动态变为被动态
参数说明:
socket: 调用 socket 函数返回的文件描述符
backlog: 同时请求连接的最大个数(还未建立连接)
返回值:成功: 返回0失败: 返回-1,并设置errno

// 接收请求 (TCP, 服务器)
int accept(int socket, struct sockaddr* address,
socklen_t* address_len);
accept 函数是一个阻寒函数,若没有新的连接请求,则一直阻寒.
从已连接队列中获取一个新的连接,并获得一个新的文件描述符,该文件描
述符用于和客户端通信. (内核会负责将请求队列中的连接拿到已连接队列中)
函数参数:
sockfd:调用 socket 函数返回的文件描述符
addr:传出参数,保存客户端的地址信息
addrlen: n:传入传出参数,addr变量所占内存空间大小
返回值:
成功:返回一个新的文件描述符,用于和客户端通信
失败:返回-1,并设置errno值,

// 建立连接 (TCP, 客户端)
int connect(int sockfd, const struct sockaddr *addr,
socklen_t addrlen);
函数说明: 连接服务器
函教参款:
sockfd: 调用 socket 函数返回的文件描述符
addr: 服务端的地址信息
addrlen: addr 变量的内存大小
返回值:
成功: 返回0
失败: 返回-1,并设置errno 值

socket变成用到的重要的结构体 struct sockaddr

sockaddr结构

sockaddr_in结构

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

in_addr结构
 


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


UDP通信

在网络通信中,一般不使用read和write这两个接口的。

当有人来发送消息的时候,你想知道是谁发的消息。

可以使用recvfrom函数,这个函数中存在一个src_addr参数,这个结构体需要我们自己定义,然后将结构体对象传入进去,就可以知道是谁发送的信息了。

发送数据可以使用sendto。


在这里,简单的写了一个日志系统,后面就不再使用perror,而是直接使用日志系统来提示错误。

#pragma once#include <iostream>
#include <time.h>
#include <stdarg.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>
#include <cstring>#define SIZE 1024#define Info 0
#define Debug 1
#define Warning 2 
#define Error 3
#define Fatal 4#define Screen 1
#define Onefile 2
#define Classfile 3#define LogFile "log.txt"class Log
{
public:Log(){printMethod = Screen;path = "./log/";}void Enable(int method){printMethod = method;}std::string levelToString(int level){switch (level){case Info:return "Info";case Debug:return "Debug";case Warning:return "Warning";case Error:return "Error";case Fatal:return "Fatal";default:return "None";}}// void logmessage(int level, const char *format, ...)// {//     time_t t = time(nullptr);//     struct tm *ctime = localtime(&t);//     char leftbuffer[SIZE];//     snprintf(leftbuffer, sizeof(leftbuffer), "[%s][%d-%d-%d %d:%d:%d]", levelToString(level).c_str(),//              ctime->tm_year + 1900, ctime->tm_mon + 1, ctime->tm_mday,//              ctime->tm_hour, ctime->tm_min, ctime->tm_sec);//     // va_list s;//     // va_start(s, format);//     char rightbuffer[SIZE];//     vsnprintf(rightbuffer, sizeof(rightbuffer), format, s);//     // va_end(s);//     // 格式:默认部分+自定义部分//     char logtxt[SIZE * 2];//     snprintf(logtxt, sizeof(logtxt), "%s %s\n", leftbuffer, rightbuffer);//     // printf("%s", logtxt); // 暂时打印//     printLog(level, logtxt);// }void printLog(int level, const std::string &logtxt){switch (printMethod){case Screen:std::cout << logtxt << std::endl;break;case Onefile:printOneFile(LogFile, logtxt);break;case Classfile:printClassFile(level, logtxt);break;default:break;}}void printOneFile(const std::string &logname, const std::string &logtxt){std::string _logname = path + logname;int fd = open(_logname.c_str(), O_WRONLY | O_CREAT | O_APPEND, 0666); // "log.txt"if (fd < 0)return;write(fd, logtxt.c_str(), logtxt.size());close(fd);}void printClassFile(int level, const std::string &logtxt){std::string filename = LogFile;filename += ".";filename += levelToString(level); // "log.txt.Debug/Warning/Fatal"printOneFile(filename, logtxt);}~Log(){}void operator()(int level, const char *format, ...){time_t t = time(nullptr);struct tm *ctime = localtime(&t);char leftbuffer[SIZE];snprintf(leftbuffer, sizeof(leftbuffer), "[%s][%d-%d-%d %d:%d:%d]", levelToString(level).c_str(),ctime->tm_year + 1900, ctime->tm_mon + 1, ctime->tm_mday,ctime->tm_hour, ctime->tm_min, ctime->tm_sec);va_list s;va_start(s, format);char rightbuffer[SIZE];vsnprintf(rightbuffer, sizeof(rightbuffer), format, s);va_end(s);// 格式:默认部分+自定义部分char logtxt[SIZE * 2];snprintf(logtxt, sizeof(logtxt), "%s %s\n", leftbuffer, rightbuffer);// printf("%s", logtxt); // 暂时打印printLog(level, logtxt);}private:int printMethod;std::string path;
};// int sum(int n, ...)
// {
//     va_list s; // char*
//     va_start(s, n);//     int sum = 0;
//     while(n)
//     {
//         sum += va_arg(s, int); // printf("hello %d, hello %s, hello %c, hello %d,", 1, "hello", 'c', 123);
//         n--;
//     }//     va_end(s); //s = NULL
//     return sum;
// }

#pragma once#include <iostream>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <string>
#include "log1.hpp"std::string defaultip = "0.0.0.0";Log log;#define SIZE 1024class UdpServer
{
public:UdpServer(uint16_t port, std::string ip = defaultip):_port(port),_ip(ip){}void Init(){// 1.创建socket文件描述符,IPV4,UDP,使用对应类型的默认协议sockfd = socket(AF_INET, SOCK_DGRAM, 0);if (sockfd < 0){log(Fatal, "socket fail, error string: %s, error code: %d", strerror(errno), errno);exit(-1);}log(Info, "sockfd successful");// 2.绑定端口号struct sockaddr_in serv;bzero(&serv, sizeof(serv)); // 初始化addrserv.sin_family = AF_INET;serv.sin_port = htons(_port);inet_pton(AF_INET, _ip.c_str(), &serv.sin_addr.s_addr);int bd = bind(sockfd, (const struct sockaddr*)&serv, sizeof(serv));if (bd < 0){log(Fatal, "bind fail, error string: %s, error code: %d", strerror(errno), errno);exit(-1);}log(bd, "bind successful");}void Run(){IsConnect = true;char inbuf[SIZE];while (IsConnect){struct sockaddr_in client;bzero(&client, sizeof(client));socklen_t len = sizeof(client);ssize_t n = recvfrom(sockfd, inbuf, sizeof(inbuf) - 1, 0,(struct sockaddr*)&client, (socklen_t *)&len);if (n < 0){log(Fatal, "recvfrom fail, error string: %s, error code: %d", strerror(errno), errno);continue;}inbuf[n] = 0;std::string info = inbuf;std::string echo_string = "server echo# " + info;sendto(sockfd, echo_string.c_str(), echo_string.size(), 0, (struct sockaddr*)&client, len);}}~UdpServer(){}
public:uint16_t _port;std::string _ip;bool IsConnect;int sockfd;
};
#include "UdpServer.hpp"
#include <memory>int main()
{std::unique_ptr<UdpServer> udp(new UdpServer(8080));udp->Init();udp->Run();return 0;
}

通过 netstat -naup可以查看运行的udp的情况。

只要能用上面的指令查到,就说明我们的服务已经启动了。


当我们把IP改成云服务器的ip,会出现这样的情况,首先端口号是没问题的。

如果你用的是虚拟机,代码是可以运行的。但我今天用的是云服务器,是禁止直接bind公网IP的。当我们服务器有多张网卡的时候,这个IP地址可能就不是唯一的,其他IP地址也可以连接我们这个服务器,所以一般在绑定IP的时候,可以设为0,bind(IP:0), 凡是发给我这台主机的数据,我们都要根据端口号向上交付。

绑定IP地址为0----任意地址bind。


现在将端口号改为80.

会出现这样的错误。

在进行提权之后,就能绑定这个端口号了。

一般情况下,[1,1023]是系统内定的端口号,一般都有固定的应用层协议使用,http:80, https:443, mysql:3306,这种端口号一般就别在使用了。可以考虑8000以上的。


修改一下UdpServer,使用命令行,以./main port的方式执行程序。

#include "UdpServer.hpp"
#include <memory>int main(int argc, char *argv[])
{if (argc != 2){std::cout << "fail!" << std::endl;return 1;}uint16_t port = std::atoi(argv[1]);std::unique_ptr<UdpServer> udp(new UdpServer(port));udp->Init();udp->Run();return 0;
}


现在简单实现一个客户端,来完成通信。

客户端需要bind吗?服务端的bind部分写了一大堆,客户端也需要绑定,服务器要找到客户端,所以也需要bind,只不过不需要用户显示的bind。bind的过程一般会有OS自由随机选择。

一个端口号只能被一个进程bind,对server是如此,对client也是如此。其实client的port是多少,其实不重要,只要能保证主机上的唯一性就可以。系统什么时候bind呢?首次发送数据的时候。


#include <iostream>
#include <sys/types.h>
#include <sys/socket.h>
#include <unistd.h>
#include <string.h>
#include <arpa/inet.h>using namespace std;int main(int argc, char *argv[])
{if (argc != 3){cout << "fail" << endl;return 1;}std::string serverip = argv[1];uint16_t serverport = std::stoi(argv[2]);struct sockaddr_in serv;bzero(&serv, sizeof(serv));serv.sin_family = AF_INET;serv.sin_port = htons(serverport);inet_pton(AF_INET, serverip.c_str(), &serv.sin_addr.s_addr);int sockfd = socket(AF_INET, SOCK_DGRAM, 0);if (sockfd < 0){cout << "socket fail" << endl;return 1;}string msg;char buffer[1024];while (true){cout << "Please Enter@ ";getline(cin, msg);sendto(sockfd, msg.c_str(), msg.size(), 0, (const sockaddr*)&serv, sizeof(serv));struct sockaddr_in temp;socklen_t len = sizeof(temp);ssize_t s = recvfrom (sockfd, buffer, 1023, 0, (struct sockaddr*)&serv, &len);if (s > 0){buffer[s] = 0;cout << buffer << endl;}}close(sockfd);return 0;
}

这就是一个简单的客户端,在运行服务端和客户端的时候,就能完成两者之间的通信了。


#pragma once#include <iostream>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <string>
#include "log1.hpp"std::string defaultip = "0.0.0.0";Log log;#define SIZE 1024class UdpServer
{
public:UdpServer(uint16_t port, std::string ip = defaultip):_port(port),_ip(ip){}void Init(){// 1.创建socket文件描述符,IPV4,UDP,使用对应类型的默认协议sockfd = socket(AF_INET, SOCK_DGRAM, 0);if (sockfd < 0){log(Fatal, "socket fail, error string: %s, error code: %d", strerror(errno), errno);exit(-1);}log(Info, "sockfd successful");// 2.绑定端口号struct sockaddr_in serv;bzero(&serv, sizeof(serv)); // 初始化addrserv.sin_family = AF_INET;serv.sin_port = htons(_port);inet_pton(AF_INET, _ip.c_str(), &serv.sin_addr.s_addr);int bd = bind(sockfd, (const struct sockaddr*)&serv, sizeof(serv));if (bd < 0){log(Fatal, "bind fail, error string: %s, error code: %d", strerror(errno), errno);exit(-1);}log(Info, "bind successful");}void Run(){IsConnect = true;char inbuf[SIZE];while (IsConnect){struct sockaddr_in client;bzero(&client, sizeof(client));socklen_t len = sizeof(client);ssize_t n = recvfrom(sockfd, inbuf, sizeof(inbuf) - 1, 0,(struct sockaddr*)&client, &len);if (n < 0){log(Fatal, "recvfrom fail, error string: %s, error code: %d", strerror(errno), errno);continue;}inbuf[n] = 0; std::string info = inbuf;std::string echo_string = "server echo# " + info;std::cout << echo_string << std::endl;sendto(sockfd, echo_string.c_str(), echo_string.size(), 0, (struct sockaddr*)&client, len);}}~UdpServer(){close(sockfd);}
public:uint16_t _port;std::string _ip;bool IsConnect;int sockfd;
};
#include "UdpServer.hpp"
#include <memory>int main(int argc, char *argv[])
{if (argc != 2){std::cout << "fail!" << std::endl;return 1;}uint16_t port = std::atoi(argv[1]);std::unique_ptr<UdpServer> udp(new UdpServer(port));udp->Init();udp->Run();return 0;
}

服务端和客户端可以直接

以这样的方式运行,

./UdpClient ip port。

通过这个选项可以将远程服务器中的文件发送到本地。

通过rz可以将本地文件发送到远程服务器。现在我使用两个不同的服务器,来运行客户端。上传之后是没有x权限的,使用chmod +x 文件名 可以获得x权限。

然后运行程序,是可以通信的。

所以网络通信最终就是这样子的,只不过可以在进行适当的结耦。


当然udp不仅仅可以只进行通信,如果客户端发送的是指令,我们接收到这个指令的时候可以去处理这个指令。

通过popen函数。

用于创建一个由调用进程执行命令并建立到该命令的标准输入或标准输出的管道。
FILE *popen(const char *command, const char *mode);
  • command 参数是一个字符串,表示要执行的命令。这个字符串会被传递给 shell 进行解释。
  • mode 参数是一个字符串,表示使用的管道的模式。 "r"(只读模式)和 "w"(只写模式)。

popen 返回一个指向 FILE 结构的指针,该结构描述了与新进程的连接。可以使用返回的文件指针进行读取或写入,就像处理常规文件一样。

std::string Command(const std::string &cmd)
{FILE* fp = popen(cmd.c_str(), "r");if (fp == nullptr){perror("popen");return "error";}std::string ret;char buf[2048];while (true){char *tail = fgets(buf, sizeof(buf), fp);if (tail == nullptr){break;}ret += buf;}return ret;
}

当然, 在处理客户端的指令之前,要进行检查,避免出现客户端执行rm -rf等之类的指令。


上述通信的源代码

// UdpServer.hpp
#pragma once#include <iostream>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <string>
#include <functional>
#include "log1.hpp"// 在Run中带一个参数,这个参数是客户端需要做的事情。可以用function进行封装。然后通过传入的参数来改变客户端需要做的事情。
using func_t = std::function<std::string(const std::string&)>;std::string defaultip = "0.0.0.0";Log log;#define SIZE 1024class UdpServer
{
public:UdpServer(uint16_t port, std::string ip = defaultip):_port(port),_ip(ip){}void Init(){// 1.创建socket文件描述符,IPV4,UDP,使用对应类型的默认协议sockfd = socket(AF_INET, SOCK_DGRAM, 0);if (sockfd < 0){log(Fatal, "socket fail, error string: %s, error code: %d", strerror(errno), errno);exit(-1);}log(Info, "sockfd successful");// 2.绑定端口号struct sockaddr_in serv;bzero(&serv, sizeof(serv)); // 初始化addrserv.sin_family = AF_INET;serv.sin_port = htons(_port);inet_pton(AF_INET, _ip.c_str(), &serv.sin_addr.s_addr);int bd = bind(sockfd, (const struct sockaddr*)&serv, sizeof(serv));if (bd < 0){log(Fatal, "bind fail, error string: %s, error code: %d", strerror(errno), errno);exit(-1);}log(Info, "bind successful");}void Run(func_t func){IsConnect = true;char inbuf[SIZE];while (IsConnect){struct sockaddr_in client;bzero(&client, sizeof(client));socklen_t len = sizeof(client);ssize_t n = recvfrom(sockfd, inbuf, sizeof(inbuf) - 1, 0,(struct sockaddr*)&client, &len);if (n < 0){log(Fatal, "recvfrom fail, error string: %s, error code: %d", strerror(errno), errno);continue;}inbuf[n] = 0; std::string info = inbuf;std::string echo_string = func(info);sendto(sockfd, echo_string.c_str(), echo_string.size(), 0, (struct sockaddr*)&client, len);}}~UdpServer(){close(sockfd);}
public:uint16_t _port;std::string _ip;bool IsConnect;int sockfd;
};
// Main.cc
#include "UdpServer.hpp"
#include <cstdio>
#include <memory>std::string Handler(const std::string &msg)
{std::string res = "Server get a message : ";res += msg;std::cout << res << std::endl;return res;
}std::string Command(const std::string &cmd)
{FILE* fp = popen(cmd.c_str(), "r");if (fp == nullptr){perror("popen");return "error";}std::string ret;char buf[2048];while (true){char *tail = fgets(buf, sizeof(buf), fp);if (tail == nullptr){break;}ret += buf;}return ret;
}int main(int argc, char *argv[])
{if (argc != 2){std::cout << "fail!" << std::endl;return 1;}uint16_t port = std::atoi(argv[1]);std::unique_ptr<UdpServer> udp(new UdpServer(port));udp->Init();udp->Run(Command);return 0;
}
// UdpClient.cc
#include <iostream>
#include <sys/types.h>
#include <sys/socket.h>
#include <unistd.h>
#include <string.h>
#include <arpa/inet.h>using namespace std;int main(int argc, char *argv[])
{if (argc != 3){cout << "fail" << endl;return 1;}std::string serverip = argv[1];uint16_t serverport = std::stoi(argv[2]);struct sockaddr_in serv;bzero(&serv, sizeof(serv));serv.sin_family = AF_INET;serv.sin_port = htons(serverport);inet_pton(AF_INET, serverip.c_str(), &serv.sin_addr.s_addr);int sockfd = socket(AF_INET, SOCK_DGRAM, 0);if (sockfd < 0){cout << "socket fail" << endl;return 1;}string msg;char buffer[1024];while (true){cout << "Please Enter@ ";getline(cin, msg);sendto(sockfd, msg.c_str(), msg.size(), 0, (const sockaddr*)&serv, sizeof(serv));struct sockaddr_in temp;socklen_t len = sizeof(temp);ssize_t s = recvfrom (sockfd, buffer, 1023, 0, (struct sockaddr*)&serv, &len);if (s > 0){buffer[s] = 0;cout << buffer << endl;}}close(sockfd);return 0;
}
.PHONY:all
all:UdpServer UdpClient
UdpServer:Main.ccg++ -o $@ $^ -std=c++11 -lpthreadUdpClient:UdpClient.ccg++ -o $@ $^ -std=c++11 -lpthread.PHONY:clean
clean:rm UdpClient UdpServer

使用Udp做一个聊天

简单的对UdpServer.hpp中的代码进行了一个更改。

#pragma once#include <iostream>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <string>
#include <functional>
#include <unordered_map>
#include <netinet/in.h>
#include "log1.hpp"// 在Run中带一个参数,这个参数是客户端需要做的事情。可以用function进行封装。然后通过传入的参数来改变客户端需要做的事情。
using func_t = std::function<void(const std::string&, std::string&, uint16_t&)>;std::string defaultip = "0.0.0.0";Log log;#define SIZE 1024class UdpServer
{
public:
UdpServer(uint16_t port, std::string ip = defaultip)
:_port(port)
,_ip(ip)
{}void Init()
{// 1.创建socket文件描述符,IPV4,UDP,使用对应类型的默认协议sockfd = socket(AF_INET, SOCK_DGRAM, 0);if (sockfd < 0){log(Fatal, "socket fail, error string: %s, error code: %d", strerror(errno), errno);exit(-1);}log(Info, "sockfd successful");// 2.绑定端口号struct sockaddr_in serv;bzero(&serv, sizeof(serv)); // 初始化addrserv.sin_family = AF_INET;serv.sin_port = htons(_port);inet_pton(AF_INET, _ip.c_str(), &serv.sin_addr.s_addr);int bd = bind(sockfd, (const struct sockaddr*)&serv, sizeof(serv));if (bd < 0){log(Fatal, "bind fail, error string: %s, error code: %d", strerror(errno), errno);exit(-1);}log(Info, "bind successful");
}void CheckOnlineUser(const struct sockaddr_in &client,std::string& ip, uint16_t& port)
{auto iter = online_user.find(ip);if (iter == online_user.end()){online_user.insert({ip, client});std::cout << "[" + ip + ":" + std::to_string(port) + "]#" << "add new client" << std::endl;}
}std::string func(std::string info, std::string ip, uint16_t port)
{std::string echo_string = "[" + ip + ":" + std::to_string(port) + "]#" + info;return echo_string;
}void BroadCast(std::string &info, std::string &ip, uint16_t &port)
{for (auto &it : online_user){sendto(sockfd, info.c_str(), info.size(), 0, (const struct sockaddr*)&it.second, sizeof(it.second));}
}void Run()
{IsConnect = true;char inbuf[SIZE];while (IsConnect){struct sockaddr_in client;bzero(&client, sizeof(client));socklen_t len = sizeof(client);ssize_t n = recvfrom(sockfd, inbuf, sizeof(inbuf) - 1, 0,(struct sockaddr*)&client, &len);if (n < 0){log(Fatal, "recvfrom fail, error string: %s, error code: %d", strerror(errno), errno);continue;}inbuf[n] = 0; std::string client_ip = inet_ntoa(client.sin_addr);// 获取用户的ipuint16_t client_port = ntohs(client.sin_port);// 获取用户的端口号CheckOnlineUser(client,client_ip, client_port);// 检查在线的用户,若有新上线的,会进行提醒std::string info = inbuf;// std::string echo_string = func(info);std::string echo_string = func(info, client_ip, client_port);// 将信息拼接为字符串BroadCast(echo_string,client_ip, client_port);// 将这条信息转发给所有人// sendto(sockfd, echo_string.c_str(), echo_string.size(), 0, (struct sockaddr*)&client, len);}}~UdpServer(){close(sockfd);}
public:uint16_t _port;std::string _ip;bool IsConnect;int sockfd;std::unordered_map<std::string, struct sockaddr_in> online_user;// 用来存储所有客户端的IP和sockaddr
};


inet_ntoa 是一个用于将 IPv4 地址从二进制表示转换为点分十进制字符串表示的函数。它通常在网络编程中使用,特别是在处理套接字编程时。

因为客户端是单线程的原因,每次循环收到一条信息后,在getline中会被阻塞,这就导致服务端转发消息的时候,不能及时的将所有的信息转发出来。


下面将客户端改为多线程。

#include <iostream>
#include <sys/types.h>
#include <sys/socket.h>
#include <unistd.h>
#include <string.h>
#include <arpa/inet.h>
#include <pthread.h>
#include <mutex>using namespace std;pthread_mutex_t mu = PTHREAD_MUTEX_INITIALIZER;struct ThreadData
{struct sockaddr_in serv;int sockfd;
};void *recv_msg(void *args)
{// OpenTerminal();ThreadData *td = static_cast<ThreadData *>(args);char buffer[1024];while (true){memset(buffer, 0, sizeof(buffer));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;cerr << buffer << endl;}}
}void *send_msg(void *args)
{ThreadData *td = static_cast<ThreadData *>(args);string message;socklen_t len = sizeof(td->serv);sendto(td->sockfd, message.c_str(), message.size(), 0, (struct sockaddr *)&(td->serv), len);while (true){cout << "Please Enter@ ";getline(cin, message);sendto(td->sockfd, message.c_str(), message.size(), 0, (struct sockaddr *)&(td->serv), len);}
}int main(int argc, char *argv[])
{if (argc != 3){cout << "fail" << endl;return 1;}struct ThreadData td;std::string serverip = argv[1];uint16_t serverport = std::stoi(argv[2]);bzero(&td.serv, sizeof(td.serv));td.serv.sin_family = AF_INET;td.serv.sin_port = htons(serverport);inet_pton(AF_INET, serverip.c_str(), &td.serv.sin_addr.s_addr);td.sockfd = socket(AF_INET, SOCK_DGRAM, 0);if (td.sockfd < 0){cout << "socket fail" << endl;return 1;}pthread_t client_send, client_recv;pthread_create(&client_send, nullptr, send_msg, &td);pthread_create(&client_recv, nullptr, recv_msg, &td);pthread_join(client_send, nullptr);pthread_join(client_recv, nullptr);close(td.sockfd);return 0;
}

这样就可以实时的将所有用户发送的信息转发到屏幕上了,类似于群聊。

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

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

相关文章

人工智能如何彻底改变身份欺诈

据 AuthenticID 称&#xff0c;近一半的企业报告合成身份欺诈有所增加&#xff0c;而生物识别欺骗和伪造 ID 欺诈尝试也有所增加。 在当今的数字化存在中&#xff0c;消费者和企业都面临着新的挑战&#xff0c;从考虑数字身份的影响到应对生成人工智能等新工具的使用和流行。与…

【小沐学GIS】基于WebGL绘制三维数字地球Earth(OpenGL)

&#x1f37a;三维数字地球系列相关文章如下&#x1f37a;&#xff1a;1【小沐学GIS】基于C绘制三维数字地球Earth&#xff08;OpenGL、glfw、glut&#xff09;第一期2【小沐学GIS】基于C绘制三维数字地球Earth&#xff08;OpenGL、glfw、glut&#xff09;第二期3【小沐学GIS】…

【C语言】C的整理记录

前言 该笔记是建立在已经系统学习过C语言的基础上&#xff0c;笔者对C语言的知识和注意事项进行整理记录&#xff0c;便于后期查阅&#xff0c;反复琢磨。C语言是一种面向过程的编程语言。 原想在此阐述一下C语言的作用&#xff0c;然而发觉这些是编程语言所共通的作用&#…

一键打造属于自己漏扫系统

0x01 工具介绍 本系统是对Web中间件和Web框架进行自动化渗透的一个系统,根据扫描选项去自动化收集资产,然后进行POC扫描,POC扫描时会根据指纹选择POC插件去扫描,POC插件扫描用异步方式扫描.前端采用vue技术,后端采用python fastapi。 0x02 安装与使用 1、Docker部署环境 编译…

C语言学习记录

牛牛学说话之-字符_牛客题霸_牛客网 (nowcoder.com) 总结&#xff1a; 字符定义为char,对应%c 整数定义为int&#xff0c;对应%d 分数对应float&#xff0c;对应%f,内存小&#xff0c;速度快 分数对应double,对应%lf&#xff0c;范围广&#xff0c;精度高 保留几位小数就是…

C++入门学习(二十七)跳转语句—break语句

1、与switch语句联合使用 C入门学习&#xff08;二十三&#xff09;选择结构-switch语句-CSDN博客 #include <iostream> #include <string> using namespace std;int main() { int number;cout<<"请为《斗萝大路》打星(1~5※)&#xff1a;" &…

Linux操作系统基础(十一):RPM软件包管理器

文章目录 RPM软件包管理器 一、rpm包的卸载 二、rpm包的安装 RPM软件包管理器 rpm&#xff08;英文全拼&#xff1a;redhat package manager&#xff09; 原本是 Red Hat Linux 发行版专门用来管理 Linux 各项软件包的程序&#xff0c;由于它遵循GPL规则且功能强大方便&…

单片机学习笔记---AT24C02(I2C总线)

目录 有关储存器的介绍 存储器的简介 存储器简化模型 AT24C02介绍 AT24C02引脚及应用电路 I2C总线介绍 I2C电路规范 开漏输出模式和弱上拉模式 其中一个设备的内部结构 I2C通信是怎么实现的 I2C时序结构 起始条件和终止条件 发送一个字节 接收一个字节 发送应答…

Stable Diffusion 模型下载:ToonYou(平涂卡通)

本文收录于《AI绘画从入门到精通》专栏&#xff0c;专栏总目录&#xff1a;点这里。 文章目录 模型介绍生成案例案例一案例二案例三案例四案例五案例六案例七案例八案例九案例十 下载地址 模型介绍 ToonYou 是一个平涂风格的卡通模型&#xff0c;它的画风独特、光感强烈、画面…

Ubuntu Desktop - Details (设备详情)

Ubuntu Desktop - Details [设备详情] 1. OverviewReferences 1. Overview System Settings -> Details -> Overview ​ References [1] Yongqiang Cheng, https://yongqiang.blog.csdn.net/

单片机在物联网中的应用

单片机&#xff0c;这个小巧的电子设备&#xff0c;可能听起来有点技术性&#xff0c;但它实际上是物联网世界中的一个超级英雄。简单来说&#xff0c;单片机就像是各种智能设备的大脑&#xff0c;它能让设备“思考”和“行动”。由于其体积小、成本低、功耗低、易于编程等特点…

【C++第二阶段】赋值运算符重载

你好你好&#xff01; 以下内容仅为当前认识&#xff0c;可能有不足之处&#xff0c;欢迎讨论&#xff01; 文章目录 赋值运算符重载 赋值运算符重载 实验①&#xff0c;还没有对析构运算符重载时 #include<iostream> #include<string> using namespace std;clas…

失去中国市场的三星仍是全球第一,但中国手机无法失去海外市场

随着2023年分析机构公布全球手机市场和中国手机市场的数据&#xff0c;业界终于看清中国市场早已没有以前那么重要&#xff0c;三星、苹果这些国际品牌对中国市场的依赖没有他们想象的那么严重&#xff0c;相反中国手机对海外市场比以往任何时候都要更依赖了。 三星在2023年被苹…

消息队列使用的四种场景介绍

一、简介 消息队列中间件是分布式系统中重要的组件&#xff0c;主要解决应用耦合&#xff0c;异步消息&#xff0c;流量削锋等问题。 实现高性能&#xff0c;高可用&#xff0c;可伸缩和最终一致性架构。 使用较多的消息队列有ActiveMQ&#xff0c;RabbitMQ&#xff0c;ZeroMQ…

【开源】SpringBoot框架开发个人健康管理系统

目录 一、摘要1.1 项目介绍1.2 项目录屏 二、功能模块2.1 健康档案模块2.2 体检档案模块2.3 健康咨询模块 三、系统展示四、核心代码4.1 查询健康档案4.2 新增健康档案4.3 查询体检档案4.4 新增体检档案4.5 新增健康咨询 五、免责说明 一、摘要 1.1 项目介绍 基于JAVAVueSpri…

docker之centos7容器常用命令和服务安装

一、前言 以前我们如果想在windows环境下使用linux系统&#xff0c;最早的是一台主机上安装双机系统&#xff0c;再后来我们有了VMware&#xff0c;可以通过workstations虚拟化平台安装虚拟机。现在我们还可以通过docker安装linux容器&#xff0c;容器更轻量也更便捷。不过凡事…

SpringCloud-高级篇(二十一)

前面解决了消息的可靠性、消息的延迟问题&#xff0c;下面研究一下消息的堆积的问题&#xff1a; &#xff08;1&#xff09;消息堆积问题 消息产生堆积&#xff0c;上限后&#xff0c;最早的消息成为死信&#xff0c;有消息被丢弃&#xff0c;这对安全性较高的业务中是不行的…

HiveSQL——条件判断语句嵌套windows子句的应用

注&#xff1a;参考文章&#xff1a; SQL条件判断语句嵌套window子句的应用【易错点】--HiveSql面试题25_sql剁成嵌套判断-CSDN博客文章浏览阅读920次&#xff0c;点赞4次&#xff0c;收藏4次。0 需求分析需求&#xff1a;表如下user_idgood_namegoods_typerk1hadoop1011hive1…

JAVA设计模式之迭代器模式详解

迭代器模式 1 迭代器模式介绍 迭代器模式是我们学习一个设计时很少用到的、但编码实现时却经常使用到的行为型设计模式。在绝大多数编程语言中&#xff0c;迭代器已经成为一个基础的类库&#xff0c;直接用来遍历集合对象。在平时开发中&#xff0c;我们更多的是直接使用它&a…

解决 postman测试接口报404 Not Found

JDK版本&#xff1a;jdk17 IDEA版本&#xff1a;IntelliJ IDEA 2022.1.3 文章目录 问题描述原因分析解决方案 问题描述 当我使用postman测试接口时&#xff0c;报了 404 Not Found 的错误&#xff0c;报错截图如下所示 但我的后端程序中已经定义了该接口&#xff0c;如下所示 …