物理链路网络运输会话表示应用
物链网运会表应
实际的数据帧
TCP和UDP的异同(笔试面试)
主机:host
转换:to
网络:network
uint32_t htonl(uint32_t hostlong); //将4字节无符号整数的主机字节序转换为网络字节序,参数是主机字节序,返回值是网络字节序
uint16_t htons(uint16_t hostshort); //将2字节无符号整数的主机字节序转换为网络字节序,参数是主机字节序,返回值是网络字节序
uint32_t ntohl(uint32_t netlong); //将4字节无符号整数的网络字节序转换为主机字节序,参数是网络字节序,返回值是主机字节序
uint16_t ntohs(uint16_t netshort); //将2字节无符号整数的网络字节序转换为主机字节序,参数是网络字节序,返回值是主机字节序
IP地址类型
A类地址 | 1.0.0.0~127.255.255.255 | 2^7(网络号) | 2^24(主机号) | 已经保留不在供给 |
B类地址 | 128.0.0.0~191.255.255.255 | 2^14 | 2^16 | 名地址网管中心 |
C类地址 | 192.0.0.0~223.255.255.255 | 2^21 | 2^8 | 校园网或企业网、家庭网 |
D类地址 | 224.0.0.0~239.255.255.255 | 组播地址 | ||
E类地址 | 240.0.0.0~255.255.255.255 | 保留 |
1-128-192-224-240-255
A B C D E
子网掩码的使用:IP地址 & 子网掩码 --->子网网段
为了区分同一主机上的多个进程,使用端口号来进行处理
端口号是一个2字节的无符号整数存储,取值范围【0,65535】
网络通信中两个决定性因素:IP + 端口号
TCP和UDP的端口号是相互独立的
可以使用的:1024~49151,就是我们平时编写服务器使用的端口号
临时端口号:49152~65535,这部分是客户端运行时候动态选择的
TCP协议:
struct sockaddr_in sin;
sin.sin_family = AF_INET; //通信域
sin.sin_port = htons(SER_PORT); //端口号
sin.sin_addr.s_addr = inet_addr(SER_IP); //ip地址
int socket([ipv4/v6],[TCPUDP], 0);
int bind([套接字名],[地址信息] , sizeof([地址信息结构体]));
int listen([套接字名],[长度一般填128]);
int accept([套接字名], [地址信息接收指针] , [地址信息的长度]);
ssize_t recv([套接字名], void *buf, sizeof (buf), [阻塞或非阻塞]);
ssize_t send([新套接字名], void *buf, sizeof (buf), [阻塞或非阻塞]);
int connect([套接字名] ,[对端地址信息结构体], sizeof([对端地址信息结构体]));
服务器端模型
客户端模型:
1.cfd = socket(AF_INET, SOCK_STREAM, 0);
//2.1 填充地址信息结构体
struct sockaddr_in cin;
cin.sin_family = AF_INET; //通信域
cin.sin_port = htons(CLI_PORT); //端口号
cin.sin_addr.s_addr = inet_addr(CLI_IP); //ip地址
2.bind(cfd, (struct sockaddr*)&cin, sizeof(cin))
//3.1 填充服务器地址信息结构体
struct sockaddr_in sin;
sin.sin_family = AF_INET; //通信域
sin.sin_port = htons(SER_PORT); //服务器端口号
sin.sin_addr.s_addr = inet_addr(SER_IP); //服务器ip地址
3.connect(cfd, (struct sockaddr*)&sin, sizeof(sin) //将套接字文件描述符连接到addr指向的地址空间中
while(1)
{
4.send(cfd, buf, strlen(buf), 0);
5.recv(cfd, buf, sizeof(buf), 0);
}
6.close();
UDP协议:
ssize_t recvfrom([套接字名], buf[起始地址], sizeof(buf), [阻塞], [对端地址信息结构体],
[对端地址信息结构体大小]);
ssize_t sendto([套接字名], buf[起始地址], sizeof(buf), [阻塞], [对端地址信息结构体],
[对端地址信息结构体大小]);
服务器端模型:
1.sfd = socket(AF_INET, SOCK_DGRAM, 0)
//2.1 填充地址信息结构体
struct sockaddr_in sin;
sin.sin_family = AF_INET; //通信域
sin.sin_port = htons(SER_PORT); //端口号
sin.sin_addr.s_addr = inet_addr(SER_IP); //ip地址
2.bind(sfd, (struct sockaddr*)&sin, sizeof(sin)
while(1)
{
3.recvfrom(sfd, buf, sizeof(buf), 0, (struct sockaddr*)&cin, &addrlen);
4.sendto(sfd, buf, strlen(buf), 0, (struct sockaddr*)&cin, sizeof(cin))
}
5.close(sfd);
客户端模型:
1.cfd = socket(AF_INET, SOCK_DGRAM, 0);
//2.1 填充地址信息结构体
struct sockaddr_in cin;
cin.sin_family = AF_INET; //通信域
cin.sin_port = htons(CLI_PORT); //端口号
cin.sin_addr.s_addr = inet_addr(CLI_IP); //ip地址
2.bind(cfd, (struct sockaddr*)&cin, sizeof(cin)
//填充服务器的地址信息结构体
struct sockaddr_in sin; //接受对端地址信息
sin.sin_family = AF_INET; //服务器的通信域
sin.sin_port = htons(SER_PORT); //服务器端口号
sin.sin_addr.s_addr = inet_addr(SER_IP); //服务器ip地址
while (1)
{
3.//将数据发送给服务器
sendto(cfd, buf, strlen(buf), 0, (struct sockaddr*)&sin, sizeof(sin));
4.//接受服务器发来的消息
recvfrom(cfd, buf, sizeof(buf), 0, NULL, NULL);
}
5.close(cfd);
TCP和UDP基础通信模型注意事项
1> 无论是TCP还是UDP通信中,服务器端必须要绑定ip地址和端口号,以便于让客户端找到该服务器。对于客户端而言,可以绑定也可以不绑定,如果绑定了,则使用自己绑定的ip和端口号。如果没有绑定,则系统会自动绑定一个当前的IP地址和一个随机的端口号供客户端使用。
2> 对于TCP通信而言,可以使用recv和send进行通信,也可以使用read、write进行通信,还可以使用sendto和recvfrom进行通信,都没有问题。
3> 对于UDP通信而言,如果当前端只是用于接收数据,不发送数据,可以使用recvfrom、recv、read进行接收;如果当前端接收数据后还要发送数据给对端,则需要使用recvfrom进行接收数据,顺便将对端地址信息结构体接收过来。
4> UDP中是否可以使用connect函数呢?
答,可以使用。当UDP服务器端使用connect会跟指定的客户端建立一个唯一的通道。其他客户端就不能再进行通信了。如果想要断开连接,需要再次使用connect函数,并且将地址信息结构体中的family设置成AF_UNSPEC.
好处:1、能够提供数据传输效率
例如:A和B同时向服务器发送消息,但是A发送的消息较大,需要较长的时间,发送过程中可能会出现时间片用完,此时B向服务器发送消息,导致,服务器处理消息时,消息混乱。这时就可以先单独跟A建立连接,等所有数据传输结束后,再跟B通信
2、传输性能高
一般的UDP通信:获取对端地址信息 --> 将信息加载到内核 ---> 数据收发 --->获取对端地址信息 --> 将信息加载到内核 ---> 数据收发 --->获取对端地址信息 --> 将信息加载到内核 ---> 数据收发 --->....
UDP建立连接后:获取对端地址信息 --> 将信息加载到内核 ---> 数据收发 --->数据收发 --->数据收发 --->数据收发 --->数据收发 --->数据收发 --->
广播:广播地址:网络号 + 255
广播的发送端流程 ---> UDP
1、socket(AF_INET, SOCK_DGRAM, 0); //创建用于通信的套接字文件描述符
2、bind(rfd, (struct sockaddr*)&rin, sizeof(rin)); //可选,可以绑定也可以不绑定
3、setsockopt(sfd, SOL_SOCKET, SO_BROADCAST, &broad, sizeof(broad);
//设置当前套接字允许广播
4、sendto(sfd, wbuf, strlen(wbuf), 0, (struct sockaddr*)&bin, sizeof(bin));
//向广播地址发送消息
5、close(); //关闭套接字
广播的接收端流程
1、socket(); //创建用于通信的套接字文件描述符
2、bind(); //必须进行,但是,绑定的广播地址和端口号
3、sendto(); //向广播地址发送消息
4、close(); //关闭套接字
组播:使用D类地址
D类地址 | 224.0.0.0~239.255.255.255 | 组播地址 |
组播可以实现小范围的数据传播:将需要接收数据的接收端加入多播组,发送端向多播组中发送消息,每个组内成员都能接收到消息
需要对接收端进行设置,将接收端加入多播组
模型和广播相同,setsockopt改为:
//准备要设置的数据
struct ip_mreqn imr;
imr.imr_multiaddr.s_addr = inet_addr("224.1.2.3"); //组播ip
imr.imr_address.s_addr = inet_addr("192.168.0.143"); //主机ip
imr.imr_ifindex = 2; //网卡编号
if(setsockopt(rfd, IPPROTO_IP, IP_ADD_MEMBERSHIP, &imr, sizeof(imr)) == -1)
三向握手四次挥手
- 第一次握手:客户端发送SYN包(SYN=1, seq=0)给服务器,并进入SYN_SENT状态,等待服务器返回确认包。
- 第二次握手:服务器接收到SYN包,确认客户端的SYN,发送ACK包(ACK=1 , ack=1),同时发送一个SYN包(SYN=1, seq=0),并进入SYN_RCVD状态。
- 第三次握手:客户端接收到服务器的SYN包,以及ACK包,进入establish状态,同时向服务器发送ACK包(ACK=1, ack=1)。此时三次握手包发送完毕,服务器也进入establish状态
- 第一次挥手,主动关闭方发送一个FIN包(FIN=1, seq = u)给被动方,进入FIN_WAIT_1状态;
- 第二次挥手:被动方接收到FIN包,给主动方发送一个ACK包(ACK=1, ack=u+1);并进入CLOKSE_WAIT状态。主动方接受到ACK包后,进入FIN_WAIT_2状态。如果有数据没有发送完毕,则继续发送,直到发送完毕为止;
- 第三次挥手:被动方发送一个FIN包(FIN=1, seq=w),进入LAST_ACK状态.
- 第四次挥手:主动关闭方收到FIN包,回复一个ACK包(ACK=1, ack=w+1)。被动关闭方收到主动关闭方的ACK后关闭连接。
linux系统中的库
静态库:
就是将xxx.c源文件直接编译成 libxxx.a文件
静态体现在:静态库在跟主程序一起生成可执行程序时,会将静态库完整的放入可执行程序中,每个可执行程序中都独立拥有整个静态库。函数调用时,效率比较高,但是,可执行程序的体积比较大。
静态库制作方法:
编译生成静态库
gcc -c XXX.c -o XXX.o //只编译不链接生成二进制文件
2、ar -crs libXXX.a XXX.o //将XXX.o的二进制文件编译生成libXXX.a的静态库
一个静态库依赖多个.o文件:ar -crs libXXX.a XXX.o AAA.o BBB.o //将XXX.o的二进制文件编译生成libXXX.a的静态库
其中ar :指令表示要编译生成静态库
-c:create,表示创建静态库
-r:replace,表示如果XXX.o文件已经存在,则被新的文件替换
-s:重置静态库索引
动态库:
动态库就是将一个XXX.c的源文件,编译生成一个libXXX.so的二进制文件。
动态体现在:动态库在跟主程序一起生成可执行程序时,只是将相关函数的入口地址列表编译到可执行程序中,当执行到对应的函数调用时,会根据该函数的入口地址,找到对应的动态库,执行该库中的函数。不会将整个库封装到可执行程序中。多个可执行程序可以连接同一个动态库,所以动态库也称为共享库。动态库的体积较小,但是,执行效率没有静态库高。
动态库的制作:
1、gcc -fPIC -c XXX.c -o XXX.o //只编译不链接生成二进制文件, -fPIC表示忽略文件位置进行编译
2、gcc -shared XXX.o -o libXXX.so //编译生成动态库
多个程序共同生成动态库:gcc -shared XXX.o AAA.o BBB.o -o libXXX.so
s也可以将上面的两个操作合成一个
gcc -fPIC -shared XXX.c -o libXXX.so
动态库的使用
gcc main.c -L 库的路径 -I 头文件路径 -l库名 -o 可执行程序
数据库sqlite3的使用
sqlite数据库创建
sqlite3 sq.db
如果sq.db存在则直接打开sq.db数据库,如果不存在则先创建再打开;
另外也可以使用.open 创建数据库
sqlite>.open test.db
系统命令,需要以 . 开头,不需要以 ; 结尾
.quit 退出数据库
.exit 退出数据库
.help 显示帮助信息,获取所有系统命令;
.table 查看当前数据库下的所有表格;
.schema 查看表的结构
create table 表名 (字段名 数据类型, 字段名 数据类型);
添加数据:
insert into 表单名(字段1,字段2,....,字段n) values(数据1,数据2,...,数据 n)
查看表单中数据:
select 字段1,字段2,...,字段n from 表单名
查看指定表单中的指定字段的所有数据:
select * from 表单名
查看指定表单中的所有字段的所有数据:
insert into 表单1(字段1,字段2,....,字段n) select 字段1,字段2,...,字段n from 表单2
先查询出表单2中的所有指定字段的数据,再将这些数据,对应的添加到表单1中的每一个字段中去
where子句:
只要有任何附加的查询条件的时候,在英语语法应该写条件的地方写上 where 条件
比如说,想要查看 姓名为"张三"的所有信息 select * from stu where 姓名="张三"
修改表单中指定数据:
update 表单名 set 字段名=新数据 where条件定位 例如:将姓名为"张三"的成绩,改成50分 update stu set 成绩=50 where 姓名="张三"
。。。详见sqlite的文章
使用方法:
1、先获取当前文件描述符的属性
int flag = fcntl(fd, F_GETFL);
2、将当前的文件描述符的属性中,添加非阻塞属性
flag |= O_NONBLOCK;
3、将更改后的状态加到当前文件描述符中
fcntl(fd, F_SETFL, flag);