Go: Ввдение в сокеты межпроцессного взаимодействия
UnixСокеты domainмежпроцессного socketsвзаимодействия offerпредлагают efficient,эффективное,безопасное secure,двухсторонее bidirectionalподключение communicationмежду betweenпроцессами processes on aна Unix/Linux machine.машинах. WhileВ channelsто areвремя greatкак forканалы communicatingотлично acrossиспользуются theдля goroutinesподключения ofмежду anгорутинами application,приложения, andи HTTP isвездесущь, ubiquitous,при whenподключении communicating betweenмежду Go applicationsприложениями(межпроцессороное (interprocessвзаимодействие) communication)запущенные runningна onтой theже sameмашине machineканалы channelsне areпомогают, noа help,подключление andк Unixсокету socketмежпроцессного communicationвзаимодействия isгораздо simpler,проще, moreэффективнее, efficient,и andболее moreбезопасно secure thanчем HTTP orили otherдругие internetинтернет protocolпротоколы communication.подключений.
Go’sВсё что вам нужно это только пакет net
isдля theзапуска only package you need to start communicating:подключения:
- Пакет
Packagenetnetпредоставляетprovidesперносимыйaинтерфейсportableдляinterface for networkсети I/O,including…Unix domain sockets. Although the package provides access to low-level networking primitives, most clients will need only the basic interface provided by the Dial, Listen, and Accept functions and the associated Conn and Listener interfaces.Unfortunately those functions and interfaces are sparsely documented (with regard toвключая Unixdomainсокеты. - Так
inжеparticular)пакет предоставляет доступ к низкоуровневым примитивам сети,andбольшинствуthere’sклиентовnoтребуедсятofficialтолько базовый интерфейс п редоставляемыйDial
,Listen
иAccept
функциями и связанныеConn
иListener
интферфейсы.
К сожалению эти фукнции и интерфейсы редко документированны(в частности сокеты межпроцессного взаимодействия), так же нет официального Go Blogблога postо onтом, howкак toс getчего startedначать withпри Unixработе socketс communication.сокетами. ThereПохоже seemsчто toнедостаток beхорошего aввдения dearth of good introductions onна StackOverflow andи Go blogs.блогах. MostБольшинство articlesстатей onо Unixсокетах sockets demonstrateпоказывают C implementations;реализацию; hereгде I’llя focusсосредоточусь onна howтом toкак getначать startedРаботать inс Go.
First,Первое, aсокерт Unixпредставлен socketспециальным isфалом. representedВаш asсервер aслушает specialчерез file.файл, Yourпринимае serverподключения listensи onчитает theданные file,через acceptsэто connections,подключение. andВаш readsклиент dataиспользует fromфайл, thoseчтобы connections.создать Yourподключение clientи dialsзатем theпишет fileданные toв createэто aсамое connection and then writes data to the connection.подключение.
YouВы mightвозможно thinkдумаете, youчто needвам toнужно createсоздать thisэтот specialспециальный fileфайл usingиспользуя theпакет os
. package.В Thereнем you’llвы findможете aнайти постоянную FileMode
, constant,ModeSocket
, ModeSocket,которая thatможет mayвзывать callк outвам. toНо you.это Butне thisпоможет: isНезадокументированный noфункцонионал help:функции AnListen
undocumentedзаключается featureв ofтом, the Listen function is that it will – it must – create the file for you, and it will exit with a potentially misleading errorчто (это должно) создать файл для вас, и он будет существовать с ошибкой вводящей в заблуждение: “bind: address already in use”
, )ifесли theфайл fileуже alreadyсуществует. exists.Отсюда Thereforeдла yourначала, first,нелогичный counterintuitiveшаг stepв inсоздании creatingсервера aэто serverудаление isфайла toкоторый deleteвы theсобираетесь fileслушать, youи wishтолько toпомто listenего to,можно and then start listening to it:слушать:
os.Remove(cfg.SocketFile)
listener, err := net.Listen("unix", cfg.SocketFile)
if err != nil {
log.Fatalf("Unable to listen on socket file %s: %s", cfg.SocketFile, err)
}
defer listener.Close()
ForДля aсервера serverкоторый toуправляет operateпостоянно indefinitelyи andобрабатывает handleмножество multipleпохожих simultaneousподключений, connections,вы you’llзахотите wantиспользовать anбесконечный infiniteцикл, loopв inкотором whichвы youпримете acceptподключение aв connectionlistener
onи theначнете listener,новую andгорутину startдля aего new goroutine to handle it:обработки:
for {
conn, err := listener.Accept()
if err != nil {
log.Fatalf("Error on accept: %s", err)
}
go message.HandleConn(conn)
}
YourВаш handlerобработчик shouldдолжен createсоздать aбуффер bufferлюбого ofжелаемого whateverразмера, sizeи youпрочитать like,в andнего readбесконечный intoцыкл theкоторый bufferостановится inкогда anRead
infiniteвыдаст loopошибку. thatRead
willедва stopдокументирован whenв Readnet
errors.пакете. ReadСтранным isобразом, barelyвы documentedдолжны inзнать theчто netдальше package.нужно Strangely,смотреть youв mustдокументацию knowio
toпакета lookдля toReader
the io package documentation for the Reader interface:интерфейса:
type Reader interface {
Read(p []byte) (n int, err error)
}
…to... learnчтобы importantузнать informationважную aboutинформацию howпро поведение Read
behavesв inдругих otherпакетах packagesстандартной of the standard library:библиотеки:
-
Reader
is-theинтерфейсinterfaceкоторыйthatоборачиваетwrapsбазовыйtheметодbasicRead
-
Read
method.читаетRead reads up tolen(p)
bytesбайтовintoвp.p
.ItОнreturnsвозвращяетtheколичествоnumberпрочитанныхof bytes readбайтов(0andвanyтомerrorчислеencountered.любуюEvenошибку.ifДаже еслиRead
returnsвернет n < len(p),itонmayможетuseиспользоватьallвсёofизp
asкакscratchиспорченноеspaceпространствоduringвtheвремяcall.вызова.IfЕслиsomeкакие-тоdataданныеisдоступны,availableноbutдлиннойnotне равнойlen(p)
bytes,байт,Read
conventionallyвернетьreturnsтоwhatчтоisдоступно,availableвместоinsteadтого,ofчтобыwaitingчего-тоforждать. - Когда
WhenRead
ReadвстречаетencountersошибкуanилиerrorEOF
orусловниеend-of-fileпослеconditionуспешногоafter successfully readingчтения n > 0bytes,байтов,itонreturnsвозвращаетtheколичествоnumberпрочитанныхofбайтов.bytesМожетread.вернутьItnon-nil
mayошибкуreturnизtheтого(non-nil)жеerrorвызоваfromилиtheвернутьsameошибкуcall or return the error (andn ==
0)0fromизaподпоследовательностиsubsequentвызовов.call.ЭкземплярAnданногоinstanceконкретногоofслучаяthisэтоgeneralтоcaseчтоisReader
thatвозвращаетa Reader returning anon-zero
numberчислоofбайтовbytesвatконцеtheпотокаendввода,ofможетtheтакinputжеstreamвернутьmayилиreturn eithererr == EOF
orилиerr ==
.nil.nilTheСледующийnextRead
должен вернуть0
,EOF
. - Функции должны всегда обрабатывать
n>0
байтов возвращенные перед учитываением ошибкиerr
. Выполнение этого условия обрабатываетI/O
ошибки, которые случаются после чтения неких байтов и так же обоим позволеноEOF
поведение. - Реализация Read
shouldобескураживаетreturnот0,возвращенияEOF.0
Callersбайтовshouldсalwaysnil
processошибкой,theзаnисключением>когда0 bytes returned before considering the error err. Doing so correctly handles I/O errors that happen after reading some bytes and also both of the allowed EOF behaviors. Implementations of Read are discouraged from returning a zero byte count with a nil error, except whenlen(p) ==
.0.0CallersФункцияshouldдолжнаtreatотнестисьaкreturnвозвратуof0
0илиandnil
,nilкакasкindicatingиндикаторуthatчтоnothingничегhappened;неinслучилось,particularвitчастностиdoesэтоnotнеindicateговоритEOF.обImplementationsEOF
(концеmustфайла) - Реализация
retainнеp:должна удерживатьp
:
As cryptically stated above, the buffer you pass to Read in the form of a byte slice must have a length greater than zero for anything to be read into it. This is consistent with passing a slice rather than a pointer to a slice, because any increase of the length of the slice inside Read would not be visible in the calling context without use a of a pointer. A relatively common bug in using Read is to pass it a buffer of length zero.
Another common bug is to ignore the admonishment above to process the bytes returned prior to handling errors. This contrasts with common error handling advice in the majority of programming contexts in Go, and is important to correct implementation of net-based communication in general.
Here is an example handler that addresses those concerns. It reads into a buffer with length greater than zero inside an infinite loop, breaking only on error. After each Read, the first count bytes of the buffer are consumed prior to error handling:
func HandleConn(c net.Conn) {
received := make([]byte, 0)
for {
buf := make([]byte, 512)
count, err := c.Read(buf)
received = append(received, buf[:count]...)
if err != nil {
ProcessMessage(received)
if err != io.EOF {
log.Errorf("Error on read: %s", err)
}
break
}
}
}
By this method, all data sent on a connection is understood by the server as being one message. The client should close the connection to signal the end of the message. Reading the closed connection will return error io.EOF, which is routine and does not need to be handled as an error. It simply signifies that the message is ended, often a cue to begin processing the message now that it is complete.
What goes in ProcessMessage is, of course, dependent on your application. Since a string is basically a read-only slice of bytes, it is little effort to communicate text data in this way. But byte slices are also common currency in the Go standard library, and any data can be encoded as a slice of bytes.
All that’s left now is to make a client. The client is simply a function that dials up the socket file to create a connection, defers closing the connection, and writes the message bytes to the connection. One does not have to worry about what size the message is, it can be arbitrarily large, the code does not change. Here is an example with logrus-style error logging:
type Sender struct {
Logger *log.Logger
SocketFile string
}
func (s *Sender) SendMessage(message []byte) {
c, err := net.Dial("unix", s.SocketFile)
if err != nil {
s.Logger.Errorf("Failed to dial: %s", err)
}
defer c.Close()
count, err := c.Write(message)
if err != nil {
s.Logger.Errorf("Write error: %s", err)
}
s.Logger.Infof("Wrote %d bytes", count)
}
A next step could be to add responses to acknowledge receipts. For many applications, the above is all that is needed to start communicating between Go processes using Unix domain sockets.