UDP 网络程序
接口
// UDP专用,发送数据到指定的IP地址和端口
int sendto(int sockfd, const void * msg, int len,
unsigned int flags, const struct sockaddr * dst_addr, int addrlen);
// UDP专用,接收数据,返回数据远端的IP地址和端口
int recvfrom(int sockfd, void * buf, size_t len,
int flags, struct sockaddr * src_addr, int * addrlen);
从网络中接收数据,把收到数据在本地打印出来,再经拼凑,返回给客户端,完成一个简单的echo server。
- udpServer.hpp
#pragma once
#include <iostream>
#include <cstdio>
#include <string>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
class udpServer{
private:
int port;
int sock;
public:
udpServer(int _port = 8080)
: port(_port)
{}
void initServer()
{
// 创建套接字,AF_INET:所创建的套接字 底层通信标准使用的是IPV4
sock = socket(AF_INET, SOCK_DGRAM, 0);// 默认AF_INET, UDP SOCK_DGRAM
std::cout<< "sock: "<< sock <<std::endl;
struct sockaddr_in local;
//填入
local.sin_family = AF_INET;
local.sin_port = htons(port); // 主机序列转换为网络序列
local.sin_addr.s_addr = INADDR_ANY; // 这个宏表示本地的任意IP地址
// 为什么需要绑定?创建完套接字本质是创建了文件,并没有和网络信息关联,是需要填入ip和端口号的。
// 绑定,把内存文件和网络信息关联起来,最重要的两个关键信息:ip和端口号,绑定的目的:要让别人能看到我
// 1、填充进当前服务器的ip地址和端口号
// 2、不同套接字的操作方法不一样,要初始化底层的函数指针,让它指向不同的方法
if(bind(sock, (struct sockaddr*)&local, sizeof(local)) < 0)
{
std::cerr<< "bind error \n";
exit(1);
}
}
//echo server
void start()
{
char msg[64];
for(;;) // 从网络中接收数据,把收到数据在本地打印出来,再经拼凑,返回给客户端,完成一个简单的echo server
{
msg[0] = '\0'; // 清空字符串
struct sockaddr_in end_point; // 对端
socklen_t len = sizeof(end_point);
ssize_t s = recvfrom(sock, msg, sizeof(msg)-1, 0, (struct sockaddr*)&end_point, &len); // 接收数据
if(s>0)
{
// 想知道是哪个ip/port连的服务器
char buf[16];
sprintf(buf, "%d", ntohs(end_point.sin_port));// 网络序列转主机序列,并把整数转字符串
// 从网络中读出来的sin_addr是四字节ip,要把它转换成点分十进制ip;同时在转换过程中,把网络序列转主机
std::string cli = inet_ntoa(end_point.sin_addr);
cli += ":";
cli += buf;
msg[s] = '\0';
std::cout << "ip:port为 " << cli << "发来消息: "<<msg<<std::endl;
std::string echo_string = msg;
echo_string += "[服务器已收到";
echo_string += cli;
echo_string += "发来的消息]";
sendto(sock, echo_string.c_str(), echo_string.size(), 0, (struct sockaddr*)&end_point, len); //发送数据
}
}
}
~udpServer()
{
close(sock);
}
};
- udpServer.cc
#include"udpServer.hpp"
void Usage(std::string proc)
{
std::cout << "输入格式:" << proc << " local_port" << std::endl;
}
int main(int argc, char* argv[])
{
if(argc != 2){
Usage(argv[0]);
exit(1);
}
udpServer* up = new udpServer(atoi(argv[1]));
up->initServer();
up->start();
delete up;
return 0;
}
- udpClient.hpp
#pragma once
#include <iostream>
#include <string>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
class udpClient{
private:
std::string ip;
int port;
int sock;
public:
// ip, port 用的是server的ip port
udpClient(std::string _ip = "127.0.0.1", int _port = 8080)
:ip(_ip), port(_port)
{}
void initClient()
{
// 客户端不需要绑定,不代表客户端不需要ip和端口
// 服务器是被动的,需要被客户端寻找,而不是主动寻找客户端。绑定的意义就是让别人找到我~
// 原因是:服务器对应的ip和port不可以轻易被更改,必须是确定的,众所周知的;如果改了,客户端就找不到了,无法访问。
// 客户端在进行bind的时候,很容易冲突,进而导致客户端无法启动!
// 客户端需要唯一性,但不需要明确必须是哪个端口号;在udp下,系统会自动对client进行ip和端口号的绑定
// 系统最清楚哪个ip号和端口号还没有被占,因此它来分配不会出现冲突
sock = socket(AF_INET, SOCK_DGRAM, 0);
std::cout<<"sock: "<< sock << std::endl;
}
//echo server
void start()
{
std::string msg;
//远端服务器 ——填的是服务器的端口IP,表明给服务器发送数据
struct sockaddr_in peer;
peer.sin_family = AF_INET;
peer.sin_port = htons(port);
peer.sin_addr.s_addr = inet_addr(ip.c_str());
for(;;)
{
std::cout << "please enter# :";
std::cin >> msg;
if(msg == "quit")
{
break;
}
sendto(sock, msg.c_str(), msg.size(), 0, (struct sockaddr*)&peer, sizeof(peer)); // 往服务器里发数据
char echo[128];
ssize_t s = recvfrom(sock, echo, sizeof(echo)-1, 0, nullptr, nullptr); // 接收数据
if(s>0)
{
echo[s] = 0;
std::cout<< "server# "<< echo << std::endl;
}
}
}
~udpClient()
{
close(sock);
}
};
- udpClient.cc
#include"udpClient.hpp"
void Usage(std::string proc)
{
std::cout << "输入格式:" << proc << " server_ip server_port" << std::endl;
}
int main(int argc, char* argv[])
{
if(argc != 3){
Usage(argv[0]);
exit(1);
}
udpClient* uc = new udpClient(argv[1], atoi(argv[2]));
uc->initClient();
uc->start();
delete uc;
}
- Makefile
.PHONY:all
all:udpClient udpServer
udpClient:udpClient.cc
g++ -o $@ $^ -std=c++11
udpServer:udpServer.cc
g++ -o $@ $^
.PHONY:clean
clean:
rm -f udpClient udpServer
效果 ↓