美文网首页
Socket编程

Socket编程

作者: 枯树恋 | 来源:发表于2019-04-15 17:24 被阅读0次

    socket函数需要指定IP协议是IPV4还是IPV6,分别对应AF_INET和AT_INET6,还需要指定是UDP还是TCP:TCP是基于数据流的,设为SOCK_STREAM;而UDP是基于数据报文的,设为SOCK_DGRAM.第三个参数为协议,传入0,系统会根据前两个参数给我们选择合适的协议族。

    int client_TCP_fd = socket(AF_INET,SOCK_STREAM,0);
    int client_UDP_fd = socket(AF_INET,SOCK_DGRAM,0);
    

    基于TCP的Socket函数调用过程

    基于TCP的socket流程.jpg

    客户端和服务端创建了socket之后,先调用bind()函数绑定IP和端口号。服务端有了IP和端口号,可以调用listen()函数进行监听,之后客户端可以发起连接。

    内核为每个socket维护两个队列:已经建立连接额队列(三次握手完成,处于Established状态)和正在建立连接的队列(尚未完成三次握手,处于SYN_REVD状态)。

    接下来服务端调用accept()函数,拿出已经完成的连接进行处理。

    在服务端等待的时候,客户端可以通过connect()发起连接,在参数中指明要连接的IP和端口号,然后发起三次握手,内核会给客户端分配临时端口,握手成功服务端的accept()函数返回一个socket.注意监听socket和真正用来传数据的socket是两个。

    连接成功建立,双方调用read()和write()开始读写数据。

    client.c:

    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <netdb.h>
    #include <sys/types.h>
    #include <sys/socket.h>
    #include <netinet/in.h>
    #include <arpa/inet.h>
    #include <errno.h>
    #include <error.h>
    #include <unistd.h>
    int main()
    {
        int communacation_socket,ret;
        struct sockaddr_in saddr;
        communacation_socket = socket(AF_INET,SOCK_STREAM,0);
        if(communacation_socket < 0)
        {
            perror("socket create error:");
            return -1;
        }
    
        saddr.sin_family = AF_INET;
        saddr.sin_port = htons(60000);
        inet_pton(AF_INET,"192.168.1.140",&(saddr.sin_addr.s_addr));
    
        ret = connect(communacation_socket,(struct sockaddr*)(&saddr),sizeof(saddr));
        if(ret < 0)
        {
            perror("connect error:");
            close(communacation_socket);
            return -1;
        }
        printf("connect success!\n");
        char str[1024] = "hello yisheng!";
        ret = write(communacation_socket,str,strlen(str) + 1);
        if(ret < 0)
        {
            perror("wtite error:");
            close(communacation_socket);
            return -1;
        }
        sleep(3);
        memset(str,'\0',1024);
        read(communacation_socket,str,1024);
        printf("read_buf = %s\n",str);
        close(communacation_socket);
        return 0;
    }
    

    server.c:

    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <netdb.h>
    #include <sys/types.h>
    #include <sys/socket.h>
    #include <netinet/in.h>
    #include <arpa/inet.h>
    #include <errno.h>
    #include <error.h>
    #include <unistd.h>
    int main()
    {
        int listen_socket,ret;
        int communacation_socket;
        struct sockaddr_in saddr,caddr;
        int addr_len;
        char buf[1024];
        char client_addr[24];
        listen_socket = socket(AF_INET,SOCK_STREAM,0);
        if(listen_socket < 0)
        {
            perror("socket create error:");
            return -1;
        }
    
        saddr.sin_family = AF_INET;
        saddr.sin_port = htons(60000);
        inet_pton(AF_INET,"192.168.1.140",&(saddr.sin_addr.s_addr));
        ret = bind(fd,(struct sockaddr*)(&saddr),sizeof(saddr));
        if(ret < 0)
        {
            perror("bind error: ");
            close(listen_socket);
            return -1;
        }
        ret = listen(listen_socket,20);
        if(ret < 0)
        {
            perror("listen error:");
            close(listen_socket);
            return -1;
        }
    
        while(1)
        {
            printf("accepting...\n");
            communacation_socket = accept(fd,(struct sockaddr*)&caddr,&addr_len);
            printf("accepting over...\n");
            if(communacation_socket < 0)
            {
                perror("accept error:");
                close(listen_socket);
                return -1;
            }
            memset(client_addr,'\0',sizeof(client_addr));
            strcpy(client_addr,inet_ntoa(caddr.sin_addr));
            printf("client addresss:   %s,and ",client_addr);  
            ret = read(communacation_socket,buf,1024);
            if(ret < 0)
            {
                perror("read error:");
            }
            else
            {
                printf("buf = %s \n",buf);
            }
            memset(buf,'\0',1024);
            strcpy(buf,"Succeed to receive!");
            write(communacation_socket,buf,1024);
            close(communacation_socket);
            //close(fd);
            communacation_socket = -1;
        }
        return 0;
    }
    

    socket在linux中以文件的形式存在,通过文件描述符写入和读出。在内核里面,socket是一个文件,对应有文件描述符。每一个进程都有一个数据结构task_struct,指向一个文件描述符数组,来列出这个进程打开的所有文件,文件描述符就是数组下标。数组中内容是指针,指向文件列表。既然是一个文件结构,就会有一个inode,真正的文件的inode是保存在磁盘上的,socket是保存在内存中的。inode指向内核中的Socket结构。

    socket相关的内核数据结构.jpg

    基于UDP协议的socket编程

    基于UDP的socket流程.jpg

    server.c:

    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <netdb.h>
    #include <sys/types.h>
    #include <sys/socket.h>
    #include <netinet/in.h>
    #include <arpa/inet.h>
    #include <errno.h>
    #include <error.h>
    #include <unistd.h>
    int main()
    {
        int communacation_socket,ret;
        struct sockaddr_in saddr,caddr;
        int addr_len;
        char buf[1024];
        char client_addr[24];
        communacation_socket = socket(AF_INET,SOCK_DGRAM,0);
        if(listen_socket < 0)
        {
            perror("socket create error:");
            return -1;
        }
    
        saddr.sin_family = AF_INET;
        saddr.sin_port = htons(60000);
        saddr.sin_addr.s_addr = INADDR_ANY;
        ret = bind(communacation_socket,(struct sockaddr*)(&saddr),sizeof(saddr));
        if(ret < 0)
        {
            perror("bind error: ");
            close(communacation_socket);
            return -1;
        }
        int len;
        while(1)
        {
            while(1)
            {
                ret = recvfrom(communacation_socket,buf,strlen(buf),0,(struct sockaddr*)(&saddr),&len);
                if(ret > 0)
                    break;
            }
            printf("read_buf = %s\n",buf);
            while(1)
            {
                memset(buf,'\0',1024);
                strcpy(buf,"Succeed to receive!!!!");
                ret = sendto(communacation_socket,buf,strlen(buf),0,(struct sockaddr*)(&saddr),sizeof(saddr));
                if(ret > 0)
                    break;
            }
            printf("write_buf = %s\n",buf);
        }
        close(communacation_socket);
        return 0;
    }
    

    client.c

    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <netdb.h>
    #include <sys/types.h>
    #include <sys/socket.h>
    #include <netinet/in.h>
    #include <arpa/inet.h>
    #include <errno.h>
    #include <error.h>
    #include <unistd.h>
    int main()
    {
        int communacation_socket,ret;
        int new_fd;
        struct sockaddr_in saddr,caddr;
        int addr_len;
        char buf[1024];
        char client_addr[24];
        communacation_socket = socket(AF_INET,SOCK_DGRAM,0);
        if(communacation_socket < 0)
        {
            perror("socket create error:");
            return -1;
        }
    
        saddr.sin_family = AF_INET;
        saddr.sin_port = htons(60000);
        saddr.sin_addr.s_addr = INADDR_ANY;
        int len;
        while(1)
        {
            while(1)
            {
                memset(buf,'\0',1024);
                strcpy(buf,"hello yisheng!!!!");
                ret = sendto(communacation_socket,buf,strlen(buf),0,(struct sockaddr*)(&saddr),sizeof(saddr));
                if(ret > 0)
                    break;
            }
            printf("write_buf = %s\n",buf);
            while(1)
            {
                ret = recvfrom(communacation_socket,buf,strlen(buf),0,(struct sockaddr*)(&saddr),&len);
                if(ret > 0)
                    break;
            }
            printf("read_buf = %s\n",buf);
            sleep(30);
        }
        close(communacation_socket);
        return 0;
    }
    

    服务端如何接更多项目

    socket的最大连接数,系统会用一个四元组来标识一个TCP连接。

    {本机IP,本机端口,对端IP,对端端口}

    服务器通常固定在某个端口处监听,等待客户端连接请求,对端IP固定的情况下,端口可变。最大TCP连接数 = 对端IP * 对端端口数。对于IPV4,IP最多232,端口最多216,因此理论上最大连接数为2^48。但是实际上远远达不到这个数字,原因在于(1)文件描述符数目的限制(2)操作系统内存有限。

    查看用户级和系统级别的文件描述符限制数

    Library liulongyang$ ulimit -a
    core file size          (blocks, -c) 0
    data seg size           (kbytes, -d) unlimited
    file size               (blocks, -f) unlimited
    max locked memory       (kbytes, -l) unlimited
    max memory size         (kbytes, -m) unlimited
    open files                      (-n) 256
    pipe size            (512 bytes, -p) 1
    stack size              (kbytes, -s) 8192
    cpu time               (seconds, -t) unlimited
    max user processes              (-u) 1418
    virtual memory          (kbytes, -v) unlimited
    liulongyang$ sysctl -a | grep maxfiles
    kern.maxfiles: 49152
    kern.maxfilesperproc: 24576
    
    1. 项目外包----->多进程。
      相当于建立一个代理在监听来的请求。一旦一个一个连接建立,就会有一个连接scoket,可以创建一个子进程然后将基于连接的socket的交互交给新的子进程来做。

      创建子进程相关(基于linux):
      fork()函数------>父进程基础上完全copy一个子进程,复制文件描述符列表,复制内存空间,还会复制一条记录当前执行到哪一行程序的进程。根据fork()返回值区分到底是父进程还是子进程。根据返回的进程ID父进程可以查看子进程执行状态。

      进程复制.jpg
    1. 项目转包给独立项目组---->多线程。
      Linux中通过pthread_create创建线程。不同之处在于虽然新的线程在task列表中会新建一项,但是很多资源还是共享的,例如文件描述符,只不过多了一个引用。

      C10K问题。如果新来一个TCP连接,就创建一个一个进程或线程。现实是一个操作系统并不能够创建很多的进程或者线程。

      线程.jpg
    1. 一个项目组支撑多个项目---->IO多路复用。

      一个项目组有多个项目了,这是需要每个项目组都有一个项目进度墙,每天提供项目进度墙查看每个项目的进度。如果某项目有了进展,就派人去看一下。

      把所有的socket加入fd_set当中,fd_set作为项目墙,通过select()或者poll()函数监听文件描述符集合是否变化。一旦有变化,轮询所有的文件描述符,对fd_set中对应未为1的socket进行socket的读写操作。然后继续下一轮监听。

    2. 一个项目组支撑多个项目---->IO多路复用,从派人监督到有事通知。

      缺点是能同时盯得的项目数收到FD_SETSIZE限制。于是诞生了和事件通知方式类似的IO多路复用机制--->epoll()机制:通过注册callback()函数,当某个文件描述符状态发生变化主动通知。

      epoll()机制可以解决C10K问题。epoll()机制使得socket数量激增的时候,效率不会下降太多。并且可以监听的socket上限是相同定义的线程打开的最大描述符数。

      epoll.jpg

    相关文章

      网友评论

          本文标题:Socket编程

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