• 基本TCP套接字编程


    在学习具体函数前,必须先有这样一个认识,socket和各种相关函数的实质是什么?socket源于Unix,而Unix有着“一切皆文件”的哲学思想,socket是“open-write/read-close”模式的实现,那么socket就会提供接口函数来实现对其进行相应操作。

    1.socket():

    在成功时返回一个小的非负整数值,它与文件描述符类似,称为套接字描述符

    2.connect():

    在此之前,不必非得调用bind函数,因为需要的话,内核会确定源IP地址,并选择一个临时端口作为源端口。

    如果是TCP套接字,调用connect函数将激发TCP的三次握手过程,而且仅在连接建立成功或失败时才返回。

    connect函数导致当前套接字从CLOSED状态(该套接字自从由socket函数创建以来一直所处状态)转移到SYN_SENT状态;若connect成功再转移到ESTABLISHED状态若失败则该套接字不再可用,必须关闭,不能对这样的套接字再次调用connect函数。

    3.bind():

    把一个本地协议地址赋予一个套接字。对于网际网协议,协议地址是32位的IPv4地址或128位的IPv6地址与16位的TCP或UDP端口号的组合

    TCP服务器不同于TCP客户,需要捆绑它们的众所周知端口被大家认识。

    4.listen():

    仅由TCP服务器调用。当socket函数创建套接字时,它被假设为一个主动套接字,即一个将调用connect发起连接的客户套接字。而listen函数把一个未连接的套接字转换为一个被动套接字,指示内核应接受指向该套接字的连接请求。

    调用listen导致套接字从CLOSED状态转换到LISTEN状态

    另外,必须认识到内核为任何一个给定的监听套接字维护两个队列

    • 未完成连接队列:每个SYN分节对应其中一项,已由某个客户发出并到达服务器,而服务器正在等待完成相应TCP三次握手过程,这些套接字处于SYN-RCVD状态
    • 已完成连接队列:每个已完成TCP三次握手过程的客户对应其中一项,这些套接字处于ESTABLISHED状态

    5.accept():

    由TCP服务器调用,用于从已完成连接队列返回下一个已完成连接,若队列为空则进程投入睡眠,直到TCP在该队列中投入一项才唤醒它。

    那么accept正确返回后,接下来发生了什么?

    如果accept成功,那么其返回值是一个内核自动生成的全新描述符,代表与所返回客户的TCP连接。

    讨论accept函数时,作为参数的是监听套接字描述符listenfd(由socket创建,随后用作bind和listen参数的描述符),作为返回值的是已连接套接字描述符connfd:一个服务器仅创建一个监听套接字,它在服务器生命期内一直存在,而内核为服务器进程接受的每个客户连接创建一个已连接套接字,当服务器完成对某给定客户的服务时,相应已连接套接字就被关闭(结合下面论述理解:已连接套接字在每次循环中关闭,但监听套接字在整个有效期内保持开放)

    6.并发编程机制

    Unix中编写并发服务器程序最简单的办法就是fork一个子进程来服务每个客户:

    图注:

    3中父进程关闭connfd,子进程关闭listenfd;

    4中达到两个套接字所期望的最终状态,即子进程处理与客户连接,父进程可在监听套接字上再次调用accept来处理下一个客户连接,这从1中服务器和4中服务器父进程对照可看出。

    结合上面处理流程就不难理解“对一个TCP套接字调用close会导致发送一个FIN,随后是正常的TCP连接终止序列,但为什么父进程对connfd调用close没有终止与客户的连接?”这个问题,因为在每个文件或套接字都有一个引用计数(在文件表项中维护),它是当前打开着的引用该文件或套接字的描述符个数。如:socket返回后与listenfd关联的文件表项的引用计数值为1,accept返回后与connfd关联的文件表项的引用计数值也为1;在fork返回后,两个描述符在父子进程间共享,因此引用计数值均为2。这么一来,当父进程关闭connfd时,它只是把引用计数值从2减为1,该套接字真正的清理和资源释放要等到引用计数值为0时才发生

    /*-------------------------------------------------
     * Name:            head.h
     * Date:            2015/10/08
     ------------------------------------------------*/
    #ifndef _HEAD_H_
    #define _HEAD_H_
    
    #include <stdio.h>
    #include <string.h>
    #include <stdlib.h>
    
    #include <unistd.h>
    #include <sys/types.h>
    #include <netinet/in.h>
    #include <errno.h>
    
    #include <time.h>
    #include <assert.h>
    #include <sys/stat.h>
    #include <sys/wait.h>
    #include <fcntl.h>
    #include <dirent.h>
    #include <signal.h>
    #include <sys/time.h>
    #include <arpa/inet.h>
    
    void perror_exit(char *s)
    {
        perror(s);
        exit(EXIT_FAILURE);
    }
    
    #endif
    /*-------------------------------------------------
     * Name:            server.c
     * Date:            2015/10/08
     ------------------------------------------------*/
    #include "head.h"
    
    int main(int argc, char *argv[])
    {
        int listenfd;
        listenfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
        if(listenfd < 0)
            perror_exit("socket error");
        else printf("create listenfd successfully...
    ");
    
        struct sockaddr_in serveraddr;
        memset(&serveraddr, 0, sizeof(serveraddr));
        serveraddr.sin_family = AF_INET;
        serveraddr.sin_port = htons(5188);
        serveraddr.sin_addr.s_addr = htonl(INADDR_ANY);
        //serveraddr.sin_addr.s_addr = inet_addr("127.0.0.1");
        //inet_aton("127.0.0.1", $serveraddr.sin_addr);
    
        int on=1;
        if(setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on))<0)
            perror_exit("setsockopt error");
    
        if(bind(listenfd, (struct sockaddr*)&serveraddr, sizeof(serveraddr)) < 0)
            perror_exit("bind error");
        else printf("bind address successfully...
    ");
    
        if(listen(listenfd, SOMAXCONN) < 0)
            perror_exit("listen error");
        else printf("keep listening...
    ");
    
        struct sockaddr_in peeraddr;
        socklen_t peerlen = sizeof(peeraddr);
        int connfd;
        int count = 0;
        pid_t pid;
        while(1)
        {
            if((connfd = accept(listenfd, (struct sockaddr*)&peeraddr, &peerlen)) < 0)
                perror_exit("accept error");
            else printf("client %d connect to server: ip = %s, port = %d
    ", ++count, inet_ntoa(peeraddr.sin_addr), peeraddr.sin_port);
    
            pid = fork();
            if(pid == -1)
                perror_exit("fork error");
            else if(pid > 0)
                close(connfd);
            else
            {
                close(listenfd);
                while(1)
                {
                    char buf[1024];
                    memset(buf, 0, sizeof(buf));
                    int ret = read(connfd, buf, sizeof(buf));
                    if(ret == -1)
                        perror_exit("read error");
                    else if(ret == 0)
                    {
                        printf("client %d was closed...
    ", count);
                        break;
                    }
                    else
                    {
                        fputs(buf, stdout);
    
                        int len = strlen(buf);
                        buf[len-1] = '*';
                        buf[len] = '
    ';
    
                        write(connfd, buf, ret);
                    }
                }
                exit(EXIT_SUCCESS);
            }
        }
        return 0;
    }
    /*-------------------------------------------------
     * Name:            client.c
     * Date:            2015/10/08
     ------------------------------------------------*/
    #include "head.h"
    
    int main(int argc, char *argv[])
    {
        int socketfd;
        socketfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
        if(socketfd == -1)
            perror_exit("socket error");
        else printf("create socketfd successfully...
    ");
    
        struct sockaddr_in clientaddr;
        memset(&clientaddr, 0, sizeof(clientaddr));
        clientaddr.sin_family = AF_INET;
        clientaddr.sin_port = htons(5188);
        clientaddr.sin_addr.s_addr = inet_addr("127.0.0.1");
    
        int conn;
        conn = connect(socketfd, (struct sockaddr *)&clientaddr, sizeof(clientaddr));
        if(conn == -1)
            perror_exit("connect error");
        else printf("connect to server successfully...
    ");
    
        char recvbuf[1024];
        char sendbuf[1024];
        memset(recvbuf, 0, sizeof(recvbuf));
        memset(sendbuf, 0, sizeof(sendbuf));
    
        int ret;
        pid_t pid;
        pid = fork();
    
        if(pid == -1)
            perror_exit("fork error");
        else if(pid > 0)
        {
            while(1)
            {
                ret = read(socketfd, recvbuf, sizeof(recvbuf));
                if(ret == -1)
                    perror_exit("read error");
                else if(ret == 0)
                {
                    printf("server was closed...
    ");
                    break;
                }
                else
                    fputs(recvbuf, stdout);
            }
            close(socketfd);
        }
        else
        {
            while(fgets(sendbuf, sizeof(sendbuf), stdin) != NULL)
            {
                write(socketfd, sendbuf, sizeof(sendbuf));
                memset(sendbuf, 0, sizeof(sendbuf));
            }
            exit(EXIT_SUCCESS);
        }
        return 0;
    }
  • 相关阅读:
    基于模糊Choquet积分的目标检测算法
    Android开发5:布局管理器2(表格布局TableLayout)
    JAVA WEB开发环境搭建教程
    linux下自助获取帮助
    dsp下基于双循环缓冲队列的视频采集和显示记录
    找工作笔试面试那些事儿(11)---数据库知识总结(2)范式
    【Todo】Zookeeper系列文章
    VC2010对Excel的操作
    hdu2647解题报告
    premake 在64位Ubuntu系统下编译32位GCC程序
  • 原文地址:https://www.cnblogs.com/1203ljh/p/4621428.html
Copyright © 2020-2023  润新知