Linux网络编程之TCP

目录

TCP协议

(1) 概述

(2) 三次握手协议

 socket信息数据结构

1、绑定ip,端口号结构体

2、数据存储优先顺序的转换

3、地址格式转化

socket编程

TCP协议的流程图


TCP/IP 五层模型中,将 OSI 七层模型的最上三层(应用层、表示层和会话层)合并为一个层,即应用层,所以 TCP/IP 五层模型包括:应用层、传输层、网络层、数据链路层以及物理层。除了 TCP/IP 五层模型外,还有 TCP/IP 四层模型,与五层模型唯一不同的就是将数据链路层和物理层合并为网络接口层

Linux网络编程之TCP_第1张图片

 网络编程,再加上不停地man,对当前的学习做个总结:

TCP协议

(1) 概述

TCP是TCP/IP体系中面向连接的运输层协议,它提供全双工和可靠交付的服务。它采用许多机制来确保端到端结点之间的可靠数据传输,如采用序列号、确认重传、滑动窗口等。

首先,TCP要为所发送的每一个报文段加上序列号,保证每一个报文段能被接收方接收,并只被正确的接收一次。

其次,TCP采用具有重传功能的积极确认技术作为可靠数据流传输服务的基础。这里“确认”是指接收端在正确收到报文段之后向发送端回送一个确认(ACK)信息。发送方将每个已发送的报文段备份在自己的缓冲区里,而且在收到相应的确认之前是不会丢弃所保存的报文段的。“积极”是指发送发在每一个报文段发送完毕的同时启动一个定时器,加入定时器的定时期满而关于报文段的确认信息还没有达到,则发送发认为该报文段已经丢失并主动重发。为了避免由于网络延时引起迟到的确认和重复的确认,TCP规定在确认信息中捎带一个报文段的序号,使接收方能正确的将报文段与确认联系起来。

最后,采用可变长的滑动窗口协议进行流量控制,以防止由于发送端与接收端之间的不匹配而引起的数据丢失。这里所采用的滑动窗口协议与数据链路层的滑动窗口协议在工作原理上完全相同,唯一的区别在于滑动窗口协议用于传输层是为了在端对端节点之间实现流量控制,而用于数据链路层是为了在相邻节点之间实现流量控制。TCP采用可变长的滑动窗口,使得发送端与接收端可根据自己的CPU和数据缓存资源对数据发送和接收能力来进行动态调整,从而灵活性更强,也更合理。

(2) 三次握手协议

在利用TCP实现源主机和目的主机通信时,目的主机必须同意,否则TCP连接无法建立。为了确保TCP连接的成功建立,TCP采用了一种称为三次握手的方式,三次握手方式使得“序号/确认号”系统能够正常工作,从而使它们的序号达成同步。如果三次握手成功,则连接建立成功,可以开始传送数据信息。

其三次握手分别为:

1)源主机A的TCP向主机B发送连接请求报文段,其首部中的SYN(同步)标志位应置为1,表示想跟目标主机B建立连接,进行通信,并发送一个同步序列号X(例:SEQ=100)进行同步,表明在后面传送数据时的第一个数据字节的序号为X+1(即101)。

2)目标主机B的TCP收到连接请求报文段后,如同意,则发回确认。再确认报中应将ACK位和SYN位置为1.确认号为X+1,同时也为自己选择一个序号Y。

3)源主机A的TCP收到目标主机B的确认后要想目标主机B给出确认。其ACK置为1,确认号为Y+1,而自己的序号为X+1。TCP的标准规定,SYN置1的报文段要消耗掉一个序号。

运行客户进程的源主机A的TCP通知上层应用进程,连接已经建立。当源主机A向目标主机B发送第一个数据报文段时,其序号仍为X+1,因为前一个确认报文段并不消耗序号。

当运行服务进程的目标主机B的TCP收到源主机A的确认后,也通知其上层应用进程,连接已经建立。至此建立了一个全双工的连接。

三次握手:为应用程序提供可靠的通信连接。适合于一次传输大批数据的情况。并适用于要求得到响应的应用程序。 但是为了传输的速度,会使用UDP,加上TCP的协议

Linux网络编程之TCP_第2张图片

 socket信息数据结构

1、绑定ip,端口号结构体

/* Address to accept any incoming messages.  */
#define INADDR_ANY              ((in_addr_t) 0x00000000)  //服务器IP地址

/* Internet address.  */
typedef uint32_t in_addr_t;
struct in_addr
  {
    in_addr_t s_addr;  
  };
/* Structure describing an Internet socket address.  */
struct sockaddr_in
  {
    __SOCKADDR_COMMON (sin_);
    in_port_t sin_port;                 /* 端口号  */
    struct in_addr sin_addr;            /* IP地址  */

    /* Pad to size of `struct sockaddr'.  */
    unsigned char sin_zero[sizeof (struct sockaddr) -
                           __SOCKADDR_COMMON_SIZE -
                           sizeof (in_port_t) -
                           sizeof (struct in_addr)];
  };
头文件

sa_family:AF_INET  IPv4协议   AF_INET6  IPv6协议

2、数据存储优先顺序的转换

内存的低地址存储数据的低字节,高地址存储数据的高字节的方式叫小端模式。内存的高地址存储数据的低字节,低地址存储数据高字节的方式称为大端模式。

eg:对于内存中存放的数0x12345678来说

如果是采用大端模式存放的,则其真实的数是:0x12345678

如果是采用小端模式存放的,则其真实的数是:0x78563412

要把主机字节序和网络字节序相互对应起来,需要对这两个字节存储优先顺序进行相互转化。这里用到四个函数:htons(),ntohs(),htonl()和ntohl()

h代表host,n代表network,s代表short,l代表long。通常16位的IP端口号用s代表,而IP地址用l来代表。

#include 

       uint32_t htonl(uint32_t hostlong); 

       uint16_t htons(uint16_t hostshort);

       uint32_t ntohl(uint32_t netlong);

       uint16_t ntohs(uint16_t netshort);

3、地址格式转化

通常用户在表达地址时采用的是点分十进制表示的数值(或者是为冒号分开的十进制Ipv6地址),而在通常使用的socket编程中使用的则是32位的网络字节序的二进制值,这就需要将这两个数值进行转换。这里在Ipv4中用到的函数有inet_aton()、inet_addr()和inet_ntoa(),而IPV4和Ipv6兼容的函数有inet_pton()和inet_ntop()。

函数原型

#include 
#include 
#include 

int inet_aton(const char *cp, struct in_addr *inp);

in_addr_t inet_addr(const char *cp);

in_addr_t inet_network(const char *cp);

char *inet_ntoa(struct in_addr in);

struct in_addr inet_makeaddr(in_addr_t net, in_addr_t host);

in_addr_t inet_lnaof(struct in_addr in);

in_addr_t inet_netof(struct in_addr in);

函数inet_addr:成功返回功能与inet_aton相同,但是结果传递的方式不同。inet_addr()若成功则返回32位二进制的网络字节序地址。

#define ADDRESS "192.168.1.103"   
 
struct sockaddr_in s_addr;

bzero(&s_addr , sizeof(s_addr));

s_addr.sin_family = AF_INET;
s_addr.sin_port = htons(8888);
s_addr.sin_addr.s_addr = inet_addr(ADDRESS);;
if(connect(client_fd, (struct sockaddr *)&s_addr,sizeof(s_addr)) == -1)
            handle_error("connect");

socket编程

TCP协议的流程图

 服务器-客户端

Linux网络编程之TCP_第3张图片

套接字------文件描述符

#include           /* See NOTES */
#include 

int socket(int domain, int type, int protocol);

//第一个参数、AF_INET      IPv4 
//第二个参数、SOCK_STREAM  TCP
//第三个参数、0

处理端口复用冲突

#include           /* See NOTES */
#include 

int setsockopt(int sockfd, int level, int optname,
                      const void *optval, socklen_t optlen);


需要查看头文件 进行配置参数
int sockfd 套接字文件描述符
int level   SOL_SOCKET
int optname  SO_REUSEADDR

//定义一个int变量  
const void *optval
socklen_t optlen    变量长度

绑定

 #include           /* See NOTES */
#include 

int bind(int sockfd, const struct sockaddr *addr,
                socklen_t addrlen);

//第一个参数、套接字
//第二个参数、IP地址 端口号 结构体
//第三个参数、结构体的长度

监听

#include           /* See NOTES */
#include 

int listen(int sockfd, int backlog);

//sockfd 套接字
//backlog最大绝连数 一般为总数的一半

等待客户连接

#include           /* See NOTES */
#include 

int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);

//sockfd  套接字文件描述符
//struct sockaddr *addr  客户端结的信息


端口复用

       /* According to POSIX.1-2001, POSIX.1-2008 */
       #include 

       /* According to earlier standards */
       #include 
       #include 
       #include 


       //端口复用
       int select(int nfds, fd_set *readfds, fd_set *writefds,
                  fd_set *exceptfds, struct timeval *timeout);

       nfds:最大文件描述符+1
       readset, writset, exceptset:指向描述符集
       readset:读描述符集
       writset:写描述符集
       structtimeval* timeout:select的超时时间
 
       /* 清除某个位 */ 
       void FD_CLR(int fd, fd_set *set);

       /* 测试某个位是否被置位 */
       int  FD_ISSET(int fd, fd_set *set);

       /* 变量的某个位置位 */
       void FD_SET(int fd, fd_set *set);

       /* fd_set类型变量的所有位都设为 0 */
       void FD_ZERO(fd_set *set);

服务器:

#include 
#include          
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include      
#include 
#include 
       
/* 错误处理 */
#define handle_error(msg) \
           do { perror(msg); exit(EXIT_FAILURE); } while (0)

/* ip信息 */
struct net_info{
    int net_fd;
    char address_ip[50];
};



/**
 * @description:  服务器初始化
 * @param {void}
 * @return {struct net_info*}  info
 */
struct net_info* server_init()
{
    struct net_info *info = malloc(sizeof(struct net_info)); 

    /* 创建文件描述符 */
    int server_fd= socket(AF_INET, SOCK_STREAM , 0);
    if(server_fd == -1) 
        handle_error("socket");
    
    /* 处理端口被占用 */
    int on=1;
    if(setsockopt(server_fd , SOL_SOCKET , SO_REUSEADDR,
                        &on, sizeof(int)) == -1)
            handle_error("setsockopt");

    /* 绑定ip地址 端口号 */
    struct sockaddr_in s_addr;
    bzero(&s_addr , sizeof(s_addr));
    s_addr.sin_family = AF_INET;
    s_addr.sin_port = htons(8888);
    s_addr.sin_addr.s_addr = INADDR_ANY;
    if(bind(server_fd, (struct sockaddr *)&s_addr,sizeof(struct sockaddr_in)) == -1)
        handle_error("bind");
    
    /* 监听 */
    if(listen(server_fd, 10) == -1) 
        handle_error("listen");

    /* 等待连接 */
    struct sockaddr_in client_info;
    int len = sizeof(client_info);
    bzero(&client_info , sizeof(client_info));
    info->net_fd = accept(server_fd , (struct sockaddr *)&client_info , &len);
    if(info->net_fd == -1)
        handle_error("accept");

    /* 客户机连接 */
    strcpy(info->address_ip , inet_ntoa(client_info.sin_addr));
    printf("主机:%s---->上线了\n",info->address_ip);
    return info;
}
/**
 * @description:   释放内存
 * @param {struct net_info} *_info
 * @return {*}
 */
void destroy_server(struct net_info *_info)
{
    close(_info->net_fd);
    free(_info);
}
/**
 * @description: 
 * @param {int} argc
 * @param {char} *
 * @return {*}
 */
int main(int argc, char **argv)
{
    char buffer[1024]={0};
    struct net_info *client_info = server_init();
    if(client_info->net_fd == -1) 
        return -1;
    while(1)
    {       
            /*端口复用*/
            fd_set rfds;
            FD_ZERO(&rfds);
            FD_SET(0, &rfds);
            FD_SET(client_info->net_fd, &rfds);
            int ret = select(client_info->net_fd+1, &rfds, NULL,
                  NULL, NULL);
            if(ret == -1)
                return -1;
            else if(FD_ISSET(0 , &rfds))
                {
                        bzero(buffer, sizeof(buffer));
                        scanf("%s", buffer);
                        printf("发送的数据:%s" , buffer);
                        if(send(client_info->net_fd , buffer , strlen(buffer) , 0) == -1)
                            handle_error("send");
                }
            else if(FD_ISSET(client_info->net_fd , &rfds))
                {
                        bzero(buffer, sizeof(buffer));
                        if(recv(client_info->net_fd , buffer , 1024 , 0) <= 0)
                            handle_error("recv");
                        printf("\n%s------->%s\n" , client_info->address_ip , buffer);
                }
    }
    destroy_server(client_info);
    return 0;
}

客户端:

#include 
#include          
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

/* 错误处理 */
#define handle_error(msg) \
           do { perror(msg); exit(EXIT_FAILURE); } while (0)

/* 服务器ip地址 */
#define ADDRESS "192.168.1.103"


/**
 * @description: 
 * @param {*}
 * @return {*}
 */
int client_init(void)
{
    /* 创建文件描述符 */
    int client_fd = socket(AF_INET, SOCK_STREAM , 0);
    if(client_fd < 0) 
        handle_error("socket");

    /* 绑定ip地址---->端口号----->发起连接*/
    struct sockaddr_in s_addr;
    bzero(&s_addr , sizeof(s_addr));
    s_addr.sin_family = AF_INET;
    s_addr.sin_port = htons(8888);
    s_addr.sin_addr.s_addr = inet_addr(ADDRESS);;
    if(connect(client_fd, (struct sockaddr *)&s_addr,sizeof(s_addr)) == -1)
            handle_error("connect");

    return client_fd;
}
/**
 * @description: 
 * @param {int} argc
 * @param {char} **argv
 * @return {int} 0
 */
int main(int argc , char **argv )
{
    char buffer[1024]={0};
    /* 初始化客户端 */
    int fd = client_init();
    while(1)
    {       
            /* 端口复用 */
            fd_set rfds;
            FD_ZERO(&rfds);
            FD_SET(0, &rfds);
            FD_SET(fd, &rfds);
            bzero(buffer, sizeof(buffer));
            int ret = select(fd+1, &rfds, NULL,
                  NULL, NULL);
            if(ret == -1)
                handle_error("select");
            else if(FD_ISSET(0 , &rfds))
                {
                        printf("请输入要发送的数据:");
                        scanf("%s", buffer);
                        if(send(fd , buffer , strlen(buffer) , 0) == -1)
                            handle_error("send");
                }
            else if(FD_ISSET(fd , &rfds))
                {
                        if(recv(fd , buffer , 1024 , 0) <= 0)
                            handle_error("recv");
                        printf("服务器:%s------->%s\n" , ADDRESS , buffer);
                }
    }
    return 0;
}

你可能感兴趣的:(网络,linux,网络协议)