一、socket编程接口
以下是socket常见的API:

1.1socket套接字(创建套接字文件)

1.2bind绑定接口以及ip地址的转换

1.3recv接口接收消息

1.4popen创建管道和子进程读取命令

二、创建一个服务器(并让它启动)
2.1udp.hpp文件
cpp
#pragma once
#include<stdio.h>
#include<iostream>
#include<memory>
#include<vector>
#include<string.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<string>
#include<arpa/inet.h>
#include<strings.h>
#include<functional>
#include<stdbool.h>
#include "Log.hpp"
typedef std::function<std::string(const std::string &, const std::string& , uint16_t)> func_t;
//也可以写成下面这种形式
//using func_t = std::function<std::string(const std::string&)>;
#define SIZE 1024
extern log lg;
enum
{
SOCKET_ERR = 1,
BIND_ERR = 2
};
//给ip地址设置一个缺省值
std::string defaultip = "0.0.0.0";
//给端口号设置一个缺省值
uint16_t defaultport = 8080;
class UdpServer
{
public:
UdpServer(const uint16_t& port = defaultport, const std::string& ip = defaultip)
:_sockfd(0)
,_port(port)
,_ip(ip)
,_isrunning(false)
{}
void Run(func_t func)//对代码分层解耦
{
_isrunning = true;
char in_buffer[SIZE];
while(_isrunning)
{
struct sockaddr_in client;
socklen_t len = sizeof(client);
ssize_t n = recvfrom(_sockfd, in_buffer, sizeof(in_buffer)-1, 0, (struct sockaddr*)&client, &len);
if(n < 0)
{
lg.logmessage(Warning, "Recbfrom error, errno:%d, erroe string: %s",errno, strerror(errno));
continue;
}
in_buffer[n] = '\0';
//获取客户端的端口号和ip地址
uint16_t clientport = ntohs(client.sin_port);
std::string clientip = inet_ntoa(client.sin_addr);
//处理拿到的数据
std::string info = in_buffer;
std::string echo_string = func(info, clientip, clientport);
std::cout<<echo_string<<std::endl;
//把信息发送回去
sendto(_sockfd, echo_string.c_str(), echo_string.size(), 0 ,(const sockaddr*)&client, len);
}
}
void Init()
{
//1、创建UDP套接字
_sockfd = socket(AF_INET, SOCK_DGRAM, 0);
if(_sockfd < 0)
{
//创建套接字失败
lg.logmessage(Fatal, "socket create error, sockfd: %d",_sockfd);
exit(SOCKET_ERR);
}
else
{
lg.logmessage(Info, "socket create success, sockfd: %d",_sockfd);
}
//2、绑定端口号
struct sockaddr_in local;
bzero(&local, sizeof(local));//把local全部清理成0
local.sin_family = AF_INET;//表明使用IPV4的网络结构
local.sin_port = htons(_port);//需要绑定的端口号,要保证端口号是网络字节序列,因为端口号是要发送给对方的
// local.sin_addr.s_addr = inet_addr(_ip.c_str()); //1、将ip地址的string转换成uint16_t 2、转换成的字节序也必须是网络的字节序
//这里还有一种写法:
local.sin_addr.s_addr = htonl(INADDR_ANY);
//绑定端口号
int n = bind(_sockfd, (const struct sockaddr*)&local, sizeof(local));
if(n < 0)
{
//绑定失败
lg.logmessage(Fatal, "bind error, error:%d, error message:%s", errno, strerror(errno));
exit(BIND_ERR);
}
else
{
lg.logmessage(Info, "bind ssuccess");
}
}
~UdpServer()
{
if(_sockfd > 0)
{
close(_sockfd);
}
}
private:
int _sockfd;//网络文件描述符
uint16_t _port;//服务器的端口号(表明服务器的端口号)
std::string _ip;//确定主机的ip地址
bool _isrunning;//表示服务器是否运行
};
2.2main.cc文件
cpp
#include "UdpServer.hpp"
#define SIZE1 4096
log lg;
void Usage(std::string usage)
{
std::cout << "Usage:" << usage << "port[1024+]" << std::endl;
}
std::string Handler(const std::string &info, const std::string &clientip, uint16_t clientport)
{
std::cout << "[" << clientip << ": " << clientport << "]#" << info << std::endl;
std::string res = "Server get a menssage:";
res += info;
return res;
}
bool SafeCheck(const std::string &cmd)
{
int safe = false;
std::vector<std::string> keyword = {"rm", "mv", "cp", "kill", "sudo", "yum", "unlink", "top", "uninstall"};
for (auto &word : keyword)
{
auto pos = cmd.find(word);
if (pos != std::string::npos)
{
return false;
}
}
return true;
}
std::string ExcuteComand(const std::string &cmd)
{
std::cout << "get a request cmand:" << cmd << std::endl;
// 安全检查
SafeCheck(cmd);
FILE *fp = popen(cmd.c_str(), "r");
if (fp == nullptr)
{
perror("popen faile");
return "error";
}
std::string result;
char buffer[SIZE1];
while (true)
{
char *ok = fgets(buffer, sizeof(buffer), fp);
if (ok == nullptr)
{
break;
}
result += buffer;
}
pclose(fp);
return result;
}
int main(int argc, char *argv[])
{
if (argc != 2)
{
Usage(argv[0]);
exit(0);
}
uint16_t port = std::stoi(argv[1]);
std::unique_ptr<UdpServer> svr(new UdpServer(port));
svr->Init();
svr->Run(Handler);
return 0;
}
2.3运行后以及相关端口号和ip地址的设计细节

三、udp创建一个服务端和一个客户端,并实现通信
3.1服务端的实现
udpserver.hpp文件
cpp
#pragma once
#include<stdio.h>
#include<iostream>
#include<memory>
#include<vector>
#include<string.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<string>
#include<arpa/inet.h>
#include<strings.h>
#include<functional>
#include<stdbool.h>
#include "Log.hpp"
typedef std::function<std::string(const std::string &, const std::string& , uint16_t)> func_t;
//也可以写成下面这种形式
//using func_t = std::function<std::string(const std::string&)>;
#define SIZE 1024
extern log lg;
enum
{
SOCKET_ERR = 1,
BIND_ERR = 2
};
//给ip地址设置一个缺省值
std::string defaultip = "0.0.0.0";
//给端口号设置一个缺省值
uint16_t defaultport = 8080;
class UdpServer
{
public:
UdpServer(const uint16_t& port = defaultport, const std::string& ip = defaultip)
:_sockfd(0)
,_port(port)
,_ip(ip)
,_isrunning(false)
{}
void Run(func_t func)//对代码分层解耦
{
_isrunning = true;
char in_buffer[SIZE];
while(_isrunning)
{
struct sockaddr_in client;
socklen_t len = sizeof(client);
ssize_t n = recvfrom(_sockfd, in_buffer, sizeof(in_buffer)-1, 0, (struct sockaddr*)&client, &len);
if(n < 0)
{
lg.logmessage(Warning, "Recbfrom error, errno:%d, erroe string: %s",errno, strerror(errno));
continue;
}
in_buffer[n] = '\0';
//获取客户端的端口号和ip地址
uint16_t clientport = ntohs(client.sin_port);
std::string clientip = inet_ntoa(client.sin_addr);
//处理拿到的数据
std::string info = in_buffer;
std::string echo_string = func(info, clientip, clientport);
std::cout<<echo_string<<std::endl;
//把信息发送回去
sendto(_sockfd, echo_string.c_str(), echo_string.size(), 0 ,(const sockaddr*)&client, len);
}
}
void Init()
{
//1、创建UDP套接字
_sockfd = socket(AF_INET, SOCK_DGRAM, 0);
if(_sockfd < 0)
{
//创建套接字失败
lg.logmessage(Fatal, "socket create error, sockfd: %d",_sockfd);
exit(SOCKET_ERR);
}
else
{
lg.logmessage(Info, "socket create success, sockfd: %d",_sockfd);
}
//2、绑定端口号
struct sockaddr_in local;
bzero(&local, sizeof(local));//把local全部清理成0
local.sin_family = AF_INET;//表明使用IPV4的网络结构
local.sin_port = htons(_port);//需要绑定的端口号,要保证端口号是网络字节序列,因为端口号是要发送给对方的
// local.sin_addr.s_addr = inet_addr(_ip.c_str()); //1、将ip地址的string转换成uint16_t 2、转换成的字节序也必须是网络的字节序
//这里还有一种写法:
local.sin_addr.s_addr = htonl(INADDR_ANY);
//绑定端口号
int n = bind(_sockfd, (const struct sockaddr*)&local, sizeof(local));
if(n < 0)
{
//绑定失败
lg.logmessage(Fatal, "bind error, error:%d, error message:%s", errno, strerror(errno));
exit(BIND_ERR);
}
else
{
lg.logmessage(Info, "bind ssuccess");
}
}
~UdpServer()
{
if(_sockfd > 0)
{
close(_sockfd);
}
}
private:
int _sockfd;//网络文件描述符
uint16_t _port;//服务器的端口号(表明服务器的端口号)
std::string _ip;//确定主机的ip地址
bool _isrunning;//表示服务器是否运行
};
main.cc文件:
cpp
#include "UdpServer.hpp"
#define SIZE1 4096
log lg;
void Usage(std::string usage)
{
std::cout << "Usage:" << usage << "port[1024+]" << std::endl;
}
std::string Handler(const std::string &info, const std::string &clientip, uint16_t clientport)
{
std::cout << "[" << clientip << ": " << clientport << "]#" << info << std::endl;
std::string res = "Server get a menssage:";
res += info;
return res;
}
bool SafeCheck(const std::string &cmd)
{
int safe = false;
std::vector<std::string> keyword = {"rm", "mv", "cp", "kill", "sudo", "yum", "unlink", "top", "uninstall"};
for (auto &word : keyword)
{
auto pos = cmd.find(word);
if (pos != std::string::npos)
{
return false;
}
}
return true;
}
std::string ExcuteComand(const std::string &cmd)
{
std::cout << "get a request cmand:" << cmd << std::endl;
// 安全检查
SafeCheck(cmd);
FILE *fp = popen(cmd.c_str(), "r");
if (fp == nullptr)
{
perror("popen faile");
return "error";
}
std::string result;
char buffer[SIZE1];
while (true)
{
char *ok = fgets(buffer, sizeof(buffer), fp);
if (ok == nullptr)
{
break;
}
result += buffer;
}
pclose(fp);
return result;
}
int main(int argc, char *argv[])
{
if (argc != 2)
{
Usage(argv[0]);
exit(0);
}
uint16_t port = std::stoi(argv[1]);
std::unique_ptr<UdpServer> svr(new UdpServer(port));
svr->Init();
svr->Run(Handler);
return 0;
}
3.2客户端的实现
udpClient.cc文件:
cpp
#include <iostream>
#include <memory>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string>
#include <arpa/inet.h>
#include <strings.h>
#include <unistd.h>
#include <cstdlib>
void Usage(std::string proc)
{
std::cout << "\n\rUsage:" << proc << "serverip serverport\n"
<< std::endl;
}
// ./udpClient serverip serverport
int main(int argc, char *argv[])
{
if (argc != 3)
{
Usage(argv[0]);
exit(0);
}
std::string serverip = argv[1];
uint16_t serverport = std::stoi(argv[2]);
// 构建客户端套接字信息
struct sockaddr_in server;
bzero(&server, sizeof(server));
server.sin_family = AF_INET;
server.sin_port = htons(serverport);
server.sin_addr.s_addr = inet_addr(serverip.c_str());
socklen_t len = sizeof(server);
int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
if (sockfd < 0)
{
std::cout << "socket fail" << std::endl;
return 1;
}
// 客户端也是需要绑定,只不过不需要用户显示的绑定,一般由操作系统随机绑定、
// 一个端口号只能被一个进程绑定,对于客户端和服务端都是如此
// 其实客户端的端口号是多少不重要,只要能保证主机上的唯一性就行
// 系统会在首次发送细心的时候给绑定
std::string messager;
char buffer[1024];
while (true)
{
// 1、拿到信息
std::cout << "please enter:";
std::getline(std::cin, messager);
std::cout << messager << std::endl;
// 2、发送信息
sendto(sockfd, messager.c_str(), messager.size(), 0, (struct sockaddr *)&server, len);
// 3、接收消息
struct sockaddr_in temp;
socklen_t ln = sizeof(temp);
ssize_t n = recvfrom(sockfd, buffer, 1023, 0, (struct sockaddr *)&temp, &ln);
if (n > 0)
{
buffer[n] = 0;
std::cout << buffer << std::endl;
}
}
close(sockfd);
return 0;
}
3.3运行结果:

四、实现Linux(服务端)和windows通信(客户端)
4.1Linux服务端的代码
udpserver.hpp文件:
cpp
#pragma once
#include<stdio.h>
#include<iostream>
#include<memory>
#include<vector>
#include<string.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<string>
#include<arpa/inet.h>
#include<strings.h>
#include<functional>
#include<stdbool.h>
#include "Log.hpp"
typedef std::function<std::string(const std::string &, const std::string& , uint16_t)> func_t;
//也可以写成下面这种形式
//using func_t = std::function<std::string(const std::string&)>;
#define SIZE 1024
extern log lg;
enum
{
SOCKET_ERR = 1,
BIND_ERR = 2
};
//给ip地址设置一个缺省值
std::string defaultip = "0.0.0.0";
//给端口号设置一个缺省值
uint16_t defaultport = 8080;
class UdpServer
{
public:
UdpServer(const uint16_t& port = defaultport, const std::string& ip = defaultip)
:_sockfd(0)
,_port(port)
,_ip(ip)
,_isrunning(false)
{}
void Run(func_t func)//对代码分层解耦
{
_isrunning = true;
char in_buffer[SIZE];
while(_isrunning)
{
struct sockaddr_in client;
socklen_t len = sizeof(client);
ssize_t n = recvfrom(_sockfd, in_buffer, sizeof(in_buffer)-1, 0, (struct sockaddr*)&client, &len);
if(n < 0)
{
lg.logmessage(Warning, "Recbfrom error, errno:%d, erroe string: %s",errno, strerror(errno));
continue;
}
in_buffer[n] = '\0';
//获取客户端的端口号和ip地址
uint16_t clientport = ntohs(client.sin_port);
std::string clientip = inet_ntoa(client.sin_addr);
//处理拿到的数据
std::string info = in_buffer;
std::string echo_string = func(info, clientip, clientport);
std::cout<<echo_string<<std::endl;
//把信息发送回去
sendto(_sockfd, echo_string.c_str(), echo_string.size(), 0 ,(const sockaddr*)&client, len);
}
}
void Init()
{
//1、创建UDP套接字
_sockfd = socket(AF_INET, SOCK_DGRAM, 0);
if(_sockfd < 0)
{
//创建套接字失败
lg.logmessage(Fatal, "socket create error, sockfd: %d",_sockfd);
exit(SOCKET_ERR);
}
else
{
lg.logmessage(Info, "socket create success, sockfd: %d",_sockfd);
}
//2、绑定端口号
struct sockaddr_in local;
bzero(&local, sizeof(local));//把local全部清理成0
local.sin_family = AF_INET;//表明使用IPV4的网络结构
local.sin_port = htons(_port);//需要绑定的端口号,要保证端口号是网络字节序列,因为端口号是要发送给对方的
// local.sin_addr.s_addr = inet_addr(_ip.c_str()); //1、将ip地址的string转换成uint16_t 2、转换成的字节序也必须是网络的字节序
//这里还有一种写法:
local.sin_addr.s_addr = htonl(INADDR_ANY);
//绑定端口号
int n = bind(_sockfd, (const struct sockaddr*)&local, sizeof(local));
if(n < 0)
{
//绑定失败
lg.logmessage(Fatal, "bind error, error:%d, error message:%s", errno, strerror(errno));
exit(BIND_ERR);
}
else
{
lg.logmessage(Info, "bind ssuccess");
}
}
~UdpServer()
{
if(_sockfd > 0)
{
close(_sockfd);
}
}
private:
int _sockfd;//网络文件描述符
uint16_t _port;//服务器的端口号(表明服务器的端口号)
std::string _ip;//确定主机的ip地址
bool _isrunning;//表示服务器是否运行
};
main.cc文件:
cpp
#include "UdpServer.hpp"
#define SIZE1 4096
log lg;
void Usage(std::string usage)
{
std::cout << "Usage:" << usage << "port[1024+]" << std::endl;
}
std::string Handler(const std::string &info, const std::string &clientip, uint16_t clientport)
{
std::cout << "[" << clientip << ": " << clientport << "]#" << info << std::endl;
std::string res = "Server get a menssage:";
res += info;
return res;
}
bool SafeCheck(const std::string &cmd)
{
int safe = false;
std::vector<std::string> keyword = {"rm", "mv", "cp", "kill", "sudo", "yum", "unlink", "top", "uninstall"};
for (auto &word : keyword)
{
auto pos = cmd.find(word);
if (pos != std::string::npos)
{
return false;
}
}
return true;
}
std::string ExcuteComand(const std::string &cmd)
{
std::cout << "get a request cmand:" << cmd << std::endl;
// 安全检查
SafeCheck(cmd);
FILE *fp = popen(cmd.c_str(), "r");
if (fp == nullptr)
{
perror("popen faile");
return "error";
}
std::string result;
char buffer[SIZE1];
while (true)
{
char *ok = fgets(buffer, sizeof(buffer), fp);
if (ok == nullptr)
{
break;
}
result += buffer;
}
pclose(fp);
return result;
}
int main(int argc, char *argv[])
{
if (argc != 2)
{
Usage(argv[0]);
exit(0);
}
uint16_t port = std::stoi(argv[1]);
std::unique_ptr<UdpServer> svr(new UdpServer(port));
svr->Init();
svr->Run(Handler);
return 0;
}
4.2windows客户端代码:
cpp
#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
#include<stdio.h>
#include<stdlib.h>
#include <WinSock2.h>
#include<Windows.h>
#include<string>
#include<string.h>
#pragma warning(disable:4996)
#pragma comment(lib, "ws2_32.lib")
uint16_t serverport = 8080;
std::string serverip = "182.254.175.111";
int main()
{
WSADATA wsd;
WSAStartup(MAKEWORD(2, 2), &wsd);
// 构建客户端套接字信息
struct sockaddr_in server;
memset(&server, 0, sizeof(server));
server.sin_family = AF_INET;
server.sin_port = htons(serverport);
server.sin_addr.s_addr = inet_addr(serverip.c_str());
int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
if (sockfd < SOCKET_ERROR)
{
std::cout << "socket fail" << std::endl;
return 1;
}
std::string messager;
char buffer[1024];
while (true)
{
// 1、拿到信息
std::cout << "please enter:";
std::getline(std::cin, messager);
std::cout << messager << std::endl;
// 2、发送信息
sendto(sockfd, messager.c_str(), messager.size(), 0, (struct sockaddr*)&server, sizeof(server));
// 3、接收消息
struct sockaddr_in temp;
int ln = sizeof(temp);
int n = recvfrom(sockfd, buffer, 1023, 0, (struct sockaddr*)&temp, &ln);
if (n > 0)
{
buffer[n] = 0;
std::cout << buffer << std::endl;
}
}
closesocket(sockfd);
WSACleanup();
return 0;
}
4.3运行结果展示

五、创建一个聊天室,完成群聊
UDP套接字是全双工的,是允许同时被写入和同时读出的,所以我们可以创建两个或者多个线程进行同时的收发信息。
5.1客户端---创建两个线程实现接收消息和发送消息
udpClient.cc文件:
cpp
#include"Terminal.hpp"
#include <iostream>
#include <memory>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string>
#include <arpa/inet.h>
#include <strings.h>
#include <unistd.h>
#include <cstdlib>
#include<pthread.h>
void Usage(std::string proc)
{
std::cout << "\n\rUsage:" << proc << "serverip serverport\n"
<< std::endl;
}
struct ThreadData
{
struct sockaddr_in server;
int sockfd;
};
void *recv_messager(void *args)
{
//让收发消息分离,直接终端重定向
//OpenTerminal();
ThreadData* td = static_cast<ThreadData*>(args);
char buffer[1024];
while(true)
{
// 接收消息
struct sockaddr_in temp;
socklen_t ln = sizeof(temp);
ssize_t n = recvfrom(td->sockfd, buffer, 1023, 0, (struct sockaddr *)&temp, &ln);
if (n > 0)
{
buffer[n] = 0;
std::cerr << buffer << std::endl;
}
}
}
void *send_messager(void *args)
{
ThreadData* td = static_cast<ThreadData*>(args);
std::string messager;
socklen_t len = sizeof(td->server);
while(true)
{
// 1、拿到信息
std::cout << "please enter@:";
std::getline(std::cin, messager);
std::cout << messager << std::endl;
// 2、发送信息
sendto(td->sockfd, messager.c_str(), messager.size(), 0, (struct sockaddr *)&td->server, len);
}
}
int main(int argc, char *argv[])
{
if (argc != 3)
{
Usage(argv[0]);
exit(0);
}
std::string serverip = argv[1];
uint16_t serverport = std::stoi(argv[2]);
struct ThreadData td;
// 构建客户端套接字信息
bzero(&td.server, sizeof(td.server));
td.server.sin_family = AF_INET;
td.server.sin_port = htons(serverport);
td.server.sin_addr.s_addr = inet_addr(serverip.c_str());
td.sockfd = socket(AF_INET, SOCK_DGRAM, 0);
if (td.sockfd < 0)
{
std::cout << "socket fail" << std::endl;
return 1;
}
//创建收发信息的两个线程
pthread_t recvr;
pthread_t sender;
pthread_create(&recvr, nullptr, recv_messager, &td);
pthread_create(&sender, nullptr, send_messager, &td);
pthread_join(recvr,nullptr);
pthread_join(sender, nullptr);
close(td.sockfd);
return 0;
}
5.2服务端实现接收信息和把信息发给所有人
udpServer.hpp文件:
cpp
#pragma once
#include <stdio.h>
#include <iostream>
#include <memory>
#include <vector>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string>
#include <arpa/inet.h>
#include <strings.h>
#include <functional>
#include <stdbool.h>
#include <unordered_map>
#include "Log.hpp"
typedef std::function<std::string(const std::string &, const std::string &, uint16_t)> func_t;
#define SIZE 1024
extern log lg;
enum
{
SOCKET_ERR = 1,
BIND_ERR = 2
};
// 给ip地址设置一个缺省值
std::string defaultip = "0.0.0.0";
// 给端口号设置一个缺省值
uint16_t defaultport = 8080;
class UdpServer
{
public:
UdpServer(const uint16_t &port = defaultport, const std::string &ip = defaultip)
: _sockfd(0), _port(port), _ip(ip), _isrunning(false)
{
}
void CheckUser(struct sockaddr_in &client)
{
uint16_t clientport = ntohs(client.sin_port);
std::string clientip = inet_ntoa(client.sin_addr);
auto iterator = _online_user.find(clientip);
if (iterator == _online_user.end())
{
// 添加用户
_online_user.insert({clientip, client});
std::cout << "[" << clientip << ":" << clientport << "]:add online user success" << std::endl;
}
}
void BoardCast(const std::string &info, const std::string clientip, const uint16_t &clientport)
{
for (const auto user : _online_user)
{
std::string message = "[";
message += clientip;
message += ":";
message += std::to_string(clientport);
message += "]#";
message += info;
// 把信息发送回去
socklen_t len = sizeof(user.second);
sendto(_sockfd, message.c_str(), message.size(), 0, (struct sockaddr *)(&user.second), len);
}
}
void Run() // 对代码分层解耦
{
_isrunning = true;
char in_buffer[SIZE];
while (_isrunning)
{
struct sockaddr_in client;
socklen_t len = sizeof(client);
ssize_t n = recvfrom(_sockfd, in_buffer, sizeof(in_buffer) - 1, 0, (struct sockaddr *)&client, &len);
if (n < 0)
{
lg.logmessage(Warning, "Recbfrom error, errno:%d, erroe string: %s", errno, strerror(errno));
continue;
}
// 获取客户端的端口号和ip地址
uint16_t clientport = ntohs(client.sin_port);
std::string clientip = inet_ntoa(client.sin_addr);
// 检查该客户是否已经在表中,不在就在表中添加用户
CheckUser(client);
in_buffer[n] = '\0';
// 处理拿到的数据
std::string info = in_buffer;
// 给所有人广播拿到的消息
BoardCast(info, clientip, clientport);
}
}
void Init()
{
// 1、创建UDP套接字
_sockfd = socket(AF_INET, SOCK_DGRAM, 0);
if (_sockfd < 0)
{
// 创建套接字失败
lg.logmessage(Fatal, "socket create error, sockfd: %d", _sockfd);
exit(SOCKET_ERR);
}
else
{
lg.logmessage(Info, "socket create success, sockfd: %d", _sockfd);
}
// 2、绑定端口号
struct sockaddr_in local;
bzero(&local, sizeof(local)); // 把local全部清理成0
local.sin_family = AF_INET; // 表明使用IPV4的网络结构
local.sin_port = htons(_port); // 需要绑定的端口号,要保证端口号是网络字节序列,因为端口号是要发送给对方的
// local.sin_addr.s_addr = inet_addr(_ip.c_str()); //1、将ip地址的string转换成uint16_t 2、转换成的字节序也必须是网络的字节序
// 这里还有一种写法:
local.sin_addr.s_addr = htonl(INADDR_ANY);
// 绑定端口号
int n = bind(_sockfd, (const struct sockaddr *)&local, sizeof(local));
if (n < 0)
{
// 绑定失败
lg.logmessage(Fatal, "bind error, error:%d, error message:%s", errno, strerror(errno));
exit(BIND_ERR);
}
else
{
lg.logmessage(Info, "bind ssuccess");
}
}
~UdpServer()
{
if (_sockfd > 0)
{
close(_sockfd);
}
}
private:
int _sockfd; // 网络文件描述符
uint16_t _port; // 服务器的端口号(表明服务器的端口号)
std::string _ip; // 确定主机的ip地址
bool _isrunning; // 表示服务器是否运行
std::unordered_map<std::string, struct sockaddr_in> _online_user;
};
main.cc文件:
cpp
#include "UdpServer.hpp"
#define SIZE1 4096
log lg;
void Usage(std::string usage)
{
std::cout << "Usage:" << usage << "port[1024+]" << std::endl;
}
int main(int argc, char *argv[])
{
if (argc != 2)
{
Usage(argv[0]);
exit(0);
}
uint16_t port = std::stoi(argv[1]);
std::unique_ptr<UdpServer> svr(new UdpServer(port));
svr->Init();
svr->Run();
return 0;
}
5.3在运行时,如果想看见收发信息的分离,可以使用以下两种方法:
5.3.1使用一个函数,进行重定向(如以下Terminal.hpp文件)
cpp
#include <iostream>
#include <string>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
std::string terminal = "/dev/pts/2";
int OpenTerminal()
{
int fd = open(terminal.c_str(), O_WRONLY);
if (fd < 0)
{
std::cerr << "open terminal error!!!" << std::endl;
return 1;
}
dup2(fd, 2);
return 0;
}
5.3.2直接在运行客户端的时候进行重定向

总结:
(1)UDP协议支持全双工,一个sockfd,既可以读取,又可以写入,对于客户端和服务端同样如此
(2)多线程客户端,同时读取和写入

六、地址转换函数
以上只是基于IPv4的socket网络编程,sockaddrin中的成员struct in_addr sin_addr表示32位的IP地址。但是我们通常用点分十进制的字符串表示IP地址,以下函数可以在字符串表示和in_addr表示之间转换
6.1字符串转in_addr的函数:

6.2in_addr转字符串的函数:

其中inet_pton和inet ntop不仅可以转换IPv4的in_addr,还可以转换IPv6的in6 addr,因此函数接口是void *addrptr
6.3inet_ntoa
inet ntoa这个函数返回了一个char*,很显然是这个函数自己在内部为我们申请了一块内存来保存ip的结果。并且这个函数是不需要调用者手动释放的。
注意:因为inetntoa会把结果放到自己内部的一个静态存储区,这样第二次调用时的结果会覆盖掉上一次的结果那么如果有多个线程调用inet_ntoa,是否会出现异常情况呢?
在APUE中,明确提出inet_ntoa不是线程安全的函数。但是在centos7上测试,并没有出现问题,可能内部的实现加了互斥锁。综上所述,在多线程环境下,推荐使用inet ntop,这个函数由调用者提供一个缓冲区保存结果,可以规避线程安全问题。