目录
前言
在这篇文章中为大家介绍如何通过编码实现数据通信,实现思路是根据前面介绍的网络编程函数编写一个服务端和客户端,实现客户端和服务端双方通信
1.接口介绍
创建套接字
#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:结构体的大小
有了上面这些的这些接口,下面我们就可以正式编写一个客户端和一个服务端了
2.编写服务器
形成makefile
.PHONY:allall:udpServer udpClientudpServer:udpServer.ccg++ -o $@ $^ -std=c++11udpClient:udpClient.ccg++ -o $@ $^ -std=c++11.PHONY:cleanclean: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;}
3.编写客户端
编写客户端: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"#includeusing 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;}
4.测试
说明:因为本次是在一台主机上,所以为了做测试将IP地址绑定为"127.0.0.1",该IP地址的特点是专门用来在本主机上做测试,称为本地环回,后续在不同主机上通信的时候只需要将IP地址改为对方主机的IP地址即可。
此时我们就可以看到当客户端向服务器发送数据时,服务器收到了数据,并且将数据回显出来
总结
通过上面的编码,使用UDP协议简单实现了一个服务器,可以用来进行数据通信了,是不是感觉很神奇呀,看到这里你就会发现,原来网络通信也并不复杂,和系统内部进程间通信有异曲同工之妙关于网络通信的更多细节,后面再一一为大家介绍,我们下次再见!
来源地址:https://blog.csdn.net/qq_65307907/article/details/132149986