文章目录
- 十、网络基础
- 5. socket编程
- socket 常见API
- sockaddr结构
- 简单的UDP网络程序
- 未完待续
十、网络基础
5. socket编程
socket 常见API
// 创建 socket 文件描述符 (TCP/UDP, 客户端 + 服务器)
int socket(int domain, int type, int protocol);
// 绑定端口号 (TCP/UDP, 服务器)
int bind(int socket, const struct sockaddr *address,
socklen_t address_len);
// 开始监听socket (TCP, 服务器)
int listen(int socket, int backlog);
// 接收请求 (TCP, 服务器)
int accept(int socket, struct sockaddr* address,
socklen_t* address_len);
// 建立连接 (TCP, 客户端)
int connect(int sockfd, const struct sockaddr *addr,
socklen_t addrlen);
sockaddr结构
socket API是一层抽象的网络编程接口,适用于各种底层网络协议,如IPv4、IPv6,以及后面要讲的UNIX DomainSocket. 然而, 各种网络协议的地址格式并不相同。
IPv4、IPv6地址类型分别定义为常数AF_INET、AF_INET6. 这样,只要取得某种sockaddr结构体的首地址,不需要知道具体是哪种类型的sockaddr结构体,就可以根据地址类型字段确定结构体中的内容。
我们真正在基于IPv4编程时, 使用的数据结构是sockaddr_in; 这个结构里主要有三部分信息: 地址类型,端口号,IP地址。
简单的UDP网络程序
我们需要我们之前写的日志程序:
Log.hpp:
#pragma once
#include <iostream>
#include <fstream>
#include <cstdio>
#include <string>
#include <ctime>
#include <cstdarg>
#include <sys/types.h>
#include <unistd.h>
#include <pthread.h>
#include "LockGuard.hpp"
// 宏定义,用于定义日志格式
#define LOG(level, format, ...) do{LogMessage(__FILE__, __LINE__, gIsSave, level, format, ##__VA_ARGS__);}while (0)
// 将日志输入到文件
#define EnableFile() do{gIsSave = true;}while (0)
// 将日志输出到显示器
#define EnableScreen() do{gIsSave = false;}while (0)
bool gIsSave = false;
// 日志文件名
const std::string logname = "log.txt";
// 枚举日志级别
enum Level
{
DEBUG = 0,
INFO,
WARNING,
ERROR,
FATAL
};
// 保存日志到文件
void SaveFile(const std::string &filename, const std::string &message)
{
std::ofstream out(filename, std::ios::app);
if (!out.is_open())
{
return;
}
out << message;
out.close();
}
// 日志级别转字符串
std::string LevelToString(int level)
{
switch (level)
{
case DEBUG:
return "Debug";
case INFO:
return "Info";
case WARNING:
return "Warning";
case ERROR:
return "Error";
case FATAL:
return "Fatal";
default:
return "Unknown";
}
}
// 获取当前时间字符串
std::string GetTimeString()
{
time_t curr_time = time(nullptr);
struct tm *format_time = localtime(&curr_time);
if (format_time == nullptr)
return "None";
char time_buffer[1024];
snprintf(time_buffer, sizeof(time_buffer), "%d-%d-%d %d:%d:%d",
format_time->tm_year + 1900,
format_time->tm_mon + 1,
format_time->tm_mday,
format_time->tm_hour,
format_time->tm_min,
format_time->tm_sec);
return time_buffer;
}
// 日志锁,同一时刻只能写一个日志
pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
// 日志信息
void LogMessage(std::string filename, int line, bool issave, int level, const char *format, ...)
{
// 日志级别
std::string levelstr = LevelToString(level);
// 时间
std::string timestr = GetTimeString();
// 进程id
pid_t selfid = getpid();
// 日志内容
char buffer[1024];
va_list arg;
va_start(arg, format);
vsnprintf(buffer, sizeof(buffer), format, arg);
va_end(arg);
// 日志格式化
std::string message = "[" + timestr + "]" + "[" + levelstr + "]" +
"[" + std::to_string(selfid) + "]" +
"[" + filename + "]" + "[" + std::to_string(line) + "] " + buffer;
LockGuard lockguard(&lock);
// 输出日志
if (!issave)
{
std::cout << message;
}
else
{
SaveFile(logname, message);
}
}
封装的RAII模式的锁:
LockGuard.hpp:
#ifndef __LOCK_GUARD_HPP__
#define __LOCK_GUARD_HPP__
#include <iostream>
#include <pthread.h>
class LockGuard
{
public:
// 构造函数加锁
LockGuard(pthread_mutex_t *mutex)
:_mutex(mutex)
{
pthread_mutex_lock(_mutex);
}
// 析构函数解锁
~LockGuard()
{
pthread_mutex_unlock(_mutex);
}
private:
pthread_mutex_t *_mutex;
};
#endif
服务端代码:
UdpServer.hpp:
#pragma once
#include <iostream>
#include <string>
#include <cerrno>
#include <cstring>
#include <cstdlib>
#include <strings.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include "Log.hpp"
#include "InetAddr.hpp"
enum
{
SOCKET_ERROR = 1,
BIND_ERROR,
USAGE_ERROR
};
const static int defaultfd = -1;
class UdpServer
{
public:
UdpServer(uint16_t port)
:_sockfd(defaultfd)
,_port(port)
,_isrunning(false)
{}
void InitServer()
{
// 创建 UDP socket 套接字
_sockfd = socket(AF_INET, SOCK_DGRAM, 0);
if (_sockfd < 0)
{
LOG(FATAL, "socket error, %s, %d\n", strerror(errno), errno);
exit(SOCKET_ERROR);
}
LOG(INFO, "socket create success, sockfd: %d\n", _sockfd);
struct sockaddr_in local;
bzero(&local, sizeof(local));
local.sin_family = AF_INET;
// 端口号要经过网络传输,需要转换成网络字节序
local.sin_port = htons(_port);
local.sin_addr.s_addr = 0;
// 绑定本地地址
int n = bind(_sockfd, (struct sockaddr*)&local, sizeof(local));
if (n < 0)
{
LOG(FATAL, "bind error, %s, %d\n", strerror(errno), errno);
exit(BIND_ERROR);
}
LOG(INFO, "socket bind success\n");
}
void Start()
{
// 启动服务器
_isrunning = true;
while (true)
{
// 接收数据
char buffer[1024];
struct sockaddr_in peer;
socklen_t len = sizeof(peer);
ssize_t n = recvfrom(_sockfd, buffer, sizeof(buffer) - 1, 0, (struct sockaddr*)&peer, &len);
if (n > 0)
{
// 收到数据,打印
buffer[n] = '\0';
InetAddr addr(peer);
LOG(DEBUG, "get message from [%s:%d]: %s\n", addr.Ip().c_str(), addr.Port(), buffer);
sendto(_sockfd, buffer, sizeof(buffer), 0, (struct sockaddr*)&peer, len);
}
// sleep(1);
// LOG(DEBUG, "server is running...\n");
}
_isrunning = false;
}
~UdpServer()
{}
private:
int _sockfd;
// std::string _ip;
// 服务器所用端口号
uint16_t _port;
bool _isrunning;
};
客户端代码:
UdpClient.hpp:
#include <iostream>
#include <string>
#include <cstring>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <cstdlib>
void Usage(std::string proc)
{
std::cout << "Usage:\n\t" << proc << " local_ip local_prot\n" << std::endl;
}
int main(int argc, char* argv[])
{
if (argc != 3)
{
Usage(argv[0]);
exit(1);
}
// 获取本地IP地址和端口号
std::string serverip = argv[1];
uint16_t serverport = std::stoi(argv[2]);
int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
if (sockfd < 0)
{
std::cerr << "socket error" << std::endl;
}
// 构建目标主机的socket信息
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());
std::string message;
// 循环发送消息
while (true)
{
// 输入消息
std::cout << "Please Enter# ";
std::getline(std::cin, message);
sendto(sockfd, message.c_str(), message.size(), 0, (struct sockaddr*)&server, sizeof(server));
// 接收消息
struct sockaddr_in peer;
socklen_t len = sizeof(peer);
char buffer[1024];
ssize_t n = recvfrom(sockfd, buffer, sizeof(buffer) - 1, 0, (struct sockaddr*)&peer, &len);
if (n > 0)
{
std::cout << "server echo# " << buffer <<std::endl;
}
}
return 0;
}
发送者数据:
InetAddr.hpp:
#pragma once
#include <iostream>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
class InetAddr
{
private:
void GetAddress(std::string *ip, uint16_t *port)
{
*port = ntohs(_addr.sin_port);
*ip = inet_ntoa(_addr.sin_addr);
}
public:
InetAddr(const struct sockaddr_in &addr) : _addr(addr)
{
GetAddress(&_ip, &_port);
}
std::string Ip()
{
return _ip;
}
uint16_t Port()
{
return _port;
}
~InetAddr()
{}
private:
struct sockaddr_in _addr;
std::string _ip;
uint16_t _port;
};
Main.cc:
#include <iostream>
#include <memory>
#include "UdpServer.hpp"
void Usage(std::string proc)
{
std::cout << "Usage:\n\t" << proc << " local_prot\n" << std::endl;
}
int main(int argc, char* argv[])
{
if (argc != 2)
{
Usage(argv[0]);
exit(USAGE_ERROR);
}
EnableScreen();
// std::string local_ip = argv[1];
uint16_t port = std::stoi(argv[1]);
std::unique_ptr<UdpServer> usvr(new UdpServer(port));
// 初始化服务器
usvr->InitServer();
// 启动服务器
usvr->Start();
return 0;
}
Makefile:
.PHONY:all
all:udpserver udpclient
udpserver:Main.cc
g++ -o $@ $^ -std=c++11
udpclient:UdpClient.cc
g++ -o $@ $^ -std=c++11
.PHONY:clean
clean:
rm -f udpserver udpclient
结果:
服务端输入:./udpserver 端口号 即可
客户端输入:./udpclient 服务器的ip地址(若是本地互联,输入0即可) 端口号 即可
成功通信!