linux TCP
通信
-
管道:有名管道、无名管道
-
信号:异步操作
kill发送信号signal注册信号
-
共享内存:通信效率高
-
消息队列:增强管道,添加数据标识符
-
信号量:用于通信过程中的数据同步
system-v、POSIX(有名,无名)
-
网络:套接字文件
通信理论
-
通信特征:
-
局域网通信时要求通信设备
IP地址在同一个网段(IPV4的前三) -
在不同设备之间通信时,通信协议必须一致
-
-
soceket:套接字文件,特殊文件,可收发,使用函数打开 -
IP: 32位点分式IP地址 -
端口号:2个字节的短整型(1-65535),自用端口号一般设置大于10000
-
字节序:在
X86一般是小端模式;在网络中一般是大端模式
通信协议
IP : 网际协议,给数据分配目标地址,通过路由器将数据转发
TCP: 传输控制协议,在IP的基础上提供可靠的数据传输,确保两端通信成功UDP: 用户数据报协议,在IP的基础上提供快速、简单的数据传输,不考虑接收情况
| TCP | UDP | IP | |
|---|---|---|---|
| 协议层 | 传输层 | 传输层 | 网络层 |
| 连接性(主要) | 面向连接 | 可无连接 | 无连接 |
| 可靠性(主要) | 可靠,需两端连接成功 | 不可靠,无需两端连接成功 | 不可靠,用于寻址 |
| 传输速率 | 慢 | 快 | 快 |
| 场景 | 文件传输(HTTP | 直播、实时视频 | 所有网络数据传输 |
通信模型
-
七层模型(OSI):理论上的模型 各层协议
- 应用层: 生成数据 DNS Telnet HTTP FTP
- 表示层: 加密数据,形成数据包 NA
- 会话层: 管理对话 NA
- 传输层: 实现管理下面的通讯子网提供定位端到端 TCP UDP
- 网络层: 寻找对应地址 IP
- 数据链路层:传输数据 PPP CSLIP
- 物理层: 通过网络传输数据包 ISO2110 IEEE802 IEEE802.2
-
四层模型(TCP/IP):实际应用
- 应用层: 整合应用层、表示层、会话层
- 传输层:会话管理,
- 网络层:找到对应的地址,
- 网络接口层: 整合数据链路层、物理层,将二进制转化成数据帧
三次握手
-
第一次握手:客户端发送一个 SYN给服务器,客户端进入SYN_SEND 状态,然后等待服务器的回发确认信息;
第二次握手: 服务器发一个 SYN+ACK 给客户端,确认已经收到客户端发来的信息,此时服务器进入SYN_RECV状态;
第三次握手:客户端接收到服务器发来的确认信息后,再反馈一个 ACK给服务器,完成三次握手,客户端和服务器进入ESTABLISHED状态,到此一个TCP连接就完成了。 -
三次握手用于请求客户端和服务端连接,发生在客户端发起
connect和服务端使用accept接收

四次挥手
-
四次挥手,即TCP连接的释放过程,是指终止TCP连接协议时,需要在客户端和服务器之间发送四个包,以确保双方都能正常且有序地关闭连接。以下是四次挥手的详细过程:
-
第一次挥手:客户端发送一个FIN报文段给服务器,表示客户端要关闭到服务器的数据传送,客户端进入FIN_WAIT_1状态。
-
第二次挥手:服务器收到FIN报文段后,发送一个ACK报文段给客户端,确认收到客户端的关闭请求,服务器进入CLOSE_WAIT状态。此时,TCP连接处于半关闭状态,即客户端到服务器的连接关闭,但服务器到客户端的连接仍然打开。
-
第三次挥手:服务器发送一个FIN报文段给客户端,表示服务器也要关闭到客户端的数据传送,服务器进入LAST_ACK状态。
-
第四次挥手:客户端收到FIN报文段后,进入TIME_WAIT状态,并发送一个ACK报文段给服务器,确认收到服务器的关闭请求,服务器进入CLOSED状态,完成四次挥手。
此时,TCP连接同时完全关闭,双方都不能再发送数据。
-
-
需要注意的是,在第二次挥手和第三次挥手之间,双方都会进行确认操作,以确保数据已经完全发送和接收。此外,在第三次挥手后,服务器仍然可以向客户端发送数据,直到收到客户端的ACK报文段为止。
-
四次挥手的目的是确保TCP连接的可靠关闭,避免数据丢失或连接状态不一致等问题。通过四次挥手,客户端和服务器可以有序地关闭连接,释放资源,为下一次连接做好准备。
TCP

//客户端#include "head.h"
// 名 IP 端口号
//./cfd 127.0.0.1 10000
int main(int argc,char *argv[])
{//创建套接字文件//IPV4,TCP,0int cfd = socket(AF_INET,SOCK_STREAM,0);if(cfd == -1){printf("sfd socket fail \n");return -1;}//申请连接 struct sockaddr_in addr; //通过参数 获取地址,端口addr.sin_family = AF_INET; //地址族 IPV4addr.sin_port = atoi(argv[2]); //端口号//IPV4地址int res = inet_pton(AF_INET,argv[1],(struct in_addr*)&addr.sin_addr);if(res < 1){printf("pton fail");close(cfd);return 0;}res = connect(cfd,(struct sockaddr*)&addr,sizeof(addr));if(res < 0){printf("bind fail");close(cfd);return 0;}printf("cfd success \n");//发送数据char data[100];while(1){bzero(data,0);fputs("input:",stdout);fgets(data,sizeof(data),stdin);send(cfd,data,sizeof(data),0);if(strstr(data,"#") != NULL ){close(cfd);fputs("quit\n",stdout);break;}}close(cfd);printf("close \n");return 0;
}
//服务端#include "head.h"
// 名 IP 端口号
//./sfd 127.0.0.1 10000
int main(int argc,char *argv[])
{//创建套接字文件//IPV4,TCP,0int sfd = socket(AF_INET,SOCK_STREAM,0);if(sfd == -1){printf("sfd socket fail \n");return -1;}//绑定IP地址和端口号 struct sockaddr_in addr;addr.sin_family = AF_INET; //addr.sin_port = atoi(argv[2]); //端口号//IPV4地址int res = inet_pton(AF_INET,argv[1],(struct in_addr*)&addr.sin_addr);if(res < 1){printf("pton fail");close(sfd);return 0;}//绑定套接字和网络地址+res = bind(sfd,(struct sockaddr*)&addr,sizeof(addr));if(res < 0){printf("bind fail");close(sfd);return 0;} //监听套接字res = listen(sfd,6);if(res < 0){printf("listen fail");close(sfd);return 0;}//等待连接struct sockaddr_in c_addr;int len = sizeof(c_addr);int cfd_addr = accept(sfd,(struct sockaddr*)&c_addr,&len);//返回if(cfd_addr < 0){printf("accrpt fail");close(sfd);return 0;}printf("success \n");char data[100];while(1){bzero(data,0);recv(cfd_addr,data,sizeof(data),0);if(strstr(data,"#") != NULL){close(sfd);printf("退出 \n");break;}printf("the str: %s \n",data);}printf("close \n");return 0;
}
UDP

//cfd.c 客户端
#include "head.h"
// 名 端口号
//./cfd 10000
int main(int argc,char *argv[])
{ //创建套接字文件//IPV4,UDP,0int cfd = socket(AF_INET,SOCK_DGRAM,0);if(cfd == -1){printf("cfd socket fail \n");return -1;} //绑定IP地址和端口号,就是当前的使用的IP struct sockaddr_in cfd_addr;cfd_addr.sin_family = AF_INET; //IPV4cfd_addr.sin_port = atoi(argv[1]); //端口号int ret = inet_pton(AF_INET, argv[1], (struct in_addr*)&cfd_addr.sin_addr);//绑定指定IP int cfd_len = sizeof(cfd_addr);/*res = bind(cfd,(struct sockaddr*)&cfd_addr,sizeof(cfd_addr));*/ char buf[100];while(1){bzero(buf, sizeof(buf));fgets(buf,sizeof(buf),stdin);sendto(cfd, buf, sizeof(buf) - 1, 0,(struct sockaddr*)&cfd_addr,cfd_len);if (strstr(buf, "quit") != NULL){break;}printf("from client[%d] data: %s\n", cfd, buf);}close(cfd);return 0;
}
//sfd.c#include "head.h"
// 名 端口号
//./sfd 10000
int main(int argc,char *argv[])
{//创建套接字文件//IPV4,UDP,0int sfd = socket(AF_INET,SOCK_DGRAM,0);if(sfd == -1){printf("sfd socket fail \n");return -1;} //绑定IP地址和端口号 struct sockaddr_in sfd_addr;sfd_addr.sin_family = AF_INET; //IPV4sfd_addr.sin_port = atoi(argv[1]); //端口号sfd_addr.sin_addr.s_addr = htonl(INADDR_ANY); //绑定所有IP地址bind(sfd,(struct sockaddr*)&sfd_addr,sizeof(sfd_addr));struct sockaddr cfd_addr; //目标的地址int cfd_len = sizeof(cfd_addr);char buf[100];while(1){bzero(buf, sizeof(buf));recvfrom(sfd, buf, sizeof(buf) - 1, 0,(struct sockaddr*)&cfd_addr,&cfd_len);if (strstr(buf, "quit") != NULL){break;} printf("from client[%d] data: %s\n", sfd, buf);} close(sfd); return 0;
}
IO通信模式
-
阻塞:
readrecvrecvfrom默认是阻塞模式,accept也会阻塞 -
非阻塞
fcntl:将文件描述符改成非阻塞模式
//获取原属性
int sta = fcntl(fd,F_GETFL);
//修改属性为非阻塞
sta |= O_NONBLOCK;
//重新设置属性
fcntl(fd,F_GETFL,sta);
-
如果将
accept传入非阻塞的套接字文件,将只执行一次,这样会获取不到信息 -
阻塞模式消耗CPU小于非阻塞模式
信号IO驱动
- 等待数据不在阻塞,通过信号通知的方式触发数据读取操作,没有信号时不会进行无限判断
- 通过
signal函数注册IO信号,在有数据时自动执行接收,反应快 - 在多线程客户端服务端之间,使用多线程时使用
IP地址
-
点分式,32位的IPV4
-
127.0.0.1是回环地址,代表所有IP
- 广播IP地址:最后一个字节是255,本地广播信息不会被路由器转发,转发是需要指明接收者的端口号
- 组播(多播):使用
D类IP地址(只要符合D类地址的规则),同一网络业务的主机一起分组,数据只发给同组用户 - 广播在局域网进行,组播在广域网进行
-
A类:网络ID占 7位 ,最高位是固定的
0;3个字节主机ID,- 范围:0.0.0.0 ~127.255.255.255,127.0.0.1 是一个回环地址
- 一般用于大型网络,可以提供最大的主机数
2^24个
-
B类:网络ID占 14 位;前两位固定为
1 0, 2个字节主机ID,- 范围:128.0.0.0 ~ 191.255.255.255固定开头四位
1110 - 一般用于中型 ,最大主机数
2^16
- 范围:128.0.0.0 ~ 191.255.255.255固定开头四位
-
C类:网络占21位,前三位固定为
1 1 0; 1 个字节主机ID- 范围:192.0.0.0 ~223.255.255.255
- 用于小型,最多主机数
2^8
-
D类:组播地址,(组IP),多点传送IP地址,固定开头四位
1110- 范围:224.0.0.0 ~ 239.255.255.255
- 用于多播(组播),将信息发送给一组主机,因为没主机
-
E类:固定开头四位
1111,剩下保留- 范围:240.0.0.0 ~ 255.255.255.255
- 用于实验室测试
接收超时
void *fun1(void *arg)
{int i = 0 ;while(1){printf("%d \n",i++);sleep(1);}
}void fun2(int sig)
{printf("manbao out");
}main()//./sfd 127.0.0.1 10005
{//创建线程pthread_t tid;if( pthread_create(&tid,NULL,fun1,NULL) != 0){perror();} pthread_detach(tid);#if 0 //SIGALRM//注册信号signal(SIGALRM,fun2);
#endif//创建套接字int sfd = socket(AF_INET,SOCK_STREAM,0);if(sfd < 0); //绑定IP地址和端口号 struct sockaddr_in sfd_addr;addr.sin_family = AF_INET; //addr.sin_port = atoi(argv[2]); //端口号//IPV4地址int res = inet_pton(AF_INET,argv[1],(struct in_addr*)&sfd_addr.sin_addr);if(res < 1)res = bind(sfd,(struct sockaddr*)&sfd_addr,sizeof(sfd_addr));if(res < 0)//监听listen(sfd,5);//获得发送方信息struct sockaddr_in cfd_addr;int cfd_len = sizeof(cfd_addr);int cfd = accept(sfd,(struct sockaddr)&cfd_addr,&cfd_len);//保存信息char cfd_ip[100];int cfd_port = ntoh(cfd_addr.sin_port);inet_ntops(AF_INET,&cfd_addr.sin_addr,cfd_ip,sizeof(cfd_ip));//取出IP地址printf("客户端ip:[%s],客户端端口号:[%d]",cfd_ip,cfd_port);
#if 1//设置超时时间struct timeval timeout;timeout.tv_sec = 5;timeout.tv_usec = 0;//设置超时,判断套接字是否超时setsockopt(cfd,SOL_SOCKET,SO_RCVTIMEO,&timeout,sizeof(timeout));
#enndif//接收char buf[256];while(1){
#if 0alarm(5);//设置5秒闹钟,到期后触发闹钟//触发一次后阻塞在接收上
#endif //recv(cfd_addr,data,sizeof(data),0);int res = read(cfd,buf,sizeof(buf));if(res < 0){if(error == EAGAIN){printf("超时 %d waiting",timeout.tv_sec);continue;}else{perror("read error");break;}}else if(res == 0){printf("断开");break;}else{if(strstr(data,"#") != NULL){close(sfd);printf("退出 \n");break;}printf("the str: %s \n",data);} }close(sfd);close(cfd);
}
多路复用
- 网络IO模型:阻塞,非阻塞,信号IO,多路复用,异步IO
- 一个线程管理多个IO事件,多个文件描述符,用于解决传统阻塞IO导致的线程资源浪费
select: 最早的多路复用,通过文件描述符集合统一监听多个文件描述符,一般不超过1024个文件,每次都要检查全部集合的就绪事件poll: 优化了select的最大固定大小的问题,改用动态数组保存文件描述符,但是每次还是需要遍历全部epoll: 这是最优解,通过红黑树存储监听的文件描述符,就绪时直接返回事件,无需遍历,支持高并发
