回顾:
OSI七层模型
应用层
表示层
会话层
传输层
网络层
数据链路层
物理层
TCP/IP四层模型
IP地址
A 0 + 7 + 24
B 10 + 14 + 16
C 110 + 21 + 8
D 1110 + 28位组播地址
E 留待备用
192.168.0.103 /24
127.0.0.1:回环地址,可用于本机网络协议的测试和本地进程间通信
端口号
16位无符号整数,0-65535 1024被系统使用
字节序
---------------------------------
socket编程:
1.网络编程又叫socket编程
套接字是一个网络编程的接口,是网络数据传输的软设备,用于网络交互。
网络编程就是编写程序使两台连网的计算机相互交换数据,这个就是网络编程的全部内容。
unix/linux系统作为服务器操作系统存在至今,因此,Linux的网络功能应该是非常全面和强大。
网络编程其实有很成熟的通信模型,并且windows也通用。
2.通信模型(基于TCP的一对一的通信模型)
服务器:
1.创建socket,使用函数socket()
2.准备通信地址,使用结构体类型
3.绑定socket和通信地址,使用bind()
4.监听,使用函数listen()
5.等待连接,使用函数accept()
6.进行通信,使用read()、write()
7.关闭socket,使用函数close()
客户端:
1.创建socket,使用函数socket()
2.准备通信地址(指服务器的地址),使用结构体类型
3.连接服务器,使用函数connect()
4.进行通信,使用read()、write()
5.关闭socket,使用函数close()
具体函数:
1.socket()函数
#include
#include
int socket(int domain, int type, int protocol);
功能:主要用于创建可以实现通信的交流点,也就是socket通信载体(相当于电话机)
第一个参数:域/协议族,决定了是本地通信还是网络通信
AF_UNIX/AF_LOCAL 用于实现本地通信
AF_INET 用于实现基于IPv4的网络通信
AF_INET6 用于实现基于IPv6的网络通信
第二个参数:通信类型,决定了具体的通信式
SOCK_STREAM 提供有序的、可靠的、双向的、面向连接的字节流通信方式,默认使用TCP协议
SOCK_DGRAM 提供无序的、不可靠的,非面向连接的数据报通信方式,默认使用UDP协议
第三个参数:指定具体的协议,默认为0,使用默认协议
返回值:成功返回新的socket描述符
失败返回-1,errno被设置
2.通信地址数据类型
1.通用地址结构
struct sockaddr
{
sa_family_t sa_family;
char sa_data[14];
};
2.IPv4通信地址结构
struct sockaddr_in
{
sa_family_t sin_family;//协议族,AF_INET
in_port_t sin_port;//16位的端口号
struct in_addr sin_addr;//IP地址
};
struct in_addr
{
in_addr_t s_addr;
};
例:
struct sockaddr_in saddr;
memset(&saddr,0,sizeof(saddr);
saddr.sin_family = AF_INET;
saddr.sin_port = htons(10086);
saddr.sin_addr.s_addr = inet_addr("192.168.0.103");
端口格式转换相关函数:
#include
uint32_t htonl(uint32_t hostlong);
uint16_t htons(uint16_t hostshort);
uint32_t ntohl(uint32_t netlong);
uint16_t ntohs(uint16_t netshort);
h:host 本地格式
n:network 网络格式(大端模式)
s:short 2字节整数
l:long 4字节整数
功能:把本地格式转成网络格式,或者反过来。
IP地址格式转换相关函数:
in_addr_t inet_addr(const char *cp);
功能:将字符串形式的IP地址转为整数类型
char *inet_ntoa(struct in_addr in);
功能:将结构类型的IP地址转为字符串形式
3.绑定函数bind()
include
#include
int bind(int sockfd, const struct sockaddr *addr,
socklen_t addrlen);
功能:主要用于绑定socket和具体的通信地址
第一个参数:socket描述符,socket函数的返回值
第二个参数:结构体指针,不管是什么协议的地址结构,都需要强转为该类型
第三个参数:通信地址结构的大小,使用sizeof()计算即可
返回值:成功返回0
失败返回-1,errno被设置
4.监听listen函数
#include
#include
int listen(int sockfd, int backlog);
功能:为套接字sockfd建立一个连接请求监听队列,在调用listen函数成功后,这个套接字便成为服务套接字,即被动套接字
第一个参数: socket描述符
第二个参数:监听队列的大小
返回值:成功返回0
失败返回-1,errno被设置
5.等待连接,accept函数
#include
#include
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
功能:用于接收服务套接字sockfd上的连接请求
第一个参数:socket描述符,socket函数的返回值
第二个参数:结构体指针,用于保存接受的客户端通信地址
第三个参数:指针类型,用于保存客户端网络地址的信息的长度
返回值:成功返回一个连接套接字
失败返回-1,errno被设置
注:如果对客户地址不感兴趣,那么第二、三两个参数写NULL即可。
如果需要客户的地址,那么第三个参数必须由调用者初始化。
6.连接函数connect
#include
#include
int connect(int sockfd, const struct sockaddr *addr,
socklen_t addrlen);
功能:用于连接服务器
第一个参数:socket描述符,socket函数的返回值
第二个参数:服务器的通信地址
第三个参数:通信地址结构的大小,使用sizeof计算
返回值:成功返回0
失败返回-1,errno被设置
迭代服务器
并发服务器
多进程服务器
多线程服务器
多路复用服务器
作业:
用多线程服务器实现群聊
./a.out 192.168.0.129 10086 name
[name] hello
[name] hello
confd用数组保存即可
当有客户连接,保存其fd
当有客户下线,移除其fd
保存和移除的操作必须同步
-------------------------
IO多路复用
假如我们要在一个文件可读的情况下,马上读到数据,我们可以在这个文件上阻塞的读
如果有多个文件,那么就需要多个进程/线程去阻塞的读每个文件
能不能用一个函数,去实现多个文件的查询是否就绪的功能?
select()
运用select()函数是最具有代表性的实现复用服务器的方法
#include
/* According to earlier standards */
#include
#include
#include
int select(int nfds, fd_set *readfds, fd_set *writefds,
fd_set *exceptfds, struct timeval *timeout);
参数:
nfds:感兴趣的文件集合中最大的文件描述符 + 1;
readfds:对读感兴趣的文件描述符集合
writefds:对写感兴趣的文件描述符集合
exceptfds:对异常感兴趣的文件描述符集合
timeout:超时时间
select实现原理:
select 在实现时,会轮询,每隔一段时间就会去询问文件描述符[0,nfds)中的每一个文件,查看是否就绪,如果就绪那么把相应的文件描述符集合中置1.
...
直到有文件就绪或超时或出错。
void FD_CLR(int fd, fd_set *set); //把指定fd从集合中移除
int FD_ISSET(int fd, fd_set *set); //判断指定的fd是否在这个集合中
void FD_SET(int fd, fd_set *set); //把指定的fd设置到集合中去
void FD_ZERO(fd_set *set); //初始化指定的集合,全部清零
struct timeval {
time_t tv_sec; /* seconds */
suseconds_t tv_usec; /* microseconds */
};
在调用select函数之前,readfds是你对读感兴趣的文件描述符集合
在调用select函数之后,readfds是你对读感兴趣并且可以读的文件描述符集合
writefds,exceptfds同理。
timeout 在调用select之前,是超时时间
在调用select之后,是剩余时间
select返回:
1.有文件数据就绪,立马返回,返回就绪的文件描述符个数 >0
2.超时返回,返回0
3.出错了,返回-1 ,errno被设置
select调用方法与顺序:
1. 设置文件描述符
指定监控范围
设置超时时间
2.调用select函数
3.查看调用结果
-----------------------------------------
1.TCP与UDP协议的比较:
1.tcp协议:
传输控制协议 类似打电话
面向连接的 (建立连接-->进行通信-->断开连接)
在通信的整个过程中必需保持连接
该协议保证了数据的传递是可靠且有序的
属于全双工的字节流通信方式
服务器压力较大,资源消耗大,执行效率较低
2.udp协议:
用户数据报协议
面向非连接的 类似发短信
在通信的整个过程中不需要保持连接
不保证数据的可靠性和有序性
属于全双工的数据报通信方式
服务器压力较小,资源消耗小,执行效率高
2.基于UDP协议的通信模型
服务器:
1.创建socket,使用socket()函数
2.准备通信地址,使用结构体类型
3.绑定socket和通信地址,使用bind函数
4.进行通信,使用sendto()/recvfrom()
5.关闭socket,使用函数close
客户端:
1.创建socket,使用socket()函数
2.准备通信地址,使用结构体类型
3.进行通信,使用sendto()/recvfrom()
4.关闭socket,使用函数close()
相关API:
发送数据报函数:sendto()
#include
#include
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,
const struct sockaddr *dest_addr, socklen_t addrlen);
第一个参数:socket描述符,socket函数的返回值
第二个参数:被发送的数据首地址
第三个参数:发送数据的大小
第四个参数:发送标志
0:功能与write一样,即阻塞
MSG_DONTWAIT: 非阻塞
第五个参数:发送数据的目标地址
第六个参数:目标地址的大小
接收数据报的函数:recvfrom()
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,
struct sockaddr *src_addr, socklen_t *addrlen);
参数:参考sendto()函数
-----------------
UDP应用:
广播
组播
套接选项:
我们正常使用套接字编程时,一般只关注数据通信,而忽略套接字的不同特性
但有时需要设置地址复用,允许发送广播消息,将主机加入多播组,设置发送与接收缓冲区的大小等
这些都需要对套接字选项进行设置。
每个套接字在不同的协议层次上有不同的行为属性,那么这个行为属性就称为套接字选项
getsockopt
setsockopt
#include
#include
int getsockopt(int sockfd, int level, int optname,
void *optval, socklen_t *optlen);
注:optlen要初始化
int setsockopt(int sockfd, int level, int optname,
const void *optval, socklen_t optlen);
第一个参数:socket描述符,socket函数的返回值
第二个参数:要设置的套接字选项所在的协议层次
第三个参数:套接字选项的名称
第四个参数:保存要设置的选项值 的 缓冲区首地址
第五个参数:第四个参数的长度
SOL_SOCKET
SO_BROADCAST 允许发送广播数据报 int
SO_REUSEADDR 允许重用本地地址 int
SO_RCVBUF 接收缓冲区大小 int
SO_SNDBUF 发送缓冲区大小 int
IPPROTO_IP
IP_ADD_MEMBERSHIP 加入多播组 ip_mreq{}
广播:
广播是向同一网络中的所有主机传输数据的方法,是基于UDP的
广播地址:
192.168.0.255 直接广播/子网内广播
255.255.255.255 本地广播/全网广播
多播/组播:
多播/组播的数据传输也是基于UDP的,是同时传递到加入特定多播组的大量主机。
多播组是D类IP地址(224.0.0.0~239.255.255.255)
加入多播组的方式:
struct ip_mreq
{
struct in_addr imr_multiaddr;//多播组的IP
struct in_addr imr_interface;//加入多播组的那台主机的IP
};
struct ip_mreq join_addr;
join_addr.imr_multiaddr.s_addr = inet_addr("224.1.2.3");
join_addr.imr_interface.s_addr = inet_addr("192.168.1.110");
setsockopt(sockfd,IPPROTO_IP,IP_ADD_MEMBERSHIP,(void*)&join_addr,sizeof(join_addr));
UNIX协议域 unix domain socket
AF_UNIX/
AF_LOCAL
#include
#define UNIX_PATH_MAX 108
struct sockaddr_un
{
sa_family_t sun_family; /* AF_UNIX */
char sun_path[UNIX_PATH_MAX]; /* pathname */
};
利用socket编程接口,来实现本地进程间通信
UDS提供两类套接字:
字节流套接字(类似于TCP)
数据报套接字(类似于UDP)
练习:
利用网络传输文件
1.文件名
2.文件大小 stat()
3.文件内容