大家好,上次我们简单介绍了Linux网络的知识,今天我们来继续上次的学习。
目录
[1. socket 编程接口](#1. socket 编程接口)
[1.1 socket函数](#1.1 socket函数)
[1.2 bind函数](#1.2 bind函数)
[1.3 构造简单UDP服务器](#1.3 构造简单UDP服务器)
[1.4 字节序转换函数](#1.4 字节序转换函数)
[1.5 IPv4 地址操作工具](#1.5 IPv4 地址操作工具)
[2. echo server](#2. echo server)
[2.1 recvfrom函数](#2.1 recvfrom函数)
[2.2 sendto函数](#2.2 sendto函数)
[2.3 INADDR_ANY](#2.3 INADDR_ANY)
[3. DictServer](#3. DictServer)
[4. 简单聊天室](#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)); //填充字段设为 0
local.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. 创建socket
int 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# "; // 1
std::getline(std::cin, input); // 0
int 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服务器实例,今天的内容就到这里,我们下期再见!