美文网首页
TCP网络编程

TCP网络编程

作者: 潘雪雯 | 来源:发表于2020-05-07 22:31 被阅读0次

    socket介绍

    网络层的IP地址可以唯一标识网络中的主机,而传输层的“协议+端口”可以唯一标识主机中的应用程序(进程)。这样利用三元组(IP地址,协议,端口)就可以标识网络的进程。
    使用TCP/IP协议的应用程序通常采用应用编程接口:套接字(socket),采用open-->write/read-->close模式实现。


    TCP交互流程
    1. 服务器根据地址类型(ipv4,ipv6)、socket类型,协议创建socket。
    2. 服务器为socket绑定IP地址和端口号
    3. 服务器socket监听端口号请求,随时准备接收客户端发来的连接,这时候服务器的socket并没有被打开
    4. 客户端创建socket
    5. 客户端打开socket,根据服务器IP地址和端口号试图连接服务器socket
    6. 服务器socket接收到客户端socket请求,被动打开,开始接收客户端请求,直到客户端返回连接信息。这时socket进入阻塞状态,即accept()方法一直到客户端返回连接信息后才返回,开始接收下一个客户端连接请求。
    7. 客户端连接成功,向服务器发送连接状态信息
    8. 服务器accept方法发挥,连接成功
    9. 客户端向socket写入信息
    10. 服务器读取信息
    11. 客户端关闭
    12. 服务器端关闭

    socket接口函数

    • socket函数用于创建一个socket描述符,唯一标识一个socket。socket函数有3个参数分别如下所述:
    1. domain:即协议域,又称协议族(family),如AF_INETAF_INET6AF_LOCAL。决定了socket的地址类型,在通信中必须采用对应的地址。如AF_INET决定了要用ipv4地址(32位)与端口号(16位)的组合。
    2. type: 指定socket类型。常见的socket类型有:SOCK_STREAM表示提供面向连接的稳定数据传输、SOCK_DGRAM表示使用不连续、不可靠的数据包连接。
    3. protocal:指定协议。常用的协议有IPPROTO_TCP对应TCP传输协议、IPPROTO_UDP对应UDP传输协议、IPPROTO_SCTP对应STCP传输协议、IPPROTO_TIPC对应TIPC传输协议。
      若protocal为0 ,会自动选择type类型对应的默认协议。
    int socket(AF_INET, SOCK_STREAM, 0)
    

    4)函数返回值:若失败就会返回-1,成功会返回新创建的套接字的描述符。为整数类型的值。

    • bind函数
      该函数把一个地址族中的特定地址赋给socket。例如对应AF_INET、AF_INET6就是把一个ipv4或ipv6地址和端口号组合赋给socket。
      bind函数原型
    int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
    

    函数的3个参数分别如下描述:

    1. sockfd:即socket描述字,是通过socket()函数创建来唯一标识一个socket的,bind()函数就是将给这个描述字绑定一个名字。
    2. addr: 是一个const struct sockaddr 指针,指向要绑定给sockfd的协议地址。这个地址结构根据地址创建socket 时的地址协议族的不同而不同。是一个结构体类型指针。
      其中
      servaddr.sin_addr.s_addr = htonl(INADDR_ANY)中参数INADDR_ANY用于多网卡的机器上,多个IP都可以连上。若设置为servaddr.sin_addr.s_addr = inet_addr("192.168.1.1")*则表示只能连接到192.168.1.1。
      image.png
    3. addrlen:对应的是地址的长度。
    4. 函数返回值:若函数执行成功,返回值为0,反之为SOCKET_ERROR
    • listen和connect函数
      调用listen()来监听socket。listen和connect函数原型是:
    int listen(int sockfd, int backlog);
    int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
    

    listen函数的第一个参数即为要监听的socket描述字,第二个参数为相应socket可以排队的最大连接个数。socket()函数创建的socket默认是一个主动类型的,listen函数将socket变为被动类型的,等待客户的连接请求。
    connect函数的第一个参数即为要监听的socket描述字,第二个参数为服务器的socket地址,第三个参数为socket地址的长度。客户端通过调用
    connect函数建立于TCP服务器的连接。

    • accept函数
      TCP客户端调用connect()之后会向TCP服务器发送一个连接请求。TCP服务器监听到这个请求后,会调用accept()函数接收请求,这样连接就建立起来了。accept的函数原型
    int accept(int sockfd, const struct sockaddr *addr, socklen_t *addrlen)
    

    accept函数的第一个参数即为服务器的socket描述字,第二个参数为struct sockaddr *的指针,用于返回客户端的协议地址,第三个参数为协议地址的长度。若accept成功,则其返回值由内核自动生成一个全新的描述符,代表与返回客户的TCP连接。

    1. read和write函数
      网络I/O操作常用的是read()和write()。read()的函数原型是:
    ssize_t read(int fd,void *buf,size_t count);
    

    read()函数负责从fd中读取内容。当读取成功时,read()返回实际所读的字节数,返回值0表示已经读到文件的结束,小于0表示出现错误,若错误为EINRT表示在读的时候出现中断错误。。3个参数分别是:1)fd表示socket描述字 2) buf表示缓冲区 3) count表示缓冲区长度。
    write()的函数原型是:

    ssize_t write(int fd,void *buf,size_t count);
    

    writed()函数将buf中的nbytes字节内容写入文件描述符fd成功时返回写的字节数,失败时返回-1,并设置errno变量。返回值大于0,表示写了部分或全部数据;返回值小于0表示出现错误,若错误为EINRT表示在写的时候出现中断错误。

    • close函数
      关闭相应的socket描述字。close操作只是使相应socket描述字的引用计数-1,只有当引用计数为0的时候,才会触发TCP客户端向服务器发送终止连接请求。

    代码

    • 服务器端server.cpp
    1. 创建socket,并给serveraddress赋值
        if( (listenfd = socket(AF_INET, SOCK_STREAM, 0)) == -1 ){
            printf("create socket error: %s(errno: %d)\n",strerror(errno),errno);
            return 0;
        }
    
        memset(&servaddr, 0, sizeof(servaddr));
        servaddr.sin_family = AF_INET;
        servaddr.sin_addr.s_addr = htonl(INADDR_ANY); // 使用自己的 ip 地址
        servaddr.sin_port = htons(1101);
    
    1. 绑定socket和端口号
        if( bind(listenfd, (struct sockaddr*)&servaddr, sizeof(servaddr)) == -1){
            printf("bind socket error: %s(errno: %d)\n",strerror(errno),errno);
            return 0;
        }
    
    1. 监听端口号
    if( listen(listenfd, 10) == -1){
            printf("listen socket error: %s(errno: %d)\n",strerror(errno),errno);
            return 0;
        }
    

    <- -----------------------服务器的准备工作完成--------------------------------- ->

    • 客户端client.cpp
    1. 创建socket
    if( (sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0){
            printf("create socket error: %s(errno: %d)\n", strerror(errno),errno);
            return 0;
        }
    
        memset(&servaddr, 0, sizeof(servaddr));
        servaddr.sin_family = AF_INET;
        servaddr.sin_port = htons(1101);
    
    1. 连接指定计算机端口
    if( connect(sockfd, (struct sockaddr*)&servaddr, sizeof(servaddr)) < 0){
            printf("connect error: %s(errno: %d)\n",strerror(errno),errno);
            return 0;
        }
    
    1. 发送数据给服务器或向socket写入数据
    printf("send msg to server: \n");
        fgets(sendline, 4096, stdin);
        if( send(sockfd, sendline, strlen(sendline), 0) < 0){
            printf("send msg error: %s(errno: %d)\n", strerror(errno), errno);
            return 0;
        }
        close(sockfd);
    
    1. 服务器接收来自客户端的连接请求从socket中读取字符accept()
    printf("======waiting for client's request======\n");
        while(1){
            if( (connfd = accept(listenfd, (struct sockaddr*)NULL, NULL)) == -1){
                printf("accept socket error: %s(errno: %d)",strerror(errno),errno);
                continue;
            }
    
    1. 服务器从socket中读取数据recv()
            n = recv(connfd, buff, MAXLINE, 0);
            buff[n] = '\0';
            printf("recv msg from client: %s\n", buff);
            close(connfd);
        }
        close(listenfd);
    
    image.png
    如果两次连接的间隔太短不到两个MSL等待时间(MSL指一个片段在网络中最大的存货时间),也就是四次挥手没有完成没有正常关闭,会出现bind socket error: Address already in use(errno: 98)

    相关文章

      网友评论

          本文标题:TCP网络编程

          本文链接:https://www.haomeiwen.com/subject/xpxnghtx.html