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_ntoa | 将struct 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
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服务器实例,今天的内容就到这里,我们下期再见!
