目录
前言
1.接口介绍
2.编写服务器
3.编写客户端
4.测试
总结
在这篇文章中为大家介绍如何通过编码实现数据通信,实现思路是根据前面介绍的网络编程函数编写一个服务端和客户端,实现客户端和服务端双方通信
创建套接字
#include
#include
int socket(int domain, int type, int protocol);
domain:网络通信采用AF_INET
type:提供的服务类型,包含TCP流式服务和UDP数据包服务
实现UDP服务器参数设置为SOCK_DGRAM
protocol:采用的协议,一般设置为0,前面的两个参数决定了第三个参数
创建套接字的本质是告诉操作系统要进行网络通信,然后由操作系统在底层维护一个文件缓冲区,创建成功返回该文件描述符,后续数据通信,上层选择将数据放到该文件缓冲区中或者从文件缓冲区中去读数据
返回值:打开的文件描述符
绑定端口号
#include
#include
int bind(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
sockfd : 创建套接字的返回值
struct sockaddr* addr:使用时填充该结构体的字段
a.协议字段
b.端口号
端口号在进行填充的时候需要先进行转换为网络字节序
#include
uint16_t htons(uint16_t hostshort); 获取对方传输过来的端口号时需要将网络字节序转化为本机
#include
uint16_t ntohs(uint16_t netshort); c.IP地址
一般在服务器的实现中IP地址是不需要绑定的
在客户端中发送数据需要填充服务端的IP地址,因为在用户层IP地址是字符串类型,在网络上传输时IP地址是整型数据,所以需要进行转换
#include
#include #include in_addr_t inet_addr(const char *cp); 需要获取对方网络中传输过来的IP地址需要将整型转换为字符串类型
#include
#include #include char *inet_ntoa(struct in_addr in); 注:因为是网络通信,所以在填充好字段之后需要在传参的时候强转为sockaddr_in
addrlen:结构体的大小
接受数据
#include
#include
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,
struct sockaddr *src_addr, socklen_t *addrlen);
sockfd:文件描述符
buf:用户缓冲区
len:用户缓冲区的大小
flags:设置为0表示阻塞式调用
src_addr:输出型参数,用来获取对方的IP地址和port
addrlen:结构体的大小
发送数据
#include
#include
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,
const struct sockaddr *dest_addr, socklen_t addrlen);
sockfd:文件描述符
buf:用户缓冲区
len:用户缓冲区的大小
flags:设置为0表示阻塞式调用
dest_addr:填充需要发送对端主机的信息,包含IP地址和port端口号
addrlen:结构体的大小
有了上面这些的这些接口,下面我们就可以正式编写一个客户端和一个服务端了
形成makefile
.PHONY:all
all:udpServer udpClient
udpServer:udpServer.cc
g++ -o $@ $^ -std=c++11
udpClient:udpClient.cc
g++ -o $@ $^ -std=c++11
.PHONY:clean
clean:
rm -rf udpServer udpClient
编写服务器:udpServer.hpp
#pragma once
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
namespace Server
{
using namespace std;
const static string defaultIP = "0.0.0.0";
enum {USAGE_ERR = 1, SOCKET_ERR, BIND_ERR};
class udpServer
{
public:
udpServer(uint16_t port, const string &ip = defaultIP)
:_port(port),_ip(ip),_sockfd(-1)
{}
void initServer()
{
//1.创建socket
_sockfd = socket(AF_INET,SOCK_DGRAM,0);
if(_sockfd == -1)
{
cerr<<"socket error:" << errno << strerror(errno) << endl;
exit(SOCKET_ERR);
}
//2.绑定port和ip
struct sockaddr_in local;
bzero(&local,sizeof(local));
local.sin_family = AF_INET;
local.sin_port = htons(_port);
local.sin_addr.s_addr = htonl(INADDR_ANY);
int n = bind(_sockfd,(struct sockaddr*)&local,sizeof(local));
if(n == -1)
{
cerr<<"bind error:" << errno << strerror(errno) << endl;
exit(BIND_ERR);
}
}
void startServer()
{
char buffer[1024];
for(;;)
{
struct sockaddr_in peer;
socklen_t len = sizeof(peer);
//peer len:输入,输出型参数:用来保存客户端的ip和port
ssize_t s = recvfrom(_sockfd,buffer,sizeof(buffer)-1,0,(struct sockaddr*)&peer,&len);
//打印发送来的数据
if(s)
{
buffer[s] = { 0 };
//inet_ntoa:网络字节序->int int->点分十进制
string clientIp = inet_ntoa(peer.sin_addr);
//ntohl:网络字节序->int
uint16_t clientPort = ntohs(peer.sin_port);
string message = buffer;
cout << clientIp << "[" << clientPort << "]" << message << endl;
}
}
}
~udpServer()
{}
private:
uint16_t _port;
string _ip;
int _sockfd;
};
}
启动服务器:udpServer.cc
#include "udpServer.hpp"
#include
using namespace Server;
static void Usage(string proc)
{
cout << "\nUsage:\n\t" << proc << " local_port\n\n";
}
int main(int argc, char *argv[])
{
if (argc != 2)
{
Usage(argv[0]);
exit(USAGE_ERR);
}
uint16_t port = atoi(argv[1]);
unique_ptr usvr(new udpServer(port));
usvr->initServer();
usvr->startServer();
return 0;
}
编写客户端:udpClient.hpp
#pragma once
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
namespace Client
{
using namespace std;
class udpClient
{
public:
udpClient(const string& serverIp,const uint16_t serverPort)
:_serverIp(serverIp),_serverPort(serverPort),_sockfd(-1) {}
void initClient()
{
//1.创建socket
_sockfd = socket(AF_INET,SOCK_DGRAM,0);
if(_sockfd == -1)
{
cerr<<"socket error:" << errno << strerror(errno) << endl;
exit(2);
}
//client一定需要bind,服务器只有一个,但是客户端有许多个,所以服务端需要显示的绑定端口号
//客户端一般不需要自己显示的绑定,而是由os自动形成端口号进行绑定
}
void run()
{
struct sockaddr_in server;
memset(&server,0,sizeof(server));
server.sin_family = AF_INET;
server.sin_addr.s_addr = inet_addr(_serverIp.c_str());
server.sin_port = htons(_serverPort);
string message;
while(1)
{
cout << "请输入:";
cin >> message;
//发送数据到客户端
sendto(_sockfd,message.c_str(),message.size(),0,(const struct sockaddr*)&server,
sizeof(server));
}
}
private:
string _serverIp;
int _sockfd;
uint16_t _serverPort;
};
}
启动客户端:udpClient.cc
#include"udpClient.hpp"
#include
using namespace Client;
static void Usage(string proc)
{
cout << "\nUsage:\n\t" << proc << " server_ip server_port\n\n";
}
int main(int argc,char* argv[])
{
if(argc != 3)
{
Usage(argv[0]);
exit(1);
}
string serverip = argv[1];
uint16_t serverport = atoi(argv[2]);
unique_ptr uct(new udpClient(serverip,serverport));
uct->initClient();
uct->run();
return 0;
}
说明:因为本次是在一台主机上,所以为了做测试将IP地址绑定为"127.0.0.1",该IP地址的特点是专门用来在本主机上做测试,称为本地环回,后续在不同主机上通信的时候只需要将IP地址改为对方主机的IP地址即可。
此时我们就可以看到当客户端向服务器发送数据时,服务器收到了数据,并且将数据回显出来
通过上面的编码,使用UDP协议简单实现了一个服务器,可以用来进行数据通信了,是不是感觉很神奇呀,看到这里你就会发现,原来网络通信也并不复杂,和系统内部进程间通信有异曲同工之妙关于网络通信的更多细节,后面再一一为大家介绍,我们下次再见!