网络编程TCP连接服务器与UDP连接服务器
网络编程相关知识
局域网和广域网的概念
局域网:
一般是由连在一个路由器下的设备组成的一个网络
广域网
将一个个的局域网连接在一块组成网络
链接一个服务器的流程
1)在浏览器输入要访问的网址https://github.com/
2)浏览器会先去访问 dns 地址解析服务器拿到域名对应的 ip
3)通过 ip 地址找到服务器
4)服务器会回数据给浏览器
5)浏览器会将服务器返回的数据进行整理
ip 地址的分类
IPv4 出现的时间比较久 IPv4 地址的数目 2^32,ipv4 的地址大概在 2015 年左右就分配完了
IPv6 共有 16 字节 128 位 2^128,为地球上的每一粒沙子分配一个 ip 地址
IPv4 地址的分类
A 类地址
第一个字节是固定的后三个字节是可变的
1.0.0.0-127.255.255.255
共有 127 个 A 类地址
一个 A 类地址 可以接 2^24 个设备,一般用于大型或者超大型的公司
B 类地址
前两个字节是固定的 后两个字节是可变的
128.0.0.0-191.255.255.255
一般用于中型的公司,每个 B 类地址可接 2^16 个设备
C 类地址
前三个字节是固定的后一个字节是可变的
192.0.0.0-223.255.255.255
一般用于局域网 也是我们日常生活中比较常见的 ip 地址
D 类地址
224.0.0.0-239.255.255.255
组播广播的地址
E 类地址
240.0.0.0-255.255.255.255 保留地址
端口号
用来对进程做区分的,由于网络编程是跨主机的进程间的通讯,不能用进程号来对进程进行区分
端口号是一个 16 位无符号整型数
范围:0-65535
0-1023 :被系统占用
1024 – 65535:可以使用的
网络字节序
在整个编程界可以接触到的字节序主要有两种
大端模式
在网络通讯里一般使用的是大端模式
高字节存放在低地址位
低字节存放在高地址位
小端模式
在 x86 主机上我们采用的就是小端模式
高字节存在高地址位
低字节存放在低地址位
客户端链接服务端的方式
TCP:
面向链接,可靠的数据传输 传输速度相对较慢
UDP
面向非连接的 不可靠的数据传输 传输速度相对较快
TCP 的三次握手
套接字
套接字的本质就是一个特殊的文件描述符
可以读也可以写
读或者写的时候可以采用 read 和 write
也可以采用 套接字专用的函数 send 和 recv
特殊在套接字可以绑定 ip 端口
TCP 的服务端模型
创建套接字
函数的头文件
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
函数的原型
int socket(int domain, int type, int protocol);
函数的参数
int domain, IP 地址的类型
AF_INET ipv4
AF_INET6 ipv6
int type, 套接字的类型
SOCK_STREAM 流式套接字 TCP
SOCK_DGRAM 数据报 UDP
int protocol 0
函数的返回值
成功返回 套接字
失败返回 -1
绑定 ip 端口信息
函数的头文件
#include <sys/types.h> /* See NOTES
#include <sys/socket.h>
函数的原型
int bind(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
函数的参数
int sockfd, 服务端本身套接字
const struct sockaddr *addr, 服务端的 ip 地址端口信息结构体
socklen_t addrlen 结构体的长度
函数的返回值
成功返回 0
失败返回 -1
struct sockaddr
{
sa_family_t sa_family; ip 地址的类型
char sa_data[14]; 包含了 ip 地址和端口的字符串
}
struct sockaddr_in
{
sa_family_t sin_family; ip 地址的类型
in_port_t sin_port; 端口号
struct in_addr sin_addr; ip 地址的结构体
};
struct in_addr
{
uint32_t s_addr; ip 地址
};
将小端的端口号转换成大端
函数的功能
将小端的端口转换成大端
函数的头文件
#include <arpa/inet.h>
函数的原型
uint16_t htons(uint16_t hostshort);
函数的参数
uint16_t hostshort:要转换的小端的端口
函数的返回值
成功返回 大端的端口号
将小端的 ip 地址转换成大端的 ip 地址
函数的功能
将小端的 ip 转换成大端的 ip
函数的头文件
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
函数的原型
in_addr_t inet_addr(const char *cp);
函数的参数
const char *cp:小端的 ip 地址
函数的返回值
成功返回 大端的 ip 地址
监听网络
函数的功能
监听网络
函数的头文件
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
函数的原型
int listen(int sockfd, int backlog);
函数的参数
int sockfd, 服务端本身的套接字
int backlog 最大监听数
函数的返回值
成功返回 0
失败返回 -1
接受客户端的链接
函数的头文件
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
函数的原型
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
函数的参数
int sockfd, 服务端本身的套接字
struct sockaddr *addr, 客户端的 ip 地址信息结构体
socklen_t *addrlen 结构体长度的指针
函数的返回值
成功返回 专门用来给客户端通讯的套接字
失败返回 -1
收发消息
函数的头文件
#include <sys/types.h>
#include <sys/socket.h>
函数的原型
ssize_t send(int sockfd, const void *buf, size_t len, int flags);
ssize_t recv(int sockfd, void *buf, size_t len, int flags);
函数的参数
sockfd : 用来通讯的套接字
buf: 发送/接收消息的存放的位置
len: 要收发的消息的大小
flags: 标志 0 阻塞的收发
函数的返回值
成功返回 成功发送/接收的字节数
失败返回 -1
将大端地址转换小端的字符串形式的 ip 地址
将大端的地址转换成小端
函数的头文件
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
函数的原型
char *inet_ntoa(struct in_addr in);
函数的参数
struct in_addr in: 用来存放大端的 ip 地址的结构体
函数的返回值
成功返回 小端的 ip 地址
失败返回 NULL
实例
#include <stdio.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>//服务端
int sfd,cfd,ret;
struct sockaddr_in seraddr;
struct sockaddr_in cliaddr;
socklen_t len=sizeof(cliaddr);
char buf[10]={0};
int main()
{
sfd = socket(AF_INET,SOCK_STREAM,0);//1.创建套接字
if(sfd < 0)
{
perror("socket");
return -1;
}
seraddr.sin_family=AF_INET;//地址类型
//端口号//转换小端到大端
seraddr.sin_port=htons(4444);
//将小端IP转换为大端IP
seraddr.sin_addr.s_addr=inet_addr("192.168.39.201");
//2.绑定 ip 端口信息
ret= bind(sfd, (struct sockaddr *)&seraddr,sizeof(seraddr));
if(ret < 0)
{
perror("bind");
return -1;
}
//3.监听网络
ret = listen(sfd,10);
if(ret < 0)
{
perror("listen");
return -1;
}
//4.接受客户端的连接请求
cfd=accept(sfd,(struct sockaddr *)&cliaddr,&len);
if(cfd < 0)
{
perror("accept");
return -1;
}
//将大端的地址转换为小端模式
printf("客户端IP:%s\\n",inet_ntoa(cliaddr.sin_addr));
// write(cfd,"LQHXX",6);
send(cfd,"LQHXXX",7,0);
// read(cfd,buf,10);
recv(cfd,buf,10,0);
printf("buf=%s\\n",buf);
close(cfd);//关闭套接字
close(sfd);
return 0;
}
TCP 的客户端模型
创建套接字
连接服务器
函数的头文件
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
函数的原型
int connect(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
函数的参数
int sockfd, 套接字
const struct sockaddr *addr, 服务器的 ip 地址信息结构体
socklen_t addrlen 结构体的大小
函数的返回值
成功返回 0
失败返回 -1
接收消息
发送消息
关闭套接字
实例
#include <stdio.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
//客户端
int fd,ret;
struct sockaddr_in seraddr;//服务端地址信息
char buf[10]={0};
int main()
{
fd = socket(AF_INET,SOCK_STREAM,0);//1.创建套接字
if(fd < 0)
{
perror("socket");
return -1;
}
// 连接服务器
seraddr.sin_family = AF_INET;
seraddr.sin_port = htons(4444);//服务端端口
seraddr.sin_addr.s_addr = inet_addr("192.168.39.201");
ret = connect(fd,(struct sockaddr *)&seraddr,sizeof(seraddr));
if(ret < 0)
{
perror("connect");
return -1;
}
//读取消息
read(fd,buf,10);
printf("buf=%s\\n",buf);
//写消息
write(fd,"nihao\\n",6);
//关闭套接字
close(fd);
return 0;
}
UDP 的通讯流程
创建套接字
绑定自己的信息
收消息
函数的头文件
#include <sys/types.h>
#include <sys/socket.h>
函数的原型
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,struct sockaddr *src_addr, socklen_t *addrlen);
函数的参数
int sockfd, 套接字
void *buf, 接收到的消息存放的缓冲区
size_t len, 要接受的消息的长度
int flags, 标志 0 阻塞的收
struct sockaddr *src_addr, 发来消息的对象的 ip 端口信息结构体
socklen_t *addrlen 对方的 ip 地址信息的长度
函数的返回值
成功返回成功接收的字节数
失败返回 -1
发消息
函数的头文件
#include <sys/types.h>
#include <sys/socket.h>
函数的原型
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,const struct sockaddr *dest_addr, socklen_t addrlen);
函数的参数
int sockfd, 套接字
const void *buf, 要发送的消息存放的缓冲区
size_t len, 要发送的消息的长度
int flags, 0 阻塞的发
const struct sockaddr *dest_addr, 要发送的对象的 ip 地址信息结构体
socklen_t addrlen 信息结构体的大小
函数的返回值
成功返回 成功发送的字节数
失败返回 -1
关闭套接字
实例
#include <stdio.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
int fd,ret;
struct sockaddr_in seraddr,cliaddr;
socklen_t len = sizeof(cliaddr);
char buf[10]={0};
int main()
{
//1创建套接字
fd = socket(AF_INET,SOCK_DGRAM,0);
if(fd < 0)
{
perror("socket");
return -1;
}
//绑定IP信息
seraddr.sin_family = AF_INET;
seraddr.sin_port = htons(5555);
seraddr.sin_addr.s_addr = inet_addr("192.168.39.201");
ret = bind(fd,(struct sockaddr *)&seraddr,sizeof(seraddr));
if(ret < 0)
{
perror("bind");
return -1;
}
// 收消息
recvfrom(fd,buf,10,0,(struct sockaddr *)&cliaddr, &len);
printf("buf=%s\\n",buf);
// 发信息
sendto(fd,"LQHXX",6, 0,(struct sockaddr *)&cliaddr, sizeof(cliaddr));//作为服务端
close(fd);
return 0;
}
UDP 的组播
组播
一个消息的发送者发送消息
加入到多播组的所有用户都能听到
本来 udp 通讯就支持组播
默认 udp 的组播他的属性是被关闭掉了
要想能够使用组播
就需要先将这个属性打开
要想打开这个属性就必须借助于一个函数
setsockopt()
组播还需要一个特殊的 ip 地址
224.0.0.0-239.255.255.255
设置套接字的属性
函数的头文件
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
函数的原型
int setsockopt(int sockfd, int level, int optname,
const void *optval, socklen_t optlen);
函数的参数
int sockfd, 要设置属性的套接字
int level, 网络的层
IPPROTO_IP
SOL_SOCKET
int optname, 要设置的属性的名字
IP_ADD_MEMBERSHIP 加入多播组
IP_MULTICAST_IF 创建多播组
SO_BROADCAST 打开广播
SO_REUSEADDR ip 地址复用
const void *optval, 属性相关的参数
socklen_t optlen 参数的大小
函数的返回值
成功返回 0
失败返回 -1
实例
创建多播组
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <net/if.h>
#include <unistd.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
//创建多播组
int sfd,ret;
struct ip_mreqn mygrop;
struct sockaddr_in addr;
int main()
{
sfd = socket(AF_INET,SOCK_DGRAM,0);//1.创建套接字
if(sfd < 0)
{
perror("socket");
return -1;
}
mygrop.imr_multiaddr.s_addr = inet_addr("224.224.224.224");//组播地址
mygrop.imr_address.s_addr = inet_addr("192.168.39.201"); //服务器地址
mygrop.imr_ifindex= if_nametoindex("ens33");//网卡
ret = setsockopt(sfd, IPPROTO_IP, IP_MULTICAST_IF,
&mygrop,sizeof(mygrop));
if(ret< 0)
{
perror("setsockopt");
return -1;
}
while(1)
{
addr.sin_family = AF_INET;//IPV4
addr.sin_port = htons(4444);
addr.sin_addr.s_addr = inet_addr("224.224.224.224");//组播地址
sendto(sfd,"LQHXX",6,0,(struct sockaddr *)&addr,sizeof(addr));
sleep(1);
}
return 0;
}
加入一个多播组
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <net/if.h>
#include <unistd.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
//加多播组
int sfd,ret;
struct ip_mreqn mygrop;
struct sockaddr_in cliaddr,addr;
socklen_t len = sizeof(struct sockaddr_in);
int main()
{
sfd = socket(AF_INET,SOCK_DGRAM,0);//1.创建套接字
if(sfd < 0)
{
perror("socket");
return -1;
}
//绑定自己的IP信息
cliaddr.sin_family = AF_INET;
cliaddr.sin_port = htons(4444);
cliaddr.sin_addr.s_addr = inet_addr("0.0.0.0");
ret = bind(sfd ,(struct sockaddr *)&cliaddr,sizeof(cliaddr));
//加入组播
mygrop.imr_multiaddr.s_addr = inet_addr("224.224.224.224");//组播地址
mygrop.imr_address.s_addr = inet_addr("192.168.39.201"); //服务器地址
mygrop.imr_ifindex= if_nametoindex("ens33");//网卡
ret = setsockopt(sfd, IPPROTO_IP, IP_ADD_MEMBERSHIP,&mygrop,sizeof(mygrop));
if(ret< 0)
{
perror("setsockopt");
return -1;
}
while(1)
{
char buf[10]={0};
recvfrom(sfd,buf,10,0,(struct sockaddr *)&addr,&len);
printf("%s:%s\\n",inet_ntoa(addr.sin_addr),buf);
}
return 0;
}
现象
评论前必须登录!
注册