网上商城网站开发与建立的意义,云南省住房和城乡建设厅勘察设计处网站,触屏网站,软件商城免费下载安装端口号
端口号
端口号是一个2字节16位的整数; 端口号用来标识一个进程, 告诉操作系统, 当前的这个数据要交给哪一个进程来处理; IP地址 端口号能够标识网络上的某一台主机的某一个进程; 一个端口号只能被一个进程占用
在公网上#xff0c;IP地址能表示唯一的一台主机…端口号
端口号
端口号是一个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表示发生了错误。
IP 地址转换函数:p-表示点分十进制的字符串形式to-到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 表示IPv4AF_INET6 表示IPv6。
src指向存储网络地址的结构体的指针。
dst用于存储转换后的字符串的缓冲区指针。
size缓冲区的大小。
该函数成功时返回指向转换后的字符串的指针失败时返回 NULL。
还有另一个函数
#include arpa/inet.hin_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.txtclass 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.txtif (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/FatalprintOneFile(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.hppstd::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文件描述符IPV4UDP使用对应类型的默认协议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 memoryint main()
{std::unique_ptrUdpServer udp(new UdpServer(8080));udp-Init();udp-Run();return 0;
} 通过 netstat -naup可以查看运行的udp的情况。 只要能用上面的指令查到就说明我们的服务已经启动了。 当我们把IP改成云服务器的ip会出现这样的情况首先端口号是没问题的。 如果你用的是虚拟机代码是可以运行的。但我今天用的是云服务器是禁止直接bind公网IP的。当我们服务器有多张网卡的时候这个IP地址可能就不是唯一的其他IP地址也可以连接我们这个服务器所以一般在绑定IP的时候可以设为0bind(IP:0) 凡是发给我这台主机的数据我们都要根据端口号向上交付。
绑定IP地址为0----任意地址bind。 现在将端口号改为80. 会出现这样的错误。 在进行提权之后就能绑定这个端口号了。
一般情况下[1,1023]是系统内定的端口号一般都有固定的应用层协议使用http:80 https443 mysql3306这种端口号一般就别在使用了。可以考虑8000以上的。 修改一下UdpServer使用命令行以./main port的方式执行程序。
#include UdpServer.hpp
#include memoryint main(int argc, char *argv[])
{if (argc ! 2){std::cout fail! std::endl;return 1;}uint16_t port std::atoi(argv[1]);std::unique_ptrUdpServer 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.husing 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.hppstd::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文件描述符IPV4UDP使用对应类型的默认协议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 memoryint main(int argc, char *argv[])
{if (argc ! 2){std::cout fail! std::endl;return 1;}uint16_t port std::atoi(argv[1]);std::unique_ptrUdpServer 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::functionstd::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文件描述符IPV4UDP使用对应类型的默认协议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 memorystd::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_ptrUdpServer 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.husing 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 $ $^ -stdc11 -lpthreadUdpClient:UdpClient.ccg -o $ $^ -stdc11 -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::functionvoid(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文件描述符IPV4UDP使用对应类型的默认协议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_mapstd::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 mutexusing 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_castThreadData *(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_castThreadData *(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;
}
这样就可以实时的将所有用户发送的信息转发到屏幕上了类似于群聊。