linux下的网络编程:TCP(传输控制协议)编程

一、TCP概述

(一)特点

  1. 面向数据包

  2. 无连接

  3. 尽最大努力交付,不安全不可靠(数据丢包、数据乱序)

  4. 机制简单,资源开销小,数据实时性高

  5. 可实现一对一、一对多的通信

(二)TCP机制 ------------"三次握手和四次挥手"

各标志位

  • SYN : 请求建立连接标志位
  • ACK :响应报文标志位
  • FIN : 请求断开连接标志位

1)三次握手:TCP建立连接时,需要进行三次握手,为了确保收发双方通信之前都已准备就绪。

2)TCP四次挥手:TCP断开连接时,需要进行四次挥手,确保断开连接前双方都已通信结束。

二、TCP的编程过程

(一)TCP编程流程图

(二)核心函数

1.连接函数

|------|---------------------------------------------------------------------------|
| 连接函数 | int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen); |
| 功能 | 请求与服务端建立连接 |
| 参数 | sockfd:套接字 addr:要连接的服务端的地址信息 addrlen:服务端地址大小 |
| 返回值 | 成功:0 失败:-1 |

2.发送函数

|------|--------------------------------------------------------------------|
| 发送函数 | ssize_t send(int sockfd, const void *buf, size_t len, int flags); |
| 功能 | 发送网络数据 |
| 参数 | sockfd:网络套接字 buf:要发送的数据首地址 len:发送的字节数 flags:0 :按照默认方式发送 |
| 返回值 | 成功:实际发送的字节数 失败:-1 |

3.监听函数

|-----|--------------------------------------|
| | int listen(int sockfd, int backlog); |
| 功能 | 监听建立三次握手的客户端 |
| 参数 | sockfd:监听套接字 backlog:最大允许监听的客户端个数 |
| 返回值 | 成功:0 失败:-1 |

4.接收函数

|-----|-----------------------------------------------------------------------------------------------|
| | int accept(int socket, struct sockaddr *restrict address, socklen_t *restrict address_len); |
| 功能 | 接收建立三次握手的客户端,并产生一个通讯套接字 |
| 参数 | socket:监听套接字 address:客户端的地址信息 address_len:客户端地址长的指针 |
| 返回值 | 成功:通讯套接字 失败:-1 |

5.从网络套接字上收取数据函数

|-----|---------------------------------------------------------------|
| | ssize_t recv(int sockfd, void *buf, size_t len, int flags); |
| 功能 | 从网络套接字上接收数据 |
| 参数 | sockfd:通讯套接字 buf:存放接收数据的首地址 len:期望接收到的字节数 flag : 0:默认方式接收(阻塞) |
| 返回值 | 成功:实际接收到的字节数 失败:-1 对方断开连接:0 |

(三)示例程序

实现功能:客户端不断从终端接收数据,使用TCP发送给服务端,服务端输出。

1.客户端

复制代码
#include <stdio.h>
#include <stdio.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h> /* superset of previous */
#include <sys/types.h>          /* See NOTES */
#include <arpa/inet.h>
#include <unistd.h>
#include <string.h>
#include <pthread.h>

int main(int argc,const char *argv[])
{
    //创建套接字
    int sockfd = socket(AF_INET,SOCK_STREAM,0);
    if(sockfd < 0)
    {
        perror("socket error");
        return -1;
    }
    
    struct sockaddr_in seraddr;
    seraddr.sin_family = AF_INET;
    seraddr.sin_port = htons(50000);
    seraddr.sin_addr.s_addr = inet_addr("192.168.100.102");
    //请求建立连接
    int ret = connect(sockfd,(struct sockaddr *)&seraddr,sizeof(seraddr));
    if(ret < 0)
    {
        perror("connect error");
        return -1;
    }

    while(1)
    {
        char buff[1024] = {0};
        fgets(buff,sizeof(buff),stdin);                    //从终端读取数据
        ssize_t cnt = send(sockfd,buff,strlen(buff),0);    //发送数据
        if(cnt < 0)
        {
            perror("send error");
            return -1;
        }
        printf("cnt = %ld\n",cnt);
    }
    
    close(sockfd);                                    //关闭套接字

    return 0;
}

2.服务端

复制代码
#include <stdio.h>
#include <stdio.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h> /* superset of previous */
#include <sys/types.h>          /* See NOTES */
#include <arpa/inet.h>
#include <unistd.h>
#include <string.h>
#include <pthread.h>

int main(int argc,const char *argv[])
{
    //创建监听套接字------sockfd
    int sockfd = socket(AF_INET,SOCK_STREAM,0);
    if(sockfd < 0)
    {
        perror("socket error");
        return -1;
    }

    struct sockaddr_in seraddr;
    seraddr.sin_family = AF_INET;
    seraddr.sin_port = htons(50000);
    seraddr.sin_addr.s_addr = inet_addr("192.168.100.102");

    //绑定IP地址
    int ret = bind(sockfd,(struct sockaddr *)&seraddr,sizeof(seraddr));
    if(ret < 0)
    {
        perror("bind error");
        return -1;
    }

    //监听和服务端建立连接的客户端
    ret = listen(sockfd,10);
    if(ret < 0)
    {
        perror("listen error");
        return -1;
    }
    //接收完成三次握手的客户端并产生一个通信套接字------connfd
    int connfd = accept(sockfd,NULL,NULL);
    if(connfd < 0)
    {
        perror("accept error");
        return -1;
    }
    while(1)
    {
        char buff[1024] = {0};
        ssize_t cnt = recv(connfd,buff,sizeof(buff),0); //接收数据
        if(cnt < 0)
        {
            perror("recv error");
            return -1;
        }
        if(0 == cnt)
        {
            break;
        }
        printf("cnt = %ld,buff = %s\n",cnt,buff);
    }
    close(sockfd);        //关闭监听套接字

    close(connfd);        //关闭通信套接字

    return 0;
}

三、TCP粘包问题

TCP粘包问题:发送方应用层发送的多包数据,将来在接收方可能一次读到,多包数据产生了粘连。

1.产生原因

  • 发送方速度较快,TCP底层可能对多包数据进行重新组帧;
  • 接收方数据处理速度较慢,导致多包数据在接收缓冲区缓存,应用层读时,一次将多包数据读出。

2.解决方法

  • 调整发送速率
  • 发送指定大小,将来接收方也接受指定大小(即,使用结构体)。

注意:

  1. 跨平台之间的数据传输时,注意结构体对齐问题。

struct a

{

char a;

int b;

long c;

};

32bits平台和64位平台所占用的字节不同。

  • 应用层位发送的数据增加分隔符,利用分隔符解析(eg: hello world\nhow are you\n)
  • 封装自定义数据帧格式进行发送(协议),严格根据协议进行解析。

示例:

AA C0 00 00 00 F0 00 BB 10 A0 00 00 00 10 校验 BB AA C0 00 00 00 F0 00 BB 10 A0 00 00 00 10 校验 BB AA C0 00 00 00 F0 00 BB 10 A0 00 00 00 10 校验 BB

帧头:AA

帧尾:BB

有效数据长度:C0

有效数据:00 00 00 F0 00 BB 10 A0 00 00 00 10

校验:

8位和校验

16位和校验

CRC校验

四、TCP的其他机制

(一)TCP的头部标志

SYN:请求建立连接标志位

ACK:响应报文标志位

PSH:携带数据标志位,通知接收方该从缓冲区读数据

FIN: 请求断开连接标志位

RST:复位标志位

URG: 紧急数据标志位

(二)其他机制

1)确保其安全可靠

  1. 三次握手和四次挥手机制

  2. 应答机制:TCP对于每一包数据都会给出相应的应答。发送数据时序列号表示这包数据的起始编号,响应报文中的确认号是接收方收到的最后一个字节编号+1。

  1. 超时重传机制:当数据发送出去等待指定时间没有收到响应,此时认为这包数据丢失则进行冲传。

  2. 滑动窗口机制:一段缓冲区,缓存TCP已发送未收到响应,准备发送等数据

2)提高效率

  1. 延迟应答机制:发送数据的同时可以等待应答。
  1. 流量控制机制:结合TCP头部的窗口大小,动态调整发送速率。

  2. 捎带应答机制:ACK报文可能和应用层的数据同时发送。

相关推荐
iFulling18 分钟前
【云原生】CentOS安装Kubernetes+Jenkins
linux·云原生·kubernetes·centos·jenkins
喜欢你,还有大家2 小时前
Linux笔记10——shell编程基础-4
linux·运维·服务器·笔记
不懂机器人2 小时前
linux编程----网络通信(TCP)
linux·服务器·tcp/ip
weixin_456904272 小时前
C# .NET Framework 4.0 网络编程完全指南
网络·c#·.net
小白白3 小时前
局域网共享文件夹
网络
tanyongxi663 小时前
简易shell
linux·运维·服务器
vortex53 小时前
Python包管理与安装机制详解
linux·python·pip
zcz16071278213 小时前
CentOS 7 服务器初始化完整流程
linux·服务器·centos
Clain3 小时前
如何快速建站 | 云服务器配置+公网ip部署网站全流程详解
linux·运维·服务器