Linux Socket 网络编程 阻塞与非阻塞 断线重连机制


三种非阻塞模式的方法:
(1) fcntl函数
int Mode = fcntl(sockfd, F_GETFL, 0); //获取文件的Mode值
fcntl(sockfd, F_SETFL, Mode | O_NONBLOCK); //设置成非阻塞模式;
(2) recvfrom函数
int size = recvfrom(sockfd, (char *)recvbuffer, recvlength, MSG_DONTWAIT,(struct sockaddr*)&addr,(socklen_t*)&addlen);
参数flag不是设置为0,而是MSG_DONTWAIT
(3) ioctl ()函数
ioctl()函数,设置超时 还没细看
常见问题:
1. sendto返回值为-1
容易混淆的几点:
UDP
通过bind绑 定本机地址 (local addr)以及端口(local port), 实现从本机端口(local port)发送以及 监听 ;
通过connect指定目的地址(dst addr)以及目的端口(dst port), 实现目标地址的绑定;
UDP服务器绑定,recvfrom
接收端使用 bind() 函数,来完成地址结构与 socket 套接字的绑定,这样 ip、port 就固定了,发送端在 sendto 函数中指定接收端的 ip、port,就可以发送数据了。
接收方的绑定是绑定本地任意IP,特定端口;发送方需要知道接收方的IP地址,和接收方用的哪个端口接收,这是UDP的,而TCP不是这样的。TCP建立连接后,就已经知道客户端地址了。
1. TCP服务器
服务器一直都是监听的任意IP地址,端口号用于区分接收哪个客户端发来的数据。客户端代码向服务器哪个端口发,服务器就绑定哪个端口。
2. TCP客户端
3. UDP发送端
发送端不需要绑定操作,只需要知道IP和端口号,所以会有一个struct sockaddr_in addr;
发送,写的是对面的地址。
4. UDP接收端
相反,接收端肯定是需要绑定的了。就是监听,和TCP监听一样,监听任意IP地址,绑定发送端发送地址的端口号
一、UDP协议及其工作原理
(1) 特点
面向无连接的协议。
不保证数据一定能够到达对端,
不保证数据能够按照顺序到达对端
udp数据的可靠性需要应用层来保证。
UDP没有拥塞控制,网络出现的拥塞不会使源主机的发送速率降低
UDP支持一对一、一对多、多对一和多对多的交互通信
UDP消息头开销小,只有8个字节(TCP消息头共20个字节)
UDP相比较TCP更高效,牺牲效率
(2)udp和tcp的区别
协议 | UDP | TCP |
---|---|---|
是否面向连接 | 否 | 是 |
传输可靠性 | 不可靠 | 可靠 |
传输速度 | 慢 | 快 |
应用场合 | 数据量少, 对可靠性要求不高 | 数据量大, 对可靠性要求较高 |
udp协议的格式非常简单,它只有8个字节的首部,后面的全是数据。
UDP是面向数据包的,对应用层数据既不合并也不拆分( 保留数据包边界,不粘包 )
二、UDP在Linux下的编程方式
编程流程
阻塞,非阻塞模式
服务端:
- 创建套接字
- 初始化socket地址
- 设置阻塞或者非阻塞模式
- 绑定服务端端口
- 发送或者接收
- 关闭套接字
#include <stdio.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <string>
#include <string.h>
#include <errno.h>
#include <fcntl.h>
using namespace std;
#define UDPPORT 60060
int main()
int sockfd;
struct sockaddr_in addr;
sockfd = socket(AF_INET, SOCK_DGRAM, 0);
memset(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_port = htons(UDPPORT);
addr.sin_addr.s_addr = htonl(INADDR_ANY);//客户端IP
//绑定服务器UDP端口
int bret = bind(sockfd, (struct sockaddr *)&addr, sizeof(addr));
if (bret < 0)
perror("bind fail");
exit(1);
char recvbuffer[1024];
int recvlength = 0;
recvlength = 1;
int sendlength=0,actual_send_length=0;
unsigned char sendbuffer[1024];
int Mode = fcntl(sockfd, F_GETFL, 0); //获取文件的Mode值。
fcntl(sockfd, F_SETFL, Mode | O_NONBLOCK); //设置成非阻塞模式;
int addlen = sizeof(addr);
while(1)
int size = recvfrom(sockfd, (char *)recvbuffer, recvlength, 0,(struct sockaddr*)&addr,(socklen_t*)&addlen);
if(size<0)
perror("recv error");
printf("%c\n", recvbuffer[0]);
actual_send_length=sendto(sockfd,(char*)"I'm hear that you sending is:",29,0,(struct sockaddr*)&addr, sizeof(addr));
//actual_send_length=sendto(sockfd,(char*)recvbuffer,size,0,(struct sockaddr*)&addr, sizeof(addr));
if(actual_send_length<0)
perror("send error");
printf("send success\n");
//usleep(500000);
}
客户端:
- 创建套接字
- 初始化socket地址
- 设置阻塞或者非阻塞模式
- 发送或者接收
- 关闭套接字
#include <stdio.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <string>
#include <string.h>
#include <errno.h>
#include <fcntl.h>
using namespace std;
#define UDPPORT 60060
int main()
string cockpit_ip="127.0.0.1";
int socketfd = 0;
struct sockaddr_in addr;//服务器地址
socketfd = socket(AF_INET, SOCK_DGRAM, 0);
memset(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_port = htons(UDPPORT);//服务器端口
addr.sin_addr.s_addr = inet_addr("cockpit_ip.c_str()");//服务器IP
int on=1;
setsockopt(socketfd,SOL_SOCKET,SO_REUSEADDR | SO_BROADCAST,&on,sizeof(on));
unsigned char sendbuffer[1024],recvbuffer[1024];
int sendlength=0,actual_send_length=0,actual_recv_length=0,recvlength=0;
sendbuffer[0]='A';
sendlength = 1;
recvlength = 1;
//int Mode = fcntl(socketfd, F_GETFL, 0); //获取文件的Mode值。
//fcntl(socketfd, F_SETFL, Mode | O_NONBLOCK); //设置成非阻塞模式;
int addlen = sizeof(addr);
while (1)
actual_send_length = sendto(socketfd,(char*)sendbuffer,sendlength,0,(struct sockaddr*)&addr, sizeof(addr));
if(actual_send_length<0)
perror("send error");
printf("actual_send_length = %d\n",actual_send_length);
actual_recv_length = recvfrom(socketfd, (char *)recvbuffer, 29, 0,(struct sockaddr*)&addr, (socklen_t*)&addlen);
if(actual_recv_length>0)
printf("%s\n", recvbuffer);
perror("recv error");
//usleep(500000);
}
相关函数
服务器端: socket (), bind(), recvfrom()/sendto(), close();
客户端:socket(), sendto()/recvfrom(), close();
(1) socket()
#include<sys/types.h>
#include<sys/socket.h>
int socket(int domain,int type,int protocol);//创建socket
domain 表示底层协议族。其中IPv4用AF_INET(Protocol Family of Internet),IPv6用AF_INET6。
type 指定服务类型。服务类型主要有SOCK_STREAM(字节流服务)服务和SOCK_DGRAM服务(数据报服务)。对于TCP/IP协议族来说, SOCK_STREAM表 示传输层使用 TCP协议 , SOCK_DGRAM 表示传输层使用 UDP协议。
protocol 在前两个参数构成的协议集合下,再选择一个具体的协议。 一般设置为0 ,表示使用默认协议。
socket()系统调用成功时返回一个socket文件描述符,失败则返回-1并设置errno。
(2)绑定bind()
#include<sys/types.h>
#include<sys/socket.h>
int bind(int sockfd,const struct sockaddr* my_addr,socklen_t addlen);//命名(绑定)socket
bind的作用是将未命名的sockfd文件描述符 指向my_addr所指的socket地址。 其中socket地址长度由参数addlen指出。
bind成功时返回0,失败则返回-1并设置errno。
其中struct sockaddr是通用的socket地址,而TCP/IP协议族有sockaddr_in和sockaddr_in6两个专用socket地址结构体,它们分别用于IPv4和IPv6,这里我们只介绍sockaddr_in:
struct sockaddr_in
sa_family_t sin_family; //地址族:AF_INET
u_int16_t sin_port; //端口号(要用网络字节序)
struct in_addr sin_addr; //IPv4地址结构体
struct in_addr //IPv4地址结构体
u_int32_t s_addr; //IPv4地址(要用网络字节序)
};
(3) 转化函数
首先看端口号,两台主机之间要通过TCP/IP协议进行通信的时候需要调用相应的函数进行主机序 和网络序的转换。因为 主机字节序 一般为 小端模式 (Little-Endian),而 网络字节序 为 大端模式 (Big-Endian),也就是说两者的存储方式不同。所以我们介绍4个函数来完成主机字节序和网络字节序之间的转换:
#include<netinet/in.h>
unsigned long int htonl(unsigned long int hostlong);//主机字节序转网络字节序(32bit的长整型)
unsigned short int htonl(unsigned short int hostshort);//主机字节序转网络字节序(16bit的短整型)
unsigned long int ntohl(unsigned long int netlong);//网络字节序转主机字节序(32bit的长整型)
unsigned short int ntohs(unsigned short int netshort);//网络字节序转主机字节序(16bit的短整型)
(4)3个IP地址转换函数
#include<arpa/inet.h>
in_addr_t inet_addr(const char* strptr);
int inet_aton(const char* cp,struct in_addr* inp);
char* inet_ntoa(struct in_addr in);
inet_addr函数将用点分十进制字符串表示的IPv4地址转化为用网络字节序整数表示的IPv4地址。
inet_aton函数完成inet_addr函数同样的功能,但是将转化结果存储于参数inp指向的地址结构中。该函数成功时返回1,失败返回-1。
inet_ntoa函数将用网络字节序整数表示的IPv4地址转化为用点分十进制字符串表示的IPv4地址。
(5)数据读写
对文件的读写操作read和write同样适用于socket。但是socket编程接口提供了几个专门用于socket数据读写的系统调用,它们增加了对数据读写的控制。UDP与TCP不同,UDP通信没有连接的概念,所以我们每次读取数据都需要获取发送端的socket地址。所以UDP的读写函数比TCP的读写函数参数要多。
#include<sys/types.h>
#include<sys/socket.h>
int recvfrom(int sockfd,void* buf,size_t len,int flags,struct sockaddr* src_addr,socklen_t* addrlen);//读取sockfd上的数据
int sendto(int sockfd,const void* buf,size_t len,int flags,const struct sockaddr* dest_addr,socklen_t addrlen);//往sockfd上写入数据
recvfrom/sendto系统调用也可以用于面向连接的socket数据读写,只需要把最后两个参数都设置为NULL以忽略发送端、接收端的socket地址(因为我们已经建立了连接,所以已经知道其socket地址了)。
通过 setsockopt() /getsockopt() 可存取指定socket的属性值
三 、深入UDP 数据收发(上)
① 问题:如何进行一对多的UDP数据发送?
② UDP通信中的广播
广播是向同一网络中的所有主机传输数据的方法
广播类型
直接广播:IP地址中除网络地址外,其余主机地址均设置为1(eg:192.168.1.255【网络地址 主机地址】)
本地广播:无需知道网络地址,使用255.255.255.255作为IP地址使用
区别:
本地广播数据不经过路由器寻址,直接发送到本地主机
四、TCP编程流程
4.1 编程流程
服务端
- 创建socket
- 初始化socket地址
- 绑定
- 监听
- 设置是否非阻塞
- 接收客户端连接
- 接收或发送
- 关闭
#include <netinet/tcp.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <string>
#include <errno.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h>
#include <netinet/tcp.h>
#include <arpa/inet.h>
#include <fcntl.h>
步骤1:创建一个TCP的socket
步骤2:绑定socket和端口号
步骤3:监听端口号
步骤4:接受请求连接
步骤5:读取字符
步骤6:关闭
using namespace std;
#define ServerPort 60050
string cockpit_ip ="127.0.0.1";
int main()
int listenfd,connfd;//listenfd socket描述符
//创建一个TCP的socket
if( (listenfd = socket(AF_INET,SOCK_STREAM,0)) == -1) {
printf(" create socket error: %s (errno :%d)\n",strerror(errno),errno);
return 0;
struct sockaddr_in servaddr;
//先把地址清空,检测任意IP
memset(&servaddr,0,sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
servaddr.sin_port = htons(ServerPort);
//地址绑定到listenfd
if ( bind(listenfd, (struct sockaddr*)&servaddr, sizeof(servaddr)) == -1) {
printf(" bind socket error: %s (errno :%d)\n",strerror(errno),errno);
return 0;
//监听端口号listenfd 最多10个等待
if( listen(listenfd,10) == -1) {
printf(" listen socket error: %s (errno :%d)\n",strerror(errno),errno);
return 0;
printf("====waiting for client's request=======\n");
//accept 和recv,注意接收字符串添加结束符'\0'
char buff[4096];
int n;
//获取文件的Mode值
int Mode = fcntl(listenfd, F_GETFL, 0);
fcntl(listenfd, F_SETFL, Mode | O_NONBLOCK); //设置成非阻塞模式;
while(1)
//接受请求连接
if( (connfd = accept(listenfd, (struct sockaddr *)NULL, NULL)) == -1) {
//printf(" accpt socket error: %s (errno :%d)\n",strerror(errno),errno);
//return 0;
//读取字符
n = recv(connfd,buff,10,0);
printf("recv msg from client:%s\n",buff);
close(connfd);
//关闭连接
close(listenfd);
return 0;
}
客户端
- 创建socket
- 初始化socket地址
- 设置是否非阻塞
- 接收或发送
- 关闭
#include <netinet/tcp.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <string>
#include <errno.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h>
#include <netinet/tcp.h>
#include <arpa/inet.h>
#include <fcntl.h>
using namespace std;
#define ServerPort 60050
string cockpit_ip ="127.0.0.1";
int main()
int len=0; //总长度
int buffer[65535];
//1.建立套接字
int TCP_Client_Sockfd;
TCP_Client_Sockfd = socket(AF_INET, SOCK_STREAM, 0);//报式套接字 制定某个协议的特定类型为该TYPE的特定类型
if (TCP_Client_Sockfd == -1)
printf("create socket error: %s(errno: %d)\n", strerror(errno), errno);
//2.建立连接
//2.1设置要连接的服务器的地址
struct sockaddr_in Serveraddr;
bzero(&Serveraddr, sizeof(struct sockaddr_in));
Serveraddr.sin_family = AF_INET;//IPV4
Serveraddr.sin_port = htons(ServerPort);//服务器应用程序端口
Serveraddr.sin_addr.s_addr = inet_addr(cockpit_ip.c_str());//服务器IP
//2.连接服务器 阻塞连接和非阻塞连接
int Mode = fcntl(TCP_Client_Sockfd, F_GETFL, 0); //获取文件的Mode值。
fcntl(TCP_Client_Sockfd, F_SETFL, Mode | O_NONBLOCK); //设置成非阻塞模式;
int Connect_Time=0,Connect_Flag = 0;
//TCP不断尝试连接
while(1)
Connect_Flag = connect(TCP_Client_Sockfd, (struct sockaddr*)(&Serveraddr), sizeof(sockaddr_in));
if ( Connect_Flag == -1)
printf("connect error\n");
Connect_Time++;
usleep(400000);//400ms
if(Connect_Time>=40)
break;
else if(Connect_Flag == 0)
printf("connect success\n");
break;
struct tcp_info info;
int len1=sizeof(info);
while(1)
getsockopt(TCP_Client_Sockfd, IPPROTO_TCP, TCP_INFO, &info, (socklen_t *)&len1);
if((info.tcpi_state!=TCP_ESTABLISHED)) // 则说明断开 else 未断开
close(TCP_Client_Sockfd);
TCP_Client_Sockfd = socket(AF_INET, SOCK_STREAM, 0);//报式套接字 制定某个协议的特定类型为该TYPE的特定类型
if (TCP_Client_Sockfd == -1)
printf("create socket error: %s(errno: %d)\n", strerror(errno), errno);
//2.建立连接
//2.1设置要连接的服务器的地址
struct sockaddr_in Serveraddr;
bzero(&Serveraddr, sizeof(struct sockaddr_in));
Serveraddr.sin_family = AF_INET;//IPV4
Serveraddr.sin_port = htons(ServerPort);//服务器应用程序端口
Serveraddr.sin_addr.s_addr = inet_addr(cockpit_ip.c_str());//服务器IP
//2.连接服务器 阻塞连接和非阻塞连接
int Mode = fcntl(TCP_Client_Sockfd, F_GETFL, 0); //获取文件的Mode值。
fcntl(TCP_Client_Sockfd, F_SETFL, Mode | O_NONBLOCK); //设置成非阻塞模式;
while(1)
connect(TCP_Client_Sockfd, (struct sockaddr*)(&Serveraddr), sizeof(sockaddr_in));
getsockopt(TCP_Client_Sockfd, IPPROTO_TCP, TCP_INFO, &info, (socklen_t *)&len1);
printf("getsockopt connect error\n");
usleep(100000);//100ms
if((info.tcpi_state==TCP_ESTABLISHED))
break;
memcpy(buffer,"asdfg",5);
if((info.tcpi_state==TCP_ESTABLISHED))
len =send(TCP_Client_Sockfd,buffer,5,0);