sockaddr_in 这个结构比较关键。
UDP:服务器:
#include <stdio.h> #include <sys/socket.h> //socket相关 #include <netinet/in.h> #include <arpa/inet.h>//htons() #include <cstringt> //sendto() //#include<pthread.h> //头文件 //#include <unistd.h> //sleep头文件 //#include <vector> //#include <windows.h> #include <stdlib.h> int main() { //1.先创建socket //int socket(int domain, int type, int protocol); //domain:地址域 //type:套接字类型 //protocol:通常 0 就好了 //AF_INET:一个宏,表示使用IPv4协议 //AF_INET6:IPv6协议 //SOCK_DGRAM表示使用UDP协议 int sock = socket(AF_INET, SOCK_DGRAM, 0); if (sock < 0) { //返回结果小于0,就失败了 perror("socket"); return 1; } //2.把当前 socket 绑定一个 IP + 端口号 //int bind(int socket, const struct sockaddr *address, socklen_t address_len); //int socket:和哪个socket相连 //const struct sockaddr *address:IP 地址 //bind:一套准备工作 sockaddr_in addr; //如果文件后缀是.c 前面要加struct addr.sin_family = AF_INET; //用UDP的协议家族 //IP 也需要转成网络字节序,inet_addr 自动帮我们转了 addr.sin_addr.s_addr = inet_addr("0.0.0.0");//IP 地址 //0.0.0.0:表示把电脑的所有的IP都包含在一起,因为电脑的网卡有很多,所以有很多IP,一般选第一个。 //inet_addr():IP地址转换函数,把点分十进制,转换成整数 //inet_ntoa():整数转回点分十进制 addr.sin_port = htons(9090);//端口号 //htons : 端口号要先转成网络字节序 int ret = bind(sock,(sockaddr*)&addr,sizeof(addr)); if (ret < 0) { perror("bind"); return 1; } printf("服务器绑定成功!!\n");//前两步也就是手机开机,信号良好了。 //3.处理服务器收到的请求 while (true) { //服务器的工作流程: //1.初始化 //2.(a,b,c)代表服务器的工作流程 //a)读取客户端的请求 //面向数据报的函数接口 sockaddr_in peer; //发送端的IP 数据来自哪里 socklen_t len = sizeof(peer); char buf[1024] = {0}; ssize_t n = recvfrom(sock, buf, sizeof(buf)-1,0,(sockaddr*)&peer,&len); if (n<0) { perror("recvform"); continue; //continue:考虑到容错,不要因为一次请求结束就退出 } buf[n] = '\0'; printf("[%s:%d] buf:%s\n", inet_ntoa(peer.sin_addrs.s_addr)/*IP*/, ntohs(peer.sin_port), buf); //ssize_t recvfrom(int sock, void *buf, size_t len, int flags, struct sockaddr *from, socklen_t *fromlen); /* 参数: sock:索引将要从其接收数据的套接字。 buf:存放消息接收后的缓冲区。 len:buf所指缓冲区的容量。 flags:是以下一个或者多个标志的组合体,可通过or操作连在一起, MSG_DONTWAIT:操作不会被阻塞。 MSG_ERRQUEUE:指示应该从套接字的错误队列上接收错误值,依据不同的协议,错误值以某种辅佐性消息的方式传递进来,使用者应该提供足够大的缓冲区。 导致错误的原封包通过msg_iovec作为一般的数据来传递。导致错误的数据报原目标地址作为msg_name被提供 (0就好了,可以不用管) struct sockaddr *from:发送数据的IP,数据来自那里 socklen_t *fromlen:IP长度,记得那么写就行了 返回值: 成功执行时,返回接收到的字节数。另一端已关闭则返回0。失败返回-1,errno被设为以下的某个值 EAGAIN:套接字已标记为非阻塞,而接收操作被阻塞或者接收超时 EBADF:sock不是有效的描述词 ECONNREFUSE:远程主机阻绝网络连接 EFAULT:内存空间访问出错 EINTR:操作被信号中断 EINVAL:参数无效 ENOMEM:内存不足 ENOTCONN:与面向连接关联的套接字尚未被连接上 ENOTSOCK:sock索引的不是套接字 */ //b)根据请求进行响应 //略过,因为写的是回显服务器 //c)把相应写回客户端 n = sendto(sock, buf, strlen(buf), 0, (sockaddr*)&peer, len); if (n<0) { perror("sendto"); continue; } } close(sock); //及时关闭服务器 return 0; } Makefile文件: g++ zxczxc.cc -o server netstat:查看网络的状态命令 netstat -anp | grep 9090 (9090刚才的端口)
客户端:
#include <cstdio> #include <sys/socket.h> //socket相关 #include <netinet/in.h> #include <arpa/inet.h>//htons() #include <cstringt> //sendto() //./client 127.0.0.1 //IP 改变 连接就变了,可配置 int main(int argc, char* argv[]) { //1.先创建socket int sock = socket(AF_INET, SOCK_DGRAM, 0); if (sock < 0) { perror("socket"); return 1; //2.客户端一般不需要绑定 bind //bind 意味着和某个具体端口关联,如果没有bind,操作系统随机分配 //服务器不 bind,会导致服务器每次启动端口改变,客户端没办法连接。 //客户端也 bind 了,可能有问题,通常情况下一个端口号不能有两个进程bind,因为一个客户端bind了,后面的客户端肯能也bind。随意随机分配更科学。 //3.准备服务器的sockaddr_in结构 sockaddr_in sever_addr; sever_addr.sin_family = AF_INET; sever_addr.sin_addr.s_addr = inet_addr(argv[1]); sever_addr.sin_port = htons(9090); socklen_t len //4.直接发送数据即可、 while (1) { char buf[1024] = { 0 }; printf("请输入一段内容:"); fflush(buf); scanf("%s", buf); sendto(sock, buf, 0, (sockaddr*)&sever_addr, sizeof(sever_addr)); //从服务器接受一下结果 char buf_output[1024] = { 0 }; recvfrom(sock, buf_output, sizeof(buf_output), NULL, NULL); //两个NULL:表示对端的地址不关心 printf("sever resp:%s\n", buf_output); } return 0; }
封装之后的UDP:
SOCKET:
#include <cstdio> #include <unistd.h> #include <stdlib.h> #include <string.h> #include <cstringt> #include <sys/socket.h> //socket相关 #include <netinet/in.h> #include <arpa/inet.h>//htons() #include <cstringt> //sendto() class UdpSocket { public: UdpSocket() :fd_(-1) { } //打开一个Socket //创建成功返回true 失败返回 false bool Socket() { fd_ = socket(AF_INET, SOCK_DGRAM, 0); if (fd_ < 0) { perror("socket"); } return true; } bool Close() { if (fd_ != -1) { close(fd_); } return true; } bool Bind(const std::string& ip,uint16_t port) { //根据参数构造一个 socketaddr_in 结构 //调用bind sockaddr_in addr; addr.sin_family = AF_INET; addr.sin_addr.s_addr = inet_addr(ip.c_str()); addr.sin_port = htons(port); int ret = bind(fd_, (sockaddr*)&addr, sizeof(addr)); if (ret < 0) { perror("bind"); return false; } return true; } //除了成功失败,还需要返回 //1.读到的数据 //2.对方的IP 地址 //3.对方的端口号 bool RecvFrom(std::string* msg,std::string* ip,uint16_t * port=NULL) { char buf[1024 * 10] = { 0 }; sockaddr_in peer; socklen_t len = sizeof(peer); ssize_t n=recvfrom(fd_ ,buf, sizeof(buf)-1, (sockaddr*)&peer, &len); if (n < 0) { perror("recvfrom"); return false; } *msg = buf; if (ip != NULL) { *ip = inet_ntoa(peer.sin_addr); } if (port!=NULL) { *port = ntohs(peer.sin_port); } return true; } bool SendTo(const std::string& msg,const std::string& ip,uint16_t port) { sockaddr_in addr; addr.sin_family = AF_INET; addr.sin_addr.s_addr = inet_addr(ip.c_str()); addr.sin_port = htons(port); ssize_t n = sendto(fd_, msg.c_str(), msg.size(), 0, (sockaddr*)&addr, sizeof(addr)); if (n<0) { perror("sendto"); return false; } return true; } private: int fd_; //表示 Socket };
服务端:
#pragma once #include "SOCKET.hpp" #include <cassert> #include <functional> //通用的UDP 服务器类 //服务器的工作流程: //1.初始化 //2.(a,b,c)代表服务器的工作流程 //a)读取客户端的请求 //b)根据请求进行响应 //c)把相应写回客户端 //a,c 固定套路,在哪都一样,b 和业务相关,对应代码提取成回调函数 //修改指针handler //typedef void(*Handle)(const std::string& req, std::string* resp); //C++11 typedef function<void(const std::string& , std::string*)> Handler; class UdpServer { public: UdpServer(){ assert(sock_.Socket()); } ~UdpServer() { sock_.Close(); } //核心流程 bool Strat(const std::string& ip, uint16_t port, Handle handler) { //1.创建socket(已完成) //2.绑定端口号 boll ret = sock_.Bind(ip, port); if (!ret) { return false; } while (true) { //处理每个请求 //1.读取请求 std::string req; std::string peer_ip; std::string peer_port; uint16_t peer_port; sock_.RecvFrom(&req, &peer_ip, &peer_port); //2.根据请求响应 std::string resp; handler(req, &resp); //3.请求返回到客户端 } } private: UdpSocket sock_; };
客户端:
#pragma once #include "SOCKET.hpp" class UdpClient { public: UdpClient(std::string& ip,uint16_t port) :server_ip_(ip), server_port_(port) { sock_.Socket(); } ~UdpClient() { sock_.Close(); } bool RecvFrom(std::string* msg) { return sock_.RecvFrom(msg); } bool SendTo(std::string& msg) { return sock_.SendTo((msg),server_ip_,server_port_); } private: UdpSocket sock_; std::string server_ip_; uint16_t server_port_; };
封装之后的服务端主函数:
#include "封装UDP服务端.hpp" //核心业务功能 void Echo(const std::string& req, std::string* resp) { *resp = req; } int main() { UdpServer server; server.Strat("0.0.0.0", 9090, Echo); return 0; }
代码实践是最好学习记忆的方式的方式!!!
利用function 也可以写 lambda 表达式。这种方式很方便。要了解。