美文网首页
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