Golang Network: Core API implementation profiling (i)

Source: Internet
Author: User
Tags epoll
This is a creation in Article, where the information may have evolved or changed.

In this chapter, we will describe the implementation of the network key API, including Listen, Accept, Read, write and so on. In addition, to highlight critical processes, we choose to ignore all errors. This will make the code look simpler. And we are only concerned with TCP protocol implementations, and UDP and UNIX sockets are not our concern.

Listen

Func Listen (NET, laddr string) (Listener, error) {LA, err: = Resolveaddr ("Listen", net, laddr, Nodeadline) ... s Witch LA: = La.toaddr (). (type)  {case *tcpaddr:l, err = listentcp (NET, LA) case *unixaddr: ...} ......} For the TCP protocol, the returned Tcplistenerfunc listentcp (net string, Laddr *tcpaddr) (*tcplistener, error) {... fd, err: = Intern Etsocket (NET, laddr, nil, Nodeadline, Syscall. Sock_stream, 0, "listen") ... return &tcplistener{fd}, Nil}func internetsocket (net string, laddr, Raddr Sockadd R, Deadline time. Time, Sotype, proto int, mode string) (FD *netfd, err error) {... return socket (NET, family, Sotype, Proto, Ipv6on Ly, Laddr, RADDR, Deadline)}func sockets (net string, family, sotype, Proto int, ipv6only bool, laddr, raddr sockaddr, Deadl Ine time. Time) (FD *netfd, err Error) {//create the underlying socket, set the property to O_nonblock s, err: = Syssocket (family, Sotype, proto) ... set Defaultsockopts (S, family, Sotype, ipv6only)//Create new NETFD structure FD, ERR = NEWFD (s, family, sotype, net) ... if laddr! = Nil && raddr = nil {switch Sotype {case sy Scall. Sock_stream, Syscall.        Sock_seqpacket://Call the underlying listen listener created socket Fd.listenstream (LADDR, Listenerbacklog) return FD, nil Case Syscall.   Sock_dgram: ...} }//finally calls the function to create a socket//and sets the socket property to O_nonblockfunc syssocket (family, sotype, Proto int) (int, error) {Syscall. Forklock.rlock () s, err: = Syscall. Socket (family, Sotype, proto) if Err = = Nil {syscall. Closeonexec (s)} syscall. Forklock.runlock () if err! = Nil {return-1, err} If Err = Syscall. Setnonblock (S, true); Err! = Nil {syscall. Close (s) return-1, err} return S, Nil}func (fd *netfd) listenstream (laddr sockaddr, backlog int) error {if ERR: = setdefaultlistenersockopts (FD.SYSFD) If LSA, err: = Laddr.sockaddr (fd.family);    Err! = Nil {return err} else if LSA! = Nil {//bind bind to the socket   If err: = Syscall. Bind (FD.SYSFD, LSA); Err! = Nil {return OS. Newsyscallerror ("bind", Err)}//Listen for the socket if err: = Syscall.    Listen (FD.SYSFD, backlog); Here is the key: Initialize the socket with asynchronous IO-related content if err: = Fd.init (); Err! = Nil {return ERR} LSA, _: = Syscall. GetSockName (FD.SYSFD) fd.setaddr (Fd.addrfunc () (LSA), nil) return nil}

We see here how to implement the listen. The process is basically simple, but because we use asynchronous programming, we have to add it to the listening queue after listen the socket, and we'll be able to notify you when the socket has an event coming up.

Knowledge of Linux should know Epoll, yes Golang use epoll mechanism to implement the socket event notification. So let's look at a listening socket, how do we add it to the Epoll listening queue?

func (fd *netFD) init() error {   if err := fd.pd.Init(fd); err != nil {       return err   }   return nil}func (pd *pollDesc) Init(fd *netFD) error {   // 利用了Once机制,保证一个进程只会执行一次   // runtime_pollServerInit:    // TEXT net·runtime_pollServerInit(SB),NOSPLIT,$0-0   // JMP runtime·netpollServerInit(SB)   serverInit.Do(runtime_pollServerInit)   // runtime_pollOpen:   // TEXT net·runtime_pollOpen(SB),NOSPLIT,$0-0   // JMP runtime·netpollOpen(SB)   ctx, errno := runtime_pollOpen(uintptr(fd.sysfd))   if errno != 0 {       return syscall.Errno(errno)   }   pd.runtimeCtx = ctx   return nil}

Here is the key to socket asynchronous programming:

Netpollserverinit () initializes the asynchronous programming structure, for Epoll, the function is netpollinit, and the once mechanism is used to ensure that a process is initialized only once;

func netpollinit() {    epfd = epollcreate1(_EPOLL_CLOEXEC)    if epfd >= 0 {        return    }    epfd = epollcreate(1024)    if epfd >= 0 {        closeonexec(epfd)        return    }    ......}

Netpollopen is added to the Epoll queue after the socket is created, and for Epoll, the function is instantiated as Netpollopen.

func netpollopen(fd uintptr, pd *pollDesc) int32 {   var ev epollevent   ev.events = _EPOLLIN | _EPOLLOUT | _EPOLLRDHUP | _EPOLLET   *(**pollDesc)(unsafe.Pointer(&ev.data)) = pd   return -epollctl(epfd, _EPOLL_CTL_ADD, int32(fd), &ev)}

OK, see here, we also understand that when listening to a socket is nothing more than the traditional socket asynchronous programming, and then add the socket to the Epoll event listening queue.

Accept

Since we describe the focus of the TCP protocol, let's look at how TcpListener's Accept method is implemented:

  Func (L *tcplistener) Accept () (Conn, error) {c, err: = L.accepttcp () ...} Func (L *tcplistener) accepttcp () (*tcpconn, error) {... fd, err: = L.fd.accept () ...//return to caller a new TCPC    Onn return Newtcpconn (FD), Nil}func (FD *netfd) Accept () (Netfd *netfd, err Error) {//Why read the lock on the function? If err: = Fd.readlock ();        Err! = Nil {return nil, err} defer Fd.readunlock () ... for {//This accept is a Golang wrapper system call Used to handle cross-platform s, RSA, err = Accept (FD.SYSFD) if err! = Nil {if Err = = Syscall.                Eagain {//If there are no available connections, waitread () blocks the coprocessor//followed by a detailed analysis of Waitread. If Err = Fd.pd.WaitRead (); Err = = Nil {Continue}} else if Err = = Syscall.    econnaborted {//If the connection has been closed to the Listen queue continue}} break } netfd, err = NEWFD (S, fd.family, Fd.sotype, fd.net) ...//This is already parsed before adding the FD to theEpoll in the queue err = Netfd.init () ... LSA, _: = Syscall. GetSockName (NETFD.SYSFD) netfd.setaddr (Netfd.addrfunc () (LSA), Netfd.addrfunc () (RSA)) return NETFD, nil}

OK, from the previous programming case we know that generally in the main process will accept the new connection, using asynchronous programming we know that if there is no new connection, the process will be blocked until the new connection arrives and someone wakes up the process.

Normally call accept in the main process, if the return value is Eagain, then call Waitread to block the current coprocessor, subsequent wake when the socket has an event, Waitread and wake-up process we will carefully analyze later.

Read

Func (c *conn) Read (b []byte) (int, error) {if!c.ok () {return 0, Syscall. EINVAL} return C.fd.read (b)}func (FD *netfd) read (p []byte) (n int, err error) {//Why read lock on function call if err: = FD. Readlock ();    Err! = Nil {return 0, err} defer Fd.readunlock ()//What is this again? If err: = Fd.pd.PrepareRead (); Err! = Nil {return 0, &operror{"read", Fd.net, Fd.raddr, err}} for {n, err = Syscall. Read (int (FD.SYSFD), p) if err! = Nil {n = 0//If the Eagin is returned, blocking the current thread until there is data readable wake if err = = Syscall.        Eagain {If Err = Fd.pd.WaitRead (); err = = nil {Continue}} }//Check for errors, encapsulate IO. EOF err = Chkreaderr (n, err, FD) break} if err! = Nil && Err! = Io.  EOF {err = &operror{"read", Fd.net, Fd.raddr, err}} return}func chkreaderr (n int, err error, FD *NETFD) Error {if n = = 0 && Err = = Nil && Fd.sotype! = Syscall. Sock_dgram && Fd.sotype! = syscall. Sock_raw {return IO. EOF} return Err}

The read process is extremely consistent with the accept process and is easy to read. Believe that you don't have to explain too much, see for yourself. It is important to note that every time that read does not guarantee that you can read as much content as you want to read, such as a buffer size of 10, and actually read only 5, the application needs to be able to handle this situation.

Write

Func (fd *netfd) write (P []byte) (nn int, err error) {//Why write lock here if err: = Fd.writelock (); Err! = Nil {ret    Urn 0, err} defer Fd.writeunlock ()//What is this? If err: = Fd.pd.PrepareWrite (); Err! = Nil {return 0, &operror{"write", Fd.net, Fd.raddr, err}}//NN record the total amount of data written, each write may write only part of the data fo R {var n int n, err = Syscall. Write (int (FD.SYSFD), P[nn:]) if n > 0 {nn + = n}//If the array data has all been written, the function returns if NN = = Len (P) {break}//If the data is written by block, block the current coprocessor if err = = Syscall. Eagain {If Err = Fd.pd.WaitWrite (); err = = nil {Continue}} if Err!        = Nil {n = 0 break}//If the return value is 0, what does it mean? if n = = 0 {err = io. Errunexpectedeof Break}} if Err! = Nil {err = &operror{"Write", Fd.net, Fd.raddr, er R}} return nn, err}

Note that the write semantics are not the same as read:

Write
as far as possible to write the contents of the user buffer to the underlying socket, if the socket is temporarily not writable, it will block the current process, read on a successful read immediately return, may cause the amount of data read less than the size of the user buffer; , I think the priority of read may be higher, the application may have been waiting, we can not wait until the data has been read before returning, will block the user. And the write is not the same, the priority is relatively low, and the user is generally not anxious to write an immediate return, so you can write all the data, and this can also simplify the application of the wording.

Summarize

We've basically finished talking about the key API flow in Golang network programming, we left behind a key element: When the system call returns Eagain, it calls Waitread/waitwrite to block the current process, and I'll continue with the analysis in the next section.

Related Article

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.