天天看點

『網絡』基于UDP協定的socket程式設計概述基于UDP協定的網絡通信流程接口介紹封裝UdpSocket類字典伺服器UDP用戶端和伺服器的實作

概述

首先,我們知道,UDP是無連接配接不可靠的資料報協定,有很多場合比較适合使用UDP協定。使用UDP編寫的一些常見的應用程式有:DNS(域名系統)、NFS(網絡檔案系統)和SNMP(簡單網絡管理協定)。

下圖為典型的UDP用戶端/伺服器程式的函數調用。

『網絡』基于UDP協定的socket程式設計概述基于UDP協定的網絡通信流程接口介紹封裝UdpSocket類字典伺服器UDP用戶端和伺服器的實作

基于UDP協定的網絡通信流程

用戶端流程:

  1. 建立套接字。
  2. 為套接字綁定位址(ip + port)資訊。通常用戶端不推薦使用者手動綁定位址資訊。
  3. 發送資料(如果socket還沒有綁定位址,這時候作業系統會選擇一個合适的位址端口進行綁定)。
  4. 接收資料。
  5. 關閉套接字。

    服務端流程:

  6. 建立套接字。通過套接字使程序與網卡建立聯系。
  7. 為套接字綁定位址資訊(ip + port)。
  8. 接收資料。
  9. 發送資料。
  10. 關閉套接字。

接口介紹

建立套接字:
int socket(int domain, int type, int protocol);
參數:
	domin:位址域。
		AF_INET:IPV4網絡協定位址域。
		AF_INET6:IPV6網絡協定位址域。
	type:套接字類型。
		SOCK_STREAM:流式套接字,預設協定TCP,不支援UDP。
		SOCK_DGRAM:資料報套接字,預設協定UDP,不支援TCP。
	protocol:協定類型。
		0:試用套接字預設協定。
		6/IPPROTO_TCP:TCP協定。
		17/IPPROTO_UDP:UDP協定。
傳回值:套接字操作句柄(檔案描述符),失敗傳回-1。
           
為套接字綁定位址資訊:
int bind(int sockfd, const struct sockaddr *addr,
                socklen_t addrlen);
參數:
	sockfd:建立套接字傳回的描述符。
	addr:位址資訊。
	addrlen:位址資訊長度。
傳回值:成功傳回0,失敗傳回-1。
           
接受資料:
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,
        struct sockaddr *src_addr, socklen_t *addrlen);
參數:
	sockfd:操作句柄,套接字描述符。
	buf:用buf存儲接收的資料。
	len:想要接收的資料長度。
	flags:
		0:預設阻塞接受。
	saddr:發送端的位址資訊。
	addrlen:位址資訊長度(輸入輸出型參數),不但要指定想要接收多長還要儲存實際接受了多長。
傳回值:實際接收的資料長度,失敗傳回-1。
           
發送資料:
ssize_t sendto(
	int sockfd, const void *buf, size_t len, int flags,
	const struct sockaddr *dest_addr, socklen_t addrlen
);
參數:
	socket:套接字描述符。
	buf:要發送的資料。
	len:要發送的資料長度。
	flags:
		0:預設阻塞發送。
	dest_addr:目的端位址資訊,辨別資料要發送到哪裡去。
	addrlen:位址資訊長度。
傳回值:實際發送的資料長度,失敗傳回-1。
           
關閉套接字:
int close(int fd);
參數:
	fd:套接字描述符。
傳回值:成功傳回0,失敗傳回-1。
           

封裝UdpSocket類

#include <iostream>
#include <sys/socket.h>
#include <netinet/in.h>
#include <stdio.h>
#include <string>
#include <arpa/inet.h>
#include <unistd.h>

using std::cout;
using std::endl;

// 緩沖大小
#define BUF_SIZE 1024

// UDP socket類
class UdpSocket{
	public:
		// 構造函數
		UdpSocket()
			: _sockfd(-1)
		{}

		// 建立套接字
		bool Socket(){
			// 建立套接字
			_sockfd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
			if(_sockfd < 0){
				// 套接字建立失敗
				perror("socket error");
				return false;
			}

			return true;
		}

		// 綁定位址資訊
		bool Bind(std::string& ip, uint16_t port){
			// IPv4位址結構
			struct sockaddr_in addr;
			addr.sin_family = AF_INET;
			// 點分十進制IP轉二進制形式
			inet_pton(AF_INET, ip.c_str(), &addr.sin_addr);
			// 端口号,主機位元組序轉網絡位元組序
			addr.sin_port = htons(port);

			// 位址空間長度
			socklen_t len = sizeof(struct sockaddr_in);
			// 綁定位址資訊
			int ret = bind(_sockfd, (struct sockaddr*)&addr, len);
			if(ret < 0){
				// 綁定失敗
				perror("bind error");
				return false;
			}

			return true;
		}

		// 接收
		bool Recv(std::string& buf, struct sockaddr_in* saddr){
			// 清空緩沖區
			buf.clear();
			buf.resize(BUF_SIZE);

			// 位址空間大小
			socklen_t len = sizeof(struct sockaddr_in);
			// 阻塞接收
			int ret = recvfrom(_sockfd, &buf[0], BUF_SIZE, 0, 
					(struct sockaddr*)saddr, &len);
			if(ret < 0){
				// 接收失敗
				perror("recvfrom error");
				return false;
			}

			return true;
		}

		// 發送
		bool Send(std::string& buf, struct sockaddr_in* daddr){
			// 位址資訊長度
			socklen_t len = sizeof(struct sockaddr_in);

			// 阻塞發送
			int ret = sendto(_sockfd, buf.c_str(), buf.size(), 0,
					(struct sockaddr*)daddr, len);
			if(ret < 0){
				// 發送失敗
				perror("sendto error");
				return false;
			}

			return true;
		}

		// 關閉
		bool Close(){
			// 關閉套接字
			int ret = close(_sockfd);
			if(ret < 0){
				// 套接字關閉失敗
				perror("close error");
				return false;
			}

			return true;
		}

	private:
		// 套接字描述符
		int _sockfd;
};
           

字典伺服器

下面,我們使用前面封裝的UdpSocket類實作一個字典伺服器,下面我們封裝一個字典伺服器:

UDP用戶端和伺服器的實作

UDP伺服器

#include "udp_socket.h"

int main(int argc, char* argv[]){
	bool ret;

	if(argc != 3){
		cout << "./udp_server ip port" << endl;
		return -1;
	}

	std::string ip = argv[1];
	uint16_t port = atoi(argv[2]);

	UdpSocket sock;

	ret = sock.Socket();
	if(!ret){
		return -1;
	}

	ret = sock.Bind(ip, port);
	if(!ret){
		return -1;
	}

	while(1){
		std::string buf;
		struct sockaddr_in cli_addr;

		ret = sock.Recv(buf, &cli_addr);
		if(!ret){
			return -1;
		}

		cout << "client say: " << buf << endl;
		cout << "server say: " << endl;
		fflush(stdout);
		std::cin >> buf;

		ret = sock.Send(buf, &cli_addr);
		if(!ret){
			return -1;
		}
	}

	ret = sock.Close();
	if(!ret){
		return -1;
	}

	return 0;
}
           

UDP用戶端

#include "udp_socket.h"

int main(int argc, char* argv[]){
	bool ret;

	if(argc != 3){
		cout << "./udp_client ip port" << endl;
	}

	std::string ip = argv[1];
	uint16_t port = atoi(argv[2]);

	UdpSocket sock;
	ret = sock.Socket();

	struct sockaddr_in ser_addr;
	ser_addr.sin_family = AF_INET;
	ser_addr.sin_port = htons(port);
	inet_pton(AF_INET, ip.c_str(), &ser_addr.sin_addr);

	while(1){
		std::string buf;

		cout << "client say: ";
		fflush(stdout);

		std::cin >> buf;

		ret = sock.Send(buf, &ser_addr);
		if(!ret){
			return -1;
		}

		ret = sock.Recv(buf, &ser_addr);
		if(!ret){
			return -1;
		}

		cout << "server say: " << buf << endl;
	}
	
	ret = sock.Close();
	if(!ret){
		return -1;
	}

	return 0;
}
           

繼續閱讀