IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 网络协议 -> unix套接字编程 -> 正文阅读

[网络协议]unix套接字编程

unix套接字编程


这是学习[1]Unix网络编程第一卷的一点笔记。

在这里插入图片描述

? 图1 基本TCP客户——服务器程序的流程图

首先启动服务器,稍后的某个时刻启动客户端,它要连接到服务器上。假设客户端给服务器发送了一个请求,服务器处理这个请求,并给客户端发回一个响应。这个过程一直持续下去,直到客户端给服务器发送一个文件结束符,并关闭客户端连接,接着服务器也关闭服务器端的连接,或结束运行或等待一个新的客户端连接。

socket函数

? 为了执行网络I/O,一个进程必须做的第一件事情就是调用socket函数,指定期望的通信协议类型。

#include <sys/socket.h>

int socket(int family, int type, int protocol); // succeed on returning non-negative descriptor
解释
AF_INETIPv4协议
AF_INET6IPv6协议
AF_LOCALUnix域协议
AF_ROUTE路由套接口
AF_KEY密钥套接口

? 表1 socket函数的协议族(family)常值

类型解释
SOCK_STREAM字节流套接口
SOCK_DGRAM数据包套接口
SOCK_RAW原始套接口

? 表2 socket函数套接口类型(type)

AF_INETAF_INET6AF_LOCALAF_ROUTEAF_KEY
SOCKET_STREAMTCPTCPYes
SOCK_DGRAMUDPUDPYes
SOCK_RAWIPv4IPv6YesYes

? 表3 socket函数的族与类型的组合

? 并非所有套接口family与type的组合都是有效的,表3给出了一些有效的组合和对应的真正协议。其中标"Yes"的项也是有效的,但还没有找到便捷的缩略词;而空白项则是不支持的。

? socket函数在成功时返回一个小的非负整数,它与文件描述符类似,我们把它称为套接字接口描述字(socket descriptor),简称套接字(sockfd)。为了得到这个套接口描述字,我们只是制定了协议族(IPv4,IPv6或Unix)和套接口类型(字节流、数据包或原始套接口)。我们并没有指定本地协议地址或远程协议地址。

connect函数

? TCP客户端用connect函数来建立一个与TCP服务器的连接

#include <sys/socket.h>

int connect(int sockfd, const struct sockaddr* servaddr, socklen_t addrlen); // succeed on returning 0 else failure on returning -1

bind函数

? 函数bind给套接口分配一个本地协议端口,对于网际协议,协议地址是32位IPv4地址或128位IPv6地址与16位的TCP或UDP端口号的组合。

#include <sys/socket.h>

int bind(int sockfd, const struct sockaddr* myaddr, socklen_t addrlen);

? 第二个参数是一个指向特定协议的地址结构的指针,第三个参数是该地址结构的长度。对于TCP,调用函数bind可以指定一个端口号,指定一个IP地址,可以两者都指定,也可以一个都不指定。

listen函数

函数listen仅被TCP服务器调用,它做两件事情:

1.当函数socket创建一个套接口时,它被假设为一个主动套接口,也就是说,它是一个将调用connect发起连接的客户套接口,函数listen将未连接的套接口转换成被动套接口,指示内核应接受指向此套接口的连接。根据TCP状态转换图,调用函数listen导致套接口从CLOSED状态转换到LISTEN状态。

2.函数的第二个参数规定了内核为此套接口排队的最大连接个数。

#include <sys/socket.h>

int listen(int sockfd, int backlog);

accept函数

? accept由TCP服务器调用,从已完成连接队列头返回下一个已完成连接。若已完成连接队列为空,则进程睡眠。

#include <sys/socket.h>

int accept(int sockfd, struct sockaddr* cliaddr, socklen_t* addrlen);

? 参数cliaddr和addrlen用来返回连接对方进程的协议地址。addrlen是值-结果参数:调用前,我们将*addrlen所指的整数值置为由cliaddr所指的套接口地址结构的长度,返回时,此整数值即为由内核存在此套接口地址结构内的准确字节数。

? 如果函数accept执行成功,则返回值时内核自动生成的一个全新描述字,代表与客户的TCP连接。当我们讨论函数accept时,常把它的第一个参数称为监听套接口(listening socket)套接字(由函数socket生成的描述字,用作函数bind和listen的第一个参数),把它的返回值称为已连接套接口(connected socket)描述字。将这两个套接口区分开是很重要的。一个给定的服务器常常是只生成一个监听套接口且一直存在,直到该服务器关闭。内核为每个被接受的客户端连接创建一个已连接口(也就是说内核已为它完成TCP的三路握手过程)。当服务器完成某客户的服务时,关闭已连接套接口。

fork和exec函数

fork是unix中派生新进程的唯一方法

#include <unistd.h>

pid_t fork(void); //返回:子进程中为0,在父进程中为子进程id,-1——出错

? fork在子进程返回0而不是父进程ID,一个原因是:子进程只有一个父进程,它总可以调用getppid来得到;而父进程有许多子进程,它没有办法来得到各子进程的ID。如果父进程想跟踪所有子进程的ID,它必须记住fork的返回值。我觉得还有一个就是fork在子进程中返回0使得子进程父进程的判断更加简单。

? fork有两个典型应用:

? 1.一个进程可以为自己创建一个拷贝,这样,当一个拷贝处理一个操作时,其他的拷贝可以执行其他的任务。这是非常典型的网络服务器。

? 2.一个进程想执行其他的程序,由于创建新进程的唯一方法是调用fork,进程首先调用fork来生成一个拷贝,然后其中一个拷贝(通常为子进程)调用exec来代替自己去执行新程序。

并发服务器

pid_t pid;
int listenfd, connfd;
listenfd = Socket(...);
// fill in sockaddr_in() with server's well-known port
Bind(listenfd, ...);
Listen(listenfd, LISTENQ);
for(;;) {
	// probably blocks
	connfd = Accept(listenfd, ...);
	if((pid==Fork())==0) {
		Close(listenfd); // child closes listening socket which is set its reference value -1
		doit(connfd);
		Close(connfd);
		exit(0);
	}
	Close(connfd);
}

每个文件或套接口都有一个访问计数,该访问计数在文件表项中维护,它表示当前指向该文件或套接口的打开的描述字个数。

Close函数

? 一般Unix函数close也用来关闭套接口,终止TCP连接。

#include <unistd.h>

int close(int sockfd); //返回:0——ok,-1——出错

? TCP套接口的close其缺省功能是将套接口做上“已关闭”标记,并立即返回到进程。这个套接口描述字不能再为进程所用:它不能用作函数read或write的参数,但TCP将试着发送已排队待发的任何数据,然后按正常的TCP连接终止序列进行操作。

getsockname和getpeername函数

? 这两个函数或返回与套接口关联的本地协议地址(getsockname),或返回与套接口关联的远程协议地址(getpeername)。

#include <sys/socket.h>

int getsockname(int sockfd, struct sockaddr* localaddr, socklen_t* addrlen);
int getpeername(int sockfd, struct sockaddr* peeraddr, socklen_t* addrlen);

一个使用TCP进行通讯的例子

例子来自[2]https://github.com/troydhanson/network

客户端

#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

char *server = "127.0.0.1";  /* loopback */
int port = 6180;             /* no significance */

int main(int argc, char *argv[]) {

  char *buf = "hello, world!";
  int buflen = strlen(buf), rc;

  /**********************************************************
   * create an IPv4/TCP socket, not yet bound to any address
   *********************************************************/
  int fd = socket(AF_INET, SOCK_STREAM, 0);
  if (fd == -1) {
    printf("socket: %s\n", strerror(errno));
    exit(-1);
  }

  /**********************************************************
   * internet socket address structure, for the remote side
   *********************************************************/
  struct sockaddr_in sin;
  sin.sin_family = AF_INET;
  sin.sin_addr.s_addr = inet_addr(server);
  sin.sin_port = htons(port);

  if (sin.sin_addr.s_addr == INADDR_NONE) {
    printf("invalid remote IP %s\n", server);
    exit(-1);
  }

  /**********************************************************
   * Perform the 3 way handshake, (c)syn, (s)ack/syn, c(ack)
   *********************************************************/
  if (connect(fd, (struct sockaddr*)&sin, sizeof(sin)) == -1) {
    printf("connect: %s\n", strerror(errno));
    exit(-1);
  }

  if ( (rc=write(fd,buf,buflen)) != buflen) {
    printf("write: %s\n", (rc<0)?strerror(errno):"incomplete");
    exit(-1);
  }
}

服务器

#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

#define BUFSZ 200
int port = 6180;             /* no significance */

int main(int argc, char *argv[]) {

  char buf[BUFSZ];
  int rc;

  /**********************************************************
   * create an IPv4/TCP socket, not yet bound to any address
   *********************************************************/
  int fd = socket(AF_INET, SOCK_STREAM, 0);
  if (fd == -1) {
    printf("socket: %s\n", strerror(errno));
    exit(-1);
  }

  /**********************************************************
   * internet socket address structure: our address and port
   *********************************************************/
  struct sockaddr_in sin;
  sin.sin_family = AF_INET;
  sin.sin_addr.s_addr = htonl(INADDR_ANY);
  sin.sin_port = htons(port);

  /**********************************************************
   * bind socket to address and port we'd like to receive on
   *********************************************************/
  if (bind(fd, (struct sockaddr*)&sin, sizeof(sin)) == -1) {
    printf("bind: %s\n", strerror(errno));
    exit(-1);
  }

  /**********************************************************
   * put socket into listening state 
   *********************************************************/
  if (listen(fd,1) == -1) {
    printf("listen: %s\n", strerror(errno));
    exit(-1);
  }

  /**********************************************************
   * accept a connection, read til it closes, repeat
   *********************************************************/
  while (1) {
    struct sockaddr_in cin;
    socklen_t cin_sz = sizeof(cin);
    int fa = accept(fd, (struct sockaddr*)&cin, &cin_sz);
    if (fa == -1) {
      printf("accept: %s\n", strerror(errno));
      continue;
    }
    if (sizeof(cin)==cin_sz) printf("connection from %s:%d\n", 
      inet_ntoa(cin.sin_addr), (int)ntohs(cin.sin_port));
    do {
      rc = read(fa,buf,BUFSZ);
      if (rc==-1) printf("read: %s\n", strerror(errno));
      else if (rc==0) printf("connection closed\n");
      else printf("received %d bytes: %.*s\n", rc, rc, buf);
    } while (rc > 0);
    close(fa);
  }
}

Unix域协议

? Unix域协议并不是一个实际的协议族,它只是在同一台主机上进行客户—服务器通信时,使用与在不同主机上的客户和服务器间通信时相同的API的一种方法。

? Unix域协议提供了两种类型的套接口:字节流套接口(与TCP类似)和数据包套接口(与UDP类似)。

为什么选择使用Unix域套接口?

? 1.在源自Berkeley的实现中,当通信双方位于同一台主机上时,Unix域套接口的速度通常是TCP套接口的两倍。

? 2.Unix域套接口可以用来在同一台主机上的各个进程之间传递描述字。

? 3.Unix域套接口的较新实现中可以向服务器提供客户的凭证(用户ID和组ID),这能提供附加的安全检查。

Unix域套接口地址结构

? Unix域套接口地址结构定义在<sys/un.h>中

struct sockaddr_un {
	uint8_t sun_len;
	sa_family_t sun_family; //AF_LOCAL
	char sun_path[104]; //null-terminated pathname
};

? sun_path数组中存放的路径名必须是以空字符结尾的,即以’’\0’结尾。未指定地址以空字符串的路径名表示,也就是一个sun_path[0]为’\0’。

例子:Unix域套接口的捆绑

下面的程序建立一个Unix域套接口,给它捆绑一个路径名,然后调用getsockname输出已绑定的路径名。

#include <sys/socket.h>
#include <sys/un.h>

int main(int argc, char* argv[]) {
	int sockfd;
	socklen_t len;
	struct sockaddr_un addr1, addr2;
	
	if(argc != 2) {
		perror("usage: unixbind <pathname>");
		exit(1);
	}
	
	sockfd = Socket(AF_LOCAL, SOCK_STREAM, 0);
	unlink(argv[1]);
	bzero(&addr1, sizeof(addr1));
	addr1.sun_family = AF_LOCAL;
	strncpy(addr1.sun_path, argv[1], sizeof(addr1.sun_path)-1); // 
	Bind(sockfd, (SA*)&addr1, SUN_LEN(&addr1));
	
	len = sizeof(addr2);
	Getsockname(sockfd, (SA*)&addr2, &len);
	printf("bound name = %s, returned len = %d\n", addr2.sun_path, len);
	exit(0);
}

? unlink(argv[1])的作用是如果该路径名在系统中已存在,调用unlink将其删除。如果路径名不存在,unlink将会返回错误,程序将其忽略。

socketpair函数

? socketpair函数建立一对相互连接的套接口,这个函数只对Unix域套接口适用。

#include <sys/socket.h>

int socketpair(int family, int type, int protocol, int sockfd[2]); //成功返回0,出错返回1

? family必须为AF_LOCAL,protocol必须为0,type可以是SOCK_STREAM或SOCK_DGRAM。新创建的两个套接口描述字作为sockfd[0]和sockfd[1]返回。

? 创建的两个套接口是没有名字的,即没有涉及隐式bind。

? 以SOCKET_STREAM作为type调用socketpair所得到的结果称作流管道(stream pipe)。这和一般的Unix管道(由pipe函数生成)类似,但流管道是全双工的,即两个描述字都是可读写的。

套接口函数

? 当用于Unix套接口时,套接口函数有一些差别和限制:

  1. bind建立的路径名的缺省访问权限应为777,并被当前的umask修改。
  2. 与Unix域套接口相关联的路径名应为一个绝对路径名,而不是相对路径名。
  3. connect使用的路径名必须是一个绑定在某个已打开的Unix域套接口上的路径名,而且套接口的类型(字节流或数据包)也必须一致。下列情况将会出错:(a)该路径名存在但不是一个套接口;(b)路径名存在且是一个套接口,但没有与该路径名相关联的打开的描述字;?路径名存在且是一个打开的套接口,但类型不符——Unix域字节流套接口不能连到与Unix域数据包套接口相关联的路径名,反之亦然。
  4. 用connect连接Unix域套接口时权限检查和用open以只写方式访问路径名时完全相同。
  5. Unix域字节流套接口和TCP套接口类似:它们都为进程提供一个没有记录边界的字节流套接口。
  6. 如果Unix域字节流套接口的connect调用发现监听套接口的队列已满,会立刻返回一个ECONNECTREFUSED错误。这和TCP连接有所不同:如果监听套接口的队列已满,它将忽略到来的SYN,TCP连接的发起方会接着发送几次SYN重试。
  7. Unix域数据包套接口和UDP套接口类似:它们都提供一个保留记录边界的不可靠的数据包服务。
  8. 与UDP套接口不同的是,在未绑定的Unix域套接口上发送数据报不会给它捆绑一个路径名(回想在未绑定的UDP套接口上发送UDP数据包会为该套接口绑定一个临时端口)。这意味着,数据报的发送者除非绑定一个路径名,否者接收者无法发回应答数据报。同样,与TCP和UDP不同的是,给Unix域数据包套接口调用connect不会捆绑一个路径名。

例子Unix域字节流客户——服务器程序

本例同样来自[2]https://github.com/troydhanson/network

客户端

#include <sys/socket.h>
#include <sys/un.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

//char *socket_path = "./socket";
char *socket_path = "\0hidden";

int main(int argc, char *argv[]) {
  struct sockaddr_un addr;
  char buf[100];
  int fd,rc;

  if (argc > 1) socket_path=argv[1];

  if ( (fd = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) {
    perror("socket error");
    exit(-1);
  }

  memset(&addr, 0, sizeof(addr));
  addr.sun_family = AF_UNIX;
  if (*socket_path == '\0') {
    *addr.sun_path = '\0';
    strncpy(addr.sun_path+1, socket_path+1, sizeof(addr.sun_path)-2);
  } else {
    strncpy(addr.sun_path, socket_path, sizeof(addr.sun_path)-1);
  }

  if (connect(fd, (struct sockaddr*)&addr, sizeof(addr)) == -1) {
    perror("connect error");
    exit(-1);
  }

  while( (rc=read(STDIN_FILENO, buf, sizeof(buf))) > 0) {
    if (write(fd, buf, rc) != rc) {
      if (rc > 0) fprintf(stderr,"partial write");
      else {
        perror("write error");
        exit(-1);
      }
    }
  }

  return 0;
}

服务器

#include <stdio.h>
#include <unistd.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <stdlib.h>

//char *socket_path = "./socket";
char *socket_path = "\0hidden";

int main(int argc, char *argv[]) {
  struct sockaddr_un addr;
  char buf[100];
  int fd,cl,rc;

  if (argc > 1) socket_path=argv[1];

  if ( (fd = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) {
    perror("socket error");
    exit(-1);
  }

  memset(&addr, 0, sizeof(addr));
  addr.sun_family = AF_UNIX;
  if (*socket_path == '\0') {
    *addr.sun_path = '\0';
    strncpy(addr.sun_path+1, socket_path+1, sizeof(addr.sun_path)-2);
  } else {
    strncpy(addr.sun_path, socket_path, sizeof(addr.sun_path)-1);
    unlink(socket_path);
  }

  if (bind(fd, (struct sockaddr*)&addr, sizeof(addr)) == -1) {
    perror("bind error");
    exit(-1);
  }

  if (listen(fd, 5) == -1) {
    perror("listen error");
    exit(-1);
  }

  while (1) {
    if ( (cl = accept(fd, NULL, NULL)) == -1) {
      perror("accept error");
      continue;
    }

    while ( (rc=read(cl,buf,sizeof(buf))) > 0) {
      printf("read %u bytes: %.*s\n", rc, rc, buf);
    }
    if (rc == -1) {
      perror("read");
      exit(-1);
    }
    else if (rc == 0) {
      printf("EOF\n");
      close(cl);
    }
  }


  return 0;
}

在Golang中使用Unix域套接字

? go的net包提供了对Unix域套接字的支持,涉及的结构体和方法如下

type UnixAddr
	func ResolveUnixAddr(network, address string) (*UnixAddr, error)
	func (a *UnixAddr) Network() string
	func (a *UnixAddr) String() string
type UnixConn
	func DialUnix(network string, laddr, raddr *UnixAddr) (*UnixConn, error)
	func ListenUnixgram(network string, laddr *UnixAddr) (*UnixConn, error)
	func (c *UnixConn) Close() error
	func (c *UnixConn) CloseRead() error
	func (c *UnixConn) CloseWrite() error
	func (c *UnixConn) File() (f *os.File, err error)
	func (c *UnixConn) LocalAddr() Addr
	func (c *UnixConn) Read(b []byte) (int, error)
	func (c *UnixConn) ReadFrom(b []byte) (int, Addr, error)
	func (c *UnixConn) ReadFromUnix(b []byte) (int, *UnixAddr, error)
	func (c *UnixConn) ReadMsgUnix(b, oob []byte) (n, oobn, flags int, addr *UnixAddr, err error)
	func (c *UnixConn) RemoteAddr() Addr
	func (c *UnixConn) SetDeadline(t time.Time) error
	func (c *UnixConn) SetReadBuffer(bytes int) error
	func (c *UnixConn) SetReadDeadline(t time.Time) error
	func (c *UnixConn) SetWriteBuffer(bytes int) error
	func (c *UnixConn) SetWriteDeadline(t time.Time) error
	func (c *UnixConn) SyscallConn() (syscall.RawConn, error)
	func (c *UnixConn) Write(b []byte) (int, error)
	func (c *UnixConn) WriteMsgUnix(b, oob []byte, addr *UnixAddr) (n, oobn int, err error)
	func (c *UnixConn) WriteTo(b []byte, addr Addr) (int, error)
	func (c *UnixConn) WriteToUnix(b []byte, addr *UnixAddr) (int, error)
type UnixListener
	func ListenUnix(network string, laddr *UnixAddr) (*UnixListener, error)
	func (l *UnixListener) Accept() (Conn, error)
	func (l *UnixListener) AcceptUnix() (*UnixConn, error)
	func (l *UnixListener) Addr() Addr
	func (l *UnixListener) Close() error
	func (l *UnixListener) File() (f *os.File, err error)
	func (l *UnixListener) SetDeadline(t time.Time) error
	func (l *UnixListener) SetUnlinkOnClose(unlink bool)
	func (l *UnixListener) SyscallConn() (syscall.RawConn, error)

UnixAddr

? UnixAddr用于存放unix域套接字。

type UnixAddr struct {
	Name string
	Net  string
}

? Net可以是"unix", “unixgram"和"unixpacket”。unix类似于TCP,unixgram类似于udp,unixpacket类似于可以保证数据包顺序的udp[5]。

ResovleUnixAddr

ResolveUnixAddr根据给出的网络类型和地址,创建一个UnixAddr对象。

func ResolveUnixAddr(network, address string) (*UnixAddr, error)

UnixConn

UnixConn是Conn接口的Unix域套接字实现。

type UnixConn struct {
	// contains filtered or unexported fields
}

DialUnix

DialUnix连接到raddr的Unix域套接字。

func DialUnix(network string, laddr, raddr *UnixAddr) (*UnixConn, error)

Close

关闭Unix域套接字连接

func (c *UnixConn) Close() error

UnixListener

UnixListener是一个unix域套接字的监听者。

type UnixListener struct {
	// contains filtered or unexported fields
}

ListenUnix

类似于unix套接字的listen。其中network必须是"unix"或者"unixpacket"。

func ListenUnix(network string, laddr *UnixAddr) (*UnixListener, error)

Accept

accept是Listen接口的unix域套接字实现。返回的conn是*UnixConn类型的。

func (l *UnixListener) Accept() (Conn, error)

Close

Close停止在Unix域地址上的监听。已经建立的连接不会被关闭。

func (l *UnixListener) Close() error

? 在go中使用unix域套接口编程涉及的结构体就是UnixConn和UnixListener,其中客户端只需要使用UnixConn,服务器则需要使用UnixListener接受连接,并利用Accept方法返回的UnixConn处理和某个客户端的连接,前者是监听套接字,后者是已连接套接字。

Golang的Unix域字节流客户——服务器程序

本例子来自[7]https://gist.github.com/hakobe/6f70d69b8c5243117787fd488ae7fbf2

客户端client.go

package main

import (
	"io"
	"log"
	"net"
	"time"
)

func reader(r io.Reader) {
	buf := make([]byte, 1024)
	for {
		n, err := r.Read(buf[:])
		if err != nil {
			return
		}
		println("Client got:", string(buf[0:n]))
	}
}

func main() {
	c, err := net.Dial("unix", "/tmp/go.sock")
	if err != nil {
		log.Fatal("Dial error", err)
	}
	defer c.Close()

	go reader(c)
	for {
		msg := "hi"
		_, err := c.Write([]byte(msg))
		if err != nil {
			log.Fatal("Write error:", err)
			break
		}
		println("Client sent:", msg)
		time.Sleep(1 * time.Second)
	}
}

服务器server.go

package main

import (
	"log"
	"net"
	"os"
	"os/signal"
	"syscall"
)

func echoServer(c net.Conn) {
	for {
		buf := make([]byte, 512)
		nr, err := c.Read(buf)
		if err != nil {
			return
		}

		data := buf[0:nr]
		println("Server got:", string(data))
		_, err = c.Write(data)
		if err != nil {
			log.Fatal("Writing client error: ", err)
		}
	}
}

func main() {
	log.Println("Starting echo server")
	ln, err := net.Listen("unix", "/tmp/go.sock")
	if err != nil {
		log.Fatal("Listen error: ", err)
	}

	sigc := make(chan os.Signal, 1)
	signal.Notify(sigc, os.Interrupt, syscall.SIGTERM)
	go func(ln net.Listener, c chan os.Signal) {
		sig := <-c
		log.Printf("Caught signal %s: shutting down.", sig)
		ln.Close()
		os.Exit(0)
	}(ln, sigc)

	for {
		fd, err := ln.Accept()
		if err != nil {
			log.Fatal("Accept error: ", err)
		}

		go echoServer(fd)
	}
}

总结

? 所有的客户和服务器都从调用socket开始,返回一个套接口描述字。然后客户调用connect,服务器调用bind、listen和accept。套接口一般由标准的close函数关闭,当然也可以用shutdown函数关闭。

? 客户和服务器在同一台主机上时,Unix域套接口是IPC的一种替代方法。和IPC相比使用Unix域套接字的好处在于,对于网络客户——服务器的API几乎是相同的。当客户和服务器在同一台主机上时,Unix域套接字和TCP相比,其性能比在多数实现中要更高。

参考资料:

[1].Unix网络编程第一卷.W.Richard Stevens著. 清华大学出版社

[2].https://github.com/troydhanson/network

[3].https://linux.die.net/man/2/

[4].The Linux socket API explained.https://www.youtube.com/watch?v=XXfdzwEsxFk

[5].https://stackoverflow.com/questions/22955226/unix-domain-socket-name-in-go-language

[6].go net package. https://pkg.go.dev/net@go1.16.6

[7].https://gist.github.com/hakobe/6f70d69b8c5243117787fd488ae7fbf2

  网络协议 最新文章
使用Easyswoole 搭建简单的Websoket服务
常见的数据通信方式有哪些?
Openssl 1024bit RSA算法---公私钥获取和处
HTTPS协议的密钥交换流程
《小白WEB安全入门》03. 漏洞篇
HttpRunner4.x 安装与使用
2021-07-04
手写RPC学习笔记
K8S高可用版本部署
mySQL计算IP地址范围
上一篇文章      下一篇文章      查看所有文章
加:2021-07-14 23:16:55  更:2021-07-14 23:18:02 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/29 9:52:49-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码