[Linux] 网络套接字编程之实现简单的UDP网络程序(上)

网络套接字编程

  • socket套接字
  • socket常见API
  • 网络协议地址格式
  • 实现UDP用户数据报协议
    • 1)特点
    • 2)实现逻辑
      • 1. 服务器端创建socket套接字,然后绑定端口号,接受客户端发送的请求,做出应答
        • 1.1 在udp_server.hpp中
        • 1.2 在udp_server.cc中
      • 2. 客户端创建套接字,然后向服务器端发送请求
        • 2.1 在udp_client.cpp中
        • 2.2 在udp_client.cc中
      • 3. 程序相关转换函数

socket套接字

ip:标记网络中的一台主机的地址
port:标记一台主机(某个系统)上的一个进程
socket = ip + port :用来表示全网的唯一一个进程

socket常见API

  1. 创建 socket 文件描述符 (TCP/UDP,客户端 + 服务器)
    int socket(int domain,int type,int protocol);
    domain为域,对于IPv4协议来说,其值一般是16位地址类型AF_INET;
    type表示服务类型,面向字节流使用SOCK_STREAM,面向数据报,使用SOCK_DGRAM;
    protocol表示协议类型,socket函数可根据其前两个参数自行推导得出。
  2. 绑定端口号 (TCP/UDP,服务器)
    int bind(int socket,const struct sockaddr *address,socklen_t address_len);
  3. 监听socket (TCP, 服务器)
    int listen(int sockfd,int backlog);
  4. 接收请求 (TCP, 服务器)
    int accept(int sockfd,struct sockaddr* address,socklen_t* address_len);
  5. 建立连接 (TCP, 客户端)
    int connect(int sockfd,const struct sockaddr *addr,socklen_t addrlen);

网络协议地址格式

socket API是一层抽象的网络编程接口,适用于各种底层网络协议,如IPv4、IPv6以及UNIX Domain Socket。然而,各种网络协议的地址格式并不相同,如下图所示,常用的是sockaddr_in结构体。
[Linux] 网络套接字编程之实现简单的UDP网络程序(上)_第1张图片

实现UDP用户数据报协议

1)特点

传输层协议、无连接、不可靠传输、面向数据报

2)实现逻辑

1. 服务器端创建socket套接字,然后绑定端口号,接受客户端发送的请求,做出应答

1.1 在udp_server.hpp中

ip地址:127.0.0.1成为本地环回,通常用来进行网络通信代码的本地测试,一般使用该ip地址如果能通信的话,就代表本地环境及代码基本没有大问题。
使用recvfrom函数接受信息,sendto函数发送信息,返回值类型都为ssize_t。

class udpServer{
	private:
		std::string ip;  //ip地址
		int port;  //端口号
		int sock; //socket文件描述符

	public:
		udpServer(int _port, std::string _ip="127.0.0.1")
			:ip(_ip), port(_port){  //构造函数(带参)
			}

		void InitServer(){
			//创建套接字-->绑定
			sock = socket(AF_INET, SOCK_DGRAM, 0);  //创建套接字:成功返回3
			//tcp:SOCK_STREAM   udp:SOCK_DGRAM
			if(socket < 0){
				std::cout << "socket create error..." << std::endl;
			}
			std::cout << "sock: " << sock << std::endl;

			//创建sockaddr_in结构体:表示服务器端的网络协议地址
			struct sockaddr_in local;
			local.sin_family = AF_INET;
			local.sin_port = htons(port);  //主机端口号转换为16位的网络端口号
			local.sin_addr.s_addr = inet_addr(ip.c_str());  //首先转换为char类型,然后再转换为网络地址类型
			
			if(bind(sock, (struct sockaddr*)&local, sizeof(local)) < 0){  //绑定套接字:成功返回0,否则为-1
				std::cerr << "bind error!\n" << std::endl;
				exit(1);
			}
		}

		void Start(){
			//服务器端:接受请求-->发送应答
			char msg[64]; //接受客户端发送的信息
			for( ; ; ){
				msg[0] = '\0';
				struct sockaddr_in end_point;  
				socklen_t len = sizeof(end_point);  //网络协议地址的长度,类型为socklen_t
				ssize_t s = recvfrom(sock, msg, sizeof(msg)-1, 0, (struct sockaddr*)&end_point, &len); 
				//从远端读数据,成功返回值>0,返回类型为ssize_t
				
				if(s > 0){
					//读取成功,发送应答
					msg[s] = '\0';
		
					char buf[16];
					sprintf(buf, "%d", ntohs(end_point.sin_port));  //整型转字符串类型
					std::string client = (inet_ntoa(end_point.sin_addr));
					client += ":";
					client += buf;
					std::cout << client << "#" << msg << std::endl;  //输出客户端信息及读到的数据
					
					std::string echo_string = msg;
					echo_string += " [server echo!] ";
					sendto(sock, echo_string.c_str(), echo_string.size(), 0, (struct sockaddr*)&end_point, len);
				}
			}
		}

		~udpServer(){  //析构函数
			close(sock);
		}
};

1.2 在udp_server.cc中

以命令行参数的形式传入创建udp服务器所需要的端口号参数。

void Usage(std::string proc){  //显示当前执行程序的基本信息
	std::cout << "Usage:" << proc << "local_port" << std::endl;
}

int main(int argc, char *argv[]){
	if(argc != 2){
		Usage(argv[0]);
		exit(1);
	}

	udpServer *us = new udpServer(atoi(argv[1]));  //传入端口号
	us->InitServer();
	us->Start();
	delete us;
}

2. 客户端创建套接字,然后向服务器端发送请求

2.1 在udp_client.cpp中

udp客户端中使用的ip和端口号,不是链接,而是标记了服务器在哪,用于向服务器发送数据。

class udpClient{
	private:
		std::string ip;
		int port;
		int sock;
	
	public:
		//这里的ip和port对应的是Server的ip、port!!!
		udpClient(std::string _ip, int _port)
			:ip(_ip), port(_port){
		}

		void InitClient(){
			//创建套接字
			sock = socket(AF_INET, SOCK_DGRAM, 0);  //创建套接字:成功返回3
			std::cout << "sock: " <<sock << std::endl;
		}

		void Start(){
			//客户端:发送请求-->接受应答
			struct sockaddr_in peer;
			peer.sin_family = AF_INET;
			peer.sin_port = htons(port);  
			peer.sin_addr.s_addr = inet_addr(ip.c_str());   
			
			std::string msg;
			for(; ; ){
				std::cout << "Please Enter# ";
				std::cin >> msg;
				if(msg == "quit"){
					break;  //当前客户端退出
				}
				sendto(sock, msg.c_str(), msg.size(), 0, (struct sockaddr*)&peer, sizeof(peer));
				
				char echo[128];  
				ssize_t s = recvfrom(sock, echo, sizeof(echo)-1, 0, nullptr, nullptr);
				if(s > 0){
					echo[s] = 0;
					std::cout << "server# " << echo << std::endl;
				}
			}
		}

		~udpClient(){  //析构函数
			close(sock);
		}
};

2.2 在udp_client.cc中

以命令行参数的形式传入创建udp客户端所需要的ip地址参数和端口号参数。

void Usage(std::string proc){
	std::cout << "Usage:" << proc << "server_ip, server_port" << std::endl;
}

int main(int argc, char *argv[]){
	if(argc != 3){
		Usage(argv[0]);
		exit(1);
	}

	udpClient uc(argv[1], atoi(argv[2]));
	uc.InitClient();
	uc.Start();
	return 0;
}

3. 程序相关转换函数

主机端口号转换为16位的网络端口号----htons()
16位的网络端口号转换为主机端口号----ntohs()
char类型转换为网络地址类型----inet_addr()
网络地址类型转为字符串----inet_ntoa()----ip输出
整型转字符串类型输出----sprintf()----端口号输出

你可能感兴趣的:(Linux,网络,linux,udp)