linux编程----网络通信(TCP)

1.TCP特点

1.面向数据流;

2.有连接通信;

3.安全可靠的通信方式;

4.机制复杂,网络资源开销大;

5.本质只能实现一对一的通信(可使用TCP的并发方式实现一对多通信);

2.TCP的三次握手与四次挥手

1.TCP的三次握手

TCP建立连接时,需要进行三次握手,以确保收发双方通信之前都已就绪;

2.TCP的四次挥手

TCP断开连接时,需要四次挥手,以确保断开连接前双方都以通信结束;

SYN:请求建立连接标志;

FIN:请求断开连接标志;

ACK:响应报文标志位;

3.TCP的编程流程

int connect(int sockfd, const struct sockaddr *addr,

socklen_t addrlen);

功能:请求与服务端建立连接

参数:

sockfd:套接字

addr:要连接的服务端的地址信息

addrlen:服务端地址大小

返回值:

成功:0

失败:-1

ssize_t send(int sockfd, const void *buf, size_t len, int flags);

功能:发送网络数据

参数:

sockfd:网络套接字

buf:要发送的数据首地址

len:发送的字节数

flags:0 :按照默认方式发送

返回值:

成功:实际发送的字节数

失败:-1

int listen(int sockfd, int backlog);

功能:监听建立三次握手的客户端

参数:

sockfd:监听套接字

backlog:最大允许监听的客户端个数

返回值:

成功:0

失败:-1

int accept(int socket, struct sockaddr *restrict address,

socklen_t *restrict address_len);

功能:接收建立三次握手的客户端,并产生一个通讯套接字

参数:

socket:监听套接字

address:客户端的地址信息

address_len:客户端地址长的指针

返回值:

成功:通讯套接字

失败:-1

ssize_t recv(int sockfd, void *buf, size_t len, int flags);

功能:从网络套接字上接收数据

参数:

sockfd:通讯套接字

buf:存放接收数据的首地址

len:期望接收到的字节数

flag : 0:默认方式接收(阻塞)

返回值:

成功:实际接收到的字节数

失败:-1

对方断开连接:0

4.TCP的粘包问题

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

原因:

  1. 发送方速度较快,TCP底层可能对多包数据进行重新组帧;

  2. 接收方数据处理速度较慢,导致多包数据在接收缓冲区缓存,应用层读时,一次将多包数据读出。

解决粘包问题的常用方法:

  1. 调整发送速率

  2. 发送指定大小,将来接收方也接受指定大小。

结构体

注意:

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

struct a

{

char a;

int b;

long c;

};

32bits平台《--》64位平台

  1. 应用层位发送的数据增加分隔符,利用分隔符解析

hello world\nhow are you\n

  1. 封装自定义数据帧格式进行发送(协议),严格根据协议进行解析。

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校验

5.代码练习

  1. tcp实现图片的传输
cs 复制代码
//客户端
#include <stdio.h>
#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h> /* superset of previous */
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>
#include <unistd.h>
#include <sys/stat.h>
#include <fcntl.h>


int main(int argc, char const *argv[])
{
    if(argc != 2)
    {
        printf("./a.out <filename>\n");
        return -1;
    }

    int sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if(sockfd < 0)
    {
        perror("socket error");
        return -1;
    }

    struct sockaddr_in sockaddr;
    sockaddr.sin_family = AF_INET;
    sockaddr.sin_port = htons(50002);
    sockaddr.sin_addr.s_addr = inet_addr("192.168.0.139");
    int connfd = connect(sockfd, (struct sockaddr *)&sockaddr, sizeof(sockaddr));

    int fd = open(argv[1], O_RDONLY);
    if(fd < 0)
    {
        perror("open error");
        return -1;
    }

    char buf[1024] = {0};
    
    int ret = 0;
    do
    {   
        ret = read(fd, buf, sizeof(buf));
        size_t conter = send(sockfd, buf, ret, 0);
    }while(ret > 0);

    close(fd);
    close(sockfd);

    return 0;
}

//服务端
#include <stdio.h>
#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h> /* superset of previous */
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <string.h>
#include <sys/stat.h>
#include <fcntl.h>



int main(int argc, char const *argv[])
{
    if(argc != 2)
    {
        printf("./a.out <filename>\n");
        return -1;
    }

    int sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if(sockfd < 0)
    {
        perror("socket error");
        return -1;
    }

    struct sockaddr_in sockaddr;
    sockaddr.sin_family = AF_INET;
    sockaddr.sin_port = htons(50002);
    sockaddr.sin_addr.s_addr = inet_addr("192.168.0.139");
    
    int ret = bind(sockfd, (struct sockaddr *)&sockaddr, sizeof(sockaddr));
    if(ret < 0)
    {
        perror("bind error");
        return -1;
    }

    int n = listen(sockfd, 10);
    if(n < 0)
    {
        perror("listen error");
        return -1;
    }
    
    struct sockaddr_in dest_sockaddr;
    socklen_t len = sizeof(dest_sockaddr);

    int connfd = accept(sockfd, (struct sockaddr *)&dest_sockaddr, &len);
    printf("[%s][%d] online\n", inet_ntoa(dest_sockaddr.sin_addr), ntohs(dest_sockaddr.sin_port));

    int fd = open(argv[1], O_WRONLY | O_TRUNC | O_CREAT, 0664);
    if(fd < 0)
    {
        perror("open error");
        return -1;
    }

    char buf[1024] = {0};
    
    ssize_t cont = 0;
    do
    {   
        memset(buf, 0, sizeof(buf));
        cont = recv(connfd, buf, sizeof(buf), 0);
        write(fd, buf, cont);   
    }while(cont > 0);

    close(connfd);
    close(sockfd);
    
    return 0;
}
  1. tcp实现全双工聊天
cs 复制代码
//客户端A:
#include <stdio.h>
#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h> /* superset of previous */
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>
#include <unistd.h>

int sockfd = 0;
struct sockaddr_in sockaddr;

int send_t()
{
    char buf[1024] = {0};
        while(1)
        {   
            fgets(buf, sizeof(buf), stdin);
            buf[strlen(buf) - 1] = 0;
            size_t conter = send(sockfd, buf, strlen(buf), 0);
            if(conter > 0)
            {
                printf("conter=%ld\n", conter);
            }
            if(conter < 0)
		    {
                perror("send error");
                return -1;
		    }
        }

    close(sockfd);
}

int recv_t()
{

    char buf[1024] = {0};
    do
    {   
    memset(buf, 0, sizeof(buf));
    ssize_t cont = recv(sockfd, buf, sizeof(buf), 0);
    if(cont > 0)
    {
        printf("cont = %ld, buf = %s\n", cont, buf);
    }
    if(cont < 0)
	{
        perror("send error");
        return -1;
	}  
    }while(1);

    close(sockfd);
}

int main(int argc, char const *argv[])
{
    sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if(sockfd < 0)
    {
        perror("socket error");
        return -1;
    }

    sockaddr.sin_family = AF_INET;
    sockaddr.sin_port = htons(50001);
    sockaddr.sin_addr.s_addr = inet_addr("192.168.0.139");
    int connfd = connect(sockfd, (struct sockaddr *)&sockaddr, sizeof(sockaddr));

    //send_t();

    pid_t pid = fork();
    if(pid > 0)
    {
       send_t(); 
    }
    else if(pid == 0)
    {
      recv_t(); 
    }
    else
    {
        perror("fork error");
        return -1;
    }

    return 0;
}

//服务端B
#include <stdio.h>
#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h> /* superset of previous */
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <string.h>

struct sockaddr_in dest_sockaddr;
socklen_t len = sizeof(dest_sockaddr);
int sockfd = 0;
int connfd = 0;

int recv_t()
{
    char buf[1024] = {0};
    do
    {   
        memset(buf, 0, sizeof(buf));
        ssize_t cont = recv(connfd, buf, sizeof(buf), 0);
        if(cont > 0)
        {
            printf("cont = %ld, buf = %s\n", cont, buf);
        }
        if(cont < 0)
		{
            perror("send error");
            return -1;
		}  
    }while(1);

    close(connfd);
    close(sockfd);
}

int send_t()
{   
  
    char buf[1024] = {0};

    while(1)
    {   
        fgets(buf, sizeof(buf), stdin);
        buf[strlen(buf) - 1] = 0;
        size_t conter = send(connfd, buf, strlen(buf), 0);
        if(conter > 0)
        {
            printf("conter=%ld\n", conter);
        }
        if(conter < 0)
		{
            perror("send error");
            return -1;
		}
    }

    close(connfd);
    close(sockfd);
}

int main(int argc, char const *argv[])
{
    sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if(sockfd < 0)
    {
        perror("socket error");
        return -1;
    }

    struct sockaddr_in sockaddr;
    sockaddr.sin_family = AF_INET;
    sockaddr.sin_port = htons(50001);
    sockaddr.sin_addr.s_addr = inet_addr("192.168.0.139");

    
    int ret = bind(sockfd, (struct sockaddr *)&sockaddr, sizeof(sockaddr));
    if(ret < 0)
    {
        perror("bind error");
        return -1;
    }

    int n = listen(sockfd, 10);
    if(n < 0)
    {
        perror("listen error");
        return -1;
    }
    
    connfd = accept(sockfd, (struct sockaddr *)&dest_sockaddr, &len);
    printf("[%s][%d] online\n", inet_ntoa(dest_sockaddr.sin_addr), ntohs(dest_sockaddr.sin_port));

    //recv_t();

    pid_t pid = fork();
    if(pid > 0)
    {
        recv_t();
    }
    else if(pid == 0)
    {
        send_t();
    }
    else 
    {
        perror("fork error");
        return -1;
    }
    
    return 0;
}

6.TCP头部标志位

TCP报文头部:

SYN:请求建立连接标志位;

ACK:响应标志位;

FIN:请求断开连接标志位;

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

URG:紧急指针标志位;

RST:复位标志位/重置标志位;

7.TCP的其他机制

(1)确保安全可靠

1.三次握手与四次挥手机制
2.应答机制

sequence number:序列号;

Acknowledgment number:响应序列号;

TCP对于每一包数据都会有相应的应答;

发送数据时,序列号表示这包数据的起始编号,确认时,响应报文中的响应序列号为接收方收到的最后一个字节编号+1(即为期待下次希望收到数据的起始号,以确保数据的安全可靠)

3.超时重传机制

当数据发出,在指定时间内(根据当前网络状态,TCP实时更新)未收到响应,此时认为数据丢失,则会重新发送这包数据;

4.滑动窗口机制

使用缓冲区实现TCP已发送未响应、准备发送的数据的缓存,确保数据重新传时,可以找到相应数据;

(2)提高效率

1.延迟应答机制

连续发送数据的同时,等待对方的响应;

2.流量控制机制

结合TCP头部窗口大小,动态调整接受速率;

3.捎带应答机制

ACK可能与应用层数据同时发送;

相关推荐
CYRUS_STUDIO7 小时前
用 Frida 控制 Android 线程:kill 命令、挂起与恢复全解析
android·linux·逆向
熊猫李8 小时前
rootfs-根文件系统详解
linux
dessler11 小时前
Hadoop HDFS-高可用集群部署
linux·运维·hdfs
泽泽爱旅行11 小时前
awk 语法解析-前端学习
linux·前端
轻松Ai享生活1 天前
5 节课深入学习Linux Cgroups
linux
christine-rr1 天前
linux常用命令(4)——压缩命令
linux·服务器·redis
三坛海会大神5551 天前
LVS与Keepalived详解(二)LVS负载均衡实现实操
linux·负载均衡·lvs
東雪蓮☆1 天前
深入理解 LVS-DR 模式与 Keepalived 高可用集群
linux·运维·服务器·lvs
树码小子1 天前
Java网络编程:(socket API编程:TCP协议的 socket API -- 回显程序的服务器端程序的编写)
java·网络·tcp/ip
乌萨奇也要立志学C++1 天前
【Linux】进程概念(二):进程查看与 fork 初探
linux·运维·服务器