UDP網絡通信:
步驟1 創建套接字:
#include <sys/types.h>
#include <sys/socket.h>int socket(int domain, int type, int protocol);
參數一 domain:
AF_UNIX Local communication unix(7) 本地通信
AF_INET IPv4 Internet protocols ip(7) 網絡通信
參數二 type:
SOCK_DGRAM Supports datagrams (connectionless, unreliable messages of a fixed maximum length). 無連接不可靠面向數據報文,也就是UDP
參數三 protocol:
一般省略,直接為0.
RETURN VALUE
On success, a file descriptor for the new socket is returned. On error, -1 is returned, and errno is set appropriately.(成功返回一個文件描述符,失敗返回-1)。
步驟2 綁定 將IP端口與套接字綁定起來:
綁定的過程,需要將IP和端口號關聯起來,使用 sockaddr_in 對信息進行關聯。
使用 sockaddr_in 需要頭文件 #include <netinet/in.h> 與 #include <arpa/inet.h>
需要將本體服務端的 IP 和 port 填充進 sockaddr_in 的結構體中。
結構體包含三個需要填充的字段:
1.sin_family : 通信的類型。這個和套接字選擇的類型要相同。
2.sin_port : 端口號,如果手動寫入的端口號是16位主機序列,不符合網絡序列,因此需要利用 htons(uint16_t) 將其轉換為網絡序列。
3.sin_addr.s_addr : 網絡地址。傳入的肯定是一個字符串,但是網絡中需要的是4字節 IP,需要的是網絡序列的 IP,因此又需要轉換。其轉換函數為:inet_addr(string)。
完成上述的填充工作后,bind函數可以將填充好的字段和套接字關聯起來。關聯如下:
#include <sys/types.h>
#include <sys/socket.h>int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
RETURN VALUE
On success, zero is returned. On error, -1 is returned, and errno is set appropriately.(成功返回0,失敗-1)
這里的第二個參數是 struct sockaddr* ,而填充的時候用的是struct sockaddr_in,因此需要強轉如下。
完整代碼如下:
步驟三 使用函數讀寫數據。
UDP不能使用 read 和 write,
讀數據需要使用 recvfrom:
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr *src_addr, socklen_t *addrlen);
參數分別為:fd,緩沖區和緩沖區的大小(讀出的數據放在哪里,期望接受多少數據),flags設置為0表示為阻塞讀取。
后兩個參數為:輸入型參數,這兩個參數放的就是誰給我傳的網絡信息,放的就是對方的 IP 和 port。
返回值大于 0 讀取成功。后兩個參數的作用就是得到客戶端是誰。
recvfrom 的返回值就是接受了多少數據。
寫數據用的是 sendto:
#include <sys/types.h>
#include <sys/socket.h>
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags, const struct sockaddr *dest_addr, socklen_t addrlen);
后兩個參數為輸入型參數,分別為:
const struct sockaddr *dest_addr:代表客戶端。
socklen_t addrlen:代表客戶端的長度。
RETURN VALUE
On success, these calls return the number of bytes sent. On error, -1 is returned, and errno is set appropriately.(成功返回值大于0,失敗-1)
客戶端讀數據,recvfrom的后兩個參數直接給一個初始的無意義的參數給上就行。
網絡轉主機和主機轉網絡序列。
代碼如下:
//udpserver.hpp
#pragma once#include <iostream>
#include <cstring>
#include <unistd.h>
#include <string>
#include "nocopy.hpp"
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>static const int gsockfd = -1;
static const uint16_t glocalport = 8888;
enum{SOCKET_ERROR = 1,BIND_ERROR,
};// UdpServer user("192.1.1.1", 8888)
class UdpServer : public nocopy
{
public:UdpServer(const std::string& localip, uint16_t localport = glocalport):_sockfd(gsockfd), _localport(localport),_localip(localip),_isrunning(false){}void InitServer(){//1 創建套接字_sockfd = ::socket(AF_INET, SOCK_DGRAM, 0);if(_sockfd < 0){std::cout << "socket error! " << std::endl;exit(SOCKET_ERROR);}std::cout << "socket success, _sockfd: %d " << _sockfd << std::endl; //預計是 3//2 綁定struct sockaddr_in local;memset(&local, 0, sizeof(local));local.sin_family = AF_INET;local.sin_port = htons(_localport);local.sin_addr.s_addr = inet_addr(_localip.c_str());int n = ::bind(_sockfd, (struct sockaddr*)&local, sizeof(local));if(n<0){std::cout << "bind error! " << std::endl;exit(BIND_ERROR);}std::cout << "bind success" << std::endl;}void Start(){_isrunning = true;char inbuffer[1024];while(_isrunning){struct sockaddr peer;socklen_t len = sizeof(peer);ssize_t n = recvfrom(_sockfd, &inbuffer, sizeof(inbuffer)-1, 0, (struct sockaddr*)&peer, &len);if(n > 0){inbuffer[n] = 0;std::cout << "clint say# " << inbuffer << std::endl;std::string echo_string = "[udp_server echo] #";echo_string += inbuffer;sendto(_sockfd, echo_string.c_str(), echo_string.size(), 0, (struct sockaddr*)&peer, len);}}}~UdpServer(){if(_sockfd > gsockfd) ::close(_sockfd);}private:int _sockfd;uint16_t _localport;std::string _localip;bool _isrunning;
};
//udpserver.cc
#include "UdpServer.hpp"
#include <memory>int main()
{uint16_t port = 8899;std::string ip = "0";std::unique_ptr<UdpServer> user = std::make_unique<UdpServer>(ip, port);user->InitServer();user->Start();return 0;
}
//udpclient.cc#include <iostream>
#include <unistd.h>
#include <string>
#include <cstring>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>// 客戶端得知道服務器的 IP 地址和端口號才能操作
// 因此,在調用客戶端的時候,可以附上服務端的IP和port。
int main(int argc, char* argv[])
{if(argc != 3){std::cerr <<"Usage: " << argv[0] << "server-ip server-port " << std::endl;exit(0);}//拿到服務器的IP和端口std::string serverip = argv[1];uint16_t serverport = std::stoi(argv[2]);//1 創建套接字int sockfd = ::socket(AF_INET, SOCK_DGRAM, 0);if(sockfd < 0){std::cerr << "client socket error!"<< std::endl;exit(1);}//2 bind 綁定客戶端的IP和端口// client的端口不會被用戶去設定,而是OS去隨機選擇端口struct sockaddr_in server;memset(&server, 0, sizeof(server));server.sin_family = AF_INET;server.sin_port = htons(serverport);server.sin_addr.s_addr = inet_addr(serverip.c_str());while(1){std::string line;std::cout << "Please Enter: ";std::getline(std::cin, line);int n = sendto(sockfd, line.c_str(), line.size(), 0, (struct sockaddr*)&server, sizeof(server));if(n > 0){struct sockaddr_in temp;socklen_t len = sizeof(temp);char buffer[1024];int m = recvfrom(sockfd, &buffer, sizeof(buffer)-1, 0, (struct sockaddr*)&temp, &len);if(m>0){buffer[m] = 0;std::cout << buffer << std::endl;}else{std::cout << "recvfrom error" << std::endl;break;}}else{break;}}::close(sockfd);return 0;
}