我们了解了网络编程的大概,今天我们就来使用UDP协议来实现客户端与服务端之间的通信过程:
通过这个框架我们可以的扩展出翻译单词 , 多人聊天的功能。可以说只要实现服务端与客户端的通信,获取到的数据,就可对数据进行各种各样的处理!所以网络通信的基础很重要
我们先来回顾一下UDP socket编程的一些常用接口:
创建socket文件:
NAME
socket - create an endpoint for communication
SYNOPSIS
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
int socket(int domain, int type, int protocol);
domain: 选择通信方式 — 本地通信与网络通信 type: 选择协议— UDP/TCP protocol: 默认使用0、 返回值是创建的socket文件操作符socketfd
bind绑定 ,将socket文件与IP地址绑定和端口号,也就是将进程与文件进行绑定。这样当数据包到达该端口和地址时,操作系统知道应该将数据传递给哪个应用程序。
NAME
bind - bind a name to a socket
SYNOPSIS
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
int bind(int sockfd, const struct sockaddr *addr,
socklen_t addrlen);
绑定时需要传入对应的struct sockaddr结构体指针和空间大小。我们知道其为父类,派生类有两种: sockaddr_in 和 sockaddr_un,按照需求强制类型转换就可以!
发送数据
NAME
send, sendto, sendmsg - send a message on a socket
SYNOPSIS
#include <sys/types.h>
#include <sys/socket.h>
ssize_t send(int sockfd, const void *buf, size_t len, int flags);
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,
const struct sockaddr *dest_addr, socklen_t addrlen);
ssize_t sendmsg(int sockfd, const struct msghdr *msg, int flags);
一般使用sendto函数
获取数据
NAME
recv, recvfrom, recvmsg - receive a message from a socket
SYNOPSIS
#include <sys/types.h>
#include <sys/socket.h>
ssize_t recv(int sockfd, void *buf, size_t len, int flags);
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,
struct sockaddr *src_addr, socklen_t *addrlen);
ssize_t recvmsg(int sockfd, struct msghdr *msg, int flags);
一般使用recvfrom函数,从socket文件中获取数据,并可以得到发送者的信息
认识了这些基础的函数,接下来我们就可以来实现服务器类了!
首先我们先来搭建基础框架:
_sockfd
文件操作符,_localport
端口号 , _localip
IP地址,_isrunning
运行判断符。我们需要的就是这样的一个整体框架,实现我们后面再说;
#include <aio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <memory>
#include <string>
#include <cstring>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <iostream>
#include "Log.hpp"
#include "nocopy.hpp"
const int gsockfd = -1;
const uint16_t glocalport = 8888;
const std::string glocalip = "192.1.1.1";
using namespace log_ns;
class UdpServer:public nocopy
{
public:
UdpServer( std::string ip , uint16_t localport = glocalport) : _sockfd(gsockfd),
_localport(localport),
_localip(ip),
isrunning(false)
{
}
void InitServer()
{
}
void Start()
{
}
~UdpServer()
{
if (_sockfd > gsockfd)
::close(_sockfd);
}
private:
int _sockfd;
uint16_t _localport;
std::string _localip;
bool isrunning = false;
};
这里解决不可拷贝的做法是设置一个父类,将这个父类的拷贝构造,赋值重载都delete,那么作为派生类的UdpServer自然就不能进行拷贝了!
其中还加入了我们之前完成的日志系统
初始化化函数中需要进行以下操作:
创建socket文件,使用UDP协议的网络通信
将socket文件与IP地址和端口号进行绑定! 注意需要struct sockaddr_in
结构体,其中的成员变量要注意格式转换!!!主机序列和网络序列是不同的!!!可以通过以下函数进行转换:
NAME
htonl, htons, ntohl, ntohs - convert values between host and network byte order
SYNOPSIS
#include <arpa/inet.h>
//主机序列转换网络序列
//如果主机字节序是大端字节序,则该函数不执行任何转换;
//如果主机字节序是小端字节序,则该函数将整数的高位字节和低位字节进行交换。
uint32_t htonl(uint32_t hostlong);
//用于确保16位整数在发送到网络之前是按照大端字节序排列的。其工作原理与htonl类似,但针对16位整数。
uint16_t htons(uint16_t hostshort);
//---------------------------------
//用于将从网络接收到的32位整数转换为主机字节序。
//如果主机字节序是大端字节序,则该函数不执行任何转换;
//如果主机字节序是小端字节序,则该函数将整数的高位字节和低位字节进行交换。
uint32_t ntohl(uint32_t netlong);
//用于将从网络接收到的16位整数转换为主机字节序。其工作原理与ntohl类似,但针对16位整数
uint16_t ntohs(uint16_t netshort);
同样IP地址也需要进行转换,从字符串进行转换!!!
void InitServer()
{
// 创建立socket文件 得到文件描述符
_sockfd = socket(AF_INET, SOCK_DGRAM, 0);
if (_sockfd < 0)
{
LOG(FATAL, "socket failed!\n");
exit(SOCKER_FD);
}
LOG(DEBUG, "create socket success , _sockfd:%d \n", _sockfd);
// 创建struct sockaddr_in 结构体对象 先进行清空
struct sockaddr_in local;
memset(&local, 0, sizeof(local));
// 设置通信类型 设置端口号(主机序列转网络序列)
local.sin_family = AF_INET;
local.sin_port = htons(_localport);
// 设置IP地址 int_addr()
local.sin_addr.s_addr = inet_addr(_localip.c_str());
//local.sin_addr.s_addr = INADDR_ANY; // 服务器IP一般设置为0
// bind将套接字与IP端口号进行绑定
int n = ::bind(_sockfd, (struct sockaddr *)&local, sizeof(local));
if (n < 0)
{
LOG(FATAL, "bind failed!\n");
exit(SOCKET_BIND);
}
LOG(DEBUG, "bind socket success\n");
}
启动函数时服务器端的主要的运行过程,进行接收数据和发送数据:
void Start()
{
isrunning = true;
char inbuffer[1024];
while (isrunning)
{
// 获取发送者的IP和端口号
struct sockaddr_in peer;
socklen_t len = sizeof(peer);
// LOG(DEBUG , "开始读入数据!\n");
ssize_t n = recvfrom(_sockfd, inbuffer, sizeof(inbuffer) - 1, 0, (struct sockaddr *)&peer, &len);
// 读取到了数据
if (n > 0)
{
// LOG(DEBUG , "读到了数据!\n");
// 结尾标志
inbuffer[n] = 0;
uint16_t peerport = ntohs(peer.sin_port);
std::string peerip = inet_ntoa(peer.sin_addr);
std::cout << "[" << peerip << ": " << peerport <<"] =#" << inbuffer << std::endl;
std::string str = "[udp_server echo]#";
str += inbuffer;
size_t m = sendto(_sockfd, str.c_str(), str.size(), 0, (struct sockaddr *)&peer, len);
}
else
{
std::cout << "recvfrom , error!" << std::endl;
}
}
}
这样服务器类就写好了,接下来简单处理一下客户端
客户端相对服务端要简单一些:
#include <aio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <memory>
#include <string>
#include <cstring>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <iostream>
#include "Log.hpp"
enum
{
SOCKER_FD = 1,
SOCKET_BIND
};
using namespace log_ns;
int main(int argc, char *argv[])
{
if(argc != 3)
{
std::cerr << "Usage: " << argv[0] << " server-ip server-port" << std::endl;
exit(0);
}
//根据传入的参数获取服务端的IP和端口号
std::string ip = argv[1];-+
int port = std::stoi(argv[2]);
//建立套接字socket
int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
if (sockfd < 0)
{
LOG(FATAL, "socket failed!\n");
exit(SOCKER_FD);
}
LOG(DEBUG, "Client create socket success , _sockfd:%d \n", sockfd);
// client的端口号,一般不让用户自己设定,而是让client OS随机选择?怎么选择,什么时候选择呢?
// client 需要 bind它自己的IP和端口, 但是client 不需要 “显示” bind它自己的IP和端口,
// client 在首次向服务器发送数据的时候,OS会自动给client bind它自己的IP和端口
//设置服务器结构体
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());//ip地址
//循环发送接收数据
while(1)
{
//发送数据
std::string line;
std::cout << "Please Enter: " ;
std::getline(std::cin , line );
//std::cout << "line message is@ " << line << std::endl;
//ssize_t sendto(int sockfd, const void *buf, size_t len, int flags, const struct sockaddr *dest_addr, socklen_t addrlen);
int n = sendto(sockfd , line.c_str() , line.size() , 0 , (struct sockaddr *)&server , sizeof(server));
if(n > 0)
{
//进行获取数据
struct sockaddr_in temp;
socklen_t len = sizeof(temp);
char buffer[512];
//ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,struct sockaddr *src_addr, socklen_t *addrlen);
ssize_t m = recvfrom(sockfd , buffer , sizeof(buffer) - 1 , 0 , (struct sockaddr *)&temp , &len);
if(m > 0)
{
buffer[m] = 0;
std::cout << buffer << std::endl;
}
else
{
std::cout << "m < 0 程序退出 !"<<std::endl;
break;
}
}
else
{
std::cout << "n < 0 程序退出 !"<<std::endl;
break;
}
}
::close(sockfd);
return 0;
}
此时就可以进行通信了!!! 下一篇我们来对通信的基础上进行功能扩展!!!