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

Linux网络-Socket 编程 UDP

大家好,上次我们简单介绍了Linux网络的知识,今天我们来继续上次的学习。

目录

1. socket 编程接口

1.1 socket函数

1.2 bind函数

1.3 构造简单UDP服务器

1.4 字节序转换函数

1.5 IPv4 地址操作工具

2. echo server

2.1 recvfrom函数

2.2 sendto函数

2.3 INADDR_ANY

3. DictServer

4. 简单聊天室


1. socket 编程接口

上次我们只是介绍了有关socket编程的接口,今天我们首先来学习一下这些接口:

1.1 socket函数

// 创建 socket 文件描述符 (TCP/UDP, 客户端 + 服务器)
int socket(int domain, int type, int protocol);

socket函数是创建套接字(Socket)的核心系统调用,用于初始化一个网络通信的端点。套接字是网络通信的基础,无论是 TCP 还是 UDP 协议,都需要先通过 socket 函数创建套接字描述符,再进行后续的连接、绑定、发送 / 接收数据等操作。

socket 函数的三个参数决定了套接字的类型、地址族和协议,具体含义如下:

1. domain(地址族 / 协议族)

指定套接字使用的地址类型,决定了网络地址的格式(如 IPv4、IPv6 或本地通信)。常见取值:

    AF_INET:IPv4 地址族(最常用),对应的地址格式为 struct sockaddr_in

    AF_INET6:IPv6 地址族,对应的地址格式为 struct sockaddr_in6

    AF_UNIX(或 AF_LOCAL):本地进程间通信(非网络),对应的地址格式为 struct sockaddr_un(基于文件系统路径)。

2. type(套接字类型)

指定套接字的通信方式,决定了数据传输的特性(如是否可靠、是否面向连接)。常见取值:

    SOCK_STREAM:流式套接字(面向连接),对应 TCP 协议。特点:可靠传输、字节流、无边界、需建立连接(如 connect)。

    SOCK_DGRAM:数据报套接字(无连接),对应 UDP 协议。特点:不可靠传输、有边界、无需连接,直接发送数据报。

    SOCK_RAW:原始套接字,允许直接操作底层协议(如 IP 协议),通常用于网络工具(如 ping、traceroute),需要 root 权限。

3. protocol(协议)

指定具体使用的协议,通常填 0(让系统根据前两个参数自动选择默认协议)。若需显式指定,常见取值:

        当 type=SOCK_STREAM 时,protocol 可设为 IPPROTO_TCP(TCP 协议)。

        当 type=SOCK_DGRAM 时,protocol 可设为 IPPROTO_UDP(UDP 协议)。

返回值

成功:返回一个非负整数(套接字描述符,类似文件描述符,用于后续操作)。

失败:返回 -1,并设置全局变量 errno(可通过 perror 打印错误信息)。

#include<iostream>
#include<sys/socket.h>int main()
{// 1. 创建 socket,就是创建了文件细节int _sockfd = socket(AF_INET,SOCK_DGRAM,0);if(_sockfd<0){perror("创建套接字socket失败");return 1;}return 0;
}
1.2 bind函数

// 绑定端口号 (TCP/UDP, 服务器)
int bind(int socket, const struct sockaddr *address, socklen_t address_len);

bind 函数用于将一个套接字(socket)与特定的网络地址(IP 地址 + 端口号)绑定,确保该套接字后续只能通过绑定的地址接收或发送数据。这一步是服务器端编程的关键(服务器需要固定端口监听连接),客户端通常无需显式绑定(系统会自动分配临时端口)。

bind 函数的三个参数分别指定 “要绑定的套接字”“目标地址”“地址结构长度”,具体含义如下:

1. sockfd(套接字描述符)

        即 socket 函数返回的套接字描述符(Linux 中是 int 类型,Windows 中是 SOCKET 类型),表示要绑定地址的套接字。

2. addr(地址结构指针)

        指向一个特定协议的地址结构(需强制转换为 struct sockaddr* 类型,因为函数要求统一的地址结构接口)。

        地址结构的类型由创建套接字时的 domain(地址族)决定:

        若 domain=AF_INET(IPv4):使用 struct sockaddr_in 结构,定义如下:

struct sockaddr_in {sa_family_t     sin_family;   // 地址族(必须为 AF_INET)in_port_t       sin_port;     // 端口号(需转换为网络字节序)struct in_addr  sin_addr;     // IPv4 地址(需转换为网络字节序)char            sin_zero[8];  // 填充字段(需设为 0,保持与 sockaddr 大小一致)
};
struct in_addr {in_addr_t s_addr;  // IPv4 地址(32 位整数,如 INADDR_ANY 表示绑定所有本地地址)
};

        若 domain=AF_INET6(IPv6):使用 struct sockaddr_in6 结构(类似 IPv4,但地址为 128 位)。

        若 domain=AF_UNIX(本地通信):使用 struct sockaddr_un 结构(基于文件路径)。

3. addrlen(地址结构长度)

        指定 addr 指向的地址结构的字节长度(如 sizeof(struct sockaddr_in))。

        作用:告诉系统如何解析 addr 指向的内存(不同地址族的结构长度不同)。

返回值

成功:返回 

失败:

        Linux 中返回 -1,并设置 errno(可通过 perror 打印错误)。

        Windows 中返回 SOCKET_ERROR,需通过 WSAGetLastError() 获取错误码。

#include<iostream>
#include<sys/socket.h>
#include<netinet/in.h>
#include<strings.h>int main()
{// 1. 创建 socket,就是创建了文件细节int _sockfd = socket(AF_INET,SOCK_DGRAM,0);if(_sockfd < 0){perror("创建套接字socket失败");return 1;}// 2. 绑定指定网络信息struct sockaddr_in local;bzero(&local,sizeof(local)); //填充字段设为 0local.sin_family = AF_INET;local.sin_port = htons(8080);local.sin_addr.s_addr = INADDR_ANY;int n = bind(_sockfd, (struct sockaddr*)&local, sizeof(local));if(n < 0){perror("bind失败");}return 0;
}

目前学习这两个函数就足够支撑我们建立一个简单的server服务器了。下面我们建立一个服务器。

1.3 构造简单UDP服务器

下面是一个简单的建立服务器的代码:

#pragma once#include<iostream>
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<strings.h>
#include<string>
#include<arpa/inet.h>
#include"Log.hpp"const int defaultfd = -1;using namespace LogModule;class UDPServer{
public:UDPServer(const std::string ip , uint16_t port):_sockfd(defaultfd),_ip(ip),_port(port){}void Init(){// 1. 创建套接字_sockfd = socket(AF_INET , SOCK_DGRAM , 0);if(_sockfd < 0){LOG(LogLevel::FATAL)<< "socket failed" ;exit(1);} LOG(LogLevel::INFO)<< "socket success,sockfd: "<< _sockfd ;// 2. 绑定socket信息,ip和端口struct sockaddr_in local;bzero(&local , sizeof(local));local.sin_family = AF_INET;// IP信息和端口信息,一定要发送到网络// 本地格式->网络序列local.sin_port = htons(_port);local.sin_addr.s_addr = inet_addr(_ip.c_str());bind(_sockfd,(struct sockaddr*)&local,sizeof(local));}private:int _sockfd;uint16_t _port;std::string _ip;
};

由于要将ip地址和port端口号也发送到网络中,所以我们要对port端口号和ip地址进行格式转换,下面我们介绍一下关于这一部分的接口:

1.4 字节序转换函数

#include <arpa/inet.h>uint32_t htonl(uint32_t hostlong);
uint16_t htons(uint16_t hostshort);
uint32_t ntohl(uint32_t netlong);
uint16_t ntohs(uint16_t netshort);
函数名功能描述处理数据长度转换方向
htonl将无符号长整数从主机字节序→网络字节序32 位(4 字节)Host to Network Long
htons将无符号短整数从主机字节序→网络字节序16 位(2 字节)Host to Network Short
ntohl将无符号长整数从网络字节序→主机字节序32 位(4 字节)Network to Host Long
ntohs将无符号短整数从网络字节序→主机字节序16 位(2 字节)Network to Host Short

服务器绑定端口bind 函数中,端口号需通过 htons 转为网络字节序。

struct sockaddr_in addr;
addr.sin_port = htons(8080); // 将主机字节序的8080转为网络字节序

解析收到的 IP / 端口:从网络中收到的 IP 或端口,需通过 ntohl/ntohs 转回主机字节序才能本地使用。

uint16_t port = ntohs(net_port); // 将网络字节序的端口转回主机字节序
1.5 IPv4 地址操作工具

#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>int inet_aton(const char *cp, struct in_addr *inp);
in_addr_t inet_addr(const char *cp);
in_addr_t inet_network(const char *cp);
char *inet_ntoa(struct in_addr in);
函数名功能描述现代替代(支持 IPv6)备注
inet_aton点分十进制 IP 字符串(如"192.168.1.1")转换为struct in_addr(网络字节序整数)inet_pton线程安全,推荐使用
inet_addr将点分十进制 IP 字符串转换为网络字节序整数in_addr_t类型)inet_pton缺陷:无法区分"255.255.255.255"和错误,已不推荐
inet_network将点分十进制 IP 字符串转换为网络字节序整数(通常用于子网地址处理)-使用率低,逐渐被替代
inet_ntoastruct in_addr(网络字节序 IP)转换为点分十进制字符串inet_ntop线程不安全、不支持 IPv6,已弃用
inet_makeaddr(已弃用)将 “网络地址 + 主机地址” 组合成struct in_addr-因子网划分方式演进,已无实用价值
inet_lnaof(已弃用)从struct in_addr中提取主机部分地址-同上
inet_netof(已弃用)从struct in_addr中提取网络部分地址-

以上就是建立一个简单服务器所需要的操作,下面让我们实践几个服务器。

2. echo server

简单的回显服务器和客户端代码

UDP_Server.hpp

#pragma once#include<iostream>
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<strings.h>
#include<string>
#include<arpa/inet.h>
#include<functional>
#include"Log.hpp"using func_t = std::function<std::string(const std::string&)>;const int defaultfd = -1;using namespace LogModule;class UDPServer{
public:UDPServer(const std::string ip , uint16_t port , func_t func):_sockfd(defaultfd),_ip(ip),_port(port),_func(func){}void Init(){// 1. 创建套接字_sockfd = socket(AF_INET , SOCK_DGRAM , 0);if(_sockfd < 0){LOG(LogLevel::FATAL)<< "socket failed" ;exit(1);} LOG(LogLevel::INFO)<< "socket success,sockfd: "<< _sockfd ;// 2. 绑定socket信息,ip和端口struct sockaddr_in local;bzero(&local , sizeof(local));local.sin_family = AF_INET;// IP信息和端口信息,一定要发送到网络// 本地格式->网络序列local.sin_port = htons(_port);local.sin_addr.s_addr = inet_addr(_ip.c_str());int n = bind(_sockfd,(struct sockaddr*)&local,sizeof(local));if(n < 0){LOG(LogLevel::FATAL)<< "bind failed" ;exit(1);}LOG(LogLevel::INFO)<< "bind success, ip: " << _ip << ", port: " << _port;}void Start(){_isrunning = true;while(_isrunning){char buffer[1024];struct sockaddr_in peer;socklen_t len = sizeof(peer);// 1. 收消息ssize_t s = recvfrom(_sockfd , buffer , sizeof(buffer) - 1 , 0 , (struct sockaddr*)&peer , &len);if(s > 0){int peer_port = ntohs(peer.sin_port);std::string peer_ip = inet_ntoa(peer.sin_addr);buffer[s] = 0;std::string result = _func(buffer);// 2. 发消息sendto(_sockfd , result.c_str() , result.size() , 0 , (struct sockaddr*)&peer , len);}        }}~UDPServer(){}
private:int _sockfd;uint16_t _port;std::string _ip;bool _isrunning;func_t _func;
};

UDP_Server.cc

#include<iostream>
#include<memory>
#include"UDP_Server.hpp"std::string defaulthandler(const std::string& message)
{std::string hello = "hello ";hello += message;return hello;
}int main(int argc , char* argv[])
{if(argc!=3){std::cerr << "Usage: " << argv[0] << " port" << std::endl;}std::string ip=argv[1];uint16_t port = std::stoi(argv[2]);//Enable_Console_Log_Strategy();std::unique_ptr<UDPServer> usvr = std::make_unique<UDPServer>(ip, port, defaulthandler);usvr->Init();usvr->Start();return 0;
}

UDP_Client.cc

#include <iostream>
#include <string>
#include <cstring>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/socket.h>// ./udpclient server_ip server_port
int main(int argc, char *argv[])
{if (argc != 3){std::cerr << "Usage: " << argv[0] << " server_ip server_port" << std::endl;return 1;}std::string server_ip = argv[1];uint16_t server_port = std::stoi(argv[2]);// 1. 创建socketint sockfd = socket(AF_INET, SOCK_DGRAM, 0);if(sockfd < 0){std::cerr << "socket error" << std::endl;return 2;}// 2. 本地的ip和端口是什么?要不要和上面的“文件”关联呢?// 问题:client要不要bind?需要bind.//   client要不要显式的bind?不要!!首次发送消息,OS会自动给client进行bind,OS知道IP,端口号采用随机端口号的方式//   为什么?一个端口号,只能被一个进程bind,为了避免client端口冲突//   client端的端口号是几,不重要,只要是唯一的就行!// 填写服务器信息struct sockaddr_in server;memset(&server, 0, sizeof(server));server.sin_family = AF_INET;server.sin_port = htons(server_port);server.sin_addr.s_addr = inet_addr(server_ip.c_str());while(true){std::string input;std::cout << "Please Enter# ";std::getline(std::cin, input);int n = sendto(sockfd, input.c_str(), input.size(), 0, (struct sockaddr*)&server, sizeof(server));(void)n;char buffer[1024];struct sockaddr_in peer;socklen_t len = sizeof(peer);int m = recvfrom(sockfd, buffer, sizeof(buffer)-1, 0, (struct sockaddr*)&peer, &len);if(m > 0){buffer[m] = 0;std::cout << buffer << std::endl;}}return 0;
}

这里又要学到两个新的函数,分别是收消息和发消息:

2.1 recvfrom函数

#include <sys/socket.h>
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr *src_addr, socklen_t *addrlen);

recvfrom 是网络编程中用于接收无连接协议(如 UDP)数据报的系统调用,核心功能是从指定套接字接收数据,并同时获取发送方的网络地址(IP + 端口)。由于 UDP 是无连接的,接收数据时必须通过该函数获取发送方信息,才能进行回复(如用 sendto 回传数据)。

参数详解

1.sockfd(套接字描述符)        

  • 已创建并绑定(bind)的 UDP 套接字描述符(Linux 中为 int,Windows 中为 SOCKET),表示从哪个套接字接收数据。

2. buf(接收缓冲区)    

  • 指向一块内存区域的指针,用于存储接收的数据(需提前分配内存,如 char buf[1024])。

3. len(缓冲区长度)

  • 接收缓冲区的最大容量(字节数),用于限制接收数据的大小(避免缓冲区溢出)。
  • 若实际接收的数据超过 len,超出部分会被截断(且不会保留后续数据)。

4. flags(接收标志)

  • 控制接收行为的标志,通常设为 0(默认行为)。常见非零标志:
    • MSG_PEEK:“偷看” 数据(读取数据但不从套接字缓冲区移除,下次调用仍能读到该数据),用于预览数据。
    • MSG_OOB:接收带外数据(仅用于 TCP 紧急数据,UDP 中无意义)。
    • MSG_WAITALL:等待直到接收满 len 字节数据(但可能被信号中断,不保证一定填满)。

5. src_addr(发送方地址)

  • 指向一个地址结构(如 struct sockaddr_in 用于 IPv4)的指针,用于存储发送方的网络地址(IP + 端口)。
  • 若不需要获取发送方地址,可设为 NULL(但 UDP 通常需要,否则无法回复)。

6. addrlen(地址长度)

  • 输入输出参数
    • 输入时:指定 src_addr 指向的地址结构的字节长度(如 sizeof(struct sockaddr_in))。
    • 输出时:实际存储的发送方地址结构的长度(可能小于输入值,通常忽略差异)。
  • 若 src_addr 为 NULL,此参数也需设为 NULL

返回值

  • 成功:返回实际接收的字节数(0 通常表示连接关闭,但 UDP 无连接,0 可能是对方发送了空数据报)。
  • 失败
    • Linux 中返回 -1,并设置 errno(如 EINTR 被信号中断、EAGAIN 非阻塞模式无数据)。
    • Windows 中返回 SOCKET_ERROR,需通过 WSAGetLastError() 获取错误码。
2.2 sendto函数

#include <sys/socket.h>
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags, const struct sockaddr *dest_addr, socklen_t addrlen);

sendto 是网络编程中用于向指定目标地址发送无连接协议(如 UDP)数据报的系统调用,与 recvfrom 对应。由于 UDP 是无连接的,每次发送数据都需要显式指定目标的网络地址(IP + 端口),sendto 正是为此设计的核心函数。

参数详解

1.sockfd(套接字描述符)

  • 已创建的 UDP 套接字描述符(Linux 中为 int,Windows 中为 SOCKET),表示通过哪个套接字发送数据。
  • 若为服务器,该套接字需提前通过 bind 绑定本地地址;客户端可直接使用未绑定的套接字(系统会自动分配临时端口)。

2. buf(发送缓冲区)

  • 指向待发送数据的内存区域(如字符串、二进制数据),需提前填充要发送的内容。

3. len(数据长度)

  • 待发送数据的字节数(buf 中有效数据的长度)。
  • 若数据长度超过系统规定的最大 UDP 数据报大小(通常约 65507 字节,受 IP 层 MTU 限制),可能导致数据被分片或丢弃。

4. flags(发送标志)

  • 控制发送行为的标志,通常设为 0(默认行为)。常见非零标志:
    • MSG_DONTROUTE:绕过路由表,仅在本地网络发送(用于调试或特定本地通信)。
    • MSG_OOB:发送带外数据(仅 TCP 有效,UDP 中无意义)。

5. dest_addr(目标地址)

  • 指向一个地址结构(如 struct sockaddr_in 用于 IPv4)的指针,存储接收方的网络地址(IP + 端口)。
  • 地址结构的类型必须与套接字的协议族一致(如 AF_INET 对应 IPv4 地址)。

6. addrlen(地址长度)

  • 指定 dest_addr 指向的地址结构的字节长度(如 sizeof(struct sockaddr_in)),告诉系统如何解析目标地址。

返回值

  • 成功:返回实际发送的字节数(通常等于 len,但在某些情况下可能小于 len,如非阻塞模式下缓冲区不足)。
  • 失败
    • Linux 中返回 -1,并设置 errno(如 EINTR 被信号中断、EAGAIN 非阻塞模式发送缓冲区满)。
    • Windows 中返回 SOCKET_ERROR,需通过 WSAGetLastError() 获取错误码。

但此时我们发现,server服务器只能接受与其ip地址相同的client客户端发出的数据,并无法接收来自其他IP地址的数据,我们若想收到来自其他ip地址的数据,则需要将server服务器bing绑定的ip设置位INADDR_ANY

2.3 INADDR_ANY
server.sin_addr.s_addr = INADDR_ANY;

这表示server服务器会接收来自不同ip地址的数据:

不使用INADDR_ANY时,假设服务器绑定到 192.168.1.100:8080

  • 客户端发送到 192.168.1.100:8080 的数据包:服务器能收到。
  • 客户端发送到 127.0.0.1:8080(本机回环地址):服务器收不到(目标 IP 不匹配)。
  • 客户端发送到 10.0.0.5:8080(本机另一网卡的 IP):服务器收不到(目标 IP 不匹配)。

如果服务器绑定INADDR_ANY:8080,则会接收发送到本机所有 IP 地址(包括 127.0.0.1、所有网卡 IP 等)且目标端口为 8080 的数据包,无需严格匹配某一个 IP。

今天的内容到这里就全部介绍完了,下面是剩余的两个server服务器实现代码:

3. DictServer

实现一个简单的英译汉的网络字典:
dictionary.txt:
apple: 苹果
banana: 香蕉
cat: 猫
dog: 狗
book: 书
pen: 笔
happy: 快乐的
sad: 悲伤的
run: 跑
jump: 跳
teacher: 老师
student: 学生
car: 汽车
bus: 公交车
love: 爱
hate: 恨
hello: 你好
goodbye: 再见
summer: 夏天
winter: 冬天

Dict.hpp:

#pragma once#include <iostream>
#include <fstream>
#include <string>
#include <unordered_map>
#include "Log.hpp"
#include "InetAddr.hpp"const std::string defaultdict = "./dictionary.txt";
const std::string sep = ": ";using namespace LogModule;class Dict
{
public:Dict(const std::string &path = defaultdict) : _dict_path(path){}bool LoadDict(){std::ifstream in(_dict_path);if (!in.is_open()){LOG(LogLevel::DEBUG) << "打开字典: " << _dict_path << " 错误";return false;}std::string line;while (std::getline(in, line)){// "apple: 苹果"auto pos = line.find(sep);if (pos == std::string::npos){LOG(LogLevel::WARNING) << "解析: " << line << " 失败";continue;}std::string english = line.substr(0, pos);std::string chinese = line.substr(pos + sep.size());if (english.empty() || chinese.empty()){LOG(LogLevel::WARNING) << "没有有效内容: " << line;continue;}_dict.insert(std::make_pair(english, chinese));LOG(LogLevel::DEBUG) << "加载: " << line;}in.close();return true;}std::string Translate(const std::string &word, InetAddr &client){auto iter = _dict.find(word);if (iter == _dict.end()){LOG(LogLevel::DEBUG) << "进入到了翻译模块, [" << client.Ip() << " : " << client.Port() << "]# " << word << "->None";return "None";}LOG(LogLevel::DEBUG) << "进入到了翻译模块, [" << client.Ip() << " : " << client.Port() << "]# " << word << "->" << iter->second;return iter->second;}~Dict(){}private:std::string _dict_path; // 路径+文件名std::unordered_map<std::string, std::string> _dict;
};

InetAddr.hpp:

#pragma once#include<iostream>
#include<string>
#include<sys/socket.h>
#include<sys/types.h>
#include<arpa/inet.h>
#include<netinet/in.h>class InetAddr{
public:InetAddr(struct sockaddr_in addr):_addr(addr){_port=ntohs(addr.sin_port);_ip=inet_ntoa(addr.sin_addr);}uint16_t Port(){return _port;}std::string Ip(){return _ip;}~InetAddr(){}
private:struct sockaddr_in _addr;uint16_t _port;std::string _ip;
};

UDP_Server.hpp:

#pragma once#include<iostream>
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<strings.h>
#include<string>
#include<arpa/inet.h>
#include<functional>
#include"Log.hpp"
#include"Dict.hpp"
#include"InetAddr.hpp"using func_t = std::function<std::string(const std::string&,InetAddr&)>;const int defaultfd = -1;using namespace LogModule;class UDPServer{
public:UDPServer(uint16_t port , func_t func):_sockfd(defaultfd),_port(port),_isrunning(false),_func(func){}void Init(){// 1. 创建套接字_sockfd = socket(AF_INET , SOCK_DGRAM , 0);if(_sockfd < 0){LOG(LogLevel::FATAL)<< "socket failed" ;exit(1);} LOG(LogLevel::INFO)<< "socket success,sockfd: "<< _sockfd ;// 2. 绑定socket信息,ip和端口struct sockaddr_in local;bzero(&local , sizeof(local));local.sin_family = AF_INET;// IP信息和端口信息,一定要发送到网络// 本地格式->网络序列local.sin_port = htons(_port);local.sin_addr.s_addr = INADDR_ANY;int n = bind(_sockfd,(struct sockaddr*)&local,sizeof(local));if(n < 0){LOG(LogLevel::FATAL)<< "bind failed" ;exit(1);}LOG(LogLevel::INFO)<< "bind success, ip: " << _ip << ", port: " << _port;}void Start(){_isrunning = true;while(_isrunning){char buffer[1024];struct sockaddr_in peer;socklen_t len = sizeof(peer);// 1. 收消息ssize_t s = recvfrom(_sockfd , buffer , sizeof(buffer) - 1 , 0 , (struct sockaddr*)&peer , &len);if(s > 0){InetAddr client(peer);buffer[s]=0;std::string result=_func(buffer,client);sendto(_sockfd,result.c_str(),result.size(),0,(struct sockaddr*)&peer,len);}        }}~UDPServer(){}
private:int _sockfd;uint16_t _port;std::string _ip;bool _isrunning;func_t _func;
};

UDP_Server.cc:

#include<iostream>
#include<memory>
#include"UDP_Server.hpp"
#include"Dict.hpp"int main(int argc , char* argv[])
{if(argc!=2){std::cerr << "Usage: " << argv[0] << " port" << std::endl;return 1;}uint16_t port = std::stoi(argv[1]);//Enable_Console_Log_Strategy();Dict dict;dict.LoadDict();std::unique_ptr<UDPServer> usvr = std::make_unique<UDPServer>(port,[&dict](const std::string& word,InetAddr& cli)->std::string{return dict.Translate(word,cli);});usvr->Init();usvr->Start();return 0;
}

UDP_Client.cc:

#include <iostream>
#include <string>
#include <cstring>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/socket.h>// ./udpclient server_ip server_port
int main(int argc, char *argv[])
{if (argc != 3){std::cerr << "Usage: " << argv[0] << " port" << std::endl;return 1;}std::string ip = argv[1];uint16_t port = std::stoi(argv[2]);int sockfd = socket(AF_INET, SOCK_DGRAM, 0);if (sockfd < 0){std::cerr<< "socket eorrer"<<std::endl;exit(1);}struct sockaddr_in server;memset(&server,0,sizeof(server));server.sin_family=AF_INET;server.sin_port=htons(port);server.sin_addr.s_addr=inet_addr(ip.c_str());while(true){std::string input;std::cout<<"Please Enter# ";std::getline(std::cin,input);int n=sendto(sockfd,input.c_str(),input.size(),0,(struct sockaddr*)&server,sizeof(server));(void)n;char buffer[1024];struct sockaddr_in peer;socklen_t len=sizeof(peer);int m=recvfrom(sockfd,buffer,sizeof(buffer),0,(struct sockaddr*)&peer,&len);if(m>0){buffer[m]=0;std::cout<<buffer<<std::endl;}}return 0;
}

4. 简单聊天室

UDP_Client.cc:

#include <iostream>
#include <string>
#include <cstring>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/socket.h>
#include "Thread.hpp"using namespace ThreadModule;int sockfd = 0;
uint16_t server_port = 0;
std::string server_ip;
pthread_t id;void Send()
{struct sockaddr_in server;memset(&server, 0, sizeof(server));server.sin_family = AF_INET;server.sin_port = htons(server_port);server.sin_addr.s_addr = inet_addr(server_ip.c_str());const std::string online = "inline";sendto(sockfd, online.c_str(), online.size(), 0, (struct sockaddr *)&server, sizeof(server));while (true){std::string input;std::cout << "Please Enter# "; // 1std::getline(std::cin, input); // 0int n = sendto(sockfd, input.c_str(), input.size(), 0, (struct sockaddr *)&server, sizeof(server));(void)n;if (input == "QUIT"){pthread_cancel(id);break;}}
}void Recv()
{while (true){char buffer[1024];struct sockaddr_in peer;socklen_t len = sizeof(peer);int m = recvfrom(sockfd, buffer, sizeof(buffer) - 1, 0, (struct sockaddr *)&peer, &len);if (m > 0){buffer[m] = 0;std::cerr << buffer << std::endl; // 2}}
}// ./udpclient server_ip server_port
int main(int argc, char *argv[])
{if (argc != 3){std::cerr << "Usage: " << argv[0] << " port" << std::endl;return 1;}std::string ip = argv[1];uint16_t port = std::stoi(argv[2]);int sockfd = socket(AF_INET, SOCK_DGRAM, 0);if (sockfd < 0){std::cerr << "socket eorrer" << std::endl;return 1;}Thread recver(Recv);Thread sender(Send);recver.Start();sender.Start();id = recver.Id();recver.Join();sender.Join();return 0;
}

UDP_Server.hpp:

#pragma once#include <iostream>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <strings.h>
#include <string>
#include <arpa/inet.h>
#include <functional>
#include "Log.hpp"
#include "InetAddr.hpp"using func_t = std::function<void(int sockfd, const std::string &, InetAddr &)>;const int defaultfd = -1;using namespace LogModule;class UDPServer
{
public:UDPServer(uint16_t port, func_t func): _sockfd(defaultfd), _port(port), _isrunning(false), _func(func){}void Init(){// 1. 创建套接字_sockfd = socket(AF_INET, SOCK_DGRAM, 0);if (_sockfd < 0){LOG(LogLevel::FATAL) << "socket failed";exit(1);}LOG(LogLevel::INFO) << "socket success,sockfd: " << _sockfd;// 2. 绑定socket信息,ip和端口struct sockaddr_in local;bzero(&local, sizeof(local));local.sin_family = AF_INET;// IP信息和端口信息,一定要发送到网络// 本地格式->网络序列local.sin_port = htons(_port);local.sin_addr.s_addr = INADDR_ANY;int n = bind(_sockfd, (struct sockaddr *)&local, sizeof(local));if (n < 0){LOG(LogLevel::FATAL) << "bind failed";exit(1);}LOG(LogLevel::INFO) << "bind success, ip: " << _ip << ", port: " << _port;}void Start(){_isrunning = true;while (_isrunning){char buffer[1024];struct sockaddr_in peer;socklen_t len = sizeof(peer);// 1. 收消息ssize_t s = recvfrom(_sockfd, buffer, sizeof(buffer) - 1, 0, (struct sockaddr *)&peer, &len);if (s > 0){InetAddr client(peer);buffer[s] = 0;_func(_sockfd, buffer, client);}}}~UDPServer(){}private:int _sockfd;uint16_t _port;std::string _ip;bool _isrunning;func_t _func;
};

UDP_Server.cc:

#include<iostream>
#include<memory>
#include"UDP_Server.hpp"
#include"ThreadPool.hpp"
#include"Route.hpp"using namespace ThreadPoolModule;using task_t = std::function<void()>;int main(int argc , char* argv[])
{if(argc!=2){std::cerr << "Usage: " << argv[0] << " port" << std::endl;return 1;}uint16_t port = std::stoi(argv[1]);//Enable_Console_Log_Strategy();Route r;auto tp = ThreadPool<task_t>::GetInstance();std::unique_ptr<UDPServer> usvr = std::make_unique<UDPServer>(port, [&r, &tp](int sockfd, const std::string &message, InetAddr&peer){task_t t = std::bind(&Route::MessageRoute, &r, sockfd, message, peer);tp->Enqueue(t);});usvr->Init();usvr->Start();return 0;
}

Route.hpp:

#pragma once#include <iostream>
#include <string>
#include <vector>
#include "Log.hpp"
#include "InetAddr.hpp"using namespace LogModule;class Route
{
public:Route(){}void MessageRoute(int sockfd, const std::string &message, InetAddr &peer){if (!IsExist(peer)){AddUser(peer);}std::string send_message = peer.StringAddr() + "#" + message;for (auto user : _online_user){sendto(sockfd, send_message.c_str(), send_message.size(), 0, (const struct sockaddr *)&user.NetAddr(), sizeof(user.NetAddr()));}if (message == "QUIT"){LOG(LogLevel::INFO) << "删除一个在线用户: " << peer.StringAddr();DeleteUser(peer);}}~Route(){}private:bool IsExist(InetAddr &peer){for (auto &user : _online_user){if (user == peer){return true;}}return false;}void AddUser(InetAddr &peer){LOG(LogLevel::INFO) << "新增一个在线用户: " << peer.StringAddr();_online_user.push_back(peer);}void DeleteUser(InetAddr &peer){for (auto iter = _online_user.begin(); iter != _online_user.end(); iter++){if (*iter == peer){LOG(LogLevel::INFO) << "删除一个在线用户:" << peer.StringAddr() << "成功";_online_user.erase(iter);break;}}}private:std::vector<InetAddr> _online_user;
};

InetAddr.hpp:

#pragma once#include<iostream>
#include<string>
#include<sys/socket.h>
#include<sys/types.h>
#include<arpa/inet.h>
#include<netinet/in.h>class InetAddr{
public:InetAddr(struct sockaddr_in addr):_addr(addr){_port=ntohs(addr.sin_port);_ip=inet_ntoa(addr.sin_addr);}uint16_t Port(){return _port;}std::string Ip(){return _ip;}const struct sockaddr_in &NetAddr() { return _addr; }bool operator==(const InetAddr& addr){return addr._ip==_ip&&addr._port==_port;}std::string StringAddr(){return _ip+":"+std::to_string(_port);}~InetAddr(){}
private:struct sockaddr_in _addr;uint16_t _port;std::string _ip;
};

以上是三个server服务器实例,今天的内容就到这里,我们下期再见!

http://www.dtcms.com/a/586389.html

相关文章:

  • Rust编程进阶 - 如何基于生成器设计一套协程(Coroutine)的方案, 从而方便编写大规模高性能异步程序
  • LangChain 中 ChatPromptTemplate 的几种使用方式
  • 怎么创建企业网站同源大厦 网站建设
  • 3网合一网站天眼企业查询系统官网
  • 网站建设人工智能开发怎样建个人网页免费
  • 1.2.3AOP的底层原理
  • Android 屏幕旋转流程
  • 简述电子商务网站的建站流程佛山app开发公司
  • 精准突破 0.5mm 透明玻璃测量瓶颈 —— 泓川科技激光位移传感器的技术革新与成本优势
  • C++笔记-24-文件读写操作
  • 做网站需要会哪些计算机语言net源码的网站建设步骤
  • 做网站一般都是织梦网站 展示板
  • 设计配色推荐的网站广州深圳外贸公司
  • 怎样做建网站做淘客无锡定制网站制作公司
  • 北京网站建设 奥美通全网营销个人网页设计html代码实现
  • 网站开发多少人下载百度app到手机上
  • 网站进入沙盒期有哪些竞价网站
  • 朝阳市网站制作随州有哪些网站建设的公司
  • 雪球网 MD5_1038 逆向算法分析
  • 车辆类型特征智能识别
  • [法兰西公学院] Esterel A到Z (Gérard Berry 2018)
  • 液压矫平机:给金属板材“舒筋活血”的科学
  • 如何用图片文字做网站地图定位网站开发
  • 简述网站开发的三层架构互联网行业前景如何
  • 蛋白质核转位图像识别与分析系统
  • Javascript函数之函数的返回值?
  • 【常见的Markdown 图标语法】
  • 微网站微名片在线制作logo图片
  • 临沂住房和城乡建设局网站打不开江苏建筑工程招标信息网
  • 【Jenkins】Jenkins配置从节点 - Launch Agent