linux内核之socket

2020-05-11 10:40:01 浏览数 (1)

linux socket的基础

linux的空间分为kernel space 和 user space, 比例是1:3

linux中一切皆文件, 所以用文件描述符来表示socket, 实际上是socket函数的返回值

代码语言:javascript复制
EXAMPLE
       An example of the use of socket() is shown in (3).
accept(2), bind(2), connect(2), fcntl(2), getpeername(2), getsockname(2), getsockopt(2), ioctl(2), listen(2), read(2), recv(2), select(2),  send(2),  shutdown(2),  socketpair(2),
       write(2), getprotoent(3), ip(7), socket(7), tcp(7), udp(7), unix(7)


/* Create a new socket of type TYPE in domain DOMAIN, using
   protocol PROTOCOL.  If PROTOCOL is zero, one is chosen automatically.
   Returns a file descriptor for the new socket, or -1 for errors.  */
extern int socket (int __domain, int __type, int __protocol) __THROW;

建立socket 是什么

socket是提供tcp/ip操作的一个api, 相当于封装tcp和ip, 用来进程与进程之间的通信

socket如何创建

一个进程要创建socket的流程如下所示

代码语言:javascript复制
1. 调用socket函数创建socket, 返回一个文件描述符sfd
2. 调用bind函数绑定sfd
3. listen(sfd,...)
4. 当有client要建立socket连接, 接收来的连接 并返回一个客户端文件描述符cfd = accept(sfd,...)
5. 调用read()函数, 等待read client的输入

linux socket-io模型linux socket-io模型
代码语言:javascript复制
 #include <sys/socket.h>
       #include <sys/un.h>
       #include <stdlib.h>
       #include <stdio.h>
       #include <string.h>

       #define MY_SOCK_PATH "/somepath"
       #define LISTEN_BACKLOG 50

       #define handle_error(msg) 
           do { perror(msg); exit(EXIT_FAILURE); } while (0)

       int
       main(int argc, char *argv[])
       {
		   /*
		    *  sfd - server fd
			   cfd - client fd
		    */
           int sfd, cfd;
           struct sockaddr_un my_addr, peer_addr;
           socklen_t peer_addr_size;

           sfd = socket(AF_UNIX, SOCK_STREAM, 0);
           if (sfd == -1)
               handle_error("socket");

           memset(&my_addr, 0, sizeof(struct sockaddr_un));
                               /* Clear structure */
           my_addr.sun_family = AF_UNIX;
           strncpy(my_addr.sun_path, MY_SOCK_PATH,
                   sizeof(my_addr.sun_path) - 1);

           if (bind(sfd, (struct sockaddr *) &my_addr,
                   sizeof(struct sockaddr_un)) == -1)
               handle_error("bind");

           if (listen(sfd, LISTEN_BACKLOG) == -1)
               handle_error("listen");

           /* Now we can accept incoming connections one
              at a time using accept(2) */

           peer_addr_size = sizeof(struct sockaddr_un);
           cfd = accept(sfd, (struct sockaddr *) &peer_addr,
                        &peer_addr_size);
           if (cfd == -1)
               handle_error("accept");

           /* Code to deal with incoming connection(s)... */

           /* When no longer required, the socket pathname, MY_SOCK_PATH
              should be deleted using unlink(2) or remove(3) */
       }

如何在linux上查看socket

代码语言:javascript复制
 ll  /proc/<pid>/fd
 
 linux默认的三个文件描述符
 0 - stdin
 1 - stdout
 2 - stderr
 
 >& 是重定向到文件描述符
 

基于socket的应用

java nio

java nio用channel代替了输入输出流, 这个channel, 就是socket

其原理是使用了linux kernel里的fcntl

代码语言:javascript复制

/* Do the file control operation described by CMD on FD.
   The remaining arguments are interpreted depending on CMD.

   This function is a cancellation point and therefore not marked with
   __THROW.  */
extern int fcntl (int __fd, int __cmd, ...);

nionio

多路复用select

复用的意思时不用每个进程/线程来操控单独的一个IO,只需一个进程/线程来操控多个IO

但是缺点是

1. 每循环一次都要传递很多fd, 调用一次select

代码语言:javascript复制
SELECT(2)                                                                       
Linux Programmer's Manual                                                                       
SELECT(2)

NAME
       select, pselect, FD_CLR, FD_ISSET, FD_SET, FD_ZERO - synchronous I/O multiplexing
       
       
#include <stdio.h>
       #include <stdlib.h>
       #include <sys/time.h>
       #include <sys/types.h>
       #include <unistd.h>

       int
       main(void)
       {
           fd_set rfds;
           struct timeval tv;
           int retval;

           /* Watch stdin (fd 0) to see when it has input. */
           FD_ZERO(&rfds);
           FD_SET(0, &rfds);

           /* Wait up to five seconds. */
           tv.tv_sec = 5;
           tv.tv_usec = 0;

           retval = select(1, &rfds, NULL, NULL, &tv);
           /* Don't rely on the value of tv now! */

           if (retval == -1)
               perror("select()");
           else if (retval)
               printf("Data is available now.n");
               /* FD_ISSET(0, &rfds) will be true. */
           else
               printf("No data within five seconds.n");

           exit(EXIT_SUCCESS);
       }

epoll模型

代码语言:javascript复制
epoll - I/O event notification facility


           #define MAX_EVENTS 10
           struct epoll_event ev, events[MAX_EVENTS];
           int listen_sock, conn_sock, nfds, epollfd;

           /* Set up listening socket, 'listen_sock' (socket(),
              bind(), listen()) */

           epollfd = epoll_create(10);
           if (epollfd == -1) {
               perror("epoll_create");
               exit(EXIT_FAILURE);
           }

           ev.events = EPOLLIN;
           ev.data.fd = listen_sock;
           if (epoll_ctl(epollfd, EPOLL_CTL_ADD, listen_sock, &ev) == -1) {
               perror("epoll_ctl: listen_sock");
               exit(EXIT_FAILURE);
           }

          for (;;) {
               nfds = epoll_wait(epollfd, events, MAX_EVENTS, -1);
               if (nfds == -1) {
                   perror("epoll_pwait");
                   exit(EXIT_FAILURE);
               }

               for (n = 0; n < nfds;   n) {
                   if (events[n].data.fd == listen_sock) {
                       conn_sock = accept(listen_sock,
                                       (struct sockaddr *) &local, &addrlen);
                       if (conn_sock == -1) {
                           perror("accept");
                           exit(EXIT_FAILURE);
                       }
                       setnonblocking(conn_sock);
                       ev.events = EPOLLIN | EPOLLET;
                       ev.data.fd = conn_sock;
                       if (epoll_ctl(epollfd, EPOLL_CTL_ADD, conn_sock,
                                   &ev) == -1) {
                           perror("epoll_ctl: conn_sock");
                           exit(EXIT_FAILURE);
                       }
                   } else {
                       do_use_fd(events[n].data.fd);
                   }
               }
           }

       When  used  as  an  edge-triggered  interface,  for  performance  reasons, it is possible to add the file descriptor inside the epoll interface (EPOLL_CTL_ADD) once by specifying
       (EPOLLIN|EPOLLOUT).  This allows you to avoid continuously switching between EPOLLIN and EPOLLOUT calling epoll_ctl(2) with EPOLL_CTL_MOD.
               
                

References

https://www.bilibili.com/video/BV1V7411F7NB?from=search&seid=7973277497050423363

未完待续.......

0 人点赞