五步 1.创建套接字 — socket 2.为套接字绑定地址信息 — bind 3.接收数据 — recvfrom 4.发送数据 — sendto 5.关闭套接字 — close 头文件:
#pragma once #include <iostream> #include <string> #include <stdio.h> //perror #include <sys/socket.h> //socket, bind, recvfrom, sendto #include <unistd.h> //close #include <netinet/in.h> //inet_addr #include <arpa/inet.h> //htons #include <stdlib.h> using std::cout; using std::endl; using std::string; using std::cin; class UdpSocket { public: // 构造函数初始化 UdpSocket() { _sock = -1; } // 创建套接字 bool Socket() { _sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); if (_sock < 0) { perror("socket error"); return false; } return true; } // 为套接字绑定地址信息 bool Bind(const string& ip, const uint16_t port) { struct sockaddr_in addr; addr.sin_family = AF_INET; //IPV4协议 addr.sin_port = htons(port); //端口 addr.sin_addr.s_addr = inet_addr(ip.c_str()); //IP地址 size_t len = sizeof(struct sockaddr_in); int ret = bind(_sock, (struct sockaddr*)&addr, len); if (ret < 0) { perror("bind error"); return false; } return true; } // 接收数据 bool Recv(string& buf, struct sockaddr_in& saddr) { char tmp[1024] = {0}; size_t len = sizeof(struct sockaddr_in); size_t size = recvfrom(_sock, tmp, sizeof(tmp), 0, (struct sockaddr*)&saddr, &len); if (size < 0) { perror("recieve error"); return false; } // 将tmp字符串内容拷贝size字节到buf中 buf.assign(tmp, size); return true; } // 发送数据 bool Send(string& buf, struct sockaddr_in& daddr) { size_t len = sizeof(struct sockaddr_in); size_t size = sendto(_sock, buf.c_str(), buf.size(), 0, (struct sockaddr*)&daddr, len); if (size < 0) { perror("send error"); return false; } return true; } // 关闭套接字 void Close() { close(_sock); _sock = -1; } private: int _sock; };服务器端:
#include "udpsocket.h" #define CHECK(q) \ do { \ if ((q) == false)\ return -1; \ }while(0); int main(int argc, char* argv[]) { if (argc != 3) { perror("input error"); return -1; } string ip = argv[1]; uint16_t port = atoi(argv[2]); UdpSocket sock; CHECK(sock.Socket()); CHECK(sock.Bind(ip, port)); struct sockaddr_in cli_addr; while (1) { string buf; CHECK(sock.Recv(buf, cli_addr)); cout << "cli say:" << buf << endl;; cout << "srv say:"; fflush(stdout); cin >> buf; CHECK(sock.Send(buf, cli_addr)); } sock.Close(); return 0; }客户端:
#include "udpsocket.h" inline void CHECK(bool b) { if (b == false) { exit(-1); } } int main(int argc, char* argv[]) { if (argc != 3) { perror("input error"); return -1; } string ip = argv[1]; uint16_t port = atoi(argv[2]); UdpSocket sock; CHECK(sock.Socket()); struct sockaddr_in srv_addr; srv_addr.sin_family = AF_INET; srv_addr.sin_port = htons(port); srv_addr.sin_addr.s_addr = inet_addr(ip.c_str()); while (1) { string buf; cout << "client say:"; fflush(stdout); cin >> buf; CHECK(sock.Send(buf, srv_addr)); CHECK(sock.Recv(buf, srv_addr)); cout << "server say:" << buf << endl; } sock.Close(); return 0; }效果图: 先启动服务器: 在另一个终端下启动客户端: 然后就可以进行简单的对话了:
服务端和客户端的实现存在差别, 但是他们经历的都是上述的5个步骤. 首先, 两端都需要创建套接字, 来建立好两个通讯程序的端点; 其次服务端因为是被动接收请求, 因此需要手动绑定地址信息, 方便客户端能够得知发送请求的位置; 而客户端一般不手动绑定, 因为客户端是主动一方, 并且不只一个客户端, 服务端不可能确定要和谁建立通讯信息, 所以不进行绑定但是系统会给分配一个合理的端口进行通讯的建立. 然后客户端向服务端发送数据请求, 服务端阻塞接收请求, 当有请求过来时, 服务端才会确定客户端的地址信息然后与之通讯.