socket编程

UDP协议:向应用程序提供一种面向无连接的服务
 TCP协议:提供一种面向连接的,可靠的数据传输服务

网络通信程序编写时,到底传输层使用UDP好还是TCP?

TCP ---- 传输控制协议 ——面向连接,可靠传输,面向字节流
UDP ----用户数据报协议——面向无连接,不可靠传输,面向数据包
TCP保证可靠传输,但是传输速度没有UDP快。
TCP应用于安全性要求高 / UDP应用于实时性高的场景

socket套接字编程:网络程序的编写
udp通信编程:
 通信的编程流程:
  客户端:1.创建套接字 2.绑定地址信息(不推荐主动) 3.发送数据 4.接收数据 5.关闭套接字
  服务端:1.创建套接字 2.绑定地址信息 3.接收数据 4.发送数据 5.关闭套接字

在这里插入图片描述
客户端不主动绑定端口地址,是为了降低端口冲突的概率,但是服务端为什么必须要绑定地址?

  如果我们发送请求必须要知道服务端地址和端口信息 ——(如果不知道服务端地址和信息就无法发送请求)客户端是主动连接,而服务器是等待连接

scoket接口介绍
1.创建套接字

int socket(int domain, int type, int protocol)
domain: 地址域——确定本次socket通信使用的版本 — AF_INET(ipv4网络协议)  AF_INET6(ipv6网络协议)
type: 套接字类型——流式套接字(SOCK_STREAM)	/ 数据报套接字(SOCK_DGRAM)
protocol: 协议类型 (默认为0,流式默认TCP/数据报默认UDP)
返回值: 文件描述符 ——非负整数,套接字的操作句柄, 失败返回1

2.为套接字绑定地址信息

int bind(int sockfd, struct socketaddr* addr, socklen_t len);
sockfd: 创建套接字返回的操作句柄
addr: 要绑定的地址信息
len: 要绑定的地址信息长度
struct sockaddr{
	sa_family_t sa_family;
	char sa_data[4];
}

struct sockaddr_in{  //IPV4
	sa_family_t sa_family; //地址域
	in_port sin_port;	//端口号
	stuct in_addr{ in addr_t  addr} sin_addr; //IP地址信息
}

bind可以绑定不同的地址结构,为了实现接口统一,因为用户定义地址结构的时候,定义自己需要的地址结构(例如 IPV4就使用struct sockaddr_in, IPV6就是用 struct sockaddr_in6) 但是进行绑定的时候,统一类型强转为sockaddr* 类型

3.接收数据

ssize_t recvfrom(int sockfd, char* buf, int len, int flag, stuct sockaddr* peer_addr, socklen_t* addrlen)
sockfd: socket的操作句柄
buf: 一块缓冲区,用于接收从接收缓冲区中取出数据;
len: 想要接收的长度
flag: 操作选项标识,默认为0,表示阻塞操作
pper_addr: 发送方地址长度
addlen:  想要获取的地址信息长度以及返回的实际长度
返回值: 成功返回实际接收到的数字字节长度, 失败返回-1

4.发送数据

ssize_t	 sendto(int sockfd, char* data, int len, int flag, struct sockaddr* peeraddr, socklen_t addrlen);
sockfd: socket的操作句柄
data: 要发送数据的首地址
len: 要发送数据长度
flag: 默认为0,表示阻塞操作
peeraddr: 接收方的地址信息
addrlen: 地址信息长度

5.关闭套接字

int close(int fd);

网络字节序转换接口

uint16_t htons(uint16_t htonshort);  //主机字节序到网络字节序的转换
uint32_t htons(uint32_t htonlong);   
uint16_t ntohs(uint16_t ntohshort);  //网络字节序到主机字节序的转换
uint32_t ntohl(uint32_t ntohlong);
in_addr_t inet_addr(const char* cp);  //将字符串的点分十进制转换成网络字节序的整数IP地址
char* inet_ntoa(struct in_addr in);  //将网络字节序的整数IP地址,转换为点分十进制IP地址
const char* inet_ntop(int af, const void* src, char* dst, socklen_t size);  //将网络字节序的整数IP地址转换为字符串IP地址
int inet_pton(int af, const char* src,  void* dst);  //将字符串IP地址转换为网络字节序的IP地址

使用c++封装一个UdpSocket类,实例化每个对象都是一个udp通信套接字,并且通过成员接口实现udp通信流程

// udpsocket.hpp
#include<cstdio>
#include<string>
#include<unistd.h>
#include<netinet/in.h>  //包含地址结构信息
#include<sys/socket.h>	//套接字接口信息
#include<arpa/inet.h>	//字节序转换接口

class UdpSocket{
public:
	UdpSocket(int sockfd = -1):_sockfd(sockfd){}
	bool Socket(){//创建套接字
		_sockfd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
		if(_sockfd < 0){
			perror("socket error");
			return false;
		}
		return true;
	} 
	//为套接字绑定地址信息	
	bool Bind(const std::string& ip, uint16_t port){
		struct sockaddr_in addr;
		addr.sin_family = AF_INET;
		addr.sin_port = htons(port); //htons将主机字节序短整型数据转换为网络字节序数据
		addr.sin_addr.s_addr = inet_addr(ip.c_str()); //将字符串IP地址转换为网络字节序
		socklen_t len = sizeof(struct sockaddr_in);
		//bind(操作句柄, 地址信息, 地址信息长度)
		int ret = bind(_sockfd, (struct sockaddr*)&addr, len);
		if(ret < 0){
			perror("bind error");
			return false;
		}
		return true;
	}
	//接收数据,获取发送端地址信息
	bool Recv(std::string* buf, std::string* ip = NULL, uint16_t* port = NULL){
						//recv(套接字句柄, 接收缓冲区, 数据长度, 标志, 源端地址, 地址长度)
		struct sockaddr_in peer_addr;
		socklen_t len = sizeof(struct sockaddr_in);
		char tmp[4096] = {0};
		int ret = recvfrom(_sockfd, tmp, 4096, 0, (struct sockaddr*)&peer_addr, &len);
		if(ret < 0){
			perror("recvfrom error!");
			return false;
		}	
		buf->assign(tmp, ret); //assign 从指定字符串中截取指定长度的数据到buf
		if(port != NULL){																									
			*port = ntohs(peer_addr.sin_port); // 从网络字节序到主机字节序的转换
		}
		if(ip  != NULL){
			*ip = inet_ntoa(peer_addr.sin_addr);//网络字节序到字符串IP地址转换	
		}
		return true;
		}
	//发送数据																						
	bool Send(const std::string& data, const std::string& ip, const uint16_t port){
	//sendto(套接字句柄, 数据首地址, 数据长度, 标志, 对端地址信息, 地址信息长度)
		struct sockaddr_in addr;
		addr.sin_family = AF_INET;
		addr.sin_port = htons(port);
		addr.sin_addr.s_addr = inet_addr(ip.c_str());
		socklen_t len = sizeof(struct sockaddr_in);
		int ret = sendto(_sockfd, data.c_str(), data.size(), 0, (struct sockaddr*)&addr, len);
		if(ret < 0){
			perror("send error!");
			return false;
		}
		return true;
	}			
	//关闭套接字
	bool Close(){
		if(_sockfd > 0){
			close(_sockfd);
			_sockfd = -1;
		}
		return true;
	}														
private:
	int _sockfd;	
};
//udp_srv.cpp
#include<iostream>
#include<string>
#include"udpsocket.hpp"
#define CHECK_RET(q) if((q) == false){ return -1;}
int main(int argc, char* argv[]){
	//argc表示程序运行参数的个数
	//./udp_src  192.168.x.x  8080
	if(argc != 3){
		std::cout << "Usage: ./udp_srv  ip  port\n" << std::endl;
		return -1;	
	}
	uint16_t port = std::stoi(argv[2]);
	std::string ip = argv[1];
	UdpSocket srv_sock;
	//创建套接字
	CHECK_RET(srv_sock.Socket());
	CHECK_RET(srv_sock.Bind(ip, port));
	while(1){
		std::string buf;
		std::string peer_ip;
		uint16_t peer_port;
		CHECK_RET(srv_sock.Recv(&buf, &peer_ip, &peer_port));
		std::cout << "client[ "<< peer_ip << ":" << peer_port << "]" <<"say: " << buf << std::endl;
		buf.clear();
		std::cout << "server say :";
		std::cin >> buf;
		CHECK_RET(srv_sock.Send(buf, peer_ip, peer_port));
	}																																		
	CHECK_RET(srv_sock.Close());
}																																	
//udp_cli.cpp
#include<iostream>
#include<string>
#include"udpsocket.hpp"

#define CHECK_RET(q) if((q) == false){ return -1; }

int main(int argc, char* argv[]){
	//argc表示程序运行参数的个数
	//	// ./udp_src  192.168.x.x  8080
	if(argc != 3){
		std::cout << "Usage: ./udp_srv ip port" << std::endl;
		return -1;
	}
	uint16_t srv_port = std::stoi(argv[2]);
	std::string srv_ip = argv[1];
	UdpSocket cli_sock;
	//创建套接字
	CHECK_RET(cli_sock.Socket());
	while(1){
		std::cout << "client say:";
		std::string buf;
		std::cin >> buf;
		CHECK_RET(cli_sock.Send(buf, srv_ip, srv_port));
		buf.clear();
		CHECK_RET(cli_sock.Recv(&buf));
		std::cout << "server say:" << buf << std::endl;
	}
	CHECK_RET(cli_sock.Close());
}

tcp通信编程:
在这里插入图片描述
1.创建套接字

int socket(int domain, int type, int protocol);

2.绑定地址信息

int bind(int sockfd, struct sockaddr* addr, socklen_t len);

3.开始监听

int listen(int sockfd, int backlog); 
sockfd:将套接字设置为监听状态。并且监听状态后可以开始接收客户端连接请求
backlog:同一时间的并发连接数,决定同一时间最多可以接收多少个客户端请求

在这里插入图片描述
4.获取新连接(从已完成连接队列中取出socket,并且返回这个socket的操作符句柄)

int accept(int sockfd, struct sockaddr* cli_addr,   socklen_t len);
sockfd:监听套接字,表示要获取哪个tcp服务端套接字建立连接
cli_addr:这个新的套接字对应的客户端地址信息
返回值: 新建套接字的描述符----外部程序中的操作句柄

5.接收/发送数据(因为tcp通信套接字中已经标识了五元组,因此不需要接收数据的时候获取对方地址信息,发送数据的时候也不需要指定对方的地址信息)

ssize_t recv(int socket, char* buf, int len, int flag);
ssize_t send(int socket, char*buf, int len, int flag);

6.关闭套接字

int close(int sockfd);

7.向服务端发起连接请求

int connect(int sockfd, struct sockaddr* srv_addr,  int len);
srv_addr: 服务端地址信息---给谁发送连接请求

封装一个TcpSocket类,每一个实例化对象都是一个socket通信连接,通过

//tcpsocket.hpp
#include<cstdio>
#include<string>
#include<unistd.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<sys/socket.h>

#define MAX_LINSTEN 5
class TcpSocket{
public:
	TcpSocket():_sockfd(-1){}
	bool Socket(){
		//socket(地址域, 套接字类型, 协议信息)
		_sockfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
		if(_sockfd < 0){
			perror("socket error");
			return false;
		}
		return true;
	}
	bool Bind(const std::string& ip, uint16_t port){
		struct sockaddr_in addr;
		addr.family = AF_INET;
		addr.sin_port = htons(port);
		addr.sin_addrs.s_addr = inet_addr(ip.c_str());
		socklen_t len = sizeof(struct sockaddr_in);
		int ret  = bind(_sockfd, struct sockaddr*(sockaddr_in), len);
		if(ret < 0){
			perror("bind error");
			return false;
		}
		return true;
	}
	bool Listen(int backlog = MAX_LISTEN){
		//listen(套接字描述符, 最大并发数);
		int ret = listen(_sockfd, backlog);
		if(ret < 0){
			perror("listen error");
			return false;
		}
		return true;
	}
	bool Accept(TcpSocket* new_sock, std::string& ip = NULL, uint16_t* port = NULL){
		//新建套接字描述符 = accpet(监听套接字描述符, 客户端地址信息, 地址信息长度)
		struct sockaddr_in addr;
		socklen_t len = sizeof(struct sockaddr_in);
		int new_fd = accept(_sockfd, (struct sockaddr*)&addr, &len);
		if(new_fd < 0){
			perror("accpet error");
			return false;
		} 
		new_sock->sockfd = new_fd;
		if(ip != NULL){
			(*ip) = ntohs(addr.sin_addr);
		}
		if(port != NULL){
			*port = ntohs(addr.sin_port);
		}
		return true;
	}
	bool Recv(std::string* buf){
		//recv(描述符, 缓冲区首地址, 接收数据长度, 标志位0--阻塞)
		char tmp[4096] = {0};
		int ret = recv(_sockfd, tmp, 4096, 0);
		if(ret < 0){
			perror("recv error");
			return false;
		}else if(ret == 0){//recv默认阻塞,没有数就会等待,返回0,标识等待断开
			printf("connection broken\n");
			return false;
		}
		buf->assgin(tmp, ret);
		return true;
	}
	bool Send(const::string& data){
		//send(描述符, 要发送的数据首地址, 数据长度, 标志位)
		int ret = send(_sockfd, data.c_str(), data.size(), 0);
		if(ret < 0){
			perror("send error");
			return false;
		}
		return true;
	}
	bool Close(){
		if(_sockfd > 0){
			close(_sockfd);
			_sockfd = -1;
		}
			return true;
	}
	bool Connect(const std::string& ip, uint16_t port){
		//向服务端发起连接
		//connect(描述符, 服务端地址信息, 地址信息长度)
		struct sockaddr_in addr;
		addr.family = AF_INET;
		addr.sin_port = port;
		addr.sin_addr.s_addr = inet_addr(ip.c_str());
		socklen_t len = sizeof(struct sockaddr_in);
		int ret = connect(_sockfd, (struct sockaddr*)addr, len);
		if(ret < 0){
			perror("connect error");
			return false;
		}
		return true;
	}
private:
	int  _sockfd;
};
while (1) {
		TcpSocket new_sock;
		bool ret = lst_sock.Accept(&new_sock);//通过监听套接字获取新建连接
		if (ret == false) {
			continue;//服务端不能因为一个新建套接字失败就退出
		}
		std::string buf;
		new_sock.Recv(&buf);//通过新建连接与指定客户端进行通信
		std::cout << "client say:" << buf << std::endl;
		buf.clear();
		std::cout << "sever say:";
		std::cin >> buf;
		new_sock.Send(buf);
	}
	lst_sock.Close();


 这样的话只能与每个客户端获取一次,但如果把获取新连接放在while循环外面的话,又只能与一个客户端进行通信。
  当前在一个执行流中完成了多个操作:获取新连接,接收数据,发送数据。然而这些操作都有可能导致流程阻塞,因为我们在固定流程下,有可能对没有数据到来的socket进行操作。因此导致阻塞
将执行流分为2类:
1.获取新连接
 一旦获取到一个新连接,就启动一个新的执行流,让这个新的执行流去与客户端进行通信
  ①.因为没有新连接到来的阻塞,不会影响与客户端的通信
  ②.与客户端通信的阻塞,不会影响获取新连接
2.与客户端进行通信

//tcp_srv.cpp
#include<iostream>
#include<signal.h>
#include<sys/wait.h>
#include"tcpsocket.hpp"
void sigcb(int no) {
	//SIGCHILD是一个非可靠信号有可能丢失,因此在一次信号处理中就需要处理到没有子进程退出为止
	while(waitpid(-1, NULL, WNOHANG) > 0); //返回值大于0表示有子进程退出
}
int main(int argc, char* argv[]) {
	if (argc != 3) {
		std::cout << "usage: ./tcp_srv ip port\n";
		return -1;
	}
	signal(SIGCHLD, sigcb);
	std::string ip = argv[1];
	uint16_t port = std::stoi(argv[2]);
	TcpSocket lst_sock;
	CHECK_RET(lst_sock.Socket());//创建套接字
	CHECK_RET(lst_sock.Bind(ip, port));//开始连接
	CHECK_RET(lst_sock.Listen());//开始监听
	while (1) {
		TcpSocket new_sock;
		bool ret = lst_sock.Accept(&new_sock);//通过监听套接字获取新建连接
		if (ret == false) {
			continue;//服务端不能因为一个新建套接字失败就退出
		}
		int pid = fork();
		if (pid == 0) {
			while (1) {
				std::string buf;
				new_sock.Recv(&buf);//通过新建连接与指定客户端进行通信
				std::cout << "client say:" << buf << std::endl;
				buf.clear();
				std::cout << "sever say:";
				std::cin >> buf;
				new_sock.Send(buf);
			}
			new_sock.Close();
			exit(0);
		}
		new_sock.Close();
	}
	lst_sock.Close();
}	
//tcp_cli.cpp
#include<iostream>
#include"tcpsocket.hpp"

int main(int argc, char* argv[]) {
	if (argc != 3) {
		std::cout << "usage: ./tcp_cli ip port\n";
		return -1;
	}
	std::string srv_ip = argv[1];
	uint16_t srv_port = std::stoi(argv[2]);
	TcpSocket sock;
	CHECK_RET(sock.Socket());//创建套接字
	CHECK_RET(sock.Connect(srv_ip, srv_port));
	while (1) {
		std::string buf;
		std::cout << "client say:" << buf;
		std::cin >> buf;
		sock.Send(buf);
		buf.clear();
		sock.Recv(&buf);
		std::cout << "server say:" << buf << std::endl;
	}
	sock.Close();
	return 0;
}
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值