做餐饮系统网站建设,国外网站流量查询,小型网站建设多少钱,杭州做购物网站文章目录 一、引入二、服务端实现2.1 创建套接字socket2.2 绑定bind2.3 启动服务器2.4 IP的绑定2.5 读取数据recvfrom 三、用户端实现3.1 绑定问题3.2 发送数据sendto 四、源码 一、引入
在上一章【网络编程】socket套接字中我们讲述了TCP/UDP协议#xff0c;这一篇就是简单实… 文章目录 一、引入二、服务端实现2.1 创建套接字socket2.2 绑定bind2.3 启动服务器2.4 IP的绑定2.5 读取数据recvfrom 三、用户端实现3.1 绑定问题3.2 发送数据sendto 四、源码 一、引入
在上一章【网络编程】socket套接字中我们讲述了TCP/UDP协议这一篇就是简单实现一个UDP协议的网络服务器。 我们也讲过其实网络通信的本质就是进程间通信。而进程间通信无非就是读和写IO。 所以现在我们就要写一个服务端server接收数据客户端client发送数据。
二、服务端实现
通过上一章的介绍要通信首先需要有IP地址和绑定端口号。
uint16_t _port;
std::string _ip;2.1 创建套接字socket
在通信之前要先把网卡文件打开。
#include sys/types.h /* See NOTES */
#include sys/socket.hint socket(int domain, int type, int protocol);RETURN VALUE
On success, a file descriptor for the new socket is returned.
On error, -1 is returned, and errno is set appropriately.这个函数的作用是打开一个文件把文件和网卡关联起来。
参数介绍 domain一个域标识了这个套接字的通信类型网络或者本地。 只用关注上面两个类第一个AF_UNIX表示本地通信而AF_INET表示网络通信。 type套接字提供服务的类型。 这一章我们讲的式UDP所以使用SOCK_DGRAM。 protocol想使用的协议默认为0即可因为前面的两个参数决定了就已经决定了是TCP还是UDP协议了。 返回值 成功则返回打开的文件描述符指向网卡文件失败返回-1。 而从这里我们就联想到系统中的文件操作未来各种操作都要通过这个文件描述符所以在服务端类中还需要一个成员变量表示文件描述符。
static const std::string defaultip 0.0.0.0;// 默认IPclass UDPServer
{
public:UDPServer(const uint16_t port, const std::string ip defaultip): _port(port), _ip(ip), _sockfd(-1){}void InitServer(){// 创建套接字_sockfd socket(AF_INET, SOCK_DGRAM, 0);if(_sockfd -1){std::cerr socket error errno : strerror(errno) std::endl;exit(1);}}
private:uint16_t _port;std::string _ip;int _sockfd;
};
创建完套接字后我们还需要绑定IP和端口号。
2.2 绑定bind
#include sys/socket.hint bind(int socket, const struct sockaddr *address,socklen_t address_len);RETURN VALUE
Upon successful completion, bind() shall return 0;
otherwise, -1 shall be returned and errno set to indicate the error.参数介绍 socket创建套接字的返回值。 address通用结构体上一章【网络编程】socket套接字有详细介绍。 address_len传入结构体的长度。 所以我们要先定义一个sockaddr_in结构体填充数据在传递进去。
struct sockaddr_in {short int sin_family; // 地址族一般为AF_INET或PF_INETunsigned short int sin_port; // 端口号网络字节序struct in_addr sin_addr; // IP地址unsigned char sin_zero[8]; // 用于填充使sizeof(sockaddr_in)等于16
};创建结构体后要先清空数据初始化我们可以用memset系统也提供了接口
#include strings.hvoid bzero(void *s, size_t n);填充端口号的时候要注意端口号是两个字节的数据涉及到大小端问题。
大小端转化接口
#include arpa/inet.h
// 主机序列转网络序列
uint32_t htonl(uint32_t hostlong);
uint16_t htons(uint16_t hostshort);
// 网络序列转主机序列
uint32_t ntohl(uint32_t netlong);
uint16_t ntohs(uint16_t netshort);对于IP首先我们要先转成整数再要解决大小端问题。 系统给了直接能解决这两个问题的接口
#include sys/socket.h
#include netinet/in.h
#include arpa/inet.hint inet_aton(const char *cp, struct in_addr *inp);in_addr_t inet_addr(const char *cp);// 点分十进制字符串in_addr_t inet_network(const char *cp);char *inet_ntoa(struct in_addr in);struct in_addr inet_makeaddr(int net, int host);in_addr_t inet_lnaof(struct in_addr in);in_addr_t inet_netof(struct in_addr in);这里的inet_addr就是把一个点分十进制的字符串转化成整数再进行大小端处理。
整体代码
void InitServer()
{// 创建套接字_sockfd socket(AF_INET, SOCK_DGRAM, 0);if(_sockfd -1){std::cerr socket error errno : strerror(errno) std::endl;exit(1);}// 绑定IP与portstruct sockaddr_in si;bzero(si, sizeof si);si.sin_family AF_INET;// 协议家族si.sin_port htons(_port);// 端口号,注意大小端问题si.sin_addr.s_addr inet_addr(_ip.c_str());// ip// 绑定int n bind(_sockfd, (struct sockaddr*)si, sizeof si);assert(n ! -1);
}2.3 启动服务器
首先要知道服务器要死循环永远不退出除非用户删除。站在操作系统的角度服务器是常驻内存中的进程。
而我们启动服务器的时候要传递进去IP和端口号。
int main(int argc, char* argv[])
{if(argc ! 3){std::cout incorrect number of parameters std::endl;exit(1);}std::string ip argv[1];uint16_t port atoi(argv[2]);std::unique_ptrUDPServer ptr(new UDPServer(port, ip));return 0;
}那么IP该怎么传递呢
2.4 IP的绑定 这里的127.0.0.1叫做本地环回
它的作用就是用来做服务器代码测试的意思就是如果我们绑定的IP是127.0.0.1的话在应用层发送的消息不会进入物理层也就不会发送出去。 当我们运行起来后想要查看网络情况就可以用指令netstat 后边也可以附带参数 -a显示所有连线中的Socket -e显示网络其他相关信息 -i显示网络界面信息表单 -l显示监控中的服务器的Socket -n直接使用ip地址(数字)而不通过域名服务器 -p显示正在使用Socket的程序识别码和程序名称 -t显示TCP传输协议的连线状况 -u显示UDP传输协议的连线状况 那我们如果想要全网通信呢该用什么IP呢难道是云服务器上的公网IP吗 但是我们发现绑定不了。 因为云服务器是虚拟化服务器不是真实的IP不能直接绑定公网IP。
既然公网IP邦绑定不了那么内网IP局域网IP呢 答案是可以说明这个IP是属于这个服务器的 但是这里不是一个内网的就无法找到。
所以现在的问题是服务器启动后怎么收到信息呢消息已经发送到主机现在要向上交付 实际上一款服务器不建议指明一个IP。因为可能服务器有很多IP如果我们绑定了一个比如说IP1那么其他进程发送给IP2服务器就收不到了。 这里的INADDR_ANY实际上就是0这样绑定后发送到这台主机上所有的数据只要是访问绑定的端口8080的服务器都能收到。这样就不会因为绑定了一个具体的IP而漏掉其他IP的信息
static const std::string defaultip 0.0.0.0;// 默认IP所以现在我们就不需要传递IP了。
2.5 读取数据recvfrom
服务端要获取到用户端发送过来的数据。
#include sys/types.h
#include sys/socket.hssize_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和addrlen输出型参数返回对应的消息内容是从哪一个客户端发出的。第一个是自己定义的结构体第二个是结构体长度。 void start()
{char buf[1024];while(1){struct sockaddr_in peer;socklen_t len sizeof peer;sssize_t s recvfrom(_sockfd, buf, sizeof buf - 1, 0, (struct sockaddr*)peer, len);}
}现在我们想要知道是谁发送过来的消息信息都被保存到了peer结构体中我们知道IP信息在peer.sin_addr.s_addr中首先这是一个网络序列要转成主机序列其次为了方便观察要把它转换成点分十进制。 而这两个操作系统给了一个接口能够解决
char *inet_ntoa(struct in_addr in);同样获取端口号的时候也要由网络序列转成主机序列
uint16_t ntohs(uint16_t netshort);整体代码
void start()
{char buf[1024];while(1){struct sockaddr_in peer;socklen_t len sizeof peer;ssize_t s recvfrom(_sockfd, buf, sizeof buf - 1, 0, (struct sockaddr*)peer, len);if(s 0){std::string cip inet_ntoa(peer.sin_addr);uint16_t cport ntohs(peer.sin_port);std::string msg buf;std::cout [ cip cport ]# msg std::endl;}}
}现在只需要等待用户端发送数据即可。
三、用户端实现
首先我们要发送数据就得知道客户端的IP和port。 而这里的IP就必须指明。
uint16_t _serverport;
std::string _serverip;
int _sockfd;这里的IP和port指的是要发送给谁。
创建套接字就跟前面的一样
// 创建套接字
_sockfd socket(AF_INET, SOCK_DGRAM, 0);
if(_sockfd -1)
{std::cerr socket error errno : strerror(errno) std::endl;exit(1);
}3.1 绑定问题
这里的客户端必须绑定IP和端口来表示主机唯一性和进程唯一性。 但是不需要显示的bind。
那么为什么前面服务端必须显示的绑定port呢 因为服务器的端口号是众所周知的不能改变如果变了就找不到服务器了。 而客户端只需要有就可以只用标识唯一性即可。 举个例子 我们手机上有很多的app而每个服务端是一家公司写的但是客户端却是多个公司写的。如果我们绑定了特定的端口万一两个公司都用了同一个端口号呢这样就直接冲突了。 所以操作系统会自动形成端口进行绑定。在发送数据的时候自动绑定 所以创建客户端我们只用创建套接字即可。
void InitClient()
{// 创建套接字_sockfd socket(AF_INET, SOCK_DGRAM, 0);if(_sockfd -1){std::cerr socket error errno : strerror(errno) std::endl;exit(1);}
}3.2 发送数据sendto
#include sys/types.h
#include sys/socket.hssize_t sendto(int sockfd, const void *buf, size_t len, int flags,const struct sockaddr *dest_addr, socklen_t addrlen);这里的参数和上面的recvfrom差不多而这里的结构体内部我们要自己填充目的IP和目的端口号。
void start()
{struct sockaddr_in si;bzero(si, sizeof(si));si.sin_family AF_INET;si.sin_addr.s_addr inet_addr(_serverip.c_str());si.sin_port htons(_serverport);std::string msg;while(1){std::cout Please input: ;std::cin msg;sendto(_sockfd, msg.c_str(), msg.size(), 0, (struct sockaddr*)si, sizeof si);}
}当然这里是同一台主机之间测试如果是不同的机器我们传递参数的时候就要传递公网IP例如我们这台云服务器的公网IP是 我们在运行的时候
./UDPClient 43.143.106.44 8080四、源码
// UDPServer.hpp
#pragma once
#include iostream
#include string
#include cerrno
#include sys/types.h
#include sys/socket.h
#include unistd.h
#include arpa/inet.h
#include strings.h
#include netinet/in.h
#include string.h
#include cassert
#include functionalstatic const std::string defaultip 0.0.0.0;// 默认IPclass UDPServer
{
public:UDPServer(const uint16_t port, const std::string ip defaultip): _port(port), _ip(ip), _sockfd(-1){}void InitServer(){// 创建套接字_sockfd socket(AF_INET, SOCK_DGRAM, 0);if(_sockfd -1){std::cerr socket error errno : strerror(errno) std::endl;exit(1);}// 绑定IP与portstruct sockaddr_in si;bzero(si, sizeof si);si.sin_family AF_INET;// 协议家族si.sin_port htons(_port);// 端口号,注意大小端问题// si.sin_addr.s_addr inet_addr(_ip.c_str());// ipsi.sin_addr.s_addr INADDR_ANY;// 绑定int n bind(_sockfd, (struct sockaddr*)si, sizeof si);assert(n ! -1);}void start(){char buf[1024];while(1){struct sockaddr_in peer;socklen_t len sizeof peer;ssize_t s recvfrom(_sockfd, buf, sizeof(buf) - 1, 0, (struct sockaddr*)peer, len);if(s 0){buf[s] 0;// 结尾std::string cip inet_ntoa(peer.sin_addr);uint16_t cport ntohs(peer.sin_port);std::string msg buf;std::cout [ cip cport ]# msg std::endl;}}}
private:uint16_t _port;std::string _ip;int _sockfd;
};// UDPServer.cc
#include UDPServer.hpp
#include memoryint main(int argc, char* argv[])
{if(argc ! 2){std::cout incorrect number of parameters std::endl;exit(1);}uint16_t port atoi(argv[1]);std::unique_ptrUDPServer ptr(new UDPServer(port));ptr-InitServer();ptr-start();return 0;
}// UDPClient.hpp
#pragma once
#include iostream
#include string
#include cerrno
#include sys/types.h
#include sys/socket.h
#include unistd.h
#include arpa/inet.h
#include strings.h
#include netinet/in.h
#include string.h
#include cassertclass UDPClient
{
public:UDPClient(const std::string serverip, const uint16_t port): _serverip(serverip), _serverport(port), _sockfd(-1){}void InitClient(){// 创建套接字_sockfd socket(AF_INET, SOCK_DGRAM, 0);if(_sockfd -1){std::cerr socket error errno : strerror(errno) std::endl;exit(1);}}void start(){struct sockaddr_in si;bzero(si, sizeof(si));si.sin_family AF_INET;si.sin_addr.s_addr inet_addr(_serverip.c_str());si.sin_port htons(_serverport);std::string msg;while(1){std::cout Please input: ;std::cin msg;sendto(_sockfd, msg.c_str(), msg.size(), 0, (struct sockaddr*)si, sizeof si);}}
private:uint16_t _serverport;std::string _serverip;int _sockfd;
};// UDPClient.cc
#include UDPClient.hpp
#include memoryint main(int argc, char* argv[])
{if(argc ! 3){std::cout incorrect number of parameters std::endl;exit(1);}std::string ip argv[1];uint16_t port atoi(argv[2]);std::unique_ptrUDPClient ptr(new UDPClient(ip, port));ptr-InitClient();ptr-start();return 0;
}