传输层有两个协议:TCP
/UDP
,这两个协议特性各有不同,因此实现流程也稍有差别;因此需要分开来讲。
面向数据报:无连接的,不可靠的,无序的,有最大长度限制的数据传输服务
面向字节流:基于连接的,可靠的,有序的,双向的字节流传输服务,以字节为单位,不限制上层传输数据大小的传输方式
网络通信,是网络中的两端主机上的进程之间的通信,这两端有个名称: 客户端
/服务器端
int socket(int domain, int type, int protocol);
domain:地址域----不同的网络地址结构 AF_INET即IPv4地址域
type:套接字类型----流式套接字(SOCK_STREAM)/数据报套接字(SOCK_DGRAM)
protocol:使用协议 0表示不同套接字下的默认协议(流式套接字为TCP,数据报套接字为UDP)
IPPROTO_TCP -- TCP协议 IPPROTO_UDP -- UDP协议
返回值:套接字的操作句柄----文件描述符
int bind(int sockfd, struct sockaddr *addr, socklen_t len);
sockfd:创建套接字返回的操作句柄
addr:要绑定的地址信息结构体
len:地址信息的长度
返回值:成功返回0,失败返回-1
用户先定义sockaddr_in的IPv4地址结构,强转之后传入bind中
int sendto(int sockfd, char *data, int data_len, int flag, struct sockaddr *dest_addr, socklen_t addr_len);
sockfd:套接字句柄
data:要发送数据的首地址
data_len:要发送数据的长度
flag:选项参数 默认为0--表示当前操作是阻塞操作
MSG_DONTWAIT--设置为非阻塞
若发送数据的时候,socket发送缓冲区已经满了,设置为0就阻塞等待,非阻塞就报错返回
dest_addr:目的端地址信息结构--表示数据要发给谁
addr_len:地址信息长度
返回值:成功返回实际发送的数据字节数;失败返回-1
int recvfrom(int sockfd, char *buf, int len, int flag, struct sockaddr *src_addr, socklen_t *addr_len);
sockfd:套接字操作句柄
buf:缓冲区的首地址,用于存放接收到的数据,从内核socket接收缓冲区中取出数据放入这个buf用户态缓冲区中
len:用户想要读取的数据长度,但是不能大于buf缓冲区的长度
flag:0--默认阻塞操作,若缓冲区没有数据则一直等待
MSG_DONTWAIT--非阻塞
dest_addr:接收到的数据的源端地址--表示这个数据是谁发的,从哪来的,以此地址为回复地址
addr_len:输入输出型参数,用于指定想要获取多长的地址信息;成功地址信息的实际长度,失败返回-1
int close(int fd);
#include
#include
#include
#include
#include // sockaddr结构体 / IPPROTO_UDP
#include // 包含一些字节序转换的接口
#include // 套接字接口头文件
int main(int argc, char *argv[])
{
// argc表示参数个数,通过argv向程序传递端口参数
if(argc != 3){
printf("./udp_srv ip port em: ./udp_srv 127.0.0.1 9000\n");
return -1;
}
const char *ip_addr = argv[1];
uint16_t port_addr = atoi(argv[2]);
// socket(地址域, 套接字类型, 协议类型);
int sockfd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
if(sockfd < 0){
perror("socket error!\n");
return -1;
}
// bind(套接字描述符, 地址结构, 地址长度);
// struct sockaddr_in IPv4地址结构
// struct in_addr{ uint32_t s_addr }
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons(port_addr);
// inet_addr 将一个点分十进制的字符串IP地址转换为网络字节序的整数IP地址
addr.sin_addr.s_addr = inet_addr(ip_addr);
socklen_t len = sizeof(struct sockaddr_in); // 获取IPv4地址结构长度
int ret = bind(sockfd, (struct sockaddr*)&addr, len);
if(ret < 0){
perror("bind error!\n");
return -1;
}
while(1){
char buf[1024] = {
0};
struct sockaddr_in cli_addr;
socklen_t len = sizeof(struct sockaddr_in);
// recvfrom(描述符, 缓冲区, 长度, 参数, 客户端地址信息, 地址信息长度);
// 阻塞接收数据,将数据放入buf中,将发送端的地址放入cli_addr中
int ret = recvfrom(sockfd, buf, 1023, 0, (struct sockaddr*)&cli_addr, &len);
if(ret < 0){
perror("recvfrom error!\n");
return -1;
}
printf("clicent say : %s\n", buf);
printf("server say : ");
fflush(stdout);
memset(buf, 0x00, 1024); // 清空buf中的数据
scanf("%s", buf);
sendto(sockfd, buf, strlen(buf), 0, (struct sockaddr*)&cli_addr, len);
if(ret < 0){
perror("sendto error!\n");
close(sockfd);
return -1;
}
}
}
#include
#include
#include
#include
#include
#include
#include
#include
class UdpSocket{
public:
UdpSocket():_sockfd(-1){
}
// 1.创建套接字
bool Socket(){
_sockfd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
if(_sockfd < 0){
perror("socket error!\n");
return false;
}
return true;
}
// 2.为套接字绑定地址信息
bool Bind(const std::string &ip, uint32_t port){
// 1.定义IPv4地址结构
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons(port);
addr.sin_addr.s_addr = inet_addr(ip.c_str());
// 2.绑定地址
socklen_t len = sizeof(struct sockaddr_in);
int ret = bind(_sockfd, (struct sockaddr*)&addr, len);
if(ret < 0){
perror("bind erroe!\n");
return false;
}
return true;
}
// 3.发送数据
bool Send(const std::string &data, const std::string &ip, uint16_t port){
// 1.定义对端地址信息的IPv4地址结构
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons(port);
addr.sin_addr.s_addr = inet_addr(ip.c_str());
// 2.向这个地址发送数据
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!\n");
return false;
}
return true;
}
// 4.接收数据
bool Recv(std::string *buf, std::string *ip = NULL, uint16_t *port = NULL){
struct sockaddr_in addr;
socklen_t len = sizeof(struct sockaddr_in);
int ret;
char tmp[4096] = {
0};
ret = recvfrom(_sockfd, tmp, 4096, 0, (struct sockaddr*)&addr, &len);
if(ret < 0){
perror("recvfrom erroe!\n");
return false;
}
buf->assign(tmp, ret); // 给buf申请ret大小空间,从tmp中拷贝ret长度的数据进去
if(ip != NULL){
*ip = inet_ntoa(addr.sin_addr); // 将网络字节序整数IP地址转换为字符串地址
}
if(port != NULL){
*port = ntohs(addr.sin_port);
}
return true;
}
// 5.关闭套接字
void Close(){
close(_sockfd);
_sockfd = -1;
return;
}
private:
int _sockfd;
};
// 客户端要给服务端发送数据,那么就需要知道服务端的地址信息
// 因此通过程序运行参数传入服务端的地址信息
int main(int argc, char *argv[])
{
if(argc != 3){
printf("em : ./udp_cli 127.0.0.1 9000\n");
return -1;
}
std::string ip_addr = argv[1];
uint16_t port_addr = atoi(argv[2]);
UdpSocket sock;
sock.Socket();
while(1){
std::cout << "client say : ";
std::string buf;
std::cin >> buf;
sock.Send(buf, ip_addr, port_addr);
buf.clear();
sock.Recv(&buf);
std::cout << "server say : " << buf << std:: endl;
}
sock.Close();
return 0;
}
操作系统
会选择一个合适的地址信息进行绑定(当前没有使用的端口)不可以