一、高速网络通信基础
-
继上一章节介绍过网络编程粘包问题之后,一般的网络传输过程中,大都为以数据包组织的方式进行传输。因此,对于一般的数据包都包括了包头包尾,包长度,包中的具体数据等信息。对于校验码等相关内容无需添加,TCP网络在接收之后会对数据自行校验,具体参见这里。
-
对于之前介绍的Linux基础TCP通信的建立是不完备的,主要体现在一下几点:
- 建立socket过程中并未处理假如某些服务已经占用了当前主机端口Port而导致冲突的情况。
- 没有对客户端Client异常掉线失去连接的情况进行异常处理,这样可能导致Server端Crash崩溃!
- 对于待进行传输的数据带宽需要进行基本的计算,考虑设备硬件的基本传输能力,避免导致Linux Socket底层数据缓冲空间溢出,send或recv函数出现阻塞等。
- 对于不希望accept阻塞等待的情况,还需要考虑使用多线程或者select非阻塞等待函数来实现。
二、基本异常处理
1. 数据包结构体组织形式
typedef struct ProfileData{
int iHeader; // 0xFFFF FFFE
int iReserveS1; // 0x0000 0000
int iProfileSEQ; // 数据序号
int iReserveS2; // 0x0000 0000
int iEncoderVal; // 编码数据
int iReserveS3; // 0x0000 0000
int *piProfileData; // 数据指针
int piProfileDataLen; // 数据总长
int iPacketEnd; // 0x0000 0000
}T_StructProfileData;
2. Client客户端失联信号处理
static void socket_sig_deal(void)
{
struct sigaction sa;
sigemptyset(&sa.sa_mask); // 设定信号处理过程中被屏蔽的阻塞信号
sa.sa_handler = handle_pipe; // 设定信号处理函数地址, 一般情况下sa_handler只传入信号量值, 不携带其他信号相关信息
sa.sa_flags = 0; // 标志位设定, 配合sa_handler/sa_sigaction不同的处理函数使用, 需要传入其他参数进入处理函数时需要选择sa.sa_sigaction + SA_SIGINFO
sigaction(SIGPIPE,&sa,NULL); // SIGPIPE 管道断开信号, 产生的 error = EPIPE, 不做捕获处理会导致程序奔溃
}
3. 主动判断socket连接状态
static int net__socket_established(int iSock)
{
struct tcp_info info; // 创建tcp_info结构体用来存储tcp状态信息
int len = sizeof(info);
getsockopt(iSock, IPPROTO_TCP, TCP_INFO, &info, (socklen_t *)&len); // 获取TCP当前状态信息
if ((info.tcpi_state == TCP_ESTABLISHED)) // 判断当前TCP是否处在建立连接状态
{
return SUCCESS; // 返回连接状态
}
else
{
return ERR_CONN_LOST; // 返回失联状态
}
}
三、高速通信基本代码实现
1. 服务端代码(Server.c)
#include <sys/types.h>
#include <sys/socket.h>
#include <errno.h>
#include <netdb.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <signal.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netinet/tcp.h> // SOL_SOCKET,SO_RCVTIMO,SO_SNDTIMEO,IPPOTO_TCP,TCP_NODELAY
typedef struct ProfileData{
int iHeader; // 0xFFFF FFFE
int iReserveS1; // 0x0000 0000
int iProfileSEQ; // 数据序号
int iReserveS2; // 0x0000 0000
int iEncoderVal; // 编码数据
int iReserveS3; // 0x0000 0000
int *piProfileData; // 数据指针
int piProfileDataLen; // 数据总长
int iPacketEnd; // 0x0000 0000
}T_StructProfileData;
static void socket_sig_deal(void);
static int net__socket_established(int iSock);
int highSpeedCommunicateSetup(int *ipState);
int highSpeedCommunicateTransimit(int iSock, const T_StructProfileData t_StructDataPacket);
#define host "0.0.0.0"
#define port 50000
#define SERVICE "50000"
#define socket_domain 1
#define SUCCESS 1
#define INVALID_SOCKET -1
#define ERR_CONN_LOST -2
#define ERR_ERRNO -3
/*
TCP/IP协议中针对TCP默认开启了Nagle算法。Nagle算法通过减少需要传输的数据包,来优化网络。在内核实现中,数据包的发送和接受会先做缓存,分别对应于写缓存和读缓存。
启动TCP_NODELAY,就意味着禁用了Nagle算法,允许小包的发送。对于延时敏感型,同时数据传输量比较小的应用,开启TCP_NODELAY选项无疑是一个正确的选择。
比如,对于SSH会话,用户在远程敲击键盘发出指令的速度相对于网络带宽能力来说,绝对不是在一个量级上的,所以数据传输非常少;而又要求用户的输入能够及时获得返回,
有较低的延时。如果开启了Nagle算法,就很可能出现频繁的延时,导致用户体验极差。当然,你也可以选择在应用层进行buffer,比如使用java中的buffered stream,
尽可能地将大包写入到内核的写缓存进行发送;vectored I/O(writev接口)也是个不错的选择。
对于关闭TCP_NODELAY,则是应用了Nagle算法。数据只有在写缓存中累积到一定量之后,才会被发送出去,这样明显提高了网络利用率(实际传输数据payload与协议头的比例大大提高)。
但是这又不可避免地增加了延时;与TCP delayed ack这个特性结合,这个问题会更加显著,延时基本在40ms左右。当然这个问题只有在连续进行两次写操作的时候,才会暴露出来。
连续进行多次对小数据包的写操作,然后进行读操作,本身就不是一个好的网络编程模式;在应用层就应该进行优化。
*/
#define set_tcp_nodelay 0 //
#define DATA_LEN 3200
int net__socket_listen(void);
int net__socket_accept(int listensock);
ssize_t net__write(int sock,const void *buf, size_t count);
int packet__write(int sock,const void *buf, size_t count);
int main(void)
{
int flag = 0;
T_StructProfileData ProfileData;
memset(&ProfileData,0,sizeof(T_StructProfileData));
ProfileData.iHeader = 0xFAFFF6F8;
ProfileData.iReserveS1 = ProfileData.iReserveS2 = ProfileData.iReserveS3 = ProfileData.iPacketEnd = 0x00000000;
ProfileData.iProfileSEQ = 0x00000000;
ProfileData.iEncoderVal = 0xEFFFD2E3;
ProfileData.piProfileDataLen = 3200;
ProfileData.piProfileData = (int *)malloc(sizeof(int)*ProfileData.piProfileDataLen);
memset(ProfileData.piProfileData, 21,sizeof(int)*ProfileData.piProfileDataLen);
int sock_ok = highSpeedCommunicateSetup(&flag);
if(flag == -1){
printf("net__socket_accept failed.\n");
return 0;
}
int temp = sock_ok;
printf("####################################################### sock_ok=%d\n",temp);
sleep(3);
while(1){
if(sock_ok != temp){
printf("####################################################### sock_ok=%d\n",sock_ok);
}
ProfileData.iProfileSEQ += 1;
flag = highSpeedCommunicateTransimit(sock_ok,ProfileData);
if(flag == ERR_CONN_LOST){
printf("ERR_CONN_LOST ####################################################################!!!!!!!!!!!!!!\n");
}
if(flag >= 0){
printf("packet__write successful.\n");
}else{
printf("packet__write failed.\n");
}
sleep(1);
}
return 0;
}
int highSpeedCommunicateSetup(int *ipState)
{
int isock = 0;
socket_sig_deal();
isock = net__socket_listen();
if(-1 == isock)
{
return INVALID_SOCKET;
}
isock = net__socket_accept(isock);
if(-1 == isock)
{
return INVALID_SOCKET;
}
return isock;
}
int highSpeedCommunicateTransimit(int iSock, const T_StructProfileData t_StructDataPacket)
{
int flag = 0;
flag = packet__write(iSock, &(t_StructDataPacket.iHeader), 4); if(0 != flag) {return flag;}// &(t_StructDataPacket.iHeader)
flag = packet__write(iSock, &(t_StructDataPacket.iReserveS1), 4); if(0 != flag) {return flag;} // &(t_StructDataPacket.iHeader)
flag = packet__write(iSock, &(t_StructDataPacket.iProfileSEQ), 4); if(0 != flag) {return flag;} // &(t_StructDataPacket.iHeader)
flag = packet__write(iSock, &(t_StructDataPacket.iReserveS2), 4); if(0 != flag) {return flag;} // &(t_StructDataPacket.iHeader)
flag = packet__write(iSock, &(t_StructDataPacket.iEncoderVal), 4); if(0 != flag) {return flag;} // &(t_StructDataPacket.iHeader)
flag = packet__write(iSock, &(t_StructDataPacket.iReserveS3), 4); if(0 != flag) {return flag;} // &(t_StructDataPacket.iHeader)
flag = packet__write(iSock, &(t_StructDataPacket.iReserveS3), 4); if(0 != flag) {return flag;} // &(t_StructDataPacket.iHeader)
flag = packet__write(iSock, &(t_StructDataPacket.iReserveS3), 4); if(0 != flag) {return flag;} // &(t_StructDataPacket.iHeader)
flag = packet__write(iSock, t_StructDataPacket.piProfileData, t_StructDataPacket.piProfileDataLen * 4); if(0 != flag) {return flag;} // &(t_StructDataPacket.iHeader)
flag = packet__write(iSock, &(t_StructDataPacket.iPacketEnd), 4); if(0 != flag) {return flag;} // &(t_StructDataPacket.iHeader)
return 0;
}
int net__socket_listen(void)
{
int sock = INVALID_SOCKET;
struct addrinfo hints;
struct addrinfo *ainfo, *rp;
char service[10];
int rc;
char ss_opt = 1;
unsigned int sock_count = 0;
snprintf(service, 10, "%d", port);
memset(&hints, 0, sizeof(struct addrinfo)); // 初始化模板 hints 变量
if(socket_domain){
hints.ai_family = AF_INET; //AF_INET6
}else{
hints.ai_family = AF_UNSPEC;
}
hints.ai_flags = AI_PASSIVE;
hints.ai_socktype = SOCK_STREAM;
rc = getaddrinfo(NULL, SERVICE, &hints, &ainfo);
if (rc){
printf("Error creating listener: %s.", gai_strerror(rc));
return INVALID_SOCKET;
}
for(rp = ainfo; rp; rp = rp->ai_next){
if(rp->ai_family == AF_INET){
printf("Opening ipv4 listen socket on port %d.\n", ntohs(((struct sockaddr_in *)rp->ai_addr)->sin_port));
}else if(rp->ai_family == AF_INET6){
printf("Opening ipv6 listen socket on port %d.\n", ntohs(((struct sockaddr_in6 *)rp->ai_addr)->sin6_port));
}else{
continue;
}
sock = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol);
if(sock == INVALID_SOCKET){
printf("ERR:%s-%d\n",__FUNCTION__,__LINE__);
continue;
}
if(bind(sock, rp->ai_addr, rp->ai_addrlen) != 0){
printf("ERR:%s-%d\n",__FUNCTION__,__LINE__);
break;
}
close(sock);
}
if(listen(sock, 100) == -1){
printf("Error:");
close(sock);
return INVALID_SOCKET;
}
freeaddrinfo(ainfo);
return sock;
}
int net__socket_accept(int listensock)
{
int new_sock = INVALID_SOCKET, struct_len = 0;
struct sockaddr_in client_addr;
struct_len = sizeof(struct sockaddr_in);
new_sock = accept(listensock, (struct sockaddr *)&client_addr, &struct_len);
if(new_sock == INVALID_SOCKET){
return INVALID_SOCKET;
}
if(set_tcp_nodelay){
int flag = 1;
if(setsockopt(new_sock, IPPROTO_TCP, TCP_NODELAY, &flag, sizeof(int)) != 0){
printf("XiongGu Warning: Unable to set TCP_NODELAY.");
}
}
return new_sock;
}
/**********************************************************************
* 函数名称: // static ssize_t net__write(int sock,const void *buf, size_t count)
* 功能描述: // 尝试向Socket套接字缓冲区写入count字节数据
* 访问的表: //
* 修改的表: //
* 输入参数: // int sock 可用的socket句柄
* 输入参数: // void *buf 用于存储读准备写入的字节(一般为char)
* 输入参数: // size_t count 期望写入的字节总数, 此值需要小于等于buf空间大小/sizeof(char) >= count
* 输出参数: // 对输出参数的说明
* 返 回 值: // ssize_t: 表示实际写入的字节数
* 其它说明: // 其它说明
* 修改日期 修改人 修改内容
* -----------------------------------------------
* 2021/11/02 XXXX XXXX
***********************************************************************/
ssize_t net__write(int sock,const void *buf, size_t count)
{
return send(sock, buf, count, 0);
}
/**********************************************************************
* 函数名称: // static int packet__write(int sock,const void *buf, size_t count)
* 功能描述: // 尝试从Socket套接字缓冲区写入count字节数据, 写入到count字节数据为止
* 访问的表: //
* 修改的表: //
* 输入参数: // int sock 可用的socket句柄
* 输入参数: // void *buf 用于存储读准备写入的字节(一般为char)
* 输入参数: // size_t count 期望写入的字节总数, 此值需要小于等于buf空间大小/sizeof(char) >= count
* 输出参数: // 对输出参数的说明
* 返 回 值: // ssize_t: 表示实际写入的字节数
* 其它说明: // 其它说明
* 修改日期 修改人 修改内容
* -----------------------------------------------
* 2021/11/02 XXXX XXXX
***********************************************************************/
int packet__write(int sock,const void *buf, size_t count)
{
ssize_t write_length;
char *data = (char *)buf;
int pos=0;
int to_process = count;
while(to_process > 0)
{
write_length = net__write(sock,&data[pos],count);
if(write_length > 0){
to_process -= write_length;
pos += write_length;
}
else
{
if(errno == EAGAIN || errno == EWOULDBLOCK)
{
return 0;
}
else
{
switch(errno)
{
case ECONNRESET:
return ERR_CONN_LOST;
default:
return ERR_ERRNO;
}
}
}
}
return 0;
}
/**********************************************************************
* 函数名称: // static void handle_pipe(int Sig)
* 功能描述: // 尝试从Socket套接字缓冲区读取count字节数据
* 访问的表: //
* 修改的表: //
* 输入参数: // int Sig 当前系统产生的信号编号
* 输出参数: //
* 返 回 值: //
* 其它说明: // 其它说明
* 修改日期 修改人 修改内容
* -----------------------------------------------
* 2021/11/02 XXXX XXXX
***********************************************************************/
static void handle_pipe(int Sig)
{
printf("Handle the Disconnected Error Sig!\n"); // 捕捉信号不做任何处理, 只打印
}
/**********************************************************************
* 函数名称: // static void socket_sig_deal(void)
* 功能描述: // 尝试从Socket套接字缓冲区读取count字节数据
* 访问的表: //
* 修改的表: //
* 输入参数: //
* 输出参数: //
* 返 回 值: //
* 其它说明: // 其它说明
* 修改日期 修改人 修改内容
* -----------------------------------------------
* 2021/11/02 XXXX XXXX
***********************************************************************/
static void socket_sig_deal(void)
{
struct sigaction sa;
sigemptyset(&sa.sa_mask); // 设定信号处理过程中被屏蔽的阻塞信号
sa.sa_handler = handle_pipe; // 设定信号处理函数地址, 一般情况下sa_handler只传入信号量值, 不携带其他信号相关信息
sa.sa_flags = 0; // 标志位设定, 配合sa_handler/sa_sigaction不同的处理函数使用, 需要传入其他参数进入处理函数时需要选择sa.sa_sigaction + SA_SIGINFO
sigaction(SIGPIPE,&sa,NULL); // SIGPIPE 管道断开信号, 产生的 error = EPIPE, 不做捕获处理会导致程序奔溃
}
/**********************************************************************
* 函数名称: // static int net__socket_established(int iSock)
* 功能描述: // 尝试从Socket套接字缓冲区读取count字节数据
* 访问的表: //
* 修改的表: //
* 输入参数: // int iSock 待检测的socket句柄
* 输出参数: //
* 返 回 值: // int: 0表示传输建立稳定 -2表示传输建立断开
* 其它说明: // 其它说明
* 修改日期 修改人 修改内容
* -----------------------------------------------
* 2021/11/02 XXXX XXXX
***********************************************************************/
static int net__socket_established(int iSock)
{
struct tcp_info info; // 创建tcp_info结构体用来存储tcp状态信息
int len = sizeof(info);
getsockopt(iSock, IPPROTO_TCP, TCP_INFO, &info, (socklen_t *)&len); // 获取TCP当前状态信息
if ((info.tcpi_state == TCP_ESTABLISHED)) // 判断当前TCP是否处在建立连接状态
{
return SUCCESS; // 返回连接状态
}
else
{
return ERR_CONN_LOST; // 返回失联状态
}
}
2. 客户端代码(Client.c)
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#define Debug 1
#define BUF_SIZE 12800
/**********************************************************************
* 函数名称: // static ssize_t net__read(int sock,void *buf, size_t count)
* 功能描述: // 尝试从Socket套接字缓冲区读取count字节数据
* 访问的表: //
* 修改的表: //
* 输入参数: // int sock 可用的socket句柄
* 输入参数: // void *buf 用于存储读取得到的字节(一般为char)
* 输入参数: // size_t count 期望读取到的字节总数, 此值需要小于等于buf空间大小/sizeof(char) >= count
* 输出参数: //
* 返 回 值: // ssize_t: 表示实际读取到的字节数
* 其它说明: // 其它说明
* 修改日期 修改人 修改内容
* -----------------------------------------------
* 2021/11/02 XXXX XXXX
***********************************************************************/
ssize_t net__read(int sock,void *buf, size_t count);
/**********************************************************************
* 函数名称: // static int packet__read(int sock,const void *buf, size_t count)
* 功能描述: // 尝试从Socket套接字缓冲区读取count字节数据, 读取到count字节数据为止
* 访问的表: //
* 修改的表: //
* 输入参数: // int sock 可用的socket句柄
* 输入参数: // void *buf 用于存储读取得到的字节(一般为char)
* 输入参数: // size_t count 期望读取到的字节总数, 此值需要小于等于buf空间大小/sizeof(char) >= count
* 输出参数: //
* 返 回 值: // int: 表示函数执行的情况
* 其它说明: // 其它说明
* 修改日期 修改人 修改内容
* -----------------------------------------------
* 2021/11/02 XXXX XXXX
***********************************************************************/
int packet__read(int sock,const void *buf, size_t count);
int
main(int argc, char *argv[])
{
struct addrinfo hints;
struct addrinfo *result, *rp;
int sfd, s, j;
size_t len;
ssize_t nread;
char buf[BUF_SIZE];
if (argc < 2) {
fprintf(stderr, "Usage: %s host port msg...\n", argv[0]);
exit(EXIT_FAILURE);
}
/* Obtain address(es) matching host/port. */
memset(&hints, 0, sizeof(hints));
hints.ai_family = AF_INET; /* Allow IPv4 or IPv6 */
hints.ai_socktype = SOCK_STREAM; /* Datagram socket */
hints.ai_flags = AI_PASSIVE;
hints.ai_protocol = 0; /* Any protocol */
s = getaddrinfo(argv[1], argv[2], &hints, &result);
if (s != 0) {
fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(s));
exit(EXIT_FAILURE);
}
/* getaddrinfo() returns a list of address structures.
Try each address until we successfully connect(2).
If socket(2) (or connect(2)) fails, we (close the socket
and) try the next address. */
for (rp = result; rp != NULL; rp = rp->ai_next) {
sfd = socket(rp->ai_family, rp->ai_socktype,
rp->ai_protocol);
if (sfd == -1)
continue;
if (connect(sfd, rp->ai_addr, rp->ai_addrlen) != -1){
struct sockaddr_in * temp;
printf("temp=NULL\n");
if( rp->ai_addr == NULL )
{
printf("addr is null\n");
}
else
{
temp = (struct sockaddr_in *)(rp->ai_addr);
// char *ip = inet_ntoa(temp.sin_addr);
printf("%s\n",inet_ntoa(temp->sin_addr));
}
printf("temp=NULL********\n");
break; /* Success */
}
close(sfd);
}
freeaddrinfo(result); /* No longer needed */
if (rp == NULL) { /* No address succeeded */
fprintf(stderr, "Could not connect\n");
exit(EXIT_FAILURE);
}
/* Send remaining command-line arguments as separate
datagrams, and read responses from server. */
unsigned int i = 1;
int flag = 0;
for (;;i++) {
#if Debug
printf("iHeader:\n");
#endif
flag = packet__read(sfd,buf, 4); // iHeader
if(flag == 1){
#if Debug
for(j=0;j<4;j++){
printf("%d ", buf[j]);
}
printf("\n");
#endif
}else
{
continue;
}
#if Debug
printf("iReserveS1-iProfileSEQ-iReserveS2-iEncoderVal-iReserveS3-iReserveS3-iReserveS3:\n");
#endif
flag = packet__read(sfd,buf, 28); // iReserveS1-iProfileSEQ-iReserveS2-iEncoderVal-iReserveS3-iReserveS3-iReserveS3
if(flag == 1){
#if Debug
for(j=0;j<28;j++){
printf("%d ", buf[j]);
}
printf("\n");
#endif
}else
{
continue;
}
#if Debug
printf("piProfileData:\n");
#endif
flag = packet__read(sfd,buf, 12800); // piProfileData 3200*4Bytes = 12800 Bytes
if(flag == 1){
#if Debug
for(j=0;j<12800;j++){
printf("%d ", buf[j]);
}
printf("\n");
#endif
}else
{
continue;
}
#if Debug
printf("iPacketEnd:\n");
#endif
flag = packet__read(sfd,buf, 4); // iPacketEnd
if(flag == 1){
#if Debug
for(j=0;j<4;j++){
printf("%d ", buf[j]);
}
printf("\n");
#endif
}else
{
continue;
}
#if Debug
printf("######################################### Line[%d] #########################################\n",i);
#endif
}
exit(EXIT_SUCCESS);
}
ssize_t net__read(int sock,void *buf, size_t count)
{
return recv(sock, buf, count, 0);
}
int packet__read(int sock,const void *buf, size_t count)
{
ssize_t read_length;
char *data = (char *)buf;
int pos=0;
int to_process = count;
while(to_process > 0)
{
read_length = net__read(sock,&data[pos],to_process);
if(read_length > 0){
to_process -= read_length;
pos += read_length;
}
else
{
#if Debug
// printf("Current Status: To_Process(%d)-Write_Len(%d)\n",to_process,read_length);
// return -1; // 异常处理
#endif
}
}
return 1;
}
五、高速通信代码测试
1. 编译及运行
gcc -o MS MyServer.c
gcc -o MC MyClient.c
./MS
./MC 192.168.1.110 50000
2. 测试结果如下:
Server服务端:
Opening ipv4 listen socket on port 50000.
ERR:net__socket_listen-177
####################################################### sock_ok=4
packet__write successful.
Client客户端:
21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21
iPacketEnd:
0 0 0 0
######################################### Line[11] #########################################
iHeader:
Reference
怎样实时判断socket连接状态?:https://www.cnblogs.com/embedded-linux/p/7468442.html
SOCKET:SO_LINGER 选项:https://www.cnblogs.com/kuliuheng/p/3670353.html
Linux SIGPIPE信号产生原因与解决方法:https://blog.csdn.net/u010821666/article/details/81841755