Linux系统编程之网络编程
网络编程第一天:
进行网络通信(TCP通信)
客户端:
1.创建socket
2.bind 绑定
3.connect 连接
4.recv send 数据接受发送
5.close 关闭套接字
服务器端:
1.创建套接字 socket
2.bind绑定
3.listen 监听(检测) 最大连接数
4.accept 响应
5.send、recv 接受发送
6.close 关闭
第一个函数 创建socket
函数原型 int socket(int domain, int type, int protocol);
头 文 件:#include <sys/socket.h>
参 数: domain: 是地址族
AF_INET
PF_INET // internet 协议
PF_UNIX // unix internal协议
PF_NS // Xerox NS协议
PF_IMPLINK // Interface Message协议
type : 套接字类型】】
SOCK_STREAM // 流式套接字 TCP常用
SOCK_DGRAM // 数据报套接字 UDP常用
SOCK_RAW // 原始套接字
protocol :参数通常置为0
返回值: 如果成功返回socket描述符 失败返回-1,错误存入errno中
写个代码 创建套接字
第二个函数 bind绑定
函数原型: int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
函数参数:sockfd 是socket描述符 socket函数的返回值
addr struct sockaddr的结构体变量的首地址,指向 sockaddr_in
addrlen 是 sizeof (struct sockaddr_in)
struct sockaddr //通用结构体 {
sa_family_t sa_family; //2字节:地址族, AF_xxx
char sa_data[14]; //14字节:协议地址
};
struct sockaddr_in //专用IPv4结构体{
sa_family_t sin_family; /* 2字节:地址族: AF_INET或AF_INET6等 */
in_port_t sin_port; /* 2字节:端口按网络字节顺序排列 */
struct in_addr sin_addr; /* 4字节:ip互联网地址 */
char sin_zero[8]; 8 bytes unused,作为填充
};
struct in_addr {
uint32_t s_addr; /* 4字节:以网络字节顺序排列的地址 */ };
第三个函数:字节序的转换
#include <arpa/inet.h>
uint32_t htonl(uint32_t hostlong);
作用:本机字节序转换long类型的网络字节序
uint16_t htons(uint16_t hostshort);
作用: 本机字节序转换成short类型的网络字节序
uint32_t ntohl(uint32_t netlong);
作用: 网络字节序转换成long类型的本地字节序
uint16_t ntohs(uint16_t netshort);
作用: 网络字节序转换成short类型的本地字节序。
作用: 把ip和端口和当前程序绑定
第四个函数:字符串转换成网络字节序
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
函数原型:in_addr_t inet_addr(const char *cp);
函数作用: 将字符串转换成网络字节序
参数: 需要转换的字符串
返回值: 网络字节序。
函数原型:char *inet_ntoa(struct in_addr in);
函数参数: in_addr 结构体变量
返回值: 点分十进制字符串
第五个函数:监听
int listen(int sockfd, int backlog);
参数: sockfd 套接字描述符
backlog 最大连接数
第六个函数: 响应
函数原型:int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
函数参数:sockfd 是socket描述符
addr 客户端的地址,等价来电显示。保存了客户端的ip和端口,传出参数
addrlen 结构体的大小(结构体长度)
堵塞函数
返回值:返回值是客户端的socket描述符。
第七个函数:接受和发送
函数原型:ssize_t send(int sockfd, const void *buf, size_t len, int flags);
函数参数:sockfd 是客户端的socket描述符
buf 需要发送的数据buf
len 发送的数据长度
flags 堵塞和非堵塞的标志 0位堵塞。
函数返回: 实际发送的数据长度,失败返回 -1
函数原型: ssize_t recv(int sockfd, void *buf, size_t len, int flags);
函数参数: sockfd 是客户端的socket描述符
buf 需要接受的数据buf
len 接受的数据长度
flags 堵塞和非堵塞的标志 0位堵塞。
函数返回: 实际接受的数据长度,失败返回 -1
第八个:关闭套接字
close(套接字)
第九个:客户端连接函数
函数原型:int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
函数参数: sockfd 套接字描述符 客户端自己的
addr 服务器的ip和端口的结构体。
addrlen 结构体大小。
网络概念:
IP地址:逻辑地址,网络通信的唯一标志,使用点分十进制表示 “192.168.1.4”
IPV4:32位。最多分配40亿ip地址,ip地址已经不足够使用。
ipv6:128位,非常多。足够分配,
DHCP:动态地址分配协议,当需要上网时,从DHCP服务器租赁一个未使用的ip地址,主机拿ip地址访问网络。使用完则DHCP服务器收回ip地址,以防给其他人使用。
物理地址: 网卡地址,48位,每个网卡有一个固定的编号,编号就是网卡地址,也就是MAC地址。两个部分 前24位–厂商编号 后24个人编号
各种协议解析:(了解)
FTP(文件传输协议):是网络上两台计算机传送文件的协议,运行在 TCP 之上,。FTP协议使用TCP20号和21号端口,20号端口用于数据交换,21号端口用于建立连接,
允许目录和文件访问,上传下载,不能远程执行文件。
TFTP(简单文件传输协议):是用来在客户机与服务器之间进行简单文件传输的协议,提供不复杂、开销不大的文件传输服务,使用UDP的69号端口。
HTTP(超文本传输协议):是用于从 WWW 服务器传输超文本到本地浏览器的传送协议。它可以使浏览器更加高效,使网络传输减少。
DNS(域名系统):在 Internet 上域名与 IP 地址之间是一一对应 的,域名虽然便于人们记忆,但机器之间只能互相识别 IP 地址,
它们之间的转换工作称为域名解析,使用53号端口。
SMTP(简单邮件传输协议):建立在 TCP 之上,是一种提供可靠且有效的电子邮件传输的协议。SMTP 是建模在 FTP 文件传输服务上的一种邮件服务,
主要用于传输系统之间的邮件信息,并提供与电子邮件有关的通知,使用 25 端口。
Telnet(远程登录协议):是登录和仿真程序,建立在 TCP 之上,它的基本功能是允许用户登录并进入远程计算机系统。
DHCP(动态主机配置协议):建立在 UDP 之上,是基于客户机/服务器模型(B/S模型)设计的一个局域网的网络协议。
TCP:传输控制协议,为用户进程提供可靠的全双工字节流。
IP:网间协议,提供分组递送服务
ICMP:互联网控制信息协议,处理路由器和主机之间流通的错误和控制消息,控制消息是指网络通不通、主机是否可达、路由是否可用等网络本身的消息.如ping命令。
ARP:地址解析协议,把ipv4地址映射成MAC地址
RARP 地址解析协议,把MAC地址转换成IP地址
阐述下tcp和udp的区别(重点)
TCP是基于连接的通信,UDP是非连接通信
TCP是可靠传输,数据不丢失,而UDP是不可靠传输,有可能丢失
TCP是在传输层,UDP也在传输层
TCP需要三次握手,而UDP不需要。
UDP传输速度快,发送小尺寸数据,适合做视频等较大数据的传输
TCP是一对一通信,而UDP可以一对多通信。
TCP是基于数据流通信,而UDP是基于数据报通信
子网掩码:用来区分网络地址和主机地址部分。子网掩码也是点分十进制表示,也是32位,和ip地址一一对应关系。子网掩码中的1表示ip地址中的网络部分,0表示主机部分。
例如:我的ip地址是192.168.5.169 子网掩码是255.255.255.0
11000000.10101000.00000101.10101001
11111111.11111111.11111111.00000000
所以网络地址部分是:192.168.5 主机地址部分是169
(了解)
A类:(1.0.0.0-126.0.0.0)(默认子网掩码:255.0.0.0或 0xFF000000)第一个字节为网络号,后三个字节为主机号。该类IP地址的最前面为“0”,
所以地址的网络号取值于1~126之间。一般用于大型网络。(第一位必须是0)
B类:(128.0.0.0-191.255.0.0)(默认子网掩码:255.255.0.0或0xFFFF0000)前两个字节为网络号,后两个字节为主机号。该类IP地址的最前面为“10”,
所以地址的网络号取值于128~191之间。一般用于中等规模网络。
C类:(192.0.0.0-223.255.255.0)(子网掩码:255.255.255.0或 0xFFFFFF00)前三个字节为网络号,最后一个字节为主机号。该类IP地址的最前面为“110”,
所以地址的网络号取值于192~223之间。一般用于小型网络。
D类:是多播地址。该类IP地址的最前面为“1110”,所以地址的网络号取值于224~239之间。一般用于多路广播用户。
E类:是保留地址。该类IP地址的最前面为“1111”,所以地址的网络号取值于240~255之间。IP网络中通常用最小的IP地址标识网络本身,
将最大的IP地址作为该网络的广播地址,其余所有IP地址都分配给网络中的主机。然而,局域网中的主机并不能直接访问Internet,
说需要通过一个作为代理的网关或网络地址转换服务(NAT)才能访问Internet。通常将IP地址的第一个或最后一个留给该网络的Internet网关。
三次握手 四次挥手(看图片,重点中的重点)
作业: 客户端可以不断的发送数据给服务器,服务器收到每个消息都回复一个ok,如果客户端发送bye,则服务器和客户端都挂关闭。
网络编程第二天:
UDP通信
服务器 客户端
socket socket
bind bind
recvfrom sento
close close
UDP 通信数据发送:
函数原型:ssize_t sendto(int sockfd, const void *buf, size_t len, int flags, const struct sockaddr *dest_addr, socklen_t addrlen);
函数作用: 在使用udp时候发送数据。(也可以用于tcp)
函数参数:sockfd 是socket的返回值 描述符
buf 需要发送数据的缓存 一般是malloc或者数组
len 需要发送的数据长度
flags 是否堵塞 0为堵塞
dest_addr 发送服务器的地址结构体
addlen 结构体地址长度
返回值:成功返回实际发送的数据字节数 失败返回-1
UDP通信 数据接受
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr *src_addr, socklen_t *addrlen);
函数参数:sockfd 是socket的返回值 描述符
buf 需要接受数据的缓存 一般是malloc或者数组
len 需要接受的buf的数据长度
flags 是否堵塞 0为堵塞
src_addr 发送服务器的地址结构体
addlen 结构体的长度的地址
返回值:成功返回实际接受到的数据,失败返回-1
函数作用:在UDP通信是接受数据(也可以用于TCP)
练习:客户端发送文件到服务器端。客户端发送的时候,需要先发一个结构体和数据包一起。
结构体是struct file{
int size
char buf【128】;
int flags
}
广播通信:
一个发送端,多个接收端,所有当前网络的用户都可以接受到消息,一对多的关系。
单播: 一对一 ,广播是一对所有。
多播: 也叫组播,某一部分可以收到,另外一个部分收不到。(中间)
广播地址:最大的主机地址就是广播地址,192.168.5.124的广播地址192.168.5.255
广播是属于UDP通信方式。
客户端(发送) 服务器(接受)
socket socket
bind bind
设置允许广播 设置允许广播
设置广播地址为接收端地址 数据接受
数据发送 接续接受
关闭 关闭
设置广播:
int setsockopt(int sockfd, int level, int optname, const void *optval, socklen_t optlen);
函数作用:设置广播(只是其中一个功能)
函数参数: sockfd 是socket描述符
level 选项定义的层次,SOL_SOCKET (设置socket属性)
IPPROTO_TCP(设置tcp属性)
optname 需要设置或者获取的套接字的选项。 SO_BROADCAST
optval 指针,指向存放所获得选项的缓冲区。 如果是1则设置广播,如果是0则取消广播
optlen 指针 ,上一个参数的长度
返回值:如果成功返回0,失败返回-1
int getsockopt(int sockfd, int level, int optname, void *optval, socklen_t *optlen);
函数作用:获取socket的状态信息(获取接受发送缓存区等)
多播通信:也叫作组播通信,给某一部分发送消息
多播地址:D类地址,起始是224.0.0.0~239.255.255.255 。
在进行组播数据发送过程中,可以先自定义一个多播地址。把自己的ip地址加入到多播地址后,给多播地址发送数据,当前组中所有成员都可以收得到 。
客户端 (发送端) 服务器端(接收端)
socket socket
bind(选择) bind
设置允许多播 设置允许多播
加入到多播 加入到多播
给多播发数据 收数据
关闭 退出多播
关闭
setsockopt设置多播:
IP 多播通信必须依赖于 IP 多播地址, 在 IPv4 中它是一个 D 类 IP 地址, 范围从 224.0.0.1 到
239.255.255.255,并被划分为局部链接多播地址、预留多播地址和管理权限多播地址三类:
1)局部链接多播地址范围在 224.0.0.0~224.0.0.255,这是为路由协议和其它用途保留的地址,路由 器并不转发属于此范围的IP包;
2)预留多播地址为 224.0.1.0~238.255.255.255,可用于全球范围(如Internet)或网络协议;
3)管理权限多播地址为 239.0.0.0~239.255.255.255,可供组织内部使用,类似于私有 IP 地址,不能 用于 Internet,可限制多播范围
No such device的解决办法,把广播地址加入路有表
sudo route add -net 224.0.0.88 netmask 255.255.255.255 eth0
1>建立一个socket;
2>设置多播的参数,例如超时时间TTL,本地回环许可LOOP等
3>加入多播组
4>发送和接收数据
5>从多播组离开
2.多播程序设计使用setsockopt()函数和getsockopt()函数来实现,组播的选项是IP层的。
3.setsockopt()的选项
int setsockopt( int sockfd, int level,int optname, const void *optval, socklen_t optlen );
成功执行返回0,否则返回-1
选项 IP_ADD_MEMBERSHIP 和 IP_DROP_MEMBERSHIP
加入或者退出一个多播组,通过选项 IP_ADD_MEMBERSHIP 和 IP_DROP_MEMBERSHIP,对一个结 构 struct ip_mreq 类型的变量进行控制,struct ip_mreq 原型如下:
struct in_addr{in_addr_t s_addr;}
struct ip_mreq{
// 多播组 IP,类似于 QQ 群号
struct in_addr imn_multiaddr;
struct in_addr imr_interface; // 将要添加到多播组的 IP,类似于QQ 成员号}
网络编程第三天:
一、多播编程流程
1>建立一个socket;
2>设置多播的参数,例如超时时间TTL,本地回环许可LOOP等(略)
3>加入多播组 setsocketopt
4>发送和接收数据 (给多播组地址发)
5>从多播组离开 setsockopt
2.多播程序设计使用setsockopt()函数和getsockopt()函数来实现,组播的选项是IP层的。
3.setsockopt()的选项
int setsockopt( int sockfd, int level,int optname, const void *optval, socklen_t optlen );
成功执行返回0,否则返回-1
选项 IP_ADD_MEMBERSHIP 和 IP_DROP_MEMBERSHIP
加入或者退出一个多播组,通过选项 IP_ADD_MEMBERSHIP 和 IP_DROP_MEMBERSHIP,
对一个结 构 struct ip_mreq 类型的变量进行控制,struct ip_mreq 原型如下:
struct in_addr{in_addr_t s_addr;}
struct ip_mreq{
// 多播组 IP,类似于 QQ 群号
struct in_addr imn_multiaddr;
struct in_addr imr_interface; // 将要添加到多播组的 IP,类似于QQ 成员号}
strubt hp mrdq(
strubt hn _ccr hmr multh_ccr;
strubt hn _ccr hmr hntdre_bd;
);
strubt hp mrdq mrdq;
azdro(&mrdq, shzdoe(mrdq));
mrdq.hmr multh_ccr.s _ccr = ine_addr(“224.10.10.1”);
mrdq.hmr hntdre_bd.s _ccr = htonl(INADDR ANY);
setsockopt(sobkec, IPPR0T0_IP, IP_ADD_MEMBER, &mrdq, shzdoe(mrdq));
二、io模型
1>堵塞IO模型
最常用,超简单,效果最低。
(read write recv send recvfrom sendto accpet )
(bind listen close socket)
2>非堵塞IO模型
函数不堵塞,直接返回,需要轮询。占用cpu
第一种方式: 直接在recv或者send 或者recvfrom 或者sendto的参数设置非堵塞
例如: recv(fd,buf,sizeof(buf),MSG_DONTWAIT);
第二种方式:fcntl函数设置
int flag = fcntl(fd,F_GETFL,0);
flag |= O_NONBLOCK 接下来的read write等函数都是非堵塞的。
3> 多路复用IO模型
#include <sys/select.h>
函数原型: int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);
头 文 件: #include <sys/time.h> #include <sys/types.h> #include <unistd.h>
函数作用:做多路复用函数(等待内核通知,然后再通知给read 或者write函数)
函数参数: nfds 最大描述符值+1
readfds 需要读取的描述符的集合(需要监控的是读缓存)
writefds 需要写的描述符集合(需要监控的是写缓存)
exceptfds 需要异常的描述符集合(监控异常信息)
timeout 超时检测。如果不设置NULL默认是堵塞
返回值: 返回负数 select错误
返回正数: 某些描述符有信号。
返回0则表示等待超时。
以下函数是辅助select函数的使用。
void FD_CLR(int fd, fd_set *set);
函数作用:把所有描述符清空,把fd的描述符在set中清空。
int FD_ISSET(int fd, fd_set *set);
函数作用: 判断fd是否在set描述符集合中。
void FD_SET(int fd, fd_set *set);
函数作用:将fd设置到set描述符集中
void FD_ZERO(fd_set *set);
函数作用: 把描述符集全部清空,编程0;
4>信号驱动模型
发送信号
案例:写一个UDP的接受端,设置成非堵塞(fcntl),然后再while1中不断的读取数据。读取到数据后打印出来。
案例:写一个发送端,需要监控接受和发送复用。(select后需要监控发送结合和接受集合)。
gethostname() 获得主机名
函数原型: int gethostname(char *name, size_t len);
头文件: #include <unistd.h>
函数作用: 获得主机名
函数参数: name 得到主机名,buf存储
len 主机名buf长度
函数原型: int getpeername(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
函数作用: 获得与套接口相连的远程协议地址
头 文 件: #include <sys/socket.h>
函数参数: sockfd 是socket描述符
addr 用于保存地址的buf
addrlen 保存地址buf的长度
函数原型 struct hostent *gethostbyname(const char *name);
头 文 件: #include <netdb.h>
函数作用:根据主机名取得主机信息
函数参数: *name 主机名
结构体如下: struct hostent {
char *h_name; /* 主机规范名 */
char **h_aliases; /* 别名列表 */
int h_addrtype; /*主机地址类型 */
int h_length; /* 主机地址长度 */
char **h_addr_list; /* 主机地址列表 */
}
案例:
int main() {
struct hostent* addr = gethostbyname("www.baidu.com");
printf("%s\n",(addr->h_addrtype == PF_INET)?"IPV4":"IPV6");
for(int i = 0;addr->h_addr_list[i];++i) {
printf("%s\n" ,inet_ntoa( *(struct in_addr*)addr->h_addr_list[i]));
}
return 0;
}
//自学
getsockname() 获得本地套接口协议地址
gethostbyaddr() 根据主机地址取得主机信息
getprotobyname() 根据协议名取得主机协议信息
getservbyname() 根据服务名取得相关服务信息
getservbyport() 根据端口号取得相关服务信息
重点: 设置网络属性。
setsockopt() 获取/设置一个套接口选项
函数原型:int getsockopt(int sockfd, int level, int optname, void *optval, socklen_t *optlen);
sockfd:一个标识套接口的描述字
level:选项定义的层次。支持的层次仅有SOL_SOCKET(socket属性)和
IPPROTO_TCP(tcp属性)IPPROTO_IP
optname:需设置的套接口选项。
optval:指针,指向存放所设置选项值的缓冲区。
optlen:指针,指向optval缓冲区的长度值。
预习:unix通信和延迟处理。
网络编程第四天:
1.unix域socket
不属于网络通信内容,只在多进程之间通信。(ipc的一种通信方式)
两种方式:tcp和udp
通过普通文件的路径名来识别socket,不需要提供ip地址。
unlix的TCP域套接字
服务器 客户端
socket socket
bind bind
listen connect
accept ....
read read
write write
close close
流程:
首先创建socket。int fd =socket(PF_UNIX,SOCK_STREAM.0);
其次绑定结构体:
struct sockaddr_un {
sa_family_t sa_prefix 协议簇
char sun_path[108]; 路径名
};
再次:绑定 bind函数
接下来listen ,accpet ,read ,write close
注意:unix需要注意 再bind前需要保证文件不存在。可以提前用remove删除bind的文件。防止出错。
网络延迟检测:
setsockeopt 函数,第三个参数设置 SO_RECVTIMEO—设置接收延迟时间
SO_SENDTIMEO—设置发送的延迟时间
第四个参数: 结构体 simeval的结构体
注意:需要加上头文件 #include<sys/time.h>
struct timeval
{
__time_t tv_sec; /* 秒数. /
__suseconds_t tv_usec; / 微秒数 */
};