伯克利套接字(BSD Socket)

伯克利套接字
----------维基百科,自由的百科全书
 

伯克利套接字Berkeley sockets),也称为BSD Socket。伯克利套接字的应用编程接口API)是采用C语言进程间通信,经常用在计算机网络间的通信。 BSD Socket的应用编程接口已经是网络套接字的事实上的抽象标准。大多数其他程序语言使用一种相似的编程接口。

BSD Socket作为一种API,允许不同主机或者同一个计算机上的不同进程之间的通信。它支持多种I/O设备和驱动,但是具体的实现是依赖操作系统的。这种接口对于TCP/IP是必不可少的,所以是互联网的基础技术之一。它最初是由加州伯克利大学为Unix系统开发出来的。所有现代的操作系统都都实现了伯克利套接字接口,因为它已经是连接互联网的标准接口了。

目录

使用伯克利套接字的系统

由于伯克利套接字是第一个socket,大多数程序员很熟悉它们,所以大量系统把伯克利套接字作为其主要的网络API。 一个不完整的列表如下:

  • Windows Sockets (Winsock) ,和Berkeley Sockets很相似,最初是为了便于移植Unix程序。
  • Java Sockets
  • Python sockets
  •  头文件

主要的头文件如下,不同的系统可能具体不同。

<sys/socket.h> BSD socket 核心函数和数据结构。

<netinet/in.h> AF_INET 和AF_INET6 地址家族和他们对应的协议家族 PF_INET 和 PF_INET6。在互联网编程中广泛使用,包括IP地址以及TCP和UDP端口号。

<sys/un.h> PF_UNIX/PF_LOCAL 地址家族。用于运行在一台计算机上的程序间的本地通信,不用在网络中。

<arpa/inet.h> 和IP地址相关的一些函数。

<netdb.h> 把协议名和主机名转化成数字的一些函数。

 API函数

这些是伯克利套接字提供的库函数。

  • socket() 创造某种类型的套接字,分配一些系统资源,用返回的整数识别。
  • bind() 一般是用在服务器这边,和一个套接字地址结构相连,比如说是一个特定的本地端口号和一个IP地址。
  • listen()用在服务器一边,导致一个绑定的TCP套接字进入监听状态。
  • connect() 用在客户机这边,给套接字分配一个空闲的端口号。比如说一个TCP套接字,它会试图建立一个新的TCP连接。
  • accept() 用在服务器这边。从客户机那接受请求试图创造一个新的TCP连接,并把一个套接字和这个连接相联系起来。
  • send() and recv(), or write() and read(), or sendto() and recvfrom()用来接收和发送数据。
  • close() 关闭连接,系统释放资源。
  • gethostbyname() and gethostbyaddr()用来解析主机名和地址。
  • select() is used to prune a provided list of sockets for those that are ready to read, ready to write, or that have errors.
  • poll() is used to check on the state of a socket in a set of sockets. The set can be tested to see if any socket can be written to, read from or if an error occurred.
  • getsockopt() is used to retrieve the current value of a particular socket option for the specified socket.
  • setsockopt() is used to set a particular socket option for the specified socket.

更多的细节在下面。

socket()

socket() 为通信创造一个端点并返回一个文件描述符。 socket() 由三个参数:

  • domain, 确定协议族。例如:
    • PF_INETIPv4 或者
    • PF_INET6IPv6.
    • PF_UNIX 是本地(用一个文件).
  • type, 是下面中的一个:
    • SOCK_STREAM (可靠的面向连接的服务或者 Stream Sockets)
    • SOCK_DGRAM (数据包服务或者 Datagram Sockets)
    • SOCK_SEQPACKET (可靠的有序的分组服务),或者
    • SOCK_RAW (网络层的原始协议)。
  • protocol 确定实际使用的运输层。最常见的是 IPPROTO_TCP, IPPROTO_SCTP, IPPROTO_UDP, IPPROTO_DCCP。这些协议是在<netinet/in.h>中定义的。如果 domaintype已经确定,“0” 可以用来选择一个默认的协议。

如果出错返回-1,否则返回一个代表文件描述符的整数。

函数原型
int socket(int domain, int type, int protocol);

bind()

bind() 给套接字分配一个地址。当使用 socket()创造一个套接字时, 只是给定了协议族,并没有分配地址。在套接字能够接受来自其他主机的连接前,必须用bind()给它绑定一个地址。 bind() 由三个参数:

  • sockfd, 代表socket的文件描述符。
  • my_addr, 指向 sockaddr 结构体的指针,代表要绑定的地址 。
  • addrlen, 是sockaddr结构体的大小。

Bind()返回0表示成功,错误返回-1。

函数原型
int bind(int sockfd, const struct sockaddr *my_addr, socklen_t addrlen);

 listen()

一旦一个套接字和一个地址联系之后,listen() 监听到来的连接。但是这只适用于对面向连接的模式,例如 套接字类型是 (SOCK_STREAM, SOCK_SEQPACKET)。listen()需要两个参数:

  • sockfd,一个有效的套接字描述符。
  • backlog,一个整数,表示一次能够等待的最大连接数目。操作系统通常会对这个值设置上限。

一旦连接被接受,返回0表示成功,错误返回-1。

函数原型:
int listen(int sockfd, int backlog);

accept()

当应用程序监听来自其他主机的面对数据流的连接时,通过事件(比如Unix select()系统调用)通知它。必须用 accept()函数初始化连接。 Accept() 为每个连接创立新的套接字并从监听队列中移除这个连接。它使用如下参数:

  • sockfd,监听的套接字描述符
  • cliaddr, 指向sockaddr 结构体的指针,客户机地址信息。
  • addrlen,指向 socklen_t的指针,确定客户机地址结构体的大小 。

返回新的套接字描述符,出错返回-1。进一步的通信必须通过这个套接字。

Datagram 套接字不要求用accept()处理,因为接收方可能用监听套接字立即处理这个请求。

函数原型:
int accept(int sockfd, struct sockaddr *cliaddr, socklen_t *addrlen);

connect()

connect()系统调用为一个套接字设置连接,参数有文件描述符和主机地址。

某些类型的套接字是无连接的,大多数是UDP协议。对于这些套接字,连接时这样的:默认发送和接收数据的主机由给定的地址确定,可以使用 send()和 recv()。 返回-1表示出错,0表示成功。

函数原型:
int connect(int sockfd, const struct sockaddr *serv_addr, socklen_t addrlen);

gethostbyname() 和 gethostbyaddr()

gethostbyname()gethostbyaddr()函数是用来解析主机名和地址的。可能会使用DNS服务或者本地主机上的其他解析机制(例如查询/etc/hosts)。返回一个指向 struct hostent的指针,这个结构体描述一个IP主机。函数使用如下参数:

  • name 指定主机名。例如 www.wikipedia.org
  • addr 指向 struct in_addr的指针,包含主机的地址。
  • len 给出 addr的长度,以字节为单位。
  • type 指定地址族类型 (比如 AF_INET)。

出错返回NULL指针,可以通过检查 h_errno 来确定是临时错误还是未知主机。正确则返回一个有效的 struct hostent *

这些函数并不是伯克利套接字严格的组成部分。这些函数可能是过时了,新函数是 getaddrinfo() and getnameinfo(), 这些新函数是基于addrinfo数据结构。

函数原型:
struct hostent *gethostbyname(const char *name);
struct hostent *gethostbyaddr(const void *addr, int len, int type);

协议和地址

套接字API是Unix网络的通用接口,允许使用各种网络协议和地址。

下面列出了一些例子,在现在的 LinuxBSD中一般都已经实现了。

PF_LOCAL, PF_UNIX, PF_FILE
                Local to host (pipes and file-domain)
PF_INET         IP protocol family
PF_AX25         Amateur Radio AX.25
PF_IPX          Novell Internet Protocol
PF_APPLETALK    Appletalk DDP
PF_NETROM       Amateur radio NetROM
PF_BRIDGE       Multiprotocol bridge
PF_ATMPVC       ATM PVCs
PF_X25          Reserved for X.25 project
PF_INET6        IP version 6
PF_ROSE         Amateur Radio X.25 PLP
PF_DECnet       Reserved for DECnet project
PF_NETBEUI      Reserved for 802.2LLC project
PF_SECURITY     Security callback pseudo AF
PF_KEY          PF_KEY key management API
PF_NETLINK, PF_ROUTE
                routing API
PF_PACKET       Packet family
PF_ASH          Ash
PF_ECONET       Acorn Econet
PF_ATMSVC       ATM SVCs
PF_SNA          Linux SNA Project
PF_IRDA         IRDA sockets
PF_PPPOX        PPPoX sockets
PF_WANPIPE      Wanpipe API sockets
PF_BLUETOOTH    Bluetooth sockets

 使用TCP的服务器客户机举例

服务器

设置一个简单的TCP服务器涉及下列步骤:

  • 调用 socket()建立套接字
  • 调用Binding 把套接字绑定到一个监听端口上。在调用 bind()之前, 程序必须声明一个 sockaddr_in 结构体,用 memset()清除, and the sin_family (AF_INET), and fill its sin_port (the listening port, in network byte order) fields. Converting a short int to network byte order can be done by calling the function htons() (host to network short).
  • Preparing the socket to listen for connections (making it a listening socket), with a call to listen().
  • Accepting incoming connections, via a call to accept(). This blocks until an incoming connection is received, and then returns a socket descriptor for the accepted connection. The initial descriptor remains a listening descriptor, and accept() can be called again at any time with this socket, until it is closed.
  • Communicating with the remote host, which can be done through send() and recv() or write() and read().
  • Eventually closing each socket that was opened, once it is no longer needed, using close(). Note that if there were any calls to fork(), each process must close the sockets it knew about (the kernel keeps track of how many processes have a descriptor open), and two processes should not use the same socket at once.
  /* Server code in C */
 
  #include <sys/types.h>
  #include <sys/socket.h>
  #include <netinet/in.h>
  #include <arpa/inet.h>
  #include <stdio.h>
  #include <stdlib.h>
  #include <string.h>
  #include <unistd.h>
 
  int main(void)
  {
    struct sockaddr_in stSockAddr;
    int SocketFD = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
 
    if(-1 == SocketFD)
    {
      perror("can not create socket");
      exit(EXIT_FAILURE);
    }
 
    memset(&stSockAddr, 0, sizeof(struct sockaddr_in));
 
    stSockAddr.sin_family = AF_INET;
    stSockAddr.sin_port = htons(1100);
    stSockAddr.sin_addr.s_addr = INADDR_ANY;
 
    if(-1 == bind(SocketFD,(const struct sockaddr *)&stSockAddr, sizeof(struct sockaddr_in)))
    {
      perror("error bind failed");
      close(SocketFD);
      exit(EXIT_FAILURE);
    }
 
    if(-1 == listen(SocketFD, 10))
    {
      perror("error listen failed");
      close(SocketFD);
      exit(EXIT_FAILURE);
    }
 
    for(;;)
    {
      int ConnectFD = accept(SocketFD, NULL, NULL);
 
      if(0 > ConnectFD)
      {
        perror("error accept failed");
        close(SocketFD);
        exit(EXIT_FAILURE);
      }
 
     /* perform read write operations ... */
 
      shutdown(ConnectFD, SHUT_RDWR);
 
      close(ConnectFD);
    }
 
    close(SocketFD);
    return 0;
  }

 客户机

建立一个客户机连接涉及以下步骤:

  • 调用 socket()建立套接字。
  • connect()连接到服务器, passing a sockaddr_in structure with the sin_family set to AF_INET, sin_port set to the port the endpoint is listening (in network byte order), and sin_addr set to the IP address of the listening server (also in network byte order.)
  • send()recv() 或者 write()read()进行通信。
  • close()终止连接。如果调用fork(), 每个进程都要用close()
  /* Client code in C */
 
  #include <sys/types.h>
  #include <sys/socket.h>
  #include <netinet/in.h>
  #include <arpa/inet.h>
  #include <stdio.h>
  #include <stdlib.h>
  #include <string.h>
  #include <unistd.h>
 
  int main(void)
  {
    struct sockaddr_in stSockAddr;
    int Res;
    int SocketFD = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
 
    if (-1 == SocketFD)
    {
      perror("cannot create socket");
      exit(EXIT_FAILURE);
    }
 
    memset(&stSockAddr, 0, sizeof(struct sockaddr_in));
 
    stSockAddr.sin_family = AF_INET;
    stSockAddr.sin_port = htons(1100);
    Res = inet_pton(AF_INET, "192.168.1.3", &stSockAddr.sin_addr);
 
    if (0 > Res)
    {
      perror("error: first parameter is not a valid address family");
      close(SocketFD);
      exit(EXIT_FAILURE);
    }
    else if (0 == Res)
    {
      perror("char string (second parameter does not contain valid ipaddress");
      close(SocketFD);
      exit(EXIT_FAILURE);
    }
 
    if (-1 == connect(SocketFD, (const struct sockaddr *)&stSockAddr, sizeof(struct sockaddr_in)))
    {
      perror("connect failed");
      close(SocketFD);
      exit(EXIT_FAILURE);
    }
 
    /* perform read write operations ... */
 
    shutdown(SocketFD, SHUT_RDWR);
 
    close(SocketFD);
    return 0;
  }

使用UDP的服务器客户机举例

用户数据报协议(UDP)是一个不保证正确传输的无连接协议。 UDP数据包可能会乱序到达,多次到达或者直接丢失。但是设计的负载比TCP小。

UDP地址空间,也即是UDP端口,和TCP端口是没有关系的。

服务器

Code may set up a UDP server on port 7654 as follows:

#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <unistd.h> /* for close() for socket */ 
#include <stdlib.h>
 
int main(void)
{
  int sock = socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP);
  struct sockaddr_in sa; 
  char buffer[1024];
  ssize_t recsize;
  socklen_t fromlen;
 
  memset(&sa, 0, sizeof(sa));
  sa.sin_family = AF_INET;
  sa.sin_addr.s_addr = INADDR_ANY;
  sa.sin_port = htons(7654);
 
  if (-1 == bind(sock,(struct sockaddr *)&sa, sizeof(struct sockaddr)))
  {
    perror("error bind failed");
    close(sock);
    exit(EXIT_FAILURE);
  } 
 
  for (;;) 
  {
    printf ("recv test....\n");
    recsize = recvfrom(sock, (void *)buffer, 1024, 0, (struct sockaddr *)&sa, &fromlen);
    if (recsize < 0)
      fprintf(stderr, "%s\n", strerror(errno));
    printf("recsize: %d\n ",recsize);
    sleep(1);
    printf("datagram: %s\n",buffer);
  }
}

上面的无限循环用recvfrom()接收给UDP端口7654的数据包。使用如下参数:

  • 指向缓存数据指针
  • 缓存大小
  • 标志
  • 地址
  • 地址结构体大小

 客户机

用UDP数据包发送一个"Hello World!" 给地址127.0.0.1(回环地址),端口 7654 。


#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <unistd.h> /* for close() for socket */
 
int main(int argc, char *argv[])
{
  int sock;
  struct sockaddr_in sa;
  int bytes_sent, buffer_length;
  char buffer[200];
 
  buffer_length = snprintf(buffer, sizeof buffer, "Hello World!");
 
  sock = socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP);
  if (-1 == sock) /* if socket failed to initialize, exit */
    {
      printf("Error Creating Socket");
      exit(EXIT_FAILURE);
    }
 
  memset(&sa, 0, sizeof(sa));
  sa.sin_family = AF_INET;
  sa.sin_addr.s_addr = htonl(0x7F000001);
  sa.sin_port = htons(7654);
 
  bytes_sent = sendto(sock, buffer, buffer_length, 0,(struct sockaddr*)&sa, sizeof (struct sockaddr_in));
  if (bytes_sent < 0)
    printf("Error sending packet: %s\n", strerror(errno));
 
  close(sock); /* close the socket */
  return 0;
}

buffer指定要发送数据的指针, buffer_length指定缓存内容的大小。

  • 32
    点赞
  • 112
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值