今天来到了网络编程,主要讲了网络、网络协议模型以及UDP编程
网络
网络主要是进行:数据传输和数据共享
网络协议模型
OSI协议模型应用层 实际发送的数据表示层 发送的数据是否加密会话层 是否建立会话连接传输层 数据传输的方式(数据报、流式)网络层 数据的路由(如何从一个局域网到达另一个局域网) IP地址数据链路层 局域网下如何通信物理层 物理介质的连接TCP/IP协议模型 应用层 传输的数据传输层 传输的方式网络层 数据如何从一台主机到达另一台主机网络接口层 物理介质的连接
应用层
应用层主要的传输协议有:HTTP 超文本传输协议HTTPS FTP 文件传输协议TFTP 简单文本传输协议SMTP 邮件传输协议MQTT TELNET ..
传输层
UDP 用户数据报协议特点:1.实现机制简单2.资源开销小3.不安全不可靠TCP 传输控制协议特点:1.实现机制复杂2.资源开销大3.安全可靠
网络层
网络层这块主要讲一下IPv4
IP地址:唯一标识网络中一台主机的标号IP地址:网络位 + 主机位子网掩码:用来标识IP地址的网络位和主机位子网掩码是1的部分表示IP地址的网络位子网掩码是0的部分表示IP地址的主机位网段号:网络位不变,主机位全为0,表示网段号广播地址:网络位不变,主机位全为1,表示广播地址IP地址类型:A类1.0.0.0 - 126.255.255.255子网掩码:255.0.0.0管理超大规模网络10.0.0.0 - 10.255.255.255 B类128.0.0.0 - 191.255.255.255子网掩码:255.255.0.0 管理大中规模型网络172.16.0.0 - 172.31.255.255C类192.0.0.0 - 223.255.255.255子网掩码:255.255.255.0管理中小规模型网络 192.168.0.0 - 192.168.255.255D类224.0.0.0 - 239.0.0.0用于组播E类240.0.0.0 - 255.255.255.255 用于实验
UDP编程
也是进程间socket套接字编程
1.发端
socket
int socket(int domain, int type, int protocol);功能:创建一个用来通信的文件描述符参数:domain:使用的协议族 AF_INET (IPv4协议族)type:套接字类型SOCK_STREAM:流式套接字SOCK_DGRAM:数据报套接字SOCK_RAW:原始套接字protocol:协议默认为0 返回值:成功返回文件描述符失败返回-1
sendto
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,const struct sockaddr *dest_addr, socklen_t addrlen);功能:利用套接字向指定地址发送数据信息 参数:sockfd:套接字文件描述符buf:发送数据空间首地址len:发送数据的长度flags:属性默认为0 dest_addr:目的地址信息存放的空间首地址addrlen:目的地址的长度struct sockaddr_in {sa_family_t sin_family; /* address family: AF_INET */in_port_t sin_port; /* port in network byte order */struct in_addr sin_addr; /* internet address */};/* Internet address. */struct in_addr {uint32_t s_addr; /* address in network byte order */};返回值:成功返回实际发送字节数失败返回-1
recvfrom
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,struct sockaddr *src_addr, socklen_t *addrlen);功能:从套接字中接收数据参数:sockfd:套接字文件描述符buf:存放数据空间首地址flags:属性 默认为0 src_addr:存放IP地址信息的空间首地址addrlen:存放接收到IP地址大小空间的首地址返回值:成功返回实际接收字节数失败返回-1
inet_addr、htons
inet_addr:in_addr_t inet_addr(const char *cp);功能: 将字符串IP地址转换为内存中的IP地址 htonsuint16_t htons(uint16_t hostshort);功能:将本地字节序转换为网络的大端字节序
eg:编写程序实现从终端接收字符串发送给 windows软件调试助手,并接受软件助手的回复,显示在终端屏幕上
#include"head.h"int main(void)
{int sockfd = 0;struct sockaddr_in recvaddr; struct sockaddr_in addc;char tmpbuff[1024] = {"hello world"}; //发送的字符串char recvbuff[1024] = {0};ssize_t nsize = 0;socklen_t addrlen;sockfd = socket(AF_INET,SOCK_DGRAM,0); //参数1:IPv4协议族;参数2:数据报套接字;参数3:协议,默认为0if(-1 == sockfd){perror("fail to socket");return -1;}recvaddr.sin_family = AF_INET;recvaddr.sin_port = htons(50000); //将端口50000转为网络的大端模式recvaddr.sin_addr.s_addr = inet_addr("192.168.1.104");//将字符串IP地址转为内存中的IP地址addrlen = sizeof(addc);nsize = sendto(sockfd,tmpbuff,strlen(tmpbuff),0,(struct sockaddr *)&recvaddr,sizeof(recvaddr));if(-1 == nsize){perror("fail to sendto");return -1;}printf("成功发送 %ld 字节!\n",nsize);recvfrom(sockfd,recvbuff,sizeof(recvbuff),0,(struct sockaddr *)&addc,&addrlen); //接收printf("%s",recvbuff);close(sockfd);return 0;
}
结果:
我们可以看到
发端:socket -> sendto -> close
收端: socket -> bind -> recvfrom -> close
UDP需要注意的细节点
1.UDP是无连接,发端退出,收端没有任何影响
2.UDP发送数据上限,最好不要超过1500个字节
3.UDP是不安全不可靠的,连续且快速的传输数据容易产生数据丢失
UDP包头长度:8个字节
分别是:源端口号(2个字节)目的端口号(2个字节)长度(2个字节)校验和(2个字节)
eg:要求在不同主机中编写两个程序,实现全双工聊天功能
1.进入软件后接收当前用户的昵称
2.显示的格式为 对方用户昵称(对方IP:对方端口)>接收到的内容
3.用户输入".quit"退出聊天
4.网络通信时收发结构体
struct person
{
char name[32];
char text[512];
};
head.h
#ifndef __HEAD_H__
#define __HEAD_H__#include <sys/types.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <unistd.h>
#include <fcntl.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <dirent.h>
#include <time.h>
#include <pwd.h>
#include <grp.h>
#include <pthread.h>
#include <semaphore.h>
#include <signal.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <sys/shm.h>
#include <sys/sem.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/ip.h>
#include <netinet/in.h>struct person
{char name[32];char text[512];
};#endif
client.c
#include"head.h"int sockfd = 0;
struct sockaddr_in sendaddr;
struct sockaddr_in recvaddr;
ssize_t nsize = 0;
socklen_t addrlen = sizeof(recvaddr);void *threadSend(void *arg) //发端
{struct person user;scanf("%s",user.name);nsize = sendto(sockfd,user.name,strlen(user.name),0,(struct sockaddr *)&sendaddr,sizeof(sendaddr));if(-1 == nsize){perror("fail to sendto");return NULL;}while(1){memset(&user.text,0,sizeof(user.text));scanf("%s",user.text);nsize = sendto(sockfd,user.text,strlen(user.text),0,(struct sockaddr *)&sendaddr,sizeof(sendaddr));if(-1 == nsize){perror("fail to sendto");return NULL;}if(!strcmp(user.text,".quit")){break;}}
}void *threadRecv(void *arg)
{struct person user;nsize = recvfrom(sockfd,user.name,sizeof(user.name),0,(struct sockaddr *)&recvaddr,&addrlen); //首先接收名字if(-1 == nsize){perror("fail to recvfrom");return NULL;}while(1){memset(&user.text,0,sizeof(user.text)); //刷新nsize = recvfrom(sockfd,user.text,sizeof(user.text),0,(struct sockaddr *)&recvaddr,&addrlen);if(-1 == nsize){perror("fail to recvfrom");return NULL;}if(!strcmp(user.text,".quit")){break;}printf("%s %s:%d > %s\n",user.name,inet_ntoa(recvaddr.sin_addr),ntohs(recvaddr.sin_port),user.text);}
}int main(void)
{int ret = 0;pthread_t send;pthread_t recv;pthread_create(&send,NULL,threadSend,NULL); //创建发送线程pthread_create(&recv,NULL,threadRecv,NULL); //创建接收线程sockfd = socket(AF_INET,SOCK_DGRAM,0); if(-1 == sockfd){perror("fail to socket");return -1;}sendaddr.sin_family = AF_INET;sendaddr.sin_port = htons(50000);sendaddr.sin_addr.s_addr = inet_addr("192.168.1.174");recvaddr.sin_family = AF_INET;recvaddr.sin_port = htons(50000);recvaddr.sin_addr.s_addr = inet_addr("192.168.1.152");ret = bind(sockfd,(struct sockaddr *)&recvaddr,sizeof(recvaddr)); //绑定接收端的IP地址和端口号if(-1 == ret){perror("fail to bind");return -1;}pthread_join(send,NULL);pthread_join(recv,NULL);close(sockfd);return 0;
}
结果: